Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var ConnectCmd = &cli.Command{ Name: "connect", Usage: "Connect to server", Description: "Authenticate the client with a remote server and save the server address and access key.", Arguments: []cli.Argument{ &cli.StringArg{ Name: "server", Usage: "The server to connect to", Required: true, }, }, MaxArgs: cli.NoArgs, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "use-web-auth", Usage: "If given then authorization will be done via the web interface.", }, &cli.BoolFlag{ Name: "tls-skip-verify", Usage: "Skip TLS verification when talking to server.", ConfigPath: []string{"tls.skip_verify"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"}, DefaultValue: true, Global: true, }, &cli.StringFlag{ Name: "username", Aliases: []string{"u"}, Usage: "Username to use for authentication.", }, &cli.StringFlag{ Name: "alias", Aliases: []string{"a"}, Usage: "The server alias to use to identify the connection.", DefaultValue: "default", }, }, Commands: []*cli.Command{ connectcmd.ConnectListCmd, connectcmd.ConnectDeleteCmd, }, Run: func(ctx context.Context, cmd *cli.Command) error { var token string server := cmd.GetStringArg("server") if !strings.HasPrefix(server, "http://") && !strings.HasPrefix(server, "https://") { server = "https://" + server } fmt.Println("Connecting to server: ", server) u, err := url.Parse(server) if err != nil { fmt.Println("Failed to parse server URL") os.Exit(1) } hostname, err := os.Hostname() if err != nil { fmt.Println("Failed to get hostname") os.Exit(1) } hostname = "knot client " + hostname client, err := apiclient.NewClient( server, "", cmd.GetBool("tls-skip-verify"), ) if err != nil { fmt.Println("Failed to create API client:", err) os.Exit(1) } totp, _, err := client.UsingTOTP(context.Background()) if err != nil { fmt.Println("Failed to query server for TOTP") os.Exit(1) } if totp || cmd.GetBool("use-web-auth") { u.Path = "/api-tokens/create/" + url.PathEscape(hostname) err = open(u.String()) if err != nil { fmt.Println("Failed to open server URL, you will need to generate the API token manually") os.Exit(1) } fmt.Print("Enter token: ") _, err = fmt.Scanln(&token) if err != nil { fmt.Println("Failed to read token, you will need to generate the API token manually") os.Exit(1) } } else { username := cmd.GetString("username") var password []byte if username == "" { fmt.Print("Enter email: ") _, err = fmt.Scanln(&username) if err != nil { fmt.Println("Failed to read email address") os.Exit(1) } } fmt.Print("Enter password: ") password, err = term.ReadPassword(int(syscall.Stdin)) if err != nil { fmt.Println("Failed to read password") os.Exit(1) } fmt.Println() if username == "" || string(password) == "" { fmt.Println("Username and password must be given") os.Exit(1) } response, _, _ := client.Login(context.Background(), username, string(password), "") if response == nil || response.Token == "" { fmt.Println("Failed to login") os.Exit(1) } client.UseSessionCookie(true).SetAuthToken(response.Token) token, _, err = client.CreateToken(context.Background(), hostname) if err != nil || token == "" { fmt.Println("Failed to create token") os.Exit(1) } } alias := cmd.GetString("alias") if err := config.SaveConnection(alias, server, token, cmd); err != nil { fmt.Println("Failed to save connection:", err) os.Exit(1) } fmt.Println("Successfully connected to server:", server) return nil }, }
View Source
var GenkeyCmd = &cli.Command{ Name: "genkey", Usage: "Generate Encryption Key", Description: "Generate an encryption key for encrypting stored variables and cluster communications.", MaxArgs: cli.NoArgs, Run: func(ctx context.Context, cmd *cli.Command) error { key := crypt.CreateKey() fmt.Println("Encryption Key:", key) fmt.Println("") return nil }, }
View Source
var LegalCmd = &cli.Command{ Name: "legal", Usage: "Show legal information", Description: "Output all the legal notices.", MaxArgs: cli.NoArgs, Run: func(ctx context.Context, cmd *cli.Command) error { legal.ShowLicenses() return nil }, }
View Source
var PingCmd = &cli.Command{ Name: "ping", Usage: "Ping the server", Description: "Ping the server and display the health and version number.", MaxArgs: cli.NoArgs, Flags: []cli.Flag{ &cli.StringFlag{ Name: "server", Aliases: []string{"s"}, Usage: "The address of the remote server to ping.", EnvVars: []string{config.CONFIG_ENV_PREFIX + "_SERVER"}, }, &cli.StringFlag{ Name: "token", Aliases: []string{"t"}, Usage: "The token to use for authentication.", EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TOKEN"}, }, &cli.StringFlag{ Name: "alias", Aliases: []string{"a"}, Usage: "The server alias to use.", DefaultValue: "default", }, &cli.BoolFlag{ Name: "tls-skip-verify", Usage: "Skip TLS verification when talking to server.", ConfigPath: []string{"tls.skip_verify"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"}, DefaultValue: true, }, }, Run: func(ctx context.Context, cmd *cli.Command) error { alias := cmd.GetString("alias") cfg := config.GetServerAddr(alias, cmd) fmt.Println("Pinging server: ", cfg.HttpServer) client, err := apiclient.NewClient( cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"), ) if err != nil { return fmt.Errorf("Failed to create API client: %w", err) } version, err := client.Ping(ctx) if err != nil { return fmt.Errorf("Failed to ping server: %w", err) } fmt.Println("\nServer is healthy") fmt.Println("Version: ", version.Version) fmt.Println("Zone: ", version.Zone) return nil }, }
View Source
var ScaffoldCmd = &cli.Command{ Name: "scaffold", Usage: "Generate configuration files", Description: "Generates example configuration files for use with knot.", MaxArgs: cli.NoArgs, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "server", Usage: "Generate a server configuration file", ConfigPath: []string{"scaffold.server"}, }, &cli.BoolFlag{ Name: "client", Usage: "Generate a client configuration file", ConfigPath: []string{"scaffold.client"}, }, &cli.BoolFlag{ Name: "agent", Usage: "Generate an agent configuration file", ConfigPath: []string{"scaffold.agent"}, }, &cli.BoolFlag{ Name: "nomad", Usage: "Generate a nomad job file", ConfigPath: []string{"scaffold.nomad"}, }, &cli.BoolFlag{ Name: "system-prompt", Usage: "Generate the internal system prompt", ConfigPath: []string{"scaffold.system_prompt"}, }, &cli.BoolFlag{ Name: "nomad-spec", Usage: "Generate the internal nomad spec", ConfigPath: []string{"scaffold.nomad_spec"}, }, &cli.BoolFlag{ Name: "docker-spec", Usage: "Generate the internal docker spec", ConfigPath: []string{"scaffold.docker_spec"}, }, &cli.BoolFlag{ Name: "podman-spec", Usage: "Generate the internal podman spec", ConfigPath: []string{"scaffold.podman_spec"}, }, &cli.BoolFlag{ Name: "apple-spec", Usage: "Generate the internal apple spec", ConfigPath: []string{"scaffold.apple_spec"}, }, }, Run: func(ctx context.Context, cmd *cli.Command) error { any := false if cmd.GetBool("server") { fmt.Println(scaffold.ServerScaffold) any = true } if cmd.GetBool("client") { fmt.Println(scaffold.ClientScaffold) any = true } if cmd.GetBool("agent") { fmt.Println(scaffold.AgentScaffold) any = true } if cmd.GetBool("nomad") { fmt.Println(scaffold.NomadScaffold) any = true } if cmd.GetBool("system-prompt") { fmt.Println(scaffold.GetSystemPromptScaffold()) any = true } if cmd.GetBool("nomad-spec") { fmt.Println(scaffold.GetNomadSpecScaffold()) any = true } if cmd.GetBool("docker-spec") { fmt.Println(scaffold.GetDockerSpecScaffold()) any = true } if cmd.GetBool("podman-spec") { fmt.Println(scaffold.GetPodmanSpecScaffold()) any = true } if cmd.GetBool("apple-spec") { fmt.Println(scaffold.GetAppleSpecScaffold()) any = true } if !any { cmd.ShowHelp() } return nil }, }
View Source
var ServerCmd = &cli.Command{ Name: "server", Usage: "Start the knot server", Description: "Start the knot server and listen for incoming connections.", MaxArgs: cli.NoArgs, Flags: []cli.Flag{ &cli.StringFlag{ Name: "listen", Aliases: []string{"l"}, Usage: "The address to listen on.", ConfigPath: []string{"server.listen"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_LISTEN"}, DefaultValue: ":3000", }, &cli.StringFlag{ Name: "listen-agent", Usage: "The address to listen on for agent connections.", ConfigPath: []string{"server.listen_agent"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_LISTEN_AGENT"}, DefaultValue: "127.0.0.1:3010", }, &cli.StringFlag{ Name: "listen-tunnel", Usage: "The address to listen on for tunnel connections.", ConfigPath: []string{"server.listen_tunnel"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_LISTEN_TUNNEL"}, DefaultValue: "", }, &cli.StringFlag{ Name: "url", Aliases: []string{"u"}, Usage: "The URL to use for the server.", ConfigPath: []string{"server.url"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_URL"}, DefaultValue: "http://127.0.0.1:3000", }, &cli.StringFlag{ Name: "tunnel-server", Usage: "The URL for the tunnel client to connect to the individual server.", ConfigPath: []string{"server.tunnel_server"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TUNNEL_SERVER"}, DefaultValue: "", }, &cli.BoolFlag{ Name: "terminal-webgl", Usage: "Enable WebGL terminal renderer.", ConfigPath: []string{"server.terminal.webgl"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_WEBGL"}, DefaultValue: true, }, &cli.StringFlag{ Name: "download-path", Usage: "The path to serve download files from if set.", ConfigPath: []string{"server.download_path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DOWNLOAD_PATH"}, DefaultValue: "", }, &cli.StringFlag{ Name: "wildcard-domain", Usage: "The wildcard domain to use for proxying to spaces.", ConfigPath: []string{"server.wildcard_domain"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_WILDCARD_DOMAIN"}, DefaultValue: "", }, &cli.StringFlag{ Name: "encrypt", Usage: "The encryption key to use for encrypting stored variables.", ConfigPath: []string{"server.encrypt"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ENCRYPT"}, DefaultValue: "", }, &cli.StringFlag{ Name: "agent-endpoint", Usage: "The address agents should use to talk to the server.", ConfigPath: []string{"server.agent_endpoint"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_AGENT_ENDPOINT"}, DefaultValue: "", }, &cli.StringFlag{ Name: "zone", Usage: "The zone of the server.", ConfigPath: []string{"server.zone"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ZONE"}, }, &cli.StringFlag{ Name: "hostname", Usage: "The hostname to advertise to other servers (defaults to system hostname).", ConfigPath: []string{"server.hostname"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_HOSTNAME"}, }, &cli.StringFlag{ Name: "html-path", Usage: "The optional path to the html files to serve.", ConfigPath: []string{"server.html_path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_HTML_PATH"}, DefaultValue: "", }, &cli.StringFlag{ Name: "template-path", Usage: "The optional path to the template files to serve.", ConfigPath: []string{"server.template_path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TEMPLATE_PATH"}, DefaultValue: "", }, &cli.StringFlag{ Name: "agent-path", Usage: "The optional path to the agent files to serve.", ConfigPath: []string{"server.agent_path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_AGENT_PATH"}, DefaultValue: "", }, &cli.StringFlag{ Name: "timezone", Usage: "The timezone to use for the server.", ConfigPath: []string{"server.timezone"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TIMEZONE"}, DefaultValue: "", }, &cli.StringFlag{ Name: "tunnel-domain", Usage: "The domain to use for tunnel connections.", ConfigPath: []string{"server.tunnel_domain"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TUNNEL_DOMAIN"}, DefaultValue: "", }, &cli.IntFlag{ Name: "audit-retention", Usage: "The number of days to keep audit logs.", ConfigPath: []string{"server.audit_retention"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_AUDIT_RETENTION"}, DefaultValue: 90, }, &cli.BoolFlag{ Name: "disable-space-create", Usage: "Disable the ability to create spaces.", ConfigPath: []string{"server.disable_space_create"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DISABLE_SPACE_CREATE"}, DefaultValue: false, }, &cli.BoolFlag{ Name: "auth-ip-rate-limiting", Usage: "Enable IP rate limiting of authentication.", ConfigPath: []string{"server.auth_ip_rate_limiting"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_AUTH_IP_RATE_LIMITING"}, DefaultValue: true, }, &cli.StringFlag{ Name: "public-files-path", Usage: "The path to the a directory to serve as /public-files.", ConfigPath: []string{"server.public_files_path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_PUBLIC_FILES_PATH"}, DefaultValue: "", }, &cli.StringFlag{ Name: "private-files-path", Usage: "The path to the a directory to serve as /private-files.", ConfigPath: []string{"server.private_files_path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_PRIVATE_FILES_PATH"}, DefaultValue: "", }, &cli.StringFlag{ Name: "skills-path", Usage: "The path to the skills/knowledgebase directory for MCP access.", ConfigPath: []string{"server.skills_path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_SKILLS_PATH"}, DefaultValue: "", }, &cli.BoolFlag{ Name: "hide-support-links", Usage: "Hide the support links in the UI.", ConfigPath: []string{"server.ui.hide_support_links"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_HIDE_SUPPORT_LINKS"}, DefaultValue: false, }, &cli.BoolFlag{ Name: "hide-api-tokens", Usage: "Hide the API tokens menu item in the UI.", ConfigPath: []string{"server.ui.hide_api_tokens"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_HIDE_API_TOKENS"}, DefaultValue: false, }, &cli.BoolFlag{ Name: "enable-gravatar", Usage: "Enable Gravatar support in the UI.", ConfigPath: []string{"server.ui.enable_gravatar"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ENABLE_GRAVATAR"}, DefaultValue: true, }, &cli.StringSliceFlag{ Name: "icons", Usage: "File defining icons for use with templates and spaces, can be given multiple times.", ConfigPath: []string{"server.ui.icons"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ICONS"}, }, &cli.BoolFlag{ Name: "enable-builtin-icons", Usage: "Enable the use of the built-in icons.", ConfigPath: []string{"server.ui.enable_builtin_icons"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ENABLE_BUILTIN_ICONS"}, DefaultValue: true, }, &cli.StringFlag{ Name: "logo-url", Usage: "The URL to the logo to use in the UI.", ConfigPath: []string{"server.ui.logo_url"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_LOGO_URL"}, DefaultValue: "", }, &cli.BoolFlag{ Name: "logo-invert", Usage: "Invert the logo colors in the UI for dark mode.", ConfigPath: []string{"server.ui.logo_invert"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_LOGO_INVERT"}, DefaultValue: false, }, &cli.StringFlag{ Name: "cluster-key", Usage: "The shared cluster key.", ConfigPath: []string{"server.cluster.key"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_KEY"}, DefaultValue: "", }, &cli.StringFlag{ Name: "cluster-advertise-addr", Usage: "The address to advertise to other servers.", ConfigPath: []string{"server.cluster.advertise_addr"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_ADVERTISE_ADDR"}, DefaultValue: "", }, &cli.StringFlag{ Name: "cluster-bind-addr", Usage: "The address to bind to for cluster communication.", ConfigPath: []string{"server.cluster.bind_addr"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_BIND_ADDR"}, DefaultValue: "", }, &cli.StringSliceFlag{ Name: "cluster-peer", Usage: "The addresses of the other servers in the cluster, can be given multiple times.", ConfigPath: []string{"server.cluster.peers"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_PEERS"}, }, &cli.BoolFlag{ Name: "allow-leaf-nodes", Usage: "Allow leaf nodes to connect to the cluster.", ConfigPath: []string{"server.cluster.allow_leaf_nodes"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ALLOW_LEAF_NODES"}, DefaultValue: true, }, &cli.BoolFlag{ Name: "cluster-compression", Usage: "Enable compression for cluster communication.", ConfigPath: []string{"server.cluster.compression"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_COMPRESSION"}, DefaultValue: true, }, &cli.StringFlag{ Name: "origin-server", Usage: "The address of the origin server.", ConfigPath: []string{"server.origin.server"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ORIGIN_SERVER"}, DefaultValue: "", }, &cli.StringFlag{ Name: "origin-token", Usage: "The token to use for the origin server.", ConfigPath: []string{"server.origin.token"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ORIGIN_TOKEN"}, DefaultValue: "", }, &cli.BoolFlag{ Name: "enable-totp", Usage: "Enable TOTP for users.", ConfigPath: []string{"server.totp.enabled"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_ENABLE_TOTP"}, DefaultValue: false, }, &cli.IntFlag{ Name: "totp-window", Usage: "The number of time steps (30 seconds) to check for TOTP codes.", ConfigPath: []string{"server.totp.window"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TOTP_WINDOW"}, DefaultValue: 1, }, &cli.StringFlag{ Name: "totp-issuer", Usage: "The issuer to use for TOTP codes.", ConfigPath: []string{"server.totp.issuer"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TOTP_ISSUER"}, DefaultValue: "Knot", }, &cli.StringFlag{ Name: "cert-file", Usage: "The file with the PEM encoded certificate to use for the server.", ConfigPath: []string{"server.tls.cert_file"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CERT_FILE"}, DefaultValue: "", }, &cli.StringFlag{ Name: "key-file", Usage: "The file with the PEM encoded key to use for the server.", ConfigPath: []string{"server.tls.key_file"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_KEY_FILE"}, DefaultValue: "", }, &cli.BoolFlag{ Name: "use-tls", Usage: "Enable TLS.", ConfigPath: []string{"server.tls.use_tls"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_USE_TLS"}, DefaultValue: true, }, &cli.BoolFlag{ Name: "agent-use-tls", Usage: "Enable TLS when talking to agents.", ConfigPath: []string{"server.tls.agent_use_tls"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_AGENT_USE_TLS"}, DefaultValue: true, }, &cli.BoolFlag{ Name: "tls-skip-verify", Usage: "Skip TLS verification when talking to agents.", ConfigPath: []string{"tls.skip_verify"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"}, DefaultValue: true, }, &cli.StringFlag{ Name: "nomad-addr", Usage: "The address of the Nomad server.", ConfigPath: []string{"server.nomad.addr"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_NOMAD_ADDR"}, DefaultValue: "http://127.0.0.1:4646", }, &cli.StringFlag{ Name: "nomad-token", Usage: "The token to use for Nomad API requests.", ConfigPath: []string{"server.nomad.token"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_NOMAD_TOKEN"}, DefaultValue: "", }, &cli.BoolFlag{ Name: "mysql-enabled", Usage: "Enable MySQL database backend.", ConfigPath: []string{"server.mysql.enabled"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_ENABLED"}, DefaultValue: false, }, &cli.StringFlag{ Name: "mysql-host", Usage: "The MySQL host to connect to.", ConfigPath: []string{"server.mysql.host"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_HOST"}, DefaultValue: "localhost", }, &cli.IntFlag{ Name: "mysql-port", Usage: "The MySQL port to connect to.", ConfigPath: []string{"server.mysql.port"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_PORT"}, DefaultValue: 3306, }, &cli.StringFlag{ Name: "mysql-user", Usage: "The MySQL user to connect as.", ConfigPath: []string{"server.mysql.user"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_USER"}, DefaultValue: "root", }, &cli.StringFlag{ Name: "mysql-password", Usage: "The MySQL password to use.", ConfigPath: []string{"server.mysql.password"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_PASSWORD"}, DefaultValue: "", }, &cli.StringFlag{ Name: "mysql-database", Usage: "The MySQL database to use.", ConfigPath: []string{"server.mysql.database"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_DATABASE"}, DefaultValue: "knot", }, &cli.IntFlag{ Name: "mysql-connection-max-idle", Usage: "The maximum number of idle connections in the connection pool.", ConfigPath: []string{"server.mysql.connection_max_idle"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_CONNECTION_MAX_IDLE"}, DefaultValue: 10, }, &cli.IntFlag{ Name: "mysql-connection-max-open", Usage: "The maximum number of open connections to the database.", ConfigPath: []string{"server.mysql.connection_max_open"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_CONNECTION_MAX_OPEN"}, DefaultValue: 100, }, &cli.IntFlag{ Name: "mysql-connection-max-lifetime", Usage: "The maximum amount of time in minutes a connection may be reused.", ConfigPath: []string{"server.mysql.connection_max_lifetime"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MYSQL_CONNECTION_MAX_LIFETIME"}, DefaultValue: 5, }, &cli.BoolFlag{ Name: "badgerdb-enabled", Usage: "Enable BadgerDB database backend.", ConfigPath: []string{"server.badgerdb.enabled"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_BADGERDB_ENABLED"}, DefaultValue: false, }, &cli.StringFlag{ Name: "badgerdb-path", Usage: "The path to the BadgerDB database.", ConfigPath: []string{"server.badgerdb.path"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_BADGERDB_PATH"}, DefaultValue: "./badger", }, &cli.BoolFlag{ Name: "redis-enabled", Usage: "Enable Redis database backend.", ConfigPath: []string{"server.redis.enabled"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_REDIS_ENABLED"}, DefaultValue: false, }, &cli.StringSliceFlag{ Name: "redis-hosts", Usage: "The redis server(s), can be specified multiple times.", ConfigPath: []string{"server.redis.hosts"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_REDIS_HOSTS"}, DefaultValue: []string{"localhost:6379"}, }, &cli.StringFlag{ Name: "redis-password", Usage: "The password to use for the redis server.", ConfigPath: []string{"server.redis.password"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_REDIS_PASSWORD"}, DefaultValue: "", }, &cli.IntFlag{ Name: "redis-db", Usage: "The redis database to use.", ConfigPath: []string{"server.redis.db"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_REDIS_DB"}, DefaultValue: 0, }, &cli.StringFlag{ Name: "redis-master-name", Usage: "The name of the master to use for failover clients.", ConfigPath: []string{"server.redis.master_name"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_REDIS_MASTER_NAME"}, DefaultValue: "", }, &cli.StringFlag{ Name: "redis-key-prefix", Usage: "The prefix to use for all keys in the redis database.", ConfigPath: []string{"server.redis.key_prefix"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_REDIS_KEY_PREFIX"}, DefaultValue: "", }, &cli.StringFlag{ Name: "docker-host", Usage: "The Docker host to connect to.", ConfigPath: []string{"server.docker.host"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DOCKER_HOST"}, DefaultValue: "unix:///var/run/docker.sock", }, &cli.StringFlag{ Name: "podman-host", Usage: "The Podman host to connect to.", ConfigPath: []string{"server.podman.host"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_PODMAN_HOST"}, DefaultValue: "unix:///var/run/podman.sock", }, &cli.StringSliceFlag{ Name: "local-container-runtime-pref", Usage: "Preference order for local container runtimes (docker, podman, apple). First available will be used.", ConfigPath: []string{"server.local_containers.runtime_pref"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_LOCAL_CONTAINERS_RUNTIME_PREF"}, DefaultValue: []string{"docker", "podman", "apple"}, }, &cli.BoolFlag{ Name: "mcp-enabled", Usage: "Enable MCP (Model Context Protocol) server functionality.", ConfigPath: []string{"server.mcp.enabled"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MCP_ENABLED"}, DefaultValue: false, }, &cli.BoolFlag{ Name: "mcp-native-tools", Usage: "Use native tool registration instead of discovery-based registration.", ConfigPath: []string{"server.mcp.native_tools"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_MCP_NATIVE_TOOLS"}, DefaultValue: false, }, &cli.BoolFlag{ Name: "chat-enabled", Usage: "Enable AI chat functionality.", ConfigPath: []string{"server.chat.enabled"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_ENABLED"}, DefaultValue: false, }, &cli.BoolFlag{ Name: "chat-openai-endpoints", Usage: "Enable OpenAI-compatible endpoints for external clients.", ConfigPath: []string{"server.chat.openai_endpoints"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_OPENAI_ENDPOINTS"}, DefaultValue: false, }, &cli.StringFlag{ Name: "chat-openai-api-key", Usage: "OpenAI API key for chat functionality.", ConfigPath: []string{"server.chat.openai_api_key"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_OPENAI_API_KEY"}, DefaultValue: "", }, &cli.StringFlag{ Name: "chat-openai-base-url", Usage: "OpenAI API base URL for chat functionality.", ConfigPath: []string{"server.chat.openai_base_url"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_OPENAI_BASE_URL"}, DefaultValue: "http://127.0.0.1:11434/v1", }, &cli.StringFlag{ Name: "chat-model", Usage: "OpenAI model to use for chat.", ConfigPath: []string{"server.chat.model"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_MODEL"}, DefaultValue: "qwen2.5-coder:14b", }, &cli.IntFlag{ Name: "chat-max-tokens", Usage: "Maximum tokens for chat responses.", ConfigPath: []string{"server.chat.max_tokens"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_MAX_TOKENS"}, DefaultValue: 0, }, &cli.Float32Flag{ Name: "chat-temperature", Usage: "Temperature for chat responses.", ConfigPath: []string{"server.chat.temperature"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_TEMPERATURE"}, DefaultValue: 0.1, }, &cli.StringFlag{ Name: "chat-system-prompt-file", Usage: "Optional file path for system prompt. If not provided, uses default embedded prompt.", ConfigPath: []string{"server.chat.system_prompt_file"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_SYSTEM_PROMPT_FILE"}, DefaultValue: "", }, &cli.StringFlag{ Name: "chat-reasoning-effort", Usage: "Reasoning effort level for chat responses (low, medium, high).", ConfigPath: []string{"server.chat.reasoning_effort"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_REASONING_EFFORT"}, DefaultValue: "", ValidateFlag: func(c *cli.Command) error { value := c.GetString("chat-reasoning-effort") if value != "" && value != "none" && value != "low" && value != "medium" && value != "high" { return fmt.Errorf("If given, reasoning effort must be one of: none, low, medium, high") } return nil }, }, &cli.StringFlag{ Name: "chat-ui-style", Usage: "UI style for assistant button (icon or avatar).", ConfigPath: []string{"server.chat.ui_style"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_CHAT_UI_STYLE"}, DefaultValue: "icon", ValidateFlag: func(c *cli.Command) error { value := c.GetString("chat-ui-style") if value != "" && value != "icon" && value != "avatar" { return fmt.Errorf("UI style must be one of: icon, avatar") } return nil }, }, &cli.BoolFlag{ Name: "dns-enabled", Usage: "Enable DNS server.", ConfigPath: []string{"server.dns.enabled"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DNS_ENABLED"}, DefaultValue: false, }, &cli.StringFlag{ Name: "dns-listen", Usage: "The address and port to listen on for DNS queries.", ConfigPath: []string{"server.dns.listen"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DNS_LISTEN"}, DefaultValue: ":3053", }, &cli.StringSliceFlag{ Name: "dns-records", Usage: "The DNS records to add, can be specified multiple times.", ConfigPath: []string{"server.dns.records"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DNS_RECORDS"}, }, &cli.IntFlag{ Name: "dns-default-ttl", Usage: "Default TTL for records if a TTL isn't explicitly set.", ConfigPath: []string{"server.dns.default_ttl"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DNS_DEFAULT_TTL"}, DefaultValue: 300, }, &cli.BoolFlag{ Name: "dns-enable-upstream", Usage: "Enable resolution of unknown domains by passing to upstream DNS servers.", ConfigPath: []string{"server.dns.enable_upstream"}, EnvVars: []string{config.CONFIG_ENV_PREFIX + "_DNS_ENABLE_UPSTREAM"}, DefaultValue: false, }, }, Run: func(ctx context.Context, cmd *cli.Command) error { logger := log.WithGroup("server") cfg := buildServerConfig(cmd) listen := util.FixListenAddress(cfg.Listen) if cfg.AgentEndpoint == "" { logger.Fatal("agent endpoint not given") } logger.Info("starting knot version", "version", build.Version) logger.Info("starting on", "listen", listen) service.SetUserService(api_utils.NewApiUtilsUsers()) service.SetContainerService(containerHelper.NewContainerHelper()) middleware.Initialize() sse.GetHub().Start() roles, err := database.GetInstance().GetRoles() if err != nil { log.WithError(err).Fatal("failed to get roles:") } model.SetRoleCache(roles) if cmd.GetBool("dns-enabled") { dnsServerCfg := dns.DNSServerConfig{ ListenAddr: cmd.GetString("dns-listen"), Records: cmd.GetStringSlice("dns-records"), DefaultTTL: cmd.GetInt("dns-default-ttl"), } if cmd.GetBool("dns-enable-upstream") { dnsServerCfg.Resolver = dns.GetDefaultResolver() dnsServerCfg.Resolver.SetConfig(dns.ResolverConfig{ QueryTimeout: 2 * time.Second, EnableCache: true, MaxCacheTTL: 30, }) } dnsServer, err := dns.NewDNSServer(dnsServerCfg) if err != nil { log.Fatal("Failed to create DNS server", "error", err) } if err = dnsServer.Start(); err != nil { log.Fatal("Failed to start DNS server", "error", err) } defer dnsServer.Stop() } var router http.Handler wildcardDomain := cfg.WildcardDomain serverURL := cfg.URL u, err := url.Parse(serverURL) if err != nil { log.Fatal(err.Error()) } logger.Debug("Host", "host", u.Host) var tunnelServerUrl *url.URL = nil if cfg.TunnelServer != "" && cfg.ListenTunnel != "" { tunnelServerUrl, err = url.Parse(cfg.TunnelServer) if err != nil { logger.WithError(err).Fatal("error parsing tunnel server URL:") } logger.Debug("tunnel Server URL", "tunnel", tunnelServerUrl.Host) } routes := http.NewServeMux() api.ApiRoutes(routes) proxy.Routes(routes, cfg) web.Routes(routes, cfg) // MCP - Create server if either MCP or chat is enabled var mcpServer *mcp.Server = nil mcpEnabled := cmd.GetBool("mcp-enabled") chatEnabled := cmd.GetBool("chat-enabled") openaiEndpointEnabled := cmd.GetBool("chat-openai-endpoints") if mcpEnabled || chatEnabled || openaiEndpointEnabled { nativeTools := cmd.GetBool("mcp-native-tools") mcpServer = internal_mcp.InitializeMCPServer(routes, mcpEnabled, nativeTools) if !mcpEnabled { logger.Debug("MCP chat-only mode") } else { logger.Info("MCP server enabled") } systemprompt.SetDefaultSystemPrompt(nativeTools) } // If AI chat enabled then initialize chat service var openAIClient *openai.Client if chatEnabled || openaiEndpointEnabled { logger.Info("AI chat enabled") chatService, err := chat.NewService(cfg.Chat, mcpServer) if err != nil { logger.WithError(err).Fatal("failed to create chat service:") } openAIClient = chatService.GetOpenAIClient() if chatEnabled { routes.HandleFunc("POST /api/chat/stream", middleware.ApiAuth(middleware.ApiPermissionUseWebAssistant(chatService.HandleChatStream))) } } if openaiEndpointEnabled { logger.Info("OpenAI endpoints enabled") service := openai.NewService(openAIClient, cfg.Chat.SystemPrompt) routes.HandleFunc("GET /v1/models", middleware.ApiAuth(middleware.ApiPermissionUseWebAssistant(service.HandleGetModels))) routes.HandleFunc("POST /v1/chat/completions", middleware.ApiAuth(middleware.ApiPermissionUseWebAssistant(service.HandleChatCompletions))) } appRoutes := web.HandlePageNotFound(routes) if cfg.ListenTunnel != "" { tunnel_server.Routes(routes) } listenAddr := util.FixListenAddress(cfg.Listen) tunnelAddr := util.FixListenAddress(cfg.ListenTunnel) sameAddress := cfg.ListenTunnel != "" && listenAddr == tunnelAddr // Get tunnel domain for routing var tunnelDomainMatch *regexp.Regexp = nil if cfg.TunnelDomain != "" { tunnelDomainPattern := "^[a-zA-Z0-9-]+" + strings.TrimLeft(strings.Replace(cfg.TunnelDomain, ".", "\\.", -1), "*") + "$" tunnelDomainMatch = regexp.MustCompile(tunnelDomainPattern) logger.Debug("tunnel domain pattern", "tunnelDomainPattern", tunnelDomainPattern) } if wildcardDomain != "" || sameAddress { if wildcardDomain != "" { logger.Debug("wildcard domain", "wildcardDomain", wildcardDomain) } if sameAddress { logger.Debug("using domain routing for tunnel traffic (same listen address)") } // Remove the port from the wildcard domain var wildcardMatch *regexp.Regexp = nil if wildcardDomain != "" { if host, _, err := net.SplitHostPort(wildcardDomain); err == nil { wildcardDomain = host } wildcardMatch = regexp.MustCompile("^[a-zA-Z0-9-]+" + strings.TrimLeft(strings.Replace(wildcardDomain, ".", "\\.", -1), "*") + "$") } hostname := u.Host if host, _, err := net.SplitHostPort(hostname); err == nil { hostname = host } wildcardRoutes := proxy.PortRoutes() tunnelRoutes := http.NewServeMux() tunnel_server.Routes(tunnelRoutes) domainMux := http.NewServeMux() domainMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { requestHost := r.Host if host, _, err := net.SplitHostPort(requestHost); err == nil { requestHost = host } if sameAddress && tunnelDomainMatch != nil && tunnelDomainMatch.MatchString(requestHost) { tunnelRoutes.ServeHTTP(w, r) } else if requestHost == hostname || (tunnelServerUrl != nil && requestHost == tunnelServerUrl.Host) { appRoutes.ServeHTTP(w, r) } else if wildcardMatch != nil && wildcardMatch.MatchString(requestHost) { wildcardRoutes.ServeHTTP(w, r) } else { if r.URL.Path == "/health" { web.HandleHealthPage(w, r) } else { http.NotFound(w, r) } } }) router = domainMux } else { router = appRoutes } var tlsConfig *tls.Config = nil if cfg.TLS.UseTLS { logger.Debug("using TLS") certFile := cfg.TLS.CertFile keyFile := cfg.TLS.KeyFile if certFile != "" && keyFile != "" { logger.Info("using cert file", "certFile", certFile) logger.Info("using key file", "keyFile", keyFile) serverTLSCert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { logger.WithError(err).Fatal("Error loading certificate and key file") } tlsConfig = &tls.Config{ Certificates: []tls.Certificate{serverTLSCert}, } } else { logger.Info("generating self-signed certificate") // Build the list of domains to include in the cert var sslDomains []string serverURL := cfg.URL u, err := url.Parse(serverURL) if err != nil { logger.Fatal(err.Error()) } hostname := u.Host if host, _, err := net.SplitHostPort(hostname); err == nil { hostname = host } sslDomains = append(sslDomains, hostname) if hostname != "localhost" { sslDomains = append(sslDomains, "localhost") } if tunnelServerUrl != nil { sslDomains = append(sslDomains, tunnelServerUrl.Host) } wildcardDomain := cfg.WildcardDomain if wildcardDomain != "" { if host, _, err := net.SplitHostPort(wildcardDomain); err == nil { wildcardDomain = host } sslDomains = append(sslDomains, wildcardDomain) } cert, key, err := util.GenerateCertificate(sslDomains, []net.IP{net.ParseIP("127.0.0.1")}) if err != nil { logger.WithError(err).Fatal("error generating certificate and key") } serverTLSCert, err := tls.X509KeyPair([]byte(cert), []byte(key)) if err != nil { logger.WithError(err).Fatal("error generating server TLS cert") } tlsConfig = &tls.Config{ Certificates: []tls.Certificate{serverTLSCert}, } } } cluster := cluster.NewCluster( cfg.Cluster.Key, cfg.Cluster.AdvertiseAddr, cfg.Cluster.BindAddr, routes, cfg.Cluster.Compression, cfg.Cluster.AllowLeafNodes, ) service.SetTransport(cluster) serverCtx, serverCancel := context.WithCancel(context.Background()) server := &http.Server{ Addr: listen, Handler: router, ReadTimeout: 30 * time.Second, WriteTimeout: 5 * time.Minute, IdleTimeout: 120 * time.Second, ReadHeaderTimeout: 10 * time.Second, TLSConfig: tlsConfig, BaseContext: func(l net.Listener) context.Context { return serverCtx }, } go func() { for { if cfg.TLS.UseTLS { if err := server.ListenAndServeTLS("", ""); err != http.ErrServerClosed { logger.WithError(err).Error("web server") } } else { if err := server.ListenAndServe(); err != http.ErrServerClosed { logger.WithError(err).Error("web server") } } } }() c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) cluster.Start( cfg.Cluster.Peers, cfg.Origin.Server, cfg.Origin.Token, ) service.GetContainerService().CleanupOnBoot() agent_server.ListenAndServe(util.FixListenAddress(cfg.ListenAgent), tlsConfig) if cfg.ListenTunnel != "" && !sameAddress { tunnel_server.ListenAndServe(tunnelAddr, tlsConfig) } audit.Log( model.AuditActorSystem, model.AuditActorTypeSystem, model.AuditEventSystemStart, "", &map[string]interface{}{ "build": build.Version, }, ) <-c serverCancel() sse.GetHub().Shutdown() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) if err := server.Shutdown(ctx); err != nil { server.Close() } cancel() cluster.Stop() fmt.Print("\r") logger.Info("shutdown") return nil }, }
Functions ¶
This section is empty.
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.