Skip to content

Commit e277f05

Browse files
feat: simplify quick backup to use existing repository config
- Removed auto-creation of quick backup repository in favor of using existing configured repositories - Added repository resolution logic to use default_repository from backup.yaml - Updated help text to reflect new behavior of using existing repository configuration - Added fallback logic to use sole repository if only one exists - Added clear error message when multiple repositories exist but no default is set Secondary changes: - Improve
1 parent 8c5f5f8 commit e277f05

6 files changed

Lines changed: 470 additions & 119 deletions

File tree

cmd/backup/quick.go

Lines changed: 47 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@ package backup
66
import (
77
"errors"
88
"fmt"
9-
"os"
109
"path/filepath"
10+
"sort"
1111
"strings"
1212
"time"
1313

1414
"github.com/CodeMonkeyCybersecurity/eos/pkg/backup"
15-
"github.com/CodeMonkeyCybersecurity/eos/pkg/crypto"
1615
eos "github.com/CodeMonkeyCybersecurity/eos/pkg/eos_cli"
1716
"github.com/CodeMonkeyCybersecurity/eos/pkg/eos_err"
1817
"github.com/CodeMonkeyCybersecurity/eos/pkg/eos_io"
19-
"github.com/CodeMonkeyCybersecurity/eos/pkg/vault"
2018
"github.com/spf13/cobra"
2119
"github.com/uptrace/opentelemetry-go-extra/otelzap"
2220
"go.uber.org/zap"
@@ -28,9 +26,9 @@ var quickBackupCmd = &cobra.Command{
2826
Short: "Quick backup of current (or specified) directory",
2927
Long: `Instantly backup the current directory or specified path with timestamp.
3028
31-
This command "just works" - no configuration needed:
32-
- Auto-creates local repository at ~/.eos/quick-backups
33-
- Auto-generates secure password (stored in Vault or local file)
29+
This command reuses your existing backup configuration:
30+
- Uses the default repository defined in /etc/eos/backup.yaml
31+
- Honors repository credentials and password files you already manage
3432
- Timestamps each backup automatically
3533
- Recursive by default
3634
@@ -71,16 +69,21 @@ Restore:
7169
zap.Strings("tags", tags),
7270
zap.Bool("dry_run", dryRun))
7371

74-
// Ensure quick backup repository exists
75-
if err := ensureQuickBackupRepo(rc); err != nil {
72+
repoName, repoConfig, err := resolveQuickBackupRepository(rc)
73+
if err != nil {
7674
if eos_err.IsExpectedUserError(err) {
7775
return err
7876
}
79-
return fmt.Errorf("initializing quick backup repository: %w", err)
77+
return err
8078
}
8179

82-
// Create backup client
83-
client, err := backup.NewClient(rc, "quick-backups")
80+
logger.Info("Using repository for quick backup",
81+
zap.String("repository", repoName),
82+
zap.String("backend", repoConfig.Backend),
83+
zap.String("url", repoConfig.URL))
84+
85+
// Create backup client using existing repository configuration
86+
client, err := backup.NewClient(rc, repoName)
8487
if err != nil {
8588
return fmt.Errorf("creating backup client: %w", err)
8689
}
@@ -123,118 +126,62 @@ Restore:
123126

124127
logger.Info("terminal prompt:", zap.String("output", string(output)))
125128
logger.Info("terminal prompt:", zap.String("output", fmt.Sprintf("\n✓ Backup complete: %s", absPath)))
126-
logger.Info("terminal prompt:", zap.String("output", "Repository: ~/.eos/quick-backups"))
129+
logger.Info("terminal prompt:", zap.String("output",
130+
fmt.Sprintf("Repository: %s (%s)", repoName, repoConfig.URL)))
127131
logger.Info("terminal prompt:", zap.String("output", "Restore: eos restore ."))
128132

129133
return nil
130134
}),
131135
}
132136

133-
// ensureQuickBackupRepo creates the quick backup repository if it doesn't exist
134-
func ensureQuickBackupRepo(rc *eos_io.RuntimeContext) error {
137+
func resolveQuickBackupRepository(rc *eos_io.RuntimeContext) (string, backup.Repository, error) {
135138
logger := otelzap.Ctx(rc.Ctx)
136139

137140
config, err := backup.LoadConfig(rc)
138141
if err != nil {
139-
config = &backup.Config{
140-
Repositories: make(map[string]backup.Repository),
141-
Profiles: make(map[string]backup.Profile),
142-
}
143-
}
144-
145-
repoName := backup.QuickBackupRepositoryName
146-
repoConfig, exists := config.Repositories[repoName]
147-
148-
homeDir, err := os.UserHomeDir()
149-
if err != nil {
150-
return fmt.Errorf("getting home directory: %w", err)
151-
}
152-
153-
defaultRepoPath := filepath.Join(homeDir, backup.QuickBackupRelativePath)
154-
155-
if repoConfig.URL == "" {
156-
repoConfig.URL = defaultRepoPath
157-
}
158-
if repoConfig.Backend == "" {
159-
repoConfig.Backend = "local"
160-
}
161-
if repoConfig.Name == "" {
162-
repoConfig.Name = repoName
163-
}
164-
165-
if err := os.MkdirAll(repoConfig.URL, 0700); err != nil {
166-
return fmt.Errorf("creating repository directory: %w", err)
167-
}
168-
169-
if _, err := ensureQuickBackupPassword(rc, repoConfig.URL); err != nil {
170-
return fmt.Errorf("ensuring password: %w", err)
171-
}
172-
173-
config.Repositories[repoName] = repoConfig
174-
config.DefaultRepository = repoName
175-
176-
if err := backup.SaveConfig(rc, config); err != nil {
177-
return fmt.Errorf("saving configuration: %w", err)
178-
}
179-
180-
client, err := backup.NewClient(rc, repoName)
181-
if err != nil {
182-
return fmt.Errorf("creating backup client: %w", err)
142+
return "", backup.Repository{}, fmt.Errorf("loading backup configuration: %w", err)
183143
}
184144

185-
configPath := filepath.Join(repoConfig.URL, "config")
186-
_, statErr := os.Stat(configPath)
187-
188-
if err := client.InitRepository(); err != nil {
189-
if errors.Is(err, backup.ErrResticNotInstalled) {
190-
logger.Info("terminal prompt:", zap.String("output",
191-
"Restic is not installed. Install restic (e.g., sudo apt-get install restic) and rerun eos backup ."))
192-
userErr := eos_err.DependencyError("restic", "initialize quick backup repository", err)
193-
return eos_err.NewExpectedError(rc.Ctx, userErr)
145+
repoName := strings.TrimSpace(config.DefaultRepository)
146+
if repoName != "" {
147+
if _, ok := config.Repositories[repoName]; !ok {
148+
return "", backup.Repository{}, fmt.Errorf("default repository %q not found in configuration", repoName)
194149
}
195-
return err
196-
}
197-
198-
if !exists || os.IsNotExist(statErr) {
199-
logger.Info("terminal prompt:", zap.String("output", "✓ Quick backup repository created at ~/.eos/quick-backups"))
150+
repo := config.Repositories[repoName]
151+
logger.Info("Using default repository for quick backup",
152+
zap.String("repository", repoName))
153+
return repoName, repo, nil
200154
}
201-
return nil
202-
}
203-
204-
// ensureQuickBackupPassword retrieves or generates the password for quick backups.
205-
func ensureQuickBackupPassword(rc *eos_io.RuntimeContext, repoPath string) (string, error) {
206-
logger := otelzap.Ctx(rc.Ctx)
207155

208-
passwordFile := filepath.Join(repoPath, ".password")
209-
if data, err := os.ReadFile(passwordFile); err == nil {
210-
password := strings.TrimSpace(string(data))
211-
if password != "" {
212-
return password, nil
213-
}
214-
logger.Warn("Quick backup password file is empty, generating new password",
215-
zap.String("path", passwordFile))
156+
if _, ok := config.Repositories[backup.QuickBackupRepositoryName]; ok {
157+
repo := config.Repositories[backup.QuickBackupRepositoryName]
158+
logger.Info("Using quick backup repository from configuration",
159+
zap.String("repository", backup.QuickBackupRepositoryName))
160+
return backup.QuickBackupRepositoryName, repo, nil
216161
}
217162

218-
vaultAddr := os.Getenv("VAULT_ADDR")
219-
if vaultAddr == "" {
220-
vaultAddr = "https://localhost:8200"
163+
if len(config.Repositories) == 0 {
164+
return "", backup.Repository{}, fmt.Errorf("no repositories configured; add at least one in /etc/eos/backup.yaml")
221165
}
222-
// TODO: Implement Vault password storage for quick backups once client supports WriteKV.
223-
_, _ = vault.NewClient(vaultAddr, logger.Logger().Logger)
224166

225-
password, err := crypto.GeneratePassword(backup.QuickBackupPasswordLength)
226-
if err != nil {
227-
return "", fmt.Errorf("generating password: %w", err)
167+
if len(config.Repositories) == 1 {
168+
for name := range config.Repositories {
169+
repo := config.Repositories[name]
170+
logger.Info("Using sole configured repository for quick backup",
171+
zap.String("repository", name))
172+
return name, repo, nil
173+
}
228174
}
229175

230-
if err := os.WriteFile(passwordFile, []byte(password), 0600); err != nil {
231-
return "", fmt.Errorf("writing password file: %w", err)
176+
repoNames := make([]string, 0, len(config.Repositories))
177+
for name := range config.Repositories {
178+
repoNames = append(repoNames, name)
232179
}
180+
sort.Strings(repoNames)
233181

234-
logger.Info("Password stored in local file",
235-
zap.String("path", passwordFile))
236-
237-
return password, nil
182+
return "", backup.Repository{}, eos_err.NewExpectedError(rc.Ctx, fmt.Errorf(
183+
"multiple repositories configured (%s) but no default_repository set; update /etc/eos/backup.yaml to select one",
184+
strings.Join(repoNames, ", ")))
238185
}
239186

240187
func init() {

cmd/list/hecate.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package list
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
"strings"
7+
8+
"github.com/CodeMonkeyCybersecurity/eos/pkg/eos_cli"
9+
"github.com/CodeMonkeyCybersecurity/eos/pkg/eos_io"
10+
"github.com/CodeMonkeyCybersecurity/eos/pkg/hecate"
11+
"github.com/spf13/cobra"
12+
"github.com/uptrace/opentelemetry-go-extra/otelzap"
13+
"go.uber.org/zap"
14+
)
15+
16+
var listHecateCmd = &cobra.Command{
17+
Use: "hecate",
18+
Short: "List Hecate resources and Authentik integrations",
19+
Long: `Inspect Hecate-managed integrations.
20+
21+
Use --brands to list Authentik brands and their associated authentication flows.`,
22+
RunE: eos_cli.Wrap(runListHecate),
23+
}
24+
25+
func init() {
26+
ListCmd.AddCommand(listHecateCmd)
27+
28+
listHecateCmd.Flags().Bool("brands", false, "List Authentik brands configured for Hecate")
29+
}
30+
31+
func runListHecate(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error {
32+
logger := otelzap.Ctx(rc.Ctx)
33+
34+
showBrands, _ := cmd.Flags().GetBool("brands")
35+
36+
switch {
37+
case showBrands:
38+
return runListHecateBrands(rc)
39+
default:
40+
logger.Info("No listing option provided. Use --brands to list Authentik brands.")
41+
return cmd.Help()
42+
}
43+
}
44+
45+
func runListHecateBrands(rc *eos_io.RuntimeContext) error {
46+
logger := otelzap.Ctx(rc.Ctx)
47+
48+
brands, err := hecate.ListAuthentikBrands(rc)
49+
if err != nil {
50+
return err
51+
}
52+
53+
if len(brands) == 0 {
54+
logger.Info("terminal prompt: No Authentik brands found")
55+
return nil
56+
}
57+
58+
sort.Slice(brands, func(i, j int) bool {
59+
return strings.ToLower(brands[i].Domain) < strings.ToLower(brands[j].Domain)
60+
})
61+
62+
logger.Info("terminal prompt: Authentik Brands")
63+
logger.Info("terminal prompt: " + strings.Repeat("=", 60))
64+
logger.Info("terminal prompt: Domain Flow Authentication")
65+
logger.Info("terminal prompt: " + strings.Repeat("-", 60))
66+
67+
for _, brand := range brands {
68+
domain := brand.Domain
69+
if domain == "" {
70+
domain = "(no domain)"
71+
}
72+
73+
flow := brand.FlowAuthentication
74+
if flow == "" {
75+
flow = "-"
76+
}
77+
78+
logger.Info(fmt.Sprintf("terminal prompt: %-30s %s", domain, flow),
79+
zap.String("brand_uuid", brand.PK),
80+
zap.String("flow_invalidation", brand.FlowInvalidation))
81+
}
82+
83+
return nil
84+
}

cmd/update/hecate.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ func init() {
239239

240240
// runAddServiceFromFlag handles adding a new service when --add flag is used
241241
func runAddServiceFromFlag(rc *eos_io.RuntimeContext, cmd *cobra.Command, service string) error {
242+
if service == "authentik-email" {
243+
dryRun, _ := cmd.Flags().GetBool("dry-run")
244+
return runAddAuthentikEmail(rc, dryRun)
245+
}
246+
242247
// Parse flags
243248
dns, _ := cmd.Flags().GetString("dns")
244249
upstream, _ := cmd.Flags().GetString("upstream")
@@ -290,6 +295,16 @@ func runAddServiceFromFlag(rc *eos_io.RuntimeContext, cmd *cobra.Command, servic
290295
return add.AddService(rc, opts)
291296
}
292297

298+
// runAddAuthentikEmail configures Authentik email settings using tenant API.
299+
func runAddAuthentikEmail(rc *eos_io.RuntimeContext, dryRun bool) error {
300+
if err := hecate.ConfigureAuthentikEmail(rc, &hecate.AuthentikEmailConfig{
301+
DryRun: dryRun,
302+
}); err != nil {
303+
return fmt.Errorf("failed to configure Authentik email settings: %w", err)
304+
}
305+
return nil
306+
}
307+
293308
// runRemoveServiceFromFlag handles removing a service when --remove flag is used
294309
func runRemoveServiceFromFlag(rc *eos_io.RuntimeContext, cmd *cobra.Command, service string) error {
295310
logger := otelzap.Ctx(rc.Ctx)

0 commit comments

Comments
 (0)