command

package
v0.22.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 8, 2026 License: Apache-2.0 Imports: 45 Imported by: 0

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.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL