-
Notifications
You must be signed in to change notification settings - Fork 0
Vibe branch #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Vibe branch #73
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Setup Self-Backup | ||
|
|
||
| SimpleSaferServer can back up the files that its setup flow owns. This helps you recover the web app setup after a reinstall without copying the whole server. | ||
|
|
||
| This is not a full system backup. | ||
|
|
||
| ## What It Includes | ||
|
|
||
| The archive includes these files when they exist: | ||
|
|
||
| - SimpleSaferServer config from `/etc/SimpleSaferServer` | ||
| - the user database, secret key, encrypted secrets, alerts, and disabled timer records | ||
| - rclone config from root's rclone config folder | ||
| - msmtp config from `/etc/msmtprc` | ||
| - SimpleSaferServer-owned Samba include files | ||
|
|
||
| It does not include `/etc/fstab`. That is intentional. Restoring an old mount table can make a server fail to boot or mount the wrong disk. | ||
|
|
||
| It also does not include the full backup drive data, operating system files, package state, or unmanaged Samba config. | ||
|
|
||
| ## Automatic Backup | ||
|
|
||
| After setup is complete, SimpleSaferServer creates a self-backup on the configured backup drive. | ||
|
|
||
| It also installs a daily `setup_self_backup.timer`. This timer runs one minute before the normal cloud backup time, so the fresh self-backup archive can be copied to your cloud target by the normal cloud backup. | ||
|
|
||
| Archives are stored here on the mounted backup drive: | ||
|
|
||
| ```text | ||
| SimpleSaferServer-self-backups/ | ||
| ``` | ||
|
|
||
| Only the newest 30 archives are kept by the scheduled command. | ||
|
|
||
| ## Manual Backup | ||
|
|
||
| Run this as root: | ||
|
|
||
| ```bash | ||
| sudo /opt/SimpleSaferServer/.venv/bin/python /opt/SimpleSaferServer/scripts/setup_self_backup.py create | ||
| ``` | ||
|
|
||
| To list existing archives: | ||
|
|
||
| ```bash | ||
| sudo /opt/SimpleSaferServer/.venv/bin/python /opt/SimpleSaferServer/scripts/setup_self_backup.py list | ||
| ``` | ||
|
|
||
| To write to a specific mounted drive path: | ||
|
|
||
| ```bash | ||
| sudo /opt/SimpleSaferServer/.venv/bin/python /opt/SimpleSaferServer/scripts/setup_self_backup.py create --destination /media/backup | ||
| ``` | ||
|
|
||
| ## Restore During Setup | ||
|
|
||
| Use this after reinstalling SimpleSaferServer, before finishing the setup wizard. | ||
|
|
||
| 1. Mount the backup drive. | ||
| 2. Find the archive under `SimpleSaferServer-self-backups/`. | ||
| 3. Restore it: | ||
|
|
||
| ```bash | ||
| sudo /opt/SimpleSaferServer/.venv/bin/python /opt/SimpleSaferServer/scripts/setup_self_backup.py restore /media/backup/SimpleSaferServer-self-backups/setup-self-backup-YYYYMMDDTHHMMSSZ.tar.gz | ||
| ``` | ||
|
|
||
| By default, restore sets `system.setup_complete` to `false`. This lets the setup wizard reinstall services, timers, Samba share setup, and the managed backup-drive setup for the current machine. | ||
|
|
||
| After the restore, open the setup wizard and finish setup. Check the backup drive step carefully. The self-backup does not restore `/etc/fstab`, so the current backup drive still needs to be mounted and registered by setup. | ||
|
|
||
| ## Restore On A Running Install | ||
|
|
||
| The restore command is mainly meant for reinstall recovery. If you run it on an already working install, restart SimpleSaferServer afterwards so the web app reloads the restored files. | ||
|
|
||
| Only use `--preserve-setup-complete` when you understand that setup will not be forced to rerun service and timer installation: | ||
|
|
||
| ```bash | ||
| sudo /opt/SimpleSaferServer/.venv/bin/python /opt/SimpleSaferServer/scripts/setup_self_backup.py restore /path/to/archive.tar.gz --preserve-setup-complete | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| import argparse | ||
| import json | ||
| import logging | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| def _add_app_to_path() -> None: | ||
| script_path = Path(__file__).resolve() | ||
| candidates = [script_path.parents[1], Path("/opt/SimpleSaferServer")] | ||
| for candidate in candidates: | ||
| if (candidate / "simple_safer_server").is_dir(): | ||
| sys.path.insert(0, str(candidate)) | ||
| return | ||
|
Comment on lines
+10
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Document the path-injection fallback and implicit default command. Please add brief inline comments explaining (1) why ✍️ Suggested comment additions def _add_app_to_path() -> None:
+ # This script may run from installed locations where project root is not
+ # already on PYTHONPATH (for example under /opt).
script_path = Path(__file__).resolve()
candidates = [script_path.parents[1], Path("/opt/SimpleSaferServer")]
@@
def main() -> int:
@@
- command = args.command or "create"
+ # Timer/non-interactive invocations omit a subcommand, so default to create.
+ command = args.command or "create"As per coding guidelines, Also applies to: 75-76 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
|
|
||
| try: | ||
| from simple_safer_server.services.config_manager import ConfigManager | ||
| from simple_safer_server.services.runtime import get_runtime | ||
| from simple_safer_server.services.setup_self_backup import ( | ||
| DEFAULT_RETENTION_COUNT, | ||
| SetupSelfBackupError, | ||
| SetupSelfBackupService, | ||
| ) | ||
| except ImportError: | ||
| _add_app_to_path() | ||
| from simple_safer_server.services.config_manager import ConfigManager | ||
| from simple_safer_server.services.runtime import get_runtime | ||
| from simple_safer_server.services.setup_self_backup import ( | ||
| DEFAULT_RETENTION_COUNT, | ||
| SetupSelfBackupError, | ||
| SetupSelfBackupService, | ||
| ) | ||
|
|
||
|
|
||
| def _service() -> SetupSelfBackupService: | ||
| runtime = get_runtime() | ||
| return SetupSelfBackupService(runtime, ConfigManager(runtime=runtime)) | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser( | ||
| description="Create, list, or restore SimpleSaferServer setup self-backups." | ||
| ) | ||
| subparsers = parser.add_subparsers(dest="command") | ||
|
|
||
| create = subparsers.add_parser("create", help="Create a self-backup archive.") | ||
| create.add_argument("--destination", help="Mounted backup drive path to write under.") | ||
| create.add_argument( | ||
| "--keep", | ||
| type=int, | ||
| default=DEFAULT_RETENTION_COUNT, | ||
| help=f"Number of newest archives to keep. Default: {DEFAULT_RETENTION_COUNT}.", | ||
| ) | ||
|
|
||
| list_parser = subparsers.add_parser("list", help="List self-backup archives.") | ||
| list_parser.add_argument("--destination", help="Mounted backup drive path to inspect.") | ||
| list_parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.") | ||
|
|
||
| restore = subparsers.add_parser("restore", help="Restore a self-backup archive.") | ||
| restore.add_argument("archive", help="Archive path to restore.") | ||
| restore.add_argument( | ||
| "--preserve-setup-complete", | ||
| action="store_true", | ||
| help="Keep system.setup_complete exactly as stored in the archive.", | ||
| ) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def main() -> int: | ||
| logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") | ||
| args = parse_args() | ||
| command = args.command or "create" | ||
| service = _service() | ||
|
|
||
| if command == "create": | ||
| result = service.create_backup( | ||
| destination=args.destination, | ||
| retention_count=args.keep, | ||
| ) | ||
| print(f"Created setup self-backup: {result['archive']}") | ||
| print(f"Files included: {result['file_count']}") | ||
| return 0 | ||
|
|
||
| if command == "list": | ||
| backups = service.list_backups(destination=args.destination) | ||
| if args.json: | ||
| print(json.dumps(backups, indent=2)) | ||
| return 0 | ||
| if not backups: | ||
| print("No setup self-backups found.") | ||
| return 0 | ||
| for backup in backups: | ||
| print(f"{backup['name']} {backup['modified_at']} {backup['size']} bytes") | ||
| return 0 | ||
|
|
||
| if command == "restore": | ||
| result = service.restore_backup( | ||
| args.archive, | ||
| force_setup_incomplete=not args.preserve_setup_complete, | ||
| ) | ||
| print(f"Restored setup self-backup: {result['archive']}") | ||
| print(f"Files restored: {len(result['restored'])}") | ||
| if not args.preserve_setup_complete: | ||
| print("system.setup_complete was set to false so setup can reinstall services.") | ||
| return 0 | ||
|
|
||
| raise SetupSelfBackupError(f"Unknown command: {command}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| try: | ||
| raise SystemExit(main()) | ||
| except SetupSelfBackupError as exc: | ||
| print(f"ERROR: {exc}", file=sys.stderr) | ||
| raise SystemExit(1) from exc | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Add
relattributes to the external docs link opened in a new tab.Line 162 uses
target="_blank"withoutrel="noopener noreferrer", which weakens tab isolation.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents