commands_admin

package
v0.33.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AdminCmd = &cli.Command{
	Name:        "admin",
	Usage:       "Admin Operations",
	Description: "Run administration operations for the server.",
	MaxArgs:     cli.NoArgs,
	Flags: []cli.Flag{

		&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,
			Global:       true,
		},
		&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",
			Global:       true,
		},
		&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,
			Global:       true,
		},
		&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",
			Global:       true,
		},
		&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: "",
			Global:       true,
		},
		&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",
			Global:       true,
		},
		&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,
			Global:       true,
		},
		&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,
			Global:       true,
		},
		&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,
			Global:       true,
		},

		&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,
			Global:       true,
		},
		&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",
			Global:       true,
		},

		&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,
			Global:       true,
		},
		&cli.StringFlag{
			Name:         "redis-host",
			Usage:        "The redis server.",
			ConfigPath:   []string{"server.redis.host"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_REDIS_HOST"},
			DefaultValue: "localhost:6379",
			Global:       true,
		},
		&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: "",
			Global:       true,
		},
		&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,
			Global:       true,
		},
		&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: "",
			Global:       true,
		},
		&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: "",
			Global:       true,
		},
		&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: "",
			Global:       true,
		},
	},
	Commands: []*cli.Command{
		RenameZoneCmd,
		SetPasswordCmd,
		ResetTOTPCmd,
		BackupCmd,
		RestoreCmd,
		RefreshBaseImagesCmd,
	},
	PreRun: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
		var err error

		if ctx, err = cmd.GetRootCmd().PreRun(ctx, cmd); err != nil {
			return ctx, err
		}

		serverCfg := &config.ServerConfig{
			EncryptionKey: cmd.GetString("encrypt"),
			MySQL: config.MySQLConfig{
				Enabled:               cmd.GetBool("mysql-enabled"),
				Host:                  cmd.GetString("mysql-host"),
				Port:                  cmd.GetInt("mysql-port"),
				User:                  cmd.GetString("mysql-user"),
				Password:              cmd.GetString("mysql-password"),
				Database:              cmd.GetString("mysql-database"),
				ConnectionMaxIdle:     cmd.GetInt("mysql-connection-max-idle"),
				ConnectionMaxOpen:     cmd.GetInt("mysql-connection-max-open"),
				ConnectionMaxLifetime: cmd.GetInt("mysql-connection-max-lifetime"),
			},
			BadgerDB: config.BadgerDBConfig{
				Enabled: cmd.GetBool("badgerdb-enabled"),
				Path:    cmd.GetString("badgerdb-path"),
			},
			Redis: config.RedisConfig{
				Enabled:    cmd.GetBool("redis-enabled"),
				Hosts:      cmd.GetStringSlice("redis-hosts"),
				Password:   cmd.GetString("redis-password"),
				DB:         cmd.GetInt("redis-db"),
				MasterName: cmd.GetString("redis-master-name"),
				KeyPrefix:  cmd.GetString("redis-key-prefix"),
			},
			Audit: config.AuditConfig{
				Retention: cmd.GetInt("audit-retention"),
			},
		}
		config.SetServerConfig(serverCfg)

		return ctx, nil
	},
}
View Source
var BackupCmd = &cli.Command{
	Name:        "backup",
	Usage:       "Backup to File",
	Description: "Backup the database to a backup file.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "backupfile",
			Usage:    "The name of the backup file",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:       "templates",
			Aliases:    []string{"t"},
			Usage:      "Backup templates",
			ConfigPath: []string{"backup.templates"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_TEMPLATES"},
		},
		&cli.BoolFlag{
			Name:       "template-vars",
			Aliases:    []string{"v"},
			Usage:      "Backup template variables",
			ConfigPath: []string{"backup.template_vars"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_TEMPLATE_VARS"},
		},
		&cli.BoolFlag{
			Name:       "volumes",
			Aliases:    []string{"l"},
			Usage:      "Backup volumes",
			ConfigPath: []string{"backup.volumes"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_VOLUMES"},
		},
		&cli.BoolFlag{
			Name:       "groups",
			Aliases:    []string{"g"},
			Usage:      "Backup groups",
			ConfigPath: []string{"backup.groups"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_GROUPS"},
		},
		&cli.BoolFlag{
			Name:       "roles",
			Aliases:    []string{"r"},
			Usage:      "Backup roles",
			ConfigPath: []string{"backup.roles"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_ROLES"},
		},
		&cli.BoolFlag{
			Name:       "spaces",
			Aliases:    []string{"s"},
			Usage:      "Backup user spaces",
			ConfigPath: []string{"backup.spaces"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_SPACES"},
		},
		&cli.BoolFlag{
			Name:       "users",
			Aliases:    []string{"u"},
			Usage:      "Backup users",
			ConfigPath: []string{"backup.users"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_USERS"},
		},
		&cli.BoolFlag{
			Name:       "tokens",
			Aliases:    []string{"k"},
			Usage:      "Backup user tokens",
			ConfigPath: []string{"backup.tokens"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_TOKENS"},
		},
		&cli.BoolFlag{
			Name:       "cfg-values",
			Aliases:    []string{"o"},
			Usage:      "Backup configuration values",
			ConfigPath: []string{"backup.cfg_values"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_CFG_VALUES"},
		},
		&cli.BoolFlag{
			Name:       "scripts",
			Aliases:    []string{"c"},
			Usage:      "Backup scripts",
			ConfigPath: []string{"backup.scripts"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_SCRIPTS"},
		},
		&cli.BoolFlag{
			Name:       "skills",
			Aliases:    []string{"i"},
			Usage:      "Backup skills",
			ConfigPath: []string{"backup.skills"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_SKILLS"},
		},
		&cli.BoolFlag{
			Name:       "commands",
			Usage:      "Backup slash commands",
			ConfigPath: []string{"backup.commands"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_COMMANDS"},
		},
		&cli.BoolFlag{
			Name:       "responses",
			Aliases:    []string{"p"},
			Usage:      "Backup responses",
			ConfigPath: []string{"backup.responses"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_RESPONSES"},
		},
		&cli.BoolFlag{
			Name:       "audit-logs",
			Usage:      "Backup audit logs",
			ConfigPath: []string{"backup.audit_logs"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_AUDIT_LOGS"},
		},
		&cli.BoolFlag{
			Name:         "all",
			Aliases:      []string{"a"},
			Usage:        "Backup everything",
			ConfigPath:   []string{"backup.all"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_BACKUP_ALL"},
			DefaultValue: true,
		},
		&cli.StringFlag{
			Name:       "limit-user",
			Usage:      "Limit the backup to a specific user by username.",
			ConfigPath: []string{"backup.limit_user"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_LIMIT_USER"},
		},
		&cli.StringFlag{
			Name:       "limit-template",
			Usage:      "Limit the backup to a specific template by name.",
			ConfigPath: []string{"backup.limit_template"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_LIMIT_TEMPLATE"},
		},
		&cli.StringFlag{
			Name:       "encrypt-key",
			Aliases:    []string{"e"},
			Usage:      "Encrypt the backup file with the given key. The key must be 32 bytes long.",
			ConfigPath: []string{"backup.encrypt_key"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BACKUP_ENCRYPT_KEY"},
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		outputFile := cmd.GetStringArg("backupfile")
		fmt.Println("Backing up database to file: ", outputFile)

		backupTemplates := cmd.GetBool("templates")
		backupVars := cmd.GetBool("template-vars")
		backupVolumes := cmd.GetBool("volumes")
		backupGroups := cmd.GetBool("groups")
		backupRoles := cmd.GetBool("roles")
		backupUsers := cmd.GetBool("users")
		backupSpaces := cmd.GetBool("spaces")
		backupTokens := cmd.GetBool("tokens")
		backupScripts := cmd.GetBool("scripts")
		backupSkills := cmd.GetBool("skills")
		backupCommands := cmd.GetBool("commands")
		backupResponses := cmd.GetBool("responses")
		backupCfgValues := cmd.GetBool("cfg-values")
		backupAuditLogs := cmd.GetBool("audit-logs")
		backupAll := cmd.GetBool("all")

		if backupTemplates || backupVars || backupVolumes || backupGroups || backupRoles || backupUsers || backupSpaces || backupTokens || backupScripts || backupSkills || backupCommands || backupResponses || backupCfgValues || backupAuditLogs {
			backupAll = false
		}

		if backupAll {
			backupTemplates = true
			backupVars = true
			backupVolumes = true
			backupGroups = true
			backupRoles = true
			backupUsers = true
			backupSpaces = true
			backupTokens = true
			backupScripts = true
			backupSkills = true
			backupCommands = true
			backupResponses = true
			backupCfgValues = true
			backupAuditLogs = true
		}

		limitUser := cmd.GetString("limit-user")
		limitTemplate := cmd.GetString("limit-template")

		key := cmd.GetString("encrypt-key")
		if key != "" && len(key) != 32 {
			return fmt.Errorf("Error: Encrypt key must be 32 bytes long.")
		}

		db := database.GetInstance()
		backupData := backupData{}

		if backupAuditLogs {
			fmt.Println("Backing up audit logs...")
			auditLogs, _, err := db.GetAuditLogs(nil, 0, 0)
			if err != nil {
				return fmt.Errorf("Error getting audit logs: %w", err)
			}
			backupData.AuditLogs = make([]*model.AuditLogEntry, len(auditLogs))
			copy(backupData.AuditLogs, auditLogs)
		}

		if backupCfgValues {
			fmt.Println("Backing up configuration values...")
			cfgValues, err := db.GetCfgValues()
			if err != nil {
				return fmt.Errorf("Error getting configuration values: %w", err)
			}
			backupData.CfgValues = make([]*model.CfgValue, len(cfgValues))
			copy(backupData.CfgValues, cfgValues)
		}

		if backupTemplates {
			fmt.Println("Backing up templates...")
			templates, err := db.GetTemplates()
			if err != nil {
				return fmt.Errorf("Error getting templates: %w", err)
			}
			backupData.Templates = make([]*model.Template, 0, len(templates))
			for _, t := range templates {
				if limitTemplate == "" || t.Name == limitTemplate {
					backupData.Templates = append(backupData.Templates, t)
				}
			}
			backupData.Templates = slices.Clip(backupData.Templates)
		}

		if backupVars {
			fmt.Println("Backing up template variables...")
			variables, err := db.GetTemplateVars()
			if err != nil {
				return fmt.Errorf("Error getting template variables: %w", err)
			}
			backupData.TemplateVars = make([]*model.TemplateVar, len(variables))
			for i, v := range variables {
				backupData.TemplateVars[i] = v
			}
		}

		if backupVolumes {
			fmt.Println("Backing up volumes...")
			volumes, err := db.GetVolumes()
			if err != nil {
				return fmt.Errorf("Error getting volumes: %w", err)
			}
			backupData.Volumes = make([]*model.Volume, len(volumes))
			copy(backupData.Volumes, volumes)
		}

		if backupGroups {
			fmt.Println("Backing up groups...")
			groups, err := db.GetGroups()
			if err != nil {
				return fmt.Errorf("Error getting groups: %w", err)
			}
			backupData.Groups = make([]*model.Group, len(groups))
			copy(backupData.Groups, groups)
		}

		if backupRoles {
			fmt.Println("Backing up roles...")
			roles, err := db.GetRoles()
			if err != nil {
				return fmt.Errorf("Error getting roles: %w", err)
			}
			backupData.Roles = make([]*model.Role, len(roles))
			copy(backupData.Roles, roles)
		}

		if backupScripts {
			fmt.Println("Backing up scripts...")
			scripts, err := db.GetScripts()
			if err != nil {
				return fmt.Errorf("Error getting scripts: %w", err)
			}
			backupData.Scripts = make([]*model.Script, len(scripts))
			copy(backupData.Scripts, scripts)
		}

		if backupSkills {
			fmt.Println("Backing up skills...")
			skills, err := db.GetSkills()
			if err != nil {
				return fmt.Errorf("Error getting skills: %w", err)
			}
			backupData.Skills = make([]*model.Skill, len(skills))
			copy(backupData.Skills, skills)
		}

		if backupCommands {
			fmt.Println("Backing up slash commands...")
			commands, err := db.GetCommands()
			if err != nil {
				return fmt.Errorf("Error getting commands: %w", err)
			}
			backupData.Commands = make([]*model.Command, len(commands))
			copy(backupData.Commands, commands)
		}

		if backupResponses {
			fmt.Println("Backing up responses...")
			responses, err := db.GetResponses()
			if err != nil {
				return fmt.Errorf("Error getting responses: %w", err)
			}
			backupData.Responses = make([]*model.Response, len(responses))
			copy(backupData.Responses, responses)
		}

		if backupUsers {
			fmt.Println("Backing up users...")
			users, err := db.GetUsers()
			if err != nil {
				return fmt.Errorf("Error getting users: %w", err)
			}
			backupData.Users = make([]backupUser, 0, len(users))
			for _, u := range users {
				if limitUser != "" && u.Username != limitUser {
					continue
				}
				bu := backupUser{
					User: u,
				}
				if backupTokens {
					tokens, err := db.GetTokensForUser(u.Id)
					if err != nil {
						return fmt.Errorf("Error getting tokens for user: %w", err)
					}
					bu.Tokens = make([]*model.Token, len(tokens))
					copy(bu.Tokens, tokens)
				}
				if backupSpaces {
					spaces, err := db.GetSpacesForUser(u.Id)
					if err != nil {
						return fmt.Errorf("Error getting spaces: %w", err)
					}
					bu.Spaces = make([]*model.Space, len(spaces))
					for j, s := range spaces {
						space, err := db.GetSpace(s.Id)
						if err != nil {
							return fmt.Errorf("Error getting space: %w", err)
						}
						bu.Spaces[j] = space
					}
				}
				backupData.Users = append(backupData.Users, bu)
			}
			backupData.Users = slices.Clip(backupData.Users)
		}

		data, err := json.Marshal(backupData)
		if err != nil {
			return fmt.Errorf("Error marshalling backup data: %w", err)
		}

		if key != "" {
			data = []byte(crypt.Encrypt(key, string(data)))
		}

		err = os.WriteFile(outputFile, data, 0644)
		if err != nil {
			return fmt.Errorf("Error writing backup file: %w", err)
		}

		fmt.Println("Database backup completed successfully.")
		return nil
	},
}
View Source
var RefreshBaseImagesCmd = &cli.Command{
	Name:  "refresh-base-images",
	Usage: "Force a base image manifest refresh across the cluster",
	Description: `Force every server to fetch the base image manifest from its update URL immediately.

Each server keeps the fetched copy only if it is newer than its active catalog. By default the command fans out to all servers in the cluster (via cluster-info); pass --local-only to refresh just the connected server. Each server must have --base-images-update-enabled on; if a server uses a manifest file it must also have --base-images-update-url set, otherwise that server reports a conflict and keeps using its file.`,
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:    "server",
			Aliases: []string{"s"},
			Usage:   "The address of the remote server.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_SERVER"},
		},
		&cli.StringFlag{
			Name:    "token",
			Aliases: []string{"t"},
			Usage:   "API token for the remote server (must be valid cluster-wide).",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TOKEN"},
		},
		&cli.StringFlag{
			Name:         "alias",
			Aliases:      []string{"a"},
			Usage:        "The configured server alias to use.",
			DefaultValue: "default",
		},
		&cli.BoolFlag{
			Name:         "tls-skip-verify",
			Usage:        "Skip TLS verification when talking to the servers.",
			ConfigPath:   []string{"tls.skip_verify"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"},
			DefaultValue: true,
		},
		&cli.BoolFlag{
			Name:  "local-only",
			Usage: "Refresh only the connected server instead of fanning out across the cluster.",
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}
		token := client.GetAuthToken()
		skipVerify := cmd.GetBool("tls-skip-verify")
		localOnly := cmd.GetBool("local-only")

		refreshOne(ctx, labelFor(client), client)

		if localOnly {
			return nil
		}

		info, _, err := client.GetServerInfo(ctx)
		if err != nil || info == nil || !info.Clustered {
			return nil
		}

		peers, _, err := client.GetClusterInfo(ctx)
		if err != nil {
			fmt.Printf("  (couldn't enumerate cluster peers: %v; refreshed only the connected server)\n", cleanRestError(err))
			return nil
		}
		if peers == nil {
			return nil
		}

		seen := normalizeURL(client.GetBaseURL())
		for _, p := range *peers {
			ep := p.Metadata["api_endpoint"]
			if ep == "" || normalizeURL(ep) == seen {
				continue
			}
			seen += " " + normalizeURL(ep)
			peer, err := apiclient.NewClient(ep, token, skipVerify)
			if err != nil {
				fmt.Printf("  %s: skipped (%v)\n", nodeLabel(p), err)
				continue
			}
			refreshOne(ctx, nodeLabel(p), peer)
		}
		return nil
	},
}

RefreshBaseImagesCmd triggers a base image manifest refresh. By default it fans the request out to every server in the cluster: it asks the connected server for its peers (GET /api/cluster-info, which carries each node's api_endpoint) and POSTs the refresh endpoint to each one with the same token. Use --local-only to refresh just the connected server.

Each server fetches the manifest itself from its configured update URL — no server-to-server content sync — so the whole fleet converges on the same (newest) catalog without gossip.

View Source
var RenameZoneCmd = &cli.Command{
	Name:  "rename-zone",
	Usage: "Rename a Zone",
	Description: `Rename a zone.

The zone name is updated within the database however spaces and volumes are not moved.`,
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "old",
			Usage:    "The old zone name",
			Required: true,
		},
		&cli.StringArg{
			Name:     "new",
			Usage:    "The new zone name",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		oldZone := cmd.GetStringArg("old")
		newZone := cmd.GetStringArg("new")

		fmt.Println("Renaming zone", oldZone, "to", newZone)
		fmt.Print("This command will not move any spaces or volumes between zones.\n\n")

		// Prompt the user to confirm the deletion
		var confirm string
		fmt.Printf("Are you sure you want to rename zone %s (yes/no): ", oldZone)
		fmt.Scanln(&confirm)
		if confirm != "yes" {
			fmt.Println("Rename cancelled.")
			return nil
		}

		db := database.GetInstance()

		fmt.Print("Updating volumes\n")
		volumes, err := db.GetVolumes()
		if err != nil {
			fmt.Println("Error getting volumes: ", err)
			return nil
		}

		for _, volume := range volumes {
			fmt.Print("Checking Volume: ", volume.Name)
			if volume.Zone == oldZone {
				volume.Zone = newZone
				volume.UpdatedAt = hlc.Now()
				err := db.SaveVolume(volume, []string{"Zone", "UpdatedAt"})
				if err != nil {
					fmt.Println("Error updating volume: ", err)
					return nil
				}
				fmt.Print(" - Updated\n")
			} else {
				fmt.Print(" - Skipping\n")
			}
		}

		fmt.Print("\nUpdating spaces\n")
		spaces, err := db.GetSpaces()
		if err != nil {
			fmt.Println("Error getting spaces: ", err)
			return nil
		}

		for _, space := range spaces {
			fmt.Print("Checking Space: ", space.Name)
			if space.Zone == oldZone {
				space.Zone = newZone
				space.UpdatedAt = hlc.Now()
				err := db.SaveSpace(space, []string{"Zone", "UpdatedAt"})
				if err != nil {
					fmt.Println("Error updating space: ", err)
					return nil
				}
				fmt.Print(" - Updated\n")
			} else {
				fmt.Print(" - Skipping\n")
			}
		}

		fmt.Print("\nZone renamed\n")
		return nil
	},
}
View Source
var ResetTOTPCmd = &cli.Command{
	Name:        "reset-totp",
	Usage:       "Reset a users TOTP",
	Description: "Clear the current TOTP for the specified user so that on next login a new secret is generated.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "email-address",
			Usage:    "The email address of the user to reset the TOTP for",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		email := cmd.GetStringArg("email-address")

		fmt.Println("Resetting TOTP for user: ", email)

		db := database.GetInstance()

		user, err := db.GetUserByEmail(email)
		if err != nil {
			fmt.Println("Error getting user: ", err)
			return nil
		}

		user.TOTPSecret = ""
		user.UpdatedAt = hlc.Now()

		err = db.SaveUser(user, []string{"TOTPSecret", "UpdatedAt"})
		if err != nil {
			fmt.Println("Error saving user: ", err)
			return nil
		}

		fmt.Print("\nTOTP Reset\n")
		return nil
	},
}
View Source
var RestoreCmd = &cli.Command{
	Name:        "restore",
	Usage:       "Restore a backup file",
	Description: "Restore the database from a backup file.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "backupfile",
			Usage:    "The name of the backup file to restore",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:    "encrypt-key",
			Aliases: []string{"e"},
			Usage:   "Encrypt the backup file with the given key. The key must be 32 bytes long.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_RESTORE_ENCRYPT_KEY"},
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		inputFile := cmd.GetStringArg("backupfile")
		key := cmd.GetString("encrypt-key")
		if key != "" && len(key) != 32 {
			return fmt.Errorf("Error: Encrypt key must be 32 bytes long.")
		}

		fmt.Println("Restoring database from file: ", inputFile)
		db := database.GetInstance()
		backupData := backupData{}

		data, err := os.ReadFile(inputFile)
		if err != nil {
			return fmt.Errorf("Error loading backup file: %w", err)
		}

		if key != "" {

			data = []byte(crypt.Decrypt(key, string(data)))
		}

		err = json.Unmarshal(data, &backupData)
		if err != nil {
			return fmt.Errorf("Error unmarshalling backup file: %w", err)
		}

		fmt.Println("Restoring audit logs...")
		for _, auditLog := range backupData.AuditLogs {
			err := db.SaveAuditLog(auditLog)
			if err != nil {
				return fmt.Errorf("Error restoring audit log: %w", err)
			}
			fmt.Println("Restored audit log: ", auditLog.Event)
		}

		fmt.Println("Restoring configuration values...")
		for _, cfgValue := range backupData.CfgValues {
			err := db.SaveCfgValue(cfgValue)
			if err != nil {
				return fmt.Errorf("Error restoring configuration value: %w", err)
			}
			fmt.Println("Restored configuration value: ", cfgValue.Name)
		}

		fmt.Println("Restoring templates...")
		for _, template := range backupData.Templates {
			err := db.SaveTemplate(template, nil)
			if err != nil {
				return fmt.Errorf("Error restoring template: %w", err)
			}
			fmt.Println("Restored template: ", template.Name)
		}

		fmt.Println("Restoring template variables...")
		for _, variable := range backupData.TemplateVars {
			err := db.SaveTemplateVar(variable)
			if err != nil {
				return fmt.Errorf("Error restoring template variable: %w", err)
			}
			fmt.Println("Restored template variable: ", variable.Name)
		}

		fmt.Println("Restoring volumes...")
		for _, volume := range backupData.Volumes {
			err := db.SaveVolume(volume, nil)
			if err != nil {
				return fmt.Errorf("Error restoring volume: %w", err)
			}
			fmt.Println("Restored volume: ", volume.Name)
		}

		fmt.Println("Restoring groups...")
		for _, group := range backupData.Groups {
			err := db.SaveGroup(group)
			if err != nil {
				return fmt.Errorf("Error restoring group: %w", err)
			}
			fmt.Println("Restored group: ", group.Name)
		}

		fmt.Println("Restoring roles...")
		for _, role := range backupData.Roles {
			err := db.SaveRole(role)
			if err != nil {
				return fmt.Errorf("Error restoring role: %w", err)
			}
			fmt.Println("Restored role: ", role.Name)
		}

		fmt.Println("Restoring scripts...")
		for _, script := range backupData.Scripts {
			err := db.SaveScript(script, nil)
			if err != nil {
				return fmt.Errorf("Error restoring script: %w", err)
			}
			fmt.Println("Restored script: ", script.Name)
		}

		fmt.Println("Restoring skills...")
		for _, skill := range backupData.Skills {
			err := db.SaveSkill(skill, nil)
			if err != nil {
				return fmt.Errorf("Error restoring skill: %w", err)
			}
			fmt.Println("Restored skill: ", skill.Name)
		}

		fmt.Println("Restoring slash commands...")
		for _, command := range backupData.Commands {
			err := db.SaveCommand(command, nil)
			if err != nil {
				return fmt.Errorf("Error restoring command: %w", err)
			}
			fmt.Println("Restored command: ", command.Name)
		}

		fmt.Println("Restoring responses...")
		for _, response := range backupData.Responses {
			err := db.SaveResponse(response)
			if err != nil {
				return fmt.Errorf("Error restoring response: %w", err)
			}
			fmt.Println("Restored response: ", response.Id)
		}

		fmt.Println("Restoring users...")
		for _, user := range backupData.Users {
			err := db.SaveUser(user.User, nil)
			if err != nil {
				return fmt.Errorf("Error restoring user: %w", err)
			}
			fmt.Println("Restored user: ", user.User.Username)

			fmt.Println("Restoring tokens for user: ", user.User.Username)
			for _, token := range user.Tokens {
				err = db.SaveToken(token)
				if err != nil {
					return fmt.Errorf("Error restoring token for user: %w", err)
				}
				fmt.Println("Restored token for user: ", user.User.Username, token.Name)
			}

			fmt.Println("Restoring spaces for user: ", user.User.Username)
			for _, space := range user.Spaces {

				if space.StartedAt.IsZero() {
					space.StartedAt = time.Now().UTC()
				}

				err := db.SaveSpace(space, nil)
				if err != nil {
					return fmt.Errorf("Error restoring space: %w", err)
				}
				fmt.Println("Restored space: ", space.Name)
			}
		}

		fmt.Println("Database restore completed successfully.")
		return nil
	},
}
View Source
var SetPasswordCmd = &cli.Command{
	Name:        "set-password",
	Usage:       "Reset a users password",
	Description: "Set a new password for the specified user.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "email-address",
			Usage:    "The email address of the user to reset the password for",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		email := cmd.GetStringArg("email-address")

		fmt.Println("Setting new password for user: ", email)

		fmt.Printf("Enter the new password: ")
		password, err := term.ReadPassword(int(syscall.Stdin))
		if err != nil {
			return fmt.Errorf("Failed to read password: %w", err)
		}
		fmt.Println()

		db := database.GetInstance()

		user, err := db.GetUserByEmail(email)
		if err != nil {
			return fmt.Errorf("Error getting user: %w", err)
		}

		user.SetPassword(string(password))
		user.UpdatedAt = hlc.Now()

		err = db.SaveUser(user, []string{"Password", "UpdatedAt"})
		if err != nil {
			return fmt.Errorf("Error saving user: %w", err)
		}

		fmt.Print("\nPassword set\n")
		return nil
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

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