·······························································
: __ __ ____ ____ ____ _____ ________ :
: / /_/ /_ ___ / __ \ / __ \/ __ \/ _/ | / / ____/ :
: / __/ __ \/ _ \ / / / / / / / / /_/ // / | | / / __/ :
: / /_/ / / / __/ / /_/ / / /_/ / _, _// / | |/ / /___ :
: \__/_/ /_/\___/ \___\_\/_____/_/ |_/___/ |___/_____/ :
: :
·······························································
Own your cloud. Own your files. Run it unattended.
A config-driven, self-healing SFTP server that runs behind a VPN with dynamic port forwarding. It automatically tracks port changes, updates the firewall and SSH config, and provides remote administration through Discord. No web UI, no local console needed.
One config file. One install command. Runs indefinitely without human intervention.
┌─────────────┐ ┌──────────────┐ ┌──────────┐
│ VPN Client │──────▶│ Watchdog │──────▶│ SSHD │
│ (port logs) │ │ (port sync) │ │ (SFTP) │
└─────────────┘ └──────┬───────┘ └──────────┘
│ health check
┌──────▼───────┐
│ Discord │
│ Bot │
│ (ChatOps) │
└──────────────┘
If you run an SFTP server behind a VPN with port forwarding, your forwarded port changes unpredictably. Every time it changes, you'd normally need to manually update your SSH config, firewall rules, and tell your users the new connection details.
QDRIVE automates all of that. The watchdog monitors VPN logs, detects port changes within 60 seconds, updates everything automatically, and allows users to query the new connection info from the Discord bot. Meanwhile, the bot and watchdog monitor each other's health — if either one crashes, the other brings it back.
The result is an SFTP server you deploy once and never touch again.
QDRIVE started as a Windows project: a PowerShell watchdog, a Python Discord bot, and a Task Scheduler held together by health files and scheduled tasks. It ran on a Beelink Mini PC from November 2025 onward with zero manual interventions.
This Linux version is a ground-up redesign. Not a port, a deliberate re-architecture that replaces every Windows-specific component with Linux-native equivalents and adds security features, automated testing, and one-command deployment that the original never had.
| Windows v1.0 | Linux v2.0 | |
|---|---|---|
| Process management | Task Scheduler | systemd services with sandboxing |
| Watchdog | PowerShell while($true) loop |
Python service with signal handling |
| Firewall | Windows Firewall rules | ufw with scoped sudo |
| Permissions | icacls |
POSIX ACLs (setfacl) |
| Config | config.json with hardcoded paths |
Validated config.yaml, zero hardcoded paths |
| Installation | Manual, ~45 minutes, error-prone | sudo ./install.sh, ~5 minutes |
| Testing | None | 306 automated tests |
| Security audit | None | Scoped sudoers, systemd sandboxing, fail2ban |
| Uninstall | Manual cleanup | sudo ./uninstall.sh |
The Windows implementation remains maintained at QDRIVE v1.0 as the Windows-native reference.
Self-Healing Infrastructure
- Mutual watchdog handshake — bot and watchdog monitor each other
- Automatic VPN port synchronization (sshd_config + firewall + SSHD restart)
- Fail-safe offline mode when VPN port is unavailable
- systemd restart policies as the final safety net
Discord ChatOps
- 8 slash commands for full remote administration
- SSH key injection with cryptographic validation
- Folder access control (deny/allow per user)
- Emergency lockout and manual maintenance triggers
- Passive event notifications to a log channel
Security by Default
- Key-only SSH authentication (passwords disabled, enforced by installer)
- ChrootDirectory isolation (SFTP users cannot escape the data root)
- systemd sandboxing (ProtectSystem, ProtectHome, NoNewPrivileges)
- Scoped sudo with per-command restrictions (no blanket command grants)
- fail2ban integration for brute force protection
- Admin enforcement on every privileged Discord command
- Rate limiting (3 commands per 60 seconds per user)
- Full audit trail of every security-relevant operation
Config-Driven Design
- Single
config.yamlcontrols the entire system - Validated at startup — malformed config prevents boot, not runtime failure
- Provider-agnostic VPN layer (Proton VPN, Mullvad, WireGuard, custom)
- Configurable SFTP root, folder structure, maintenance schedule, and security thresholds
- Any Debian-based Linux system (Raspberry Pi OS Lite, Ubuntu 22.04+)
- Python 3.10 or newer
- A Discord bot token (Developer Portal)
- A VPN with port forwarding enabled (Proton VPN recommended)
sudo apt update
sudo apt install -y openssh-server python3 python3-venv ufw acl lsof psmisc fail2bangit clone https://github.com/QDRIVEdotDev/QDRIVE.git
cd QDRIVE
# Create your config
cp config.example.yaml config.yaml
nano config.yaml # Set your Discord token and admin ID
# Validate without making changes
sudo ./install.sh --dry-run
# Deploy
sudo ./install.shThe installer shows you exactly what it will do and asks for confirmation before making any changes. See What the Installer Does for full details.
From Discord:
/qdrive— see your server's IP, port, and status/qstatus— see disk usage, watchdog state, and SSHD status
From the command line:
sudo systemctl status qdrive-watchdog qdrive-bot sshd| Command | Access | Purpose |
|---|---|---|
/qdrive |
Public | Reports current IP, port, and SSHD status |
/qstatus |
Admin | System health: disk usage, service states, last maintenance |
/qstart |
Admin | Start the watchdog service |
/qlock |
Admin |
Emergency lockout — stops watchdog, terminates all connections |
/qrestart |
Admin |
Trigger weekly maintenance (QLOCK + log rotation + reboot) |
/qaddkey |
Admin |
Inject an SSH public key into a user vault |
/qdeny |
Admin | Deny standard user access to a folder |
/qallow |
Admin | Restore standard user access to a folder |
Admin commands are enforced by Discord user ID. Only the user ID specified in config.yaml can execute them. All unauthorized attempts are logged.
QDRIVE/
├── config.yaml # Your configuration (only file you edit)
├── config.example.yaml # Annotated template
├── install.sh # One-command deployment
├── uninstall.sh # Clean removal
├── requirements.txt # Python dependencies
├── src/
│ ├── core/
│ │ ├── config.py # Typed config loader with validation
│ │ ├── audit.py # Structured security event logging
│ │ └── vpn_providers.py # VPN provider registry
│ ├── bot/
│ │ └── qbot.py # Discord bot (8 slash commands)
│ ├── watchdog/
│ │ └── watchdog.py # Port sync + bot health monitor
│ └── maintenance/
│ ├── maintenance.py # Weekly routine (QLOCK → rotate → reboot)
│ └── qlock.py # Surgical file handle termination
├── systemd/ # Service units (deployed by installer)
├── templates/ # sshd_config, sudoers, fail2ban templates
├── tests/ # 306 automated tests
└── docs/
├── QDRIVE_DEPLOYMENT_GUIDE.md
├── QDRIVE_OPERATIONS_MANUAL.md
└── QDRIVE_SECURITY_OVERVIEW.md
The Watchdog (qdrive-watchdog.service) runs a 60-second loop:
- Checks if SSHD is running — starts it if not
- Reads VPN logs for the current forwarded port
- If the port changed: backs up sshd_config, updates it, updates ufw, restarts SSHD
- If the VPN port is unavailable: marks config
# Port OFFLINE(fail-safe) - Checks the bot's health file — restarts the bot service if stale or missing
The Bot (qdrive-bot.service) connects to Discord and handles slash commands:
- Writes a heartbeat timestamp every 60 seconds (the watchdog reads this)
- Responds to commands, enforces admin checks, logs security events
- Pushes notifications to the log channel when significant events occur
Maintenance (qdrive-maintenance.service) runs weekly via a systemd timer:
- Executes the QLOCK protocol (stop SSHD, clear file handles, verify clean)
- Rotates old audit log backups
- Writes a SUCCESS handshake file
- Reboots the system
Each component is isolated — the bot cannot modify sshd_config, the watchdog cannot write to Discord, and QLOCK cannot restart SSHD. These boundaries are enforced by systemd sandboxing and verified by contract tests.
Transparency matters. Here is everything install.sh does to your system, in order. The --dry-run flag runs the validation phases without making any changes.
| Phase | Action | Reversible? |
|---|---|---|
| 1 | Validates config.yaml and checks prerequisites are installed | Read-only |
| 2 | Creates system users: qdrive-system, plus the standard/admin users named in your config (qdrive and qdriveadmin by default), with nologin shell | userdel to remove |
| 3 | Creates SFTP directory structure with POSIX ACLs, plus runtime state and log directories | rm -rf to remove |
| 4 | Adds fstab entry and mounts external drive if configured (nofail) | Remove the fstab line |
| 5 | Copies application to /opt/qdrive, creates Python venv, installs dependencies | rm -rf /opt/qdrive |
| 6 | Backs up existing sshd_config, deploys hardened template, validates with sshd -t | Restore from backup |
| 7 | Deploys /etc/sudoers.d/qdrive (scoped permissions), validates with visudo -cf | rm /etc/sudoers.d/qdrive |
| 8 | Deploys systemd unit files and configures sshd.service for cross-distro compatibility | rm unit files; full cleanup via uninstall.sh |
| 9 | Configures fail2ban SSH jail if enabled | Remove jail file |
| 10 | Enables and starts services, enables ufw, restarts SSHD | sudo ./uninstall.sh |
| 11 | Runs final health check | Read-only |
The installer backs up your existing sshd_config before replacing it. Your backup is at /etc/ssh/sshd_config.bak.TIMESTAMP.
The installer validates the new sshd_config with sshd -t before applying it. If validation fails, it restores the backup automatically.
The installer verifies password authentication is disabled in the final config. If it isn't, the installer aborts.
To completely undo everything: sudo ./uninstall.sh
Copy config.example.yaml to config.yaml and edit it. Every field is commented. The essential fields:
discord:
token: "your-bot-token" # From Discord Developer Portal
admin_id: "your-discord-user-id" # Right-click → Copy User ID
log_channel_id: "" # Optional: channel for event notifications
server:
sftp_root: /srv/qdrive # Where SFTP data lives (configurable)
vpn:
provider: protonvpn # protonvpn | mullvad | wireguard | customFull configuration reference is in config.example.yaml.
QDRIVE's security posture is documented in detail in docs/QDRIVE_SECURITY_OVERVIEW.md. The highlights:
- No passwords, ever. SSH key authentication only. The installer enforces this.
- Chroot jail. SFTP users cannot escape the data directory or run commands.
- Sandboxed services. systemd prevents each service from accessing files it doesn't need.
- Scoped sudo. Every sudo entry restricts qdrive-system to specific binaries. Most commands have exact-argument restrictions:
pkillis locked topkill -9 sshd,ufwtoallow/delete allowwith/tcp, systemctl to specific service+action pairs. A small number of commands — setfacl, fuser, and lsof — currently use bounded wildcards while application-layer validation handles path scoping. Tightening these to argument-level restrictions is tracked as a future enhancement. - Audit trail. Every port change, key injection, lockout, and unauthorized access attempt is logged with timestamp, actor, and detail.
- No telemetry. QDRIVE makes no network calls except the Discord gateway and one IP lookup (
api.ipify.orgwhen/qdriveis invoked). No analytics, no phone-home, no data collection.
pip install -r requirements.txt pytest --break-system-packages
python3 -m pytest tests/ -v306 tests covering config validation, audit logging, rate limiting, SSH key validation, path sanitization, sshd_config manipulation, firewall management, bot health monitoring, QLOCK sequencing, maintenance routines, systemd unit structure, template rendering, sudoers scoping, and architectural contract enforcement.
All tests run in memory with mocked subprocess calls. No network connections, no system changes, no root required.
| Document | Purpose |
|---|---|
docs/QDRIVE_DEPLOYMENT_GUIDE.md |
Step-by-step installation walkthrough |
docs/QDRIVE_OPERATIONS_MANUAL.md |
How everything works, command reference, troubleshooting |
docs/QDRIVE_SECURITY_OVERVIEW.md |
Attack surface analysis, breach simulations, trust boundaries |
- OS: Debian-based Linux (Raspberry Pi OS Lite 64-bit, Ubuntu 22.04+)
- Python: 3.10 or newer
- Packages:
openssh-server python3 python3-venv ufw acl lsof psmisc fail2ban - Python deps:
discord.py==2.7.1,PyYAML==6.0.3(pinned, installed automatically byinstall.sh) - Hardware: Runs on anything — Raspberry Pi 5, any x86 machine, cloud VPS
- VPN: Any provider with port forwarding (Proton VPN fully supported, others in progress)
sudo ./uninstall.shRemoves all services, application files, runtime state, logs, sudoers config, and fail2ban jail. Does not remove your SFTP data, system users, or sshd_config changes (restore from backup manually if desired).
GNU Affero General Public License v3.0 — see LICENSE for the full text.
QDRIVE is licensed under AGPL v3 to keep it aligned with its purpose: a tool for self-hosters running their own infrastructure. The AGPL ensures that modified versions offered as a network service remain open-source, so the project stays in the hands of the homelab community rather than being wrapped into closed-source SaaS products.
QDRIVE is an independent open-source project. It is not affiliated with Proton VPN, Mullvad, Discord, or any other third-party service.