diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index b785341..eeaaeb7 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -23,6 +23,7 @@ Complete reference for gniza, a Linux backup manager that works as a stand-alone - [CLI Reference](#cli-reference) - [Global Settings](#global-settings) - [Security](#security) +- [Development](#development) - [Troubleshooting](#troubleshooting) --- @@ -647,9 +648,19 @@ gniza --cli schedule remove Cron entries are tagged with `# gniza4linux:` for clean management. Running `schedule install` replaces existing entries cleanly. +### How Scheduled Backups Run + +Each cron entry calls `gniza scheduled-run --schedule=`. This internal command: + +1. Reads the schedule config to determine which sources and destinations to back up. +2. Runs the backup (same as `gniza --cli backup`). +3. On success, stamps `LAST_RUN="YYYY-MM-DD HH:MM"` in the schedule config file. + +The `LAST_RUN` timestamp is displayed in the Schedules screen of the TUI. + ### Cron Logs -Scheduled backups log output to `/cron.log`. +Scheduled backups log output to `/cron.log`. Each run also creates a timestamped log file in the log directory (using local time). --- @@ -750,6 +761,19 @@ Notification emails include: - Log file path - Hostname and timestamp +### Test Email + +Verify your SMTP settings by sending a test email: + +**TUI**: Settings > Send Test Email (automatically saves settings first). + +**CLI**: +```bash +gniza --cli test-email +``` + +Requires `NOTIFY_EMAIL` and `SMTP_HOST` to be configured. + ### Fallback If SMTP is not configured, gniza falls back to the system `mail` or `sendmail` command. @@ -783,16 +807,32 @@ gniza web start --port=8080 # Custom port gniza web start --host=127.0.0.1 # Bind to localhost only ``` -### Authentication +### Configuration -Credentials are configured in `gniza.conf`: +Web dashboard settings are in `gniza.conf`: ```ini -WEB_USER="admin" -WEB_API_KEY="generated-during-install" +WEB_PORT="2323" # Dashboard port +WEB_HOST="0.0.0.0" # Bind address (0.0.0.0 = all interfaces) +WEB_USER="admin" # HTTP Basic Auth username +WEB_API_KEY="generated-during-install" # HTTP Basic Auth password ``` -The API key is generated automatically during installation if you enable the web dashboard. You can change it manually. +The API key is generated automatically during installation if you enable the web dashboard. You can change it in Settings or directly in `gniza.conf`. + +After changing web settings, restart the service: +```bash +systemctl --user restart gniza-web.service # user mode +sudo systemctl restart gniza-web.service # root mode +``` + +### Mobile Access + +The web dashboard works on mobile browsers. On small screens: +- Font size auto-adjusts to fit approximately 50 columns +- Button rows scroll horizontally +- The documentation panel hides automatically on narrow screens +- Touch scrolling is supported in all scrollable areas ### Root vs User Mode @@ -814,10 +854,25 @@ Launch with `gniza` (no arguments). Requires Python 3 and Textual. | **Backup** | Select sources and destinations, run backups | | **Restore** | Browse snapshots, restore to original or custom location | | **Running Tasks** | Monitor active jobs with live log output and progress | -| **Schedules** | Create and manage cron schedules | +| **Schedules** | Create and manage cron schedules, view last run time | | **Snapshots** | Browse stored snapshots | | **Logs** | View backup history with pagination | -| **Settings** | Configure global options | +| **Settings** | Configure global options in organized sections | + +### Navigation + +Every screen has a **← Back** button in the top-left corner next to the screen title. Press `Escape` or click the button to return to the previous screen. + +### Settings Screen + +The Settings screen organizes options into four bordered sections: + +| Section | Contents | +|---------|----------| +| **General** | Log level, log retention, retention count, bandwidth limit, disk threshold, rsync options | +| **Email Notifications** | Email address, notify mode, SMTP host/port/user/password/from/security, Send Test Email button | +| **SSH** | SSH timeout, SSH retries | +| **Web Dashboard** | Port, host, API key | ### Features @@ -826,7 +881,7 @@ Launch with `gniza` (no arguments). Requires Python 3 and Textual. - **Remote Folder Browser**: Browse SSH destination directories - **Connection Testing**: Test destination connectivity from the edit screen - **Documentation Panel**: Inline help on wide screens, help modal on narrow ones -- **Responsive Layout**: Adapts to terminal width +- **Responsive Layout**: Adapts to terminal width; button rows scroll horizontally on narrow screens - **Job Manager**: Run backups/restores in background with live log streaming ### Keyboard Shortcuts @@ -912,6 +967,7 @@ gniza --cli retention --destination=NAME # One destination gniza --cli schedule install # Install cron entries gniza --cli schedule show # Show current entries gniza --cli schedule remove # Remove all entries +gniza scheduled-run --schedule=NAME # Run a schedule (used by cron) ``` ### Logs @@ -932,6 +988,12 @@ gniza web remove-service # Remove service gniza web status # Check status ``` +### Notifications + +```bash +gniza --cli test-email # Send a test email +``` + ### System ```bash @@ -967,9 +1029,11 @@ All global settings are in `gniza.conf` in the config directory. | Setting | Default | Description | |---------|---------|-------------| -| `LOG_LEVEL` | `info` | `info` or `debug` | +| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` | | `LOG_RETAIN` | `90` | Days to keep log files | +Log files are named using local time (e.g., `gniza-20260307-040001.log`) to match cron schedules which also run in local time. + ### Notifications | Setting | Default | Description | @@ -1019,6 +1083,23 @@ All global settings are in `gniza.conf` in the config directory. --- +## Development + +### Deploy Script + +For developers, `scripts/deploy.sh` automates the deploy workflow: + +```bash +bash scripts/deploy.sh "commit message" +``` + +This script: +1. Commits and pushes all changes to git +2. Syncs updated files to the local install directory (`~/.local/share/gniza`) +3. Restarts the web dashboard service if running + +--- + ## Troubleshooting ### Checking Logs @@ -1048,11 +1129,11 @@ Create at least one destination in `/remotes.d/`. **Backup aborted due to disk space** The destination disk usage exceeds the threshold. Free space or adjust `DISK_USAGE_THRESHOLD` in `gniza.conf`. -**Cron not running** +**Cron not running or using stale flags** - Check that cron entries are installed: `gniza --cli schedule show` - Verify the cron daemon is running: `systemctl status cron` - Check cron logs: `/cron.log` -- Re-install entries: `gniza --cli schedule install` +- Re-install entries: `gniza --cli schedule install` (this regenerates all entries with current flags) **rclone required** S3 and Google Drive sources/destinations require rclone. Install from https://rclone.org/install/. diff --git a/textual-mcp.log b/textual-mcp.log index d477c4e..26f1353 100644 --- a/textual-mcp.log +++ b/textual-mcp.log @@ -213,3 +213,35 @@ {"timestamp": "2026-03-07 01:23:59.732166+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered documentation tools", "module": "server", "function": "_register_tools", "line": 65, "taskName": null} {"timestamp": "2026-03-07 01:23:59.732197+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Documentation search will be indexed on first use", "module": "server", "function": "__init__", "line": 41, "taskName": null} {"timestamp": "2026-03-07 01:23:59.732219+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Textual MCP Server initialized", "module": "server", "function": "__init__", "line": 47, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.277593+00:00", "level": "INFO", "logger": "textual_mcp.validation_tools", "message": "Registered validation tools: validate_tcss, validate_tcss_file, validate_inline_styles, check_selector", "module": "validation_tools", "function": "register_validation_tools", "line": 360, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.277653+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered validation tools", "module": "server", "function": "_register_tools", "line": 53, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.278141+00:00", "level": "INFO", "logger": "textual_mcp.analysis_tools", "message": "Registered analysis tools: analyze_selectors, extract_css_variables, detect_style_conflicts", "module": "analysis_tools", "function": "register_analysis_tools", "line": 99, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.278173+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered analysis tools", "module": "server", "function": "_register_tools", "line": 56, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.279706+00:00", "level": "INFO", "logger": "textual_mcp.widget_tools", "message": "Registered widget tools: generate_widget, list_widget_types, list_event_handlers, validate_widget_name", "module": "widget_tools", "function": "register_widget_tools", "line": 343, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.279741+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered widget tools", "module": "server", "function": "_register_tools", "line": 59, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.280789+00:00", "level": "INFO", "logger": "textual_mcp.layout_tools", "message": "Registered layout tools: generate_grid_layout", "module": "layout_tools", "function": "register_layout_tools", "line": 139, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.280843+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered layout tools", "module": "server", "function": "_register_tools", "line": 62, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.284105+00:00", "level": "INFO", "logger": "textual_mcp.documentation_tools", "message": "Registered documentation tools: search_textual_docs, index_textual_docs, search_textual_code_examples, get_css_property_info, list_css_properties", "module": "documentation_tools", "function": "register_documentation_tools", "line": 613, "taskName": null} +{"timestamp": "2026-03-07 14:46:21.284143+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered documentation tools", "module": "server", "function": "_register_tools", "line": 65, "taskName": null} +{"timestamp": "2026-03-07 14:46:26.899447+00:00", "level": "INFO", "logger": "faiss.loader", "message": "Loading faiss with AVX2 support.", "module": "loader", "function": "", "line": 125, "taskName": null} +{"timestamp": "2026-03-07 14:46:26.918799+00:00", "level": "INFO", "logger": "faiss.loader", "message": "Successfully loaded faiss with AVX2 support.", "module": "loader", "function": "", "line": 127, "taskName": null} +{"timestamp": "2026-03-07 14:46:26.921141+00:00", "level": "INFO", "logger": "faiss", "message": "Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes. This is only an error if you're trying to use GPU Faiss.", "module": "__init__", "function": "", "line": 174, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.175775+00:00", "level": "INFO", "logger": "textual_mcp.textual_docs_memory", "message": "Initialized VectorDB with embeddings: BAAI/bge-base-en-v1.5, persisting to: data/textual_docs.db", "module": "memory", "function": "__init__", "line": 45, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.175846+00:00", "level": "INFO", "logger": "textual_mcp.documentation_tools", "message": "Auto-indexing documentation on first use", "module": "documentation_tools", "function": "get_docs_memory", "line": 45, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.175887+00:00", "level": "WARNING", "logger": "textual_mcp.server", "message": "Failed to initialize documentation search: no running event loop", "module": "server", "function": "__init__", "line": 45, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.177468+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Textual MCP Server initialized", "module": "server", "function": "__init__", "line": 47, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.183325+00:00", "level": "INFO", "logger": "textual_mcp.validation_tools", "message": "Registered validation tools: validate_tcss, validate_tcss_file, validate_inline_styles, check_selector", "module": "validation_tools", "function": "register_validation_tools", "line": 360, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.183371+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered validation tools", "module": "server", "function": "_register_tools", "line": 53, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.183789+00:00", "level": "INFO", "logger": "textual_mcp.analysis_tools", "message": "Registered analysis tools: analyze_selectors, extract_css_variables, detect_style_conflicts", "module": "analysis_tools", "function": "register_analysis_tools", "line": 99, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.183823+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered analysis tools", "module": "server", "function": "_register_tools", "line": 56, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.185413+00:00", "level": "INFO", "logger": "textual_mcp.widget_tools", "message": "Registered widget tools: generate_widget, list_widget_types, list_event_handlers, validate_widget_name", "module": "widget_tools", "function": "register_widget_tools", "line": 343, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.185446+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered widget tools", "module": "server", "function": "_register_tools", "line": 59, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.186426+00:00", "level": "INFO", "logger": "textual_mcp.layout_tools", "message": "Registered layout tools: generate_grid_layout", "module": "layout_tools", "function": "register_layout_tools", "line": 139, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.186465+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered layout tools", "module": "server", "function": "_register_tools", "line": 62, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.189657+00:00", "level": "INFO", "logger": "textual_mcp.documentation_tools", "message": "Registered documentation tools: search_textual_docs, index_textual_docs, search_textual_code_examples, get_css_property_info, list_css_properties", "module": "documentation_tools", "function": "register_documentation_tools", "line": 613, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.189708+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Registered documentation tools", "module": "server", "function": "_register_tools", "line": 65, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.189753+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Documentation search will be indexed on first use", "module": "server", "function": "__init__", "line": 41, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.189784+00:00", "level": "INFO", "logger": "textual_mcp.server", "message": "Textual MCP Server initialized", "module": "server", "function": "__init__", "line": 47, "taskName": null} +{"timestamp": "2026-03-07 14:46:29.202417+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest", "module": "server", "function": "_handle_request", "line": 625, "taskName": "mcp.server.lowlevel.server.Server._handle_message"} +{"timestamp": "2026-03-07 14:46:29.204064+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListPromptsRequest", "module": "server", "function": "_handle_request", "line": 625, "taskName": "mcp.server.lowlevel.server.Server._handle_message"} +{"timestamp": "2026-03-07 14:46:29.204920+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest", "module": "server", "function": "_handle_request", "line": 625, "taskName": "mcp.server.lowlevel.server.Server._handle_message"}