Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 3 additions & 37 deletions cmd/proxsave/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"os"
"path/filepath"
"strings"

"github.com/tis24dev/proxsave/pkg/utils"
)

type configStatusLogger interface {
Expand Down Expand Up @@ -47,43 +49,7 @@ func ensureConfigExists(path string, logger configStatusLogger) error {
}

func setEnvValue(template, key, value string) string {
target := key + "="
lines := strings.Split(template, "\n")
replaced := false
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, target) {
leadingLen := len(line) - len(strings.TrimLeft(line, " \t"))
leading := ""
if leadingLen > 0 {
leading = line[:leadingLen]
}
rest := line[leadingLen:]
commentSpacing := ""
comment := ""
if idx := strings.Index(rest, "#"); idx >= 0 {
before := rest[:idx]
comment = rest[idx:]
trimmedBefore := strings.TrimRight(before, " \t")
commentSpacing = before[len(trimmedBefore):]
rest = trimmedBefore
}
newLine := leading + key + "=" + value
if comment != "" {
spacing := commentSpacing
if spacing == "" {
spacing = " "
}
newLine += spacing + comment
}
lines[i] = newLine
replaced = true
}
}
if !replaced {
lines = append(lines, key+"="+value)
}
return strings.Join(lines, "\n")
return utils.SetEnvValue(template, key, value)
}

func sanitizeEnvValue(value string) string {
Expand Down
18 changes: 18 additions & 0 deletions cmd/proxsave/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ func TestSetEnvValue_PreserveComment(t *testing.T) {
assertContains(t, result, "# this is a comment")
}

func TestSetEnvValue_PreserveCommentAfterQuotedValue(t *testing.T) {
template := `FOO="old # keep" # trailing comment`

result := setEnvValue(template, "FOO", "new")

assertContains(t, result, "FOO=new")
assertContains(t, result, "# trailing comment")
}

func TestSetEnvValue_PreserveCommentWithEscapedHash(t *testing.T) {
template := `FOO=old\#keep # trailing comment`

result := setEnvValue(template, "FOO", "new")

assertContains(t, result, "FOO=new")
assertContains(t, result, "# trailing comment")
}

func TestSetEnvValue_AddNew(t *testing.T) {
template := "FOO=bar"

Expand Down
12 changes: 12 additions & 0 deletions cmd/proxsave/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@
bootstrap.Error("ERROR: Failed to plan configuration upgrade: %v", err)
return types.ExitConfigError.Int()
}
if len(result.Warnings) > 0 {
bootstrap.Warning("Config upgrade warnings (%d):", len(result.Warnings))
for _, warning := range result.Warnings {
bootstrap.Warning(" - %s", warning)
}
}
if !result.Changed {
bootstrap.Println("Configuration is already up to date with the embedded template; no changes are required.")
return types.ExitSuccess.Int()
Expand Down Expand Up @@ -437,6 +443,12 @@
bootstrap.Error("ERROR: Failed to upgrade configuration: %v", err)
return types.ExitConfigError.Int()
}
if len(result.Warnings) > 0 {
bootstrap.Warning("Config upgrade warnings (%d):", len(result.Warnings))
for _, warning := range result.Warnings {
bootstrap.Warning(" - %s", warning)
}
}
if !result.Changed {
bootstrap.Println("Configuration is already up to date with the embedded template; no changes were made.")
return types.ExitSuccess.Int()
Expand Down Expand Up @@ -1549,7 +1561,7 @@
}
fmt.Printf("\r Remaining: %ds ", int(remaining.Seconds()))

select {

Check failure on line 1564 in cmd/proxsave/main.go

View workflow job for this annotation

GitHub Actions / security

should use a simple channel send/receive instead of select with a single case (S1000)

Check failure on line 1564 in cmd/proxsave/main.go

View workflow job for this annotation

GitHub Actions / security

should use a simple channel send/receive instead of select with a single case (S1000)

Check failure on line 1564 in cmd/proxsave/main.go

View workflow job for this annotation

GitHub Actions / security

should use a simple channel send/receive instead of select with a single case (S1000)

Check failure on line 1564 in cmd/proxsave/main.go

View workflow job for this annotation

GitHub Actions / security

should use a simple channel send/receive instead of select with a single case (S1000)
case <-ticker.C:
continue
}
Expand Down
8 changes: 8 additions & 0 deletions cmd/proxsave/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,14 @@ func printUpgradeFooter(upgradeErr error, version, configPath, baseDir, telegram
}
}

if cfgUpgradeResult != nil && len(cfgUpgradeResult.Warnings) > 0 {
fmt.Printf("Configuration warnings (%d):\n", len(cfgUpgradeResult.Warnings))
for _, warning := range cfgUpgradeResult.Warnings {
fmt.Printf(" - %s\n", warning)
}
fmt.Println()
}

fmt.Println("Next steps:")
if strings.TrimSpace(configPath) != "" {
fmt.Printf("1. Verify configuration: %s\n", configPath)
Expand Down
22 changes: 14 additions & 8 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1218,8 +1218,12 @@ func parseEnvFile(path string) (map[string]string, error) {
if !ok {
continue
}
if fields := strings.Fields(key); len(fields) >= 2 && fields[0] == "export" {
key = fields[1]
}
upperKey := strings.ToUpper(key)

if blockValueKeys[key] && trimmed == fmt.Sprintf("%s=\"", key) {
if blockValueKeys[upperKey] && trimmed == fmt.Sprintf("%s=\"", key) {
var blockLines []string
terminated := false
for scanner.Scan() {
Expand All @@ -1228,23 +1232,25 @@ func parseEnvFile(path string) (map[string]string, error) {
terminated = true
break
}
blockLines = append(blockLines, next)
if !utils.IsComment(strings.TrimSpace(next)) {
blockLines = append(blockLines, next)
}
}
if !terminated {
return nil, fmt.Errorf("unterminated multi-line value for %s", key)
}
raw[key] = strings.Join(blockLines, "\n")
raw[upperKey] = strings.Join(blockLines, "\n")
continue
}

if multiValueKeys[key] {
if existing, ok := raw[key]; ok && existing != "" {
raw[key] = existing + "\n" + value
if multiValueKeys[upperKey] {
if existing, ok := raw[upperKey]; ok && existing != "" {
raw[upperKey] = existing + "\n" + value
} else {
raw[key] = value
raw[upperKey] = value
}
} else {
raw[key] = value
raw[upperKey] = value
}
}

Expand Down
54 changes: 54 additions & 0 deletions internal/config/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,60 @@ BACKUP_BLACKLIST="
}
}

func TestParseEnvFileHandlesLowercaseKeys(t *testing.T) {
tmpDir := t.TempDir()
envFile := filepath.Join(tmpDir, "legacy.env")
content := `
custom_backup_paths="
/etc/custom
"
backup_blacklist="
/tmp
"
backup_exclude_patterns=*.log
backup_exclude_patterns=*.tmp
`
if err := os.WriteFile(envFile, []byte(content), 0600); err != nil {
t.Fatalf("failed to write temp legacy env: %v", err)
}

values, err := parseEnvFile(envFile)
if err != nil {
t.Fatalf("parseEnvFile returned error: %v", err)
}

if got := values["CUSTOM_BACKUP_PATHS"]; got != "/etc/custom" {
t.Fatalf("CUSTOM_BACKUP_PATHS = %q; want block content", got)
}
if got := values["BACKUP_BLACKLIST"]; got != "/tmp" {
t.Fatalf("BACKUP_BLACKLIST = %q; want block content", got)
}
if got := values["BACKUP_EXCLUDE_PATTERNS"]; got != "*.log\n*.tmp" {
t.Fatalf("BACKUP_EXCLUDE_PATTERNS = %q; want \"*.log\\n*.tmp\"", got)
}
}

func TestParseEnvFileHandlesExportLines(t *testing.T) {
tmpDir := t.TempDir()
envFile := filepath.Join(tmpDir, "legacy.env")
content := "export BACKUP_PATH=/data\nexport\t\tLOG_PATH=/logs\n"
if err := os.WriteFile(envFile, []byte(content), 0600); err != nil {
t.Fatalf("failed to write temp legacy env: %v", err)
}

values, err := parseEnvFile(envFile)
if err != nil {
t.Fatalf("parseEnvFile returned error: %v", err)
}

if got := values["BACKUP_PATH"]; got != "/data" {
t.Fatalf("BACKUP_PATH = %q; want %q", got, "/data")
}
if got := values["LOG_PATH"]; got != "/logs" {
t.Fatalf("LOG_PATH = %q; want %q", got, "/logs")
}
}

const testTemplate = `# test template
BACKUP_PATH=/default/backup
LOG_PATH=/default/log
Expand Down
Loading
Loading