Previously _load_registry silently dropped dead PIDs, so jobs that
completed in the background were invisible on restart. Now dead PIDs
are loaded as 'success' status so the user sees they completed. The
registry is cleaned up after loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously action_quit() killed all running jobs before exiting,
making it impossible to reconnect to them on restart. Now shows a
confirmation dialog when jobs are running and lets them continue
in the background.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace tee with _snaplog_tee shell function that writes each line
unbuffered to three destinations: snapshot log, application log file,
and stderr (which the TUI captures for live display). This fixes the
issue where rsync file-by-file output was invisible in both the Logs
screen and the Running Tasks view due to pipe buffering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Jobs are now saved to gniza-jobs.json in WORK_DIR when they start and
finish. On TUI restart, the registry is loaded and PIDs are checked —
still-running jobs appear in the Running Tasks screen and can be
killed. Reconnected jobs are polled every second to detect completion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When TARGET_INCLUDE has a directory pattern like 'embajada/', rsync
would include the directory but exclude its contents (files didn't
match any include rule before hitting --exclude=*). Now adds a
'pattern**' include for each directory pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save log, rsync_error, summary, and index files into each snapshot
directory after backup. Rsync/rclone output is captured via tee during
transfer so the TUI still shows live output while logging to disk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Jobs were tied to screen @work tasks, so switch_screen cancelled the
worker and lost the process reference. Now start_job uses
asyncio.create_task so jobs survive screen changes and can be killed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SIGTERM was being ignored by child processes. Use SIGKILL via the
actual process group ID, with proc.kill() as fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Start CLI subprocesses in their own session so SIGTERM via
os.killpg reaches child processes (rsync, etc.) not just the shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backup and restore operations now run as background jobs instead of
blocking modal screens. Users can navigate away and check progress
from a dedicated Running Tasks screen. OperationLog supports attaching
to running jobs with live output polling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exit 23 (partial transfer, permission denied) and 24 (vanished files)
are expected in non-root backups. These no longer trigger retries or
fail the backup — they log a warning and continue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Utility commands (schedule, snapshots, remotes, etc.) were creating
empty 0 B log files on every invocation. Now init_logging only runs
for commands that produce meaningful log output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cron runs with a very limited PATH that may not include sshpass, rsync,
etc. Add explicit PATH to generated cron lines and log output to
cron.log instead of /dev/null for debugging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
grep returns exit 1 when no match, which with set -eo pipefail kills
the script. Add || true to the grep pipeline in credential extraction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The grep -v pipeline combined with set -eo pipefail caused the entire
installer to exit silently when the user answered "y" to web dashboard.
Replace with proper if/else and add user-level systemd service support
(systemctl --user) for non-root installs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply shquote() to all remaining remote_exec paths that interpolate
variables into single-quoted shell strings. Covers list/resolve/clean
snapshots, symlink updates, retention pruning, and restore file listing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add shquote() to escape single quotes in paths passed to remote_exec,
preventing shell injection via REMOTE_BASE containing single quotes
- Apply shquote to remote_exec calls in remotes.sh, backup.sh, transfer.sh, ssh.sh
- Add DISK_USAGE_THRESHOLD validation in config.sh
- Export SMTP_PASSWORD (was missing from export list)
- Fix WEB_PORT default mismatch: use 2323 consistently in from_conf and settings save
- Narrow exception catch in remotes.py disk info fetch to KeyError/LookupError
- Quote REMOTE_KEY in build_rsync_ssh_cmd for paths with spaces
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse df output locally using awk to find the % field and extract
the 3 size fields before it. Handles wrapped lines and CR chars.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move awk parsing to the remote to avoid SSH output encoding issues
that caused empty fields when parsing locally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
df --output=pcent is not available on all systems. Use grep -oP to
extract the percentage field from standard df output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use df --output=pcent for reliable single-column output instead of
parsing multi-column df output through awk over SSH.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Disk usage threshold (default 95%) can now be controlled from
Settings. Set to 0 to disable the check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add TARGET_INCLUDE field for rsync include patterns (comma-separated)
- Pass TARGET_INCLUDE and TARGET_EXCLUDE to rsync in transfer_folder
- Include mode uses --include='*/' + patterns + --exclude='*' + --prune-empty-dirs
- Abort backup if remote disk usage >= 95%
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Disk info is now shown directly in the table. Speed test and detailed
disk info buttons/commands are no longer needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Disk Info: runs df -h and df -i on remote via SSH (or locally)
- Speed Test: uploads/downloads 10MB test file via rsync, measures Mbps
- Both available as CLI commands: gniza remotes disk-info/speed-test --name=NAME
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Last Run: timestamp of most recent backup log file
- Next Run: calculated from schedule type, time, and day settings
- Handles hourly, daily, weekly, monthly schedule types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Browser prompts for username/password before showing the TUI.
Credentials from gniza.conf: WEB_USER (default: admin) + WEB_API_KEY.
- Monkey-patches textual-serve's aiohttp app with auth middleware
- Uses secrets.compare_digest for timing-safe comparison
- Install script generates credentials and prints them
- Skips auth if no WEB_API_KEY configured
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The landing page wasn't interactive because public_url resolved to
localhost, making WebSocket connections fail from remote browsers.
- Added multiple IP detection methods (socket, hostname -I, gethostbyname)
- Support --port= and --host= flag formats
- Print actual serving URL on startup
- Switch web start back to textual-serve (TUI in browser)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Revert from textual-serve back to Flask (textual-serve had WebSocket issues)
- Completely redesigned dashboard: modern dark theme, stat cards, clean tables
- Redesigned login page to match
- Restored API key generation in install script
- Keep API key field in TUI settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Serves the exact same TUI in the browser via textual-serve.
No more separate Flask app, API keys, or login page needed.
- gniza web start now runs textual-serve instead of Flask
- Simplified systemd service to use python3 -m tui --web
- Removed web_enabled and web_api_key from settings/models
- Simplified install script web setup (no API key generation)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>