Skip to content

Commit e75b61e

Browse files
authored
feat: backup the original file before manage it by lazyssh (#49)
1 parent b7fb586 commit e75b61e

File tree

4 files changed

+44
-4
lines changed

4 files changed

+44
-4
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ It is simply a UI/TUI wrapper around your existing `~/.ssh/config` file.
5353
- File permissions on your SSH config are preserved to ensure security.
5454

5555

56+
## 🛡️ Config Safety: Non‑destructive writes and backups
57+
58+
- Non‑destructive edits: lazyssh only writes the minimal required changes to your ~/.ssh/config. It uses a parser that preserves existing comments, spacing, order, and any settings it didn’t touch. Your handcrafted comments and formatting remain intact.
59+
- Atomic writes: updates are written to a temporary file and then atomically renamed over the original, minimizing the risk of partial writes.
60+
- Backups:
61+
- One‑time original backup: before lazyssh makes its first change, it creates a single snapshot named config.original.backup beside your SSH config. If this file is present, it will never be recreated or overwritten.
62+
- Rolling backups: on every subsequent save, lazyssh also creates a timestamped backup named like: ~/.ssh/config-<timestamp>-lazyssh.backup. The app keeps at most 10 of these backups, automatically removing the oldest ones.
63+
5664
## 📷 Screenshots
5765

5866
<div align="center">

internal/adapters/data/ssh_config_file/backup.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,29 @@ func (r *Repository) findBackupFiles(dir string) ([]os.FileInfo, error) {
125125

126126
return backupFiles, nil
127127
}
128+
129+
// createOriginalBackupIfNeeded creates a one-time original backup of the current SSH config.
130+
func (r *Repository) createOriginalBackupIfNeeded() error {
131+
// If no SSH config file, nothing to do.
132+
if _, err := r.fileSystem.Stat(r.configPath); os.IsNotExist(err) {
133+
return nil
134+
} else if err != nil {
135+
return fmt.Errorf("failed to check if config file exists: %w", err)
136+
}
137+
138+
configDir := filepath.Dir(r.configPath)
139+
originalBackupPath := filepath.Join(configDir, OriginalBackupName)
140+
141+
if _, err := r.fileSystem.Stat(originalBackupPath); err == nil {
142+
return nil
143+
} else if !r.fileSystem.IsNotExist(err) {
144+
return fmt.Errorf("failed to check if original backup exists: %w", err)
145+
}
146+
147+
if err := r.copyFile(r.configPath, originalBackupPath); err != nil {
148+
return fmt.Errorf("failed to create original backup: %w", err)
149+
}
150+
151+
r.logger.Infof("Created original backup: %s", originalBackupPath)
152+
return nil
153+
}

internal/adapters/data/ssh_config_file/config_io.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ func (r *Repository) saveConfig(cfg *ssh_config.Config) error {
6666
return fmt.Errorf("failed to write config to temporary file: %w", err)
6767
}
6868

69+
// Ensure a one-time original backup exists before any modifications managed by lazyssh.
70+
if err := r.createOriginalBackupIfNeeded(); err != nil {
71+
return fmt.Errorf("failed to create original backup: %w", err)
72+
}
73+
6974
if err := r.createBackup(); err != nil {
7075
return fmt.Errorf("failed to create backup: %w", err)
7176
}

internal/adapters/data/ssh_config_file/crud.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import (
2323
)
2424

2525
const (
26-
MaxBackups = 10
27-
TempSuffix = ".tmp"
28-
BackupSuffix = "lazyssh.backup"
29-
SSHConfigPerms = 0o600
26+
MaxBackups = 10
27+
TempSuffix = ".tmp"
28+
BackupSuffix = "lazyssh.backup"
29+
SSHConfigPerms = 0o600
30+
OriginalBackupName = "config.original.backup"
3031
)
3132

3233
// filterServers filters servers based on the query string.

0 commit comments

Comments
 (0)