diff --git a/internal/app/repository.go b/internal/app/repository.go index 1b1f225..d598308 100644 --- a/internal/app/repository.go +++ b/internal/app/repository.go @@ -17,7 +17,7 @@ func (r *Repository) List() ([]App, error) { rows, err := r.db.Query(` SELECT id, name, server_id, domain, port, compose, git_repo, git_branch, auth_user, auth_pass, status, unique_service_name, created_at, updated_at - FROM apps ORDER BY created_at DESC + FROM apps ORDER BY name ASC `) if err != nil { return nil, err diff --git a/internal/backup/backup.go b/internal/backup/backup.go index 9b3b801..ff6fde8 100644 --- a/internal/backup/backup.go +++ b/internal/backup/backup.go @@ -209,7 +209,14 @@ func (s *Service) Export(passphrase string) (string, error) { return "", fmt.Errorf("list files for %q: %w", a.Name, err) } for _, f := range files { - ea.Files = append(ea.Files, ExportFile{Path: f.Path, Content: f.Content}) + content := f.Content + if passphrase != "" { + content, err = encrypt(f.Content, passphrase) + if err != nil { + return "", fmt.Errorf("encrypt file %q for %q: %w", f.Path, a.Name, err) + } + } + ea.Files = append(ea.Files, ExportFile{Path: f.Path, Content: content}) } data.Apps = append(data.Apps, ea) @@ -224,7 +231,7 @@ func (s *Service) Export(passphrase string) (string, error) { sb.WriteString("# Dockify Configuration Export\n") sb.WriteString("# Auth passwords (if present) are included as saved. SSH keys are not exported.\n") if passphrase != "" { - sb.WriteString("# Secret values are encrypted with the provided passphrase.\n") + sb.WriteString("# Secret values and config file contents are encrypted with the provided passphrase.\n") } sb.WriteString("# Remove or edit entries before import as needed.\n") sb.Write(out) @@ -252,6 +259,11 @@ func (s *Service) Import(yamlData, passphrase, mode string) (string, error) { return "", fmt.Errorf("%q secret %q: %w", ea.Name, sec.Key, err) } } + for _, f := range ea.Files { + if err := validateDecrypt(f.Content, passphrase); err != nil { + return "", fmt.Errorf("%q file %q: %w", ea.Name, f.Path, err) + } + } } if mode == "replace" { @@ -352,7 +364,14 @@ func (s *Service) Import(yamlData, passphrase, mode string) (string, error) { } for _, f := range ea.Files { - if err := s.appSvc.SetFile(ap.ID, f.Path, f.Content); err != nil { + content := f.Content + if passphrase != "" { + content, err = decrypt(f.Content, passphrase) + if err != nil { + return strings.Join(logLines, "\n"), fmt.Errorf("%q: wrong passphrase or corrupted data", ea.Name) + } + } + if err := s.appSvc.SetFile(ap.ID, f.Path, content); err != nil { return strings.Join(logLines, "\n"), fmt.Errorf("set file %q for %q: %w", f.Path, ea.Name, err) } } diff --git a/internal/http/templates/layout.html b/internal/http/templates/layout.html index 689865f..a911bb3 100644 --- a/internal/http/templates/layout.html +++ b/internal/http/templates/layout.html @@ -275,19 +275,6 @@ letter-spacing: 0.03em; text-transform: uppercase; } -.dev-mock-badge { - display: inline-block; - background: var(--yellow); - color: #000; - font-size: 0.72em; - font-weight: 700; - padding: 0.15em 0.5em; - border-radius: 3px; - animation: blink-pulse 1.5s ease-in-out infinite; - letter-spacing: 0.04em; - text-transform: uppercase; - margin-left: 0.5em; -} @media (max-width: 600px) { .container { padding: 0.8em 1em; } @@ -299,7 +286,7 @@ {{define "nav"}} {{if .DevMock}}
⚠ Dev Mock Mode — All SSH operations are simulated
{{end}}
- +