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
|
## Documentation
|
||||||
|
|
||||||
- **[Detailed Documentation](docs/DETAILS.md)** - Complete feature guide, configuration reference, and examples
|
- **[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
|
- **[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
|
ipng-router-backup (1.1.1) stable; urgency=low
|
||||||
|
|
||||||
* Add 'address' field to device configuration for explicit IP/hostname override
|
* 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
|
mkdir -p debian/ipng-router-backup/usr/share/man/man1
|
||||||
cp ipng-router-backup debian/ipng-router-backup/usr/bin/
|
cp ipng-router-backup debian/ipng-router-backup/usr/bin/
|
||||||
cp etc/* debian/ipng-router-backup/etc/ipng-router-backup/
|
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
|
gzip debian/ipng-router-backup/usr/share/man/man1/ipng-router-backup.1
|
||||||
|
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
|
@@ -188,5 +188,7 @@ Software Version : v25.3.2
|
|||||||
- **Permission issues**: Verify SSH key permissions (600) and output directory access
|
- **Permission issues**: Verify SSH key permissions (600) and output directory access
|
||||||
|
|
||||||
### Exit Codes
|
### Exit Codes
|
||||||
- `0`: Success
|
- `0`: Success (all devices processed successfully)
|
||||||
- `1`: Configuration error, authentication failure, or connection issues
|
- `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
|
.SH EXIT STATUS
|
||||||
.TP
|
.TP
|
||||||
.B 0
|
.B 0
|
||||||
Success
|
Success (all devices processed successfully)
|
||||||
.TP
|
.TP
|
||||||
.B 1
|
.B 1
|
||||||
General error (configuration file not found, authentication failure, etc.)
|
General error (configuration file not found, authentication failure, etc.)
|
||||||
|
.TP
|
||||||
|
.B 10
|
||||||
|
Some devices failed
|
||||||
|
.TP
|
||||||
|
.B 11
|
||||||
|
All devices failed
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Written by Pim van Pelt.
|
Written by Pim van Pelt.
|
||||||
.SH REPORTING BUGS
|
.SH REPORTING BUGS
|
10
src/main.go
10
src/main.go
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "1.1.1"
|
const Version = "1.2.0"
|
||||||
|
|
||||||
// Config and SSH types are now in separate packages
|
// 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)
|
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
|
filename := rb.hostname
|
||||||
filepath := filepath.Join(outputDir, filename)
|
finalPath := filepath.Join(outputDir, filename)
|
||||||
|
tempPath := finalPath + ".new"
|
||||||
|
|
||||||
// Truncate file at start
|
// Create temporary file
|
||||||
file, err := os.Create(filepath)
|
file, err := os.Create(tempPath)
|
||||||
if err != nil {
|
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()
|
file.Close()
|
||||||
|
|
||||||
successCount := 0
|
successCount := 0
|
||||||
|
hasErrors := false
|
||||||
|
|
||||||
for i, command := range commands {
|
for i, command := range commands {
|
||||||
fmt.Printf("Running command %d/%d: %s\n", i+1, len(commands), command)
|
fmt.Printf("Running command %d/%d: %s\n", i+1, len(commands), command)
|
||||||
output, err := rb.RunCommand(command)
|
output, err := rb.RunCommand(command)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error executing '%s': %v\n", command, err)
|
fmt.Printf("Error executing '%s': %v\n", command, err)
|
||||||
|
hasErrors = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append to file
|
// Append to temporary file
|
||||||
file, err := os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY, 0644)
|
file, err := os.OpenFile(tempPath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to open file for writing: %v\n", err)
|
fmt.Printf("Failed to open file for writing: %v\n", err)
|
||||||
|
hasErrors = true
|
||||||
continue
|
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))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user