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>
- Fix GNIZA_CONFIG_DIR in systemd service: /usr/local/gniza/etc -> /etc/gniza
- Add LOG_DIR and PYTHONPATH to systemd environment
- Fix default CONFIG_DIR in web/app.py and web/__main__.py
- Refactor install.sh web setup into function for robustness
- Replace Unicode box-drawing chars with ASCII in install.sh
- Read from /dev/tty to prevent stdin issues
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>