Compare commits
3 Commits
f2c484e9c1
...
7442a83c9d
Author | SHA1 | Date | |
---|---|---|---|
|
7442a83c9d | ||
|
7f6b030b31 | ||
|
f05124b703 |
@@ -103,5 +103,5 @@ This allows connecting to older routers that require legacy SSH algorithms while
|
||||
## Documentation
|
||||
|
||||
- **[Detailed Documentation](docs/DETAILS.md)** - Complete feature guide, configuration reference, and examples
|
||||
- **[Manual Page](docs/router_backup.1)** - Unix manual page
|
||||
- **[Manual Page](docs/ipng-router-backup.1)** - Unix manual page
|
||||
- **[Changelog](debian/changelog)** - Version history and changes
|
||||
|
8
debian/changelog
vendored
8
debian/changelog
vendored
@@ -1,3 +1,11 @@
|
||||
ipng-router-backup (1.2.0) stable; urgency=low
|
||||
|
||||
* Add atomic file operations with .new suffix for backup reliability
|
||||
* Add exit codes: 10 (some devices failed), 11 (all devices failed)
|
||||
* Update manpage filename to ipng-router-backup.1
|
||||
|
||||
-- Pim van Pelt <pim@ipng.ch> Sun, 07 Jul 2025 20:00:00 +0100
|
||||
|
||||
ipng-router-backup (1.1.1) stable; urgency=low
|
||||
|
||||
* Add 'address' field to device configuration for explicit IP/hostname override
|
||||
|
2
debian/rules
vendored
2
debian/rules
vendored
@@ -18,7 +18,7 @@ override_dh_auto_install:
|
||||
mkdir -p debian/ipng-router-backup/usr/share/man/man1
|
||||
cp ipng-router-backup debian/ipng-router-backup/usr/bin/
|
||||
cp etc/* debian/ipng-router-backup/etc/ipng-router-backup/
|
||||
cp docs/router_backup.1 debian/ipng-router-backup/usr/share/man/man1/ipng-router-backup.1
|
||||
cp docs/ipng-router-backup.1 debian/ipng-router-backup/usr/share/man/man1/ipng-router-backup.1
|
||||
gzip debian/ipng-router-backup/usr/share/man/man1/ipng-router-backup.1
|
||||
|
||||
override_dh_auto_clean:
|
||||
|
@@ -188,5 +188,7 @@ Software Version : v25.3.2
|
||||
- **Permission issues**: Verify SSH key permissions (600) and output directory access
|
||||
|
||||
### Exit Codes
|
||||
- `0`: Success
|
||||
- `1`: Configuration error, authentication failure, or connection issues
|
||||
- `0`: Success (all devices processed successfully)
|
||||
- `1`: Configuration error, authentication failure, or connection issues
|
||||
- `10`: Some devices failed
|
||||
- `11`: All devices failed
|
@@ -98,10 +98,16 @@ Example configuration file
|
||||
.SH EXIT STATUS
|
||||
.TP
|
||||
.B 0
|
||||
Success
|
||||
Success (all devices processed successfully)
|
||||
.TP
|
||||
.B 1
|
||||
General error (configuration file not found, authentication failure, etc.)
|
||||
.TP
|
||||
.B 10
|
||||
Some devices failed
|
||||
.TP
|
||||
.B 11
|
||||
All devices failed
|
||||
.SH AUTHOR
|
||||
Written by Pim van Pelt.
|
||||
.SH REPORTING BUGS
|
10
src/main.go
10
src/main.go
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const Version = "1.1.1"
|
||||
const Version = "1.2.0"
|
||||
|
||||
// Config and SSH types are now in separate packages
|
||||
|
||||
@@ -120,6 +120,14 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Printf("\nOverall summary: %d/%d devices processed successfully\n", successCount, totalCount)
|
||||
|
||||
// Set exit code based on results
|
||||
if successCount == 0 {
|
||||
os.Exit(11) // All devices failed
|
||||
} else if successCount < totalCount {
|
||||
os.Exit(10) // Some devices failed
|
||||
}
|
||||
// Exit code 0 (success) when all devices succeeded
|
||||
},
|
||||
}
|
||||
|
||||
|
33
src/ssh.go
33
src/ssh.go
@@ -209,29 +209,34 @@ func (rb *RouterBackup) BackupCommands(commands []string, outputDir string) erro
|
||||
}
|
||||
|
||||
filename := rb.hostname
|
||||
filepath := filepath.Join(outputDir, filename)
|
||||
finalPath := filepath.Join(outputDir, filename)
|
||||
tempPath := finalPath + ".new"
|
||||
|
||||
// Truncate file at start
|
||||
file, err := os.Create(filepath)
|
||||
// Create temporary file
|
||||
file, err := os.Create(tempPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file %s: %v", filepath, err)
|
||||
return fmt.Errorf("failed to create temporary file %s: %v", tempPath, err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
successCount := 0
|
||||
hasErrors := false
|
||||
|
||||
for i, command := range commands {
|
||||
fmt.Printf("Running command %d/%d: %s\n", i+1, len(commands), command)
|
||||
output, err := rb.RunCommand(command)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error executing '%s': %v\n", command, err)
|
||||
hasErrors = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Append to file
|
||||
file, err := os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
// Append to temporary file
|
||||
file, err := os.OpenFile(tempPath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open file for writing: %v\n", err)
|
||||
hasErrors = true
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -243,9 +248,21 @@ func (rb *RouterBackup) BackupCommands(commands []string, outputDir string) erro
|
||||
}
|
||||
|
||||
fmt.Printf("Summary: %d/%d commands successful\n", successCount, len(commands))
|
||||
if successCount > 0 {
|
||||
fmt.Printf("Output saved to %s\n", filepath)
|
||||
|
||||
if hasErrors || successCount == 0 {
|
||||
// Remove .new suffix and log error
|
||||
if err := os.Remove(tempPath); err != nil {
|
||||
fmt.Printf("Failed to remove temporary file %s: %v\n", tempPath, err)
|
||||
}
|
||||
return fmt.Errorf("device backup incomplete due to command failures")
|
||||
}
|
||||
|
||||
// All commands succeeded, move file into place atomically
|
||||
if err := os.Rename(tempPath, finalPath); err != nil {
|
||||
return fmt.Errorf("failed to move temporary file to final location: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Output saved to %s\n", finalPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user