Self-hosted apt fleet manager — one dashboard, every server, real terminal output.
A focused alternative to AWX / Ansible Tower for Ubuntu, Debian, Raspbian, and Proxmox fleets.
📐 Architecture · 🔒 Security · 📋 Changelog · 📦 Releases
🤖 This project was entirely written by Claude (Anthropic's AI assistant) via Claude Code. All code, configuration, and documentation — from the FastAPI backend and asyncssh integration to the React frontend and Docker setup — was generated through an iterative, conversation-driven development process with no manual coding.
The fleet is the unit, not the host. Most apt UIs are per-server. apt-ui treats your Ubuntu / Debian / Raspbian / Proxmox fleet as one thing — Check All, Upgrade All, Reboot All, Autoremove All all multiplexed into one terminal stream with per-server filter chips and live status. No more SSH'ing to twelve boxes to roll a security patch.
One container, zero agents. Single Docker image (under 250 MB). Talks to managed servers over plain SSH — no daemons on the targets, no message bus, no Postgres, no Redis. The whole control plane is FastAPI + SQLite + APScheduler, designed to run on a Pi 4 and manage 50 servers comfortably.
Built for staged rollouts. Tag servers with ring:test / ring:prod and auto-upgrade promotes through them in alphabetical ring order, aborting the rollout if any host fails. Maintenance windows block scheduled work outside approved hours. Pre/post-upgrade hooks let you take a BTRFS / ZFS snapshot first. Rolling reboot orchestrates kernel reboots in batches with reachability checks between them.
Security-aware, not just scheduling. A daily CVE matcher annotates every pending package with USN / CVE-IDs sourced from the Ubuntu USN database. The fleet-wide CVE inventory pivots that data into "which hosts are exposed to CVE-2025-XXXXX." A Prometheus /metrics endpoint feeds Grafana. Notifications cover daily summaries, weekly digests, security alerts, and reboot-required events across email / Telegram / Slack / webhook. Auth includes TOTP 2FA, scrypt-hashed API tokens, and admin / read-only RBAC.
One single-container control plane for an apt fleet — fleet-wide actions, scheduled automation, security visibility, and integrations to keep it honest.
| Feature | Highlights | |
|---|---|---|
| 🗺 | Dashboard & fleet view | server card grid · update / security / reboot / autoremove counts · clickable filters · search across hostnames + tags |
| 🏷 | Groups & tags | colour-coded groups (many-to-many) · freeform tags · auto-tagging by OS and virt type · ring tags drive staged rollouts |
| ⚡ | Fleet-wide actions | Check All · Refresh All · Upgrade All · Autoremove All · Rolling Reboot — all multiplexed via WebSocket with per-server filter chips |
| 📡 | Reachability monitor | TCP ping every 5 minutes (independent of SSH) · offline servers dimmed and banner-flagged · is_reachable + last_seen per server |
| 🐳 | Docker host detection | identifies the host running the dashboard and blocks upgrades of container-runtime packages mid-flight |
| 🔍 | Fleet-wide package search | five match modes (exact / contains / starts-with / ends-with / regex) · pivoted CVE-style table · diverged-version highlight |
| ⚖️ | Multi-server compare | side-by-side installed-package inventory across any combination of servers · Diverged / Common / All filter modes |
| ✅ | Bulk selection + action bar | select any subset of servers and run Check / Upgrade / Reboot / Enable-Disable / Tag across them — parallel calls honour the concurrency cap |
| 📏 | Density view | toggle the card grid ↔ a compact list; persisted like the sort order |
| 🏃 | Fleet command runner | run an admin-allowlisted, audited command across selected servers and group identical outputs ("47 said X, 3 said Y") |
| ✨ | Toasts & styled confirms | app-wide non-blocking toast notifications and themed confirm dialogs, with opt-in undo for reversible actions |
| Feature | Highlights | |
|---|---|---|
| 📋 | Upgradable list | full version deltas · repo source · security flag · phased-update column · package descriptions on hover |
| 🎯 | Selective upgrade | check the boxes for individual packages instead of upgrading everything |
| 🔬 | Upgrade impact preview | pre-flight panel parses the dry-run plan and runs needrestart -b to show which services restart and whether a reboot is actually required |
| ⏪ | Snapshot & rollback | auto btrfs/zfs snapshot before apt · snapshot name recorded on the upgrade · admin-gated "rollback to pre-upgrade snapshot" |
| 🐛 | Dist-upgrade detection | parallel apt-get dist-upgrade --dry-run surfaces new dependency packages and "kept back" rows that plain upgrade would skip |
| 🖥 | Live terminal | WebSocket stream of apt-get output with carriage-return progress lines updating in place; ANSI colour preserved |
| 📦 | Package install | search the apt cache and install new packages on any host from the UI |
| 💿 | .deb installs | URL (validated, wget-pulled) or browser upload (SFTP'd via asyncssh) — both stream dpkg -i + apt-get install -f live |
| 🧱 | Templates | named package sets applied to one or more hosts in one click — useful for provisioning identical roles |
| 📌 | Held packages | per-package hold / unhold from the Packages tab; held-package chips with one-click ✕ unhold |
| 📝 | Apt sources editor | tabbed editor for /etc/apt/sources.list* files · save / delete / create · "Test with apt-get update" streams live |
| Feature | Highlights | |
|---|---|---|
| 🛡 | CVE matcher | daily Ubuntu USN sync · per-package severity-coloured 🛡 badge · USN + CVE links in tooltips |
| 🚨 | Fleet CVE inventory | /security page pivots CVE → servers · severity / status / group filters · CSV export · nav badge with critical-CVE count |
| 🔐 | Per-server SSH keys | Fernet-encrypted in DB · falls back to global SSH_PRIVATE_KEY or SSH_AUTH_SOCK |
| 🛡 | Auto security updates | per-server unattended-upgrades toggle with shield-badge state · streams live SSH output when toggling |
| 🔢 | TOTP 2FA | QR enrolment in Settings → Account · login flow asks for a 6-digit code when enabled |
| 🔑 | API tokens + /api/v1 |
aptui_<32 url-safe bytes> · scrypt-hashed · shown once · per-token scopes (read / check / upgrade / calendar) and optional expiry · inbound automation API returns a pollable job_id |
| 👥 | RBAC | admin / read-only roles · require_admin on ~28 mutation endpoints · "read-only" badge in the nav |
| 🚫 | Brute-force lockout | per-(username, IP) backoff + lockout on repeated login / 2FA failures · TOTP replay protection · real-time alert on lockout |
| 🕵 | Actor attribution + auth-event log | every action records who triggered it · logins / failures / token use / role changes in an Auth Events history tab |
| Feature | Highlights | |
|---|---|---|
| 🗓 | Scheduled checks | configurable cron for fleet-wide update checks |
| 🤖 | Auto-upgrade | optional hands-off upgrades on a schedule · concurrency cap · phased-update toggle · conffile-action choice · canary-first health verification before a ring promotes |
| 🚦 | Maintenance windows | enforced change-control gates on all mutating actions (not just auto-upgrade) · audited admin override · allow-only mode · midnight-wrap · iCal feed |
| 🪝 | Pre/post-upgrade hooks | shell or HTTP/webhook calls before / after every upgrade (drain a load balancer, silence Alertmanager, file a ticket) · pre-hook failure aborts · global or per-server scope |
| 🎟 | Staged rollout (rings) | ring:* tags promote upgrades through environments in alphabetical order · optional dependency / anti-affinity ordering for HA pairs + DB primary/replica · per-batch failure aborts |
| 🔁 | Rolling reboot | fleet-wide reboot of reboot_required servers in ring order with per-batch waits and reachability checks |
| 🐧 | Reboot-after-upgrade | optional checkbox auto-reboots after a successful upgrade if /var/run/reboot-required exists |
| Channel | Notes | |
|---|---|---|
| 📧 | aiosmtplib · STARTTLS / SSL · HTML + text fallback | |
| Telegram | Bot API · auto-chunk for messages over 4 K | |
| 💬 | Slack | incoming webhook · Block Kit messages with header + section blocks |
| 🪝 | Webhook | JSON POST · optional X-Hub-Signature-256 HMAC-SHA256 |
| 📣 | On-call destinations | Discord · Mattermost · ntfy · PagerDuty · Opsgenie — per-event routing with dedup to avoid alert storms |
| 🗓 | Events | upgrade complete · upgrade error · security updates found · reboot required · daily summary · weekly digest |
| 🎚 | Per-channel × per-event toggles | independently enable each event on each channel |
| 📅 | Weekly patch digest | opt-in summary on a configurable cron · headline counters · by-server table · still-pending list · CVE summary · health flags |
| 📜 | Notification log | every send recorded — channel, event, summary, success/failure |
| Feature | Highlights | |
|---|---|---|
| 📜 | Upgrade history | per-server and fleet-wide log · filterable by server / status · full terminal output expandable per run |
| 🔍 | SSH audit log | every command apt-ui dispatches recorded (command, exit, duration, 4 KB output excerpt) · sub-tab in History |
| 🗒 | dpkg log | parses /var/log/dpkg.log + rotated .gz archives · filter by package / action / time |
| 📊 | Reports & change records | Patch Coverage · Upgrade Success Rate · Security SLA · auditable per-window change records (planned vs actual) — CSV / Markdown export |
| 📈 | Fleet trends | snapshot history + trend charts — pending packages, security debt, and % up-to-date over time (not just the point-in-time donut) |
| Config drift detection | flags abandoned .dpkg-dist / .ucf-dist / .dpkg-new conffiles per host · drill-down modal lists the files + the diff / rm commands to reconcile |
|
| 📈 | Prometheus /metrics | fleet-state counters / gauges for Grafana · optional METRICS_TOKEN bearer auth |
| 🌐 | Public /status.json | opt-in fleet health snapshot for embedding · disabled by default |
| 📅 | iCal feed | subscribable maintenance-window calendar at /api/calendar.ics?token=… |
| 🕒 | OS EOL countdown | dashboard 🕒 badge when OS reaches end-of-life within 365 days · severity-coloured · ESM note for Ubuntu LTS · self-updating daily from endoflife.date with bundled offline fallback |
Each managed server gets its own page with tabs:
Packages · Upgrade · Health · Apt Repos · dpkg Log · History · Stats · Shell
| Feature | Highlights | |
|---|---|---|
| 🐧 | OS detection | Ubuntu · Debian · Raspbian · Armbian · Proxmox VE · Proxmox Backup Server · Proxmox Mail Gateway · bare-metal / VM / LXC / Docker via systemd-detect-virt |
| 🔶 | Proxmox VE awareness | dedicated pveupgrade button · PVE-managed packages highlighted in the Packages tab |
| 🏥 | Health panel | on-demand probe of systemctl --failed, last 20 boot-priority journalctl errors, recent reboot history · restart-service per failed unit |
| 🍓 | Raspberry Pi EEPROM | firmware update detection for Pi 4 / 400 / CM4 / 5 · one-click apply |
| 💾 | Disk + boot health | red badge when /boot free < 100 MB or < 10% · kernel install date with 60d / 180d age tinting |
| 🔒 | Read-only / overlay root | detects a read-only / or a RAM-backed overlay root (Raspberry Pi overlay FS, overlayroot) · blocks package changes that would fail or be lost at reboot · flags stale checks when apt-get update fails |
| 📸 | Snapshot capability | BTRFS / ZFS / LXC detected · banner with copy-pastable pre-hook command suggestion in the Upgrade tab |
| ⚡ | apt proxy | detect + manage apt-cacher-ng proxy or auto-apt-proxy · live SSH output when toggling |
| Path | Status | |
|---|---|---|
| 🐳 | Docker Compose | docker compose up -d — docker-compose.ghcr.yml pulls the prebuilt image |
| ☸️ | Kubernetes | k8s/deployment.yaml — Deployment + ClusterIP Service + Longhorn PVC |
| 🌐 | Tailscale sidecar | optional overlay — joins the container/pod to your tailnet · automatic HTTPS via tailscale serve |
| 🛠 | Build from source | ./build-run.sh — dev workflow with hot rebuild |
| 🏗 | Multi-arch images | linux/amd64 + linux/arm64 published to GHCR every release |
⚠️ Requires Docker + Docker Compose v2 and SSH access to the target servers.
cat > .env <<EOF
SSH_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)"
# Optional but recommended — fixes JWT secret so sessions survive restarts
JWT_SECRET=$(openssl rand -hex 32)
# Optional overrides
# TZ=America/Montreal
# LOG_LEVEL=INFO
EOFThe key must be inside double quotes with literal newlines preserved (the heredoc above handles this).
docker compose -f docker-compose.ghcr.yml up -dTo pin to a specific release instead of latest, edit docker-compose.ghcr.yml and change the image tag, e.g. ghcr.io/mzac/apt-ui:2026.05.01-03.
./build-run.shThe app will be available at http://localhost:8111.
Default login: admin / admin — change this immediately via Settings → Account.
Two approaches. Pick whichever fits your setup.
If root has a password set the account is active and you can add your public key:
# Run on each managed server
sudo mkdir -p /root/.ssh
sudo cat ~/.ssh/id_ed25519.pub >> /root/.ssh/authorized_keys
sudo chmod 600 /root/.ssh/authorized_keysThen set username = root when adding each server in the dashboard. No sudo configuration required.
# Run on each managed server
echo Read the rest on GitHubScan report · 2026-10-09
- ✓ Prohibited terms or links
- ✓ Repository eligibility
- ✓ slopscore.md paperwork
- ✓ Content policy
- ✓ Risk review
From the balcony · 0 of 4 clapped
Schnitzel, Cap'm Slop, Princess and Crusoe read it and passed. Their reasons are on the balcony, with every other verdict.
Critics are accounts on this site with no GitHub account behind them. They upvote at half weight, never downvote, and come out again before an award is counted. Who they are.
report this listing
— log in to report
0 comments
log in to comment.