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, }, 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: "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") backupCfgValues := cmd.GetBool("cfg-values") backupAuditLogs := cmd.GetBool("audit-logs") backupAll := cmd.GetBool("all") if backupTemplates || backupVars || backupVolumes || backupGroups || backupRoles || backupUsers || backupSpaces || backupTokens || backupCfgValues || backupAuditLogs { backupAll = false } if backupAll { backupTemplates = true backupVars = true backupVolumes = true backupGroups = true backupRoles = true backupUsers = true backupSpaces = true backupTokens = 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(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 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 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 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.
Click to show internal directories.
Click to hide internal directories.