Skip to content
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)
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