https://github.com/linit01/patchpilot
Self-hosted patch management for Linux & macOS — real-time dashboard, SSH patching, automated schedules
https://github.com/linit01/patchpilot
ansible docker fastapi homelab k3s kubernetes linux macos patch-management self-hosted
Last synced: 21 days ago
JSON representation
Self-hosted patch management for Linux & macOS — real-time dashboard, SSH patching, automated schedules
- Host: GitHub
- URL: https://github.com/linit01/patchpilot
- Owner: linit01
- License: other
- Created: 2026-02-27T16:48:33.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-02T17:46:11.000Z (25 days ago)
- Last Synced: 2026-04-03T06:49:45.782Z (24 days ago)
- Topics: ansible, docker, fastapi, homelab, k3s, kubernetes, linux, macos, patch-management, self-hosted
- Language: Python
- Homepage: https://getpatchpilot.app
- Size: 2.43 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# PatchPilot — Patch Management for Linux & macOS
# https://github.com/linit01/patchpilot
#
PatchPilot
**Automated patch management system for Linux and macOS hosts — real-time monitoring, secure SSH execution, and a dark-themed web dashboard.**


---
## Table of Contents
- [Features](#features)
- [Architecture](#architecture)
- [Installation](#installation)
- [Licensing](#licensing)
- [Configuration](#configuration)
- [Usage](#usage)
- [Updating](#updating)
- [Security](#security)
- [API Reference](#api-reference)
- [Troubleshooting](#troubleshooting)
---
## Features
### Core Functionality
- **Multi-Platform Host Support** — Debian/Ubuntu (`apt`), RHEL/CentOS (`dnf`/`yum`), macOS (`brew` + `softwareupdate` + Mac App Store via `mas`)
- **Real-Time Patching Progress** — WebSocket streaming of live Ansible task output, per-task timestamps
- **Background Checks** — Configurable interval (default 5 min), with countdown timer in the UI
- **Single-Host Checks** — Fast targeted scan (~30 s) via `/api/check/{hostname}` — auto-triggered on host creation
- **Scheduled Patching** — Time-based patch windows with encrypted sudo-password storage
- **In-App Updates** — Automatic update checking with one-click upgrades for both Docker Compose and Kubernetes deployments
### Security & Authentication
- **Multi-User RBAC** — Three-tier role model: Full Admin (app owner, sees/manages all), Admin (own resources only), Viewer (read-only across all resources)
- **Resource Ownership** — Hosts, SSH keys, and schedules tracked by creator; Admin users see only their own resources
- **Login Required** — Session-based auth before any dashboard access
- **Fernet Encryption (AES-256)** — All SSH private keys and sudo passwords encrypted at rest in PostgreSQL
- **Saved SSH Keys Library** — Store, reuse, upload, and set defaults per host; scoped per user
- **Per-Host SSH Configuration** — Different key, user, and port per target
- **Control Node Protection** — Detects when a managed host is also running PatchPilot; warns before patching, never auto-reboots it
- **Debug Logging Toggle** — Runtime-switchable verbose logging via Settings → Advanced (no restart needed)
### Infrastructure & Deployment
- **Docker Compose** — Single-command local or LAN deployment
- **K3s / Kubernetes** — Full manifest set with Traefik ingress, cert-manager, Let's Encrypt TLS (DNS-01 Cloudflare or HTTP-01)
- **CI/CD** — GitHub Actions builds multi-arch images (amd64 + arm64) and auto-creates GitHub Releases on tag push
- **PostgreSQL 15** — Persistent storage for hosts, packages, SSH keys, settings, schedules, and audit history
- **Ansible** — Remote execution engine; playbook and inventory configurable per deployment
### Backup & Restore
- **Full application backup** — Database, settings, Ansible files, and optionally the encryption key
- **Standalone encryption key export** — Key file written alongside the backup tarball for easy retrieval
- **Smart retention policy** — Configurable retain count; uninstall backups excluded from count; companion key files cleaned up; at least one encryption-key-bearing backup preserved
- **Web UI and CLI** — Create, download, upload (.tgz and .tar.gz), and restore backups from Settings → Backup & Restore
- **License-gated** — Backup features require an active license (trial users see a lock overlay)
### Licensing & Trial
- **14-day free trial** — Starts automatically on first-run setup; full functionality except backup/restore
- **LemonSqueezy integration** — License keys validated via LemonSqueezy License API with machine binding
- **Activation limit** — Each key activates on one installation; deactivate to move to a new machine
- **Periodic validation** — Background check every 7 days confirms license status with LemonSqueezy
- **30-day grace period** — Offline installations continue working for 30 days between validations
- **Subscription management** — Expiry, cancellation, and admin-disabled states detected automatically
---
## Architecture
```
Browser (HTTPS / HTTP)
│
▼
Traefik Ingress ─────────────────────────── (k3s only)
or Nginx (Docker)
│
▼
patchpilot-frontend (Nginx serving static HTML/JS/CSS)
│
│ /api/* and /ws/*
▼
patchpilot-backend (Python 3.11 · FastAPI · Uvicorn)
│
┌────┴────┐
│ │
▼ ▼
PostgreSQL Ansible Runner
(port 5432) (SSH → managed hosts)
```
### Technology Stack
| Layer | Technology |
|-------|-----------|
| Frontend | HTML5 · Vanilla JS · WebSocket API |
| Backend | Python 3.11 · FastAPI · Uvicorn |
| Database | PostgreSQL 15 |
| Remote execution | Ansible (inside backend container) |
| Encryption | `cryptography` (Fernet / AES-256) |
| Web server | Nginx (Alpine) |
| Container runtime | Docker / containerd (k3s) |
| Ingress (k3s) | Traefik v3 |
| TLS (k3s) | cert-manager + Let's Encrypt |
| CI/CD | GitHub Actions · Docker Hub |
---
## Installation
PatchPilot ships with a single installer that supports multiple deployment modes.
### Prerequisites
| Requirement | Notes |
|-------------|-------|
| Docker (Desktop or Engine) | Must be running |
| Python 3.8+ | For config parsing and key generation |
| Git | To clone the repo (or use the curl installer) |
### Quick Install
```bash
# One-line installer (clone or tarball — auto-detected)
curl -fsSL https://getpatchpilot.app/install.sh | bash
# Or clone and run manually
git clone https://github.com/linit01/patchpilot.git
cd patchpilot
./install.sh # Web wizard (default)
./install.sh --docker # Docker Compose
./install.sh --k3s # K3s / Kubernetes
```
Visit **[getpatchpilot.app](https://getpatchpilot.app)** for screenshots and full details.
### Docker Compose
The installer generates a Fernet encryption key, writes `.env`, pulls pre-built images from Docker Hub, and starts all services. Accessible at `http://:8080`.
### K3s / Kubernetes
See **[KUBERNETES.md](KUBERNETES.md)** for the full step-by-step guide.
---
## Licensing
PatchPilot includes a **14-day free trial** with full functionality (except backup/restore). After the trial, a license key is required.
### Pricing
| Plan | Price |
|------|-------|
| Monthly | $5.99/month |
| Annual | $49/year (save 32%) |
Purchase at **[getpatchpilot.app](https://getpatchpilot.app)** or via the PatchPilot store at `patchpilot.lemonsqueezy.com`.
### Activating a License
1. Purchase a subscription — you'll receive a license key (UUID format)
2. Open **Settings → License** in PatchPilot
3. Enter the key and click **Activate**
4. PatchPilot validates the key with LemonSqueezy and binds it to your installation
Each key activates on **one installation**. To move your license to a new machine, deactivate it first in Settings → License, then re-activate on the new installation.
---
## Configuration
### Environment Variables (Docker Compose — `.env`)
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `PATCHPILOT_ENCRYPTION_KEY` | ✅ | — | Fernet key (auto-generated by installer) |
| `POSTGRES_USER` | | `patchpilot` | DB username |
| `POSTGRES_PASSWORD` | ✅ | — | DB password |
| `POSTGRES_DB` | | `patchpilot` | DB name |
| `APP_BASE_URL` | | `http://localhost:8080` | Public URL (CORS + cookies) |
| `ALLOWED_ORIGINS` | | `*` | Comma-separated CORS origins |
| `AUTO_REFRESH_INTERVAL` | | `300` | Background check interval (seconds) |
| `BACKUP_RETAIN_COUNT` | | `10` | Max backups to keep |
| `INSTALL_DIR` | | — | Host path of PatchPilot directory (set by installer) |
---
## Usage
### Dashboard
Stats cards show hosts up to date, needing updates, unreachable, and total pending packages. Host table with status badges and last-checked timestamps. View Details for per-host package lists.
### Adding a Host
Settings → Hosts → Add New Host. Fill in hostname/IP, SSH user, port, select a saved SSH key, click Test Connection, then Save. A background check runs automatically within 30 seconds.
### Patching
Select hosts → Patch Selected → enter sudo password → watch real-time progress → dashboard auto-refreshes on completion.
### Backup & Restore
See **[README_BACKUP_RESTORE.md](README_BACKUP_RESTORE.md)** for full instructions.
---
## Updating
PatchPilot includes a built-in update checker and one-click update mechanism.
### Automatic Checks
The backend periodically checks for new versions (default: every 24 hours). It queries GitHub Releases first; if the repo is private and no token is configured, it falls back to Docker Hub tags. When an update is available, a badge appears on the sidebar and Settings → Updates shows the available version with release notes.
### One-Click Updates
Click **Update Now** in Settings → Updates. For Kubernetes, the backend updates deployment image tags and issues a rollout restart. For Docker Compose, it rewrites `docker-compose.yml` tags, pulls new images, and spawns a helper container to restart services. The frontend auto-reconnects and reloads.
### Manual Updates
```bash
# Docker Compose
docker compose pull && docker compose up -d
# Kubernetes
kubectl -n patchpilot set image deployment/patchpilot-backend backend=linit01/patchpilot:backend-
kubectl -n patchpilot set image deployment/patchpilot-frontend frontend=linit01/patchpilot:frontend-
kubectl -n patchpilot rollout restart deployment/patchpilot-backend deployment/patchpilot-frontend
```
---
## Security
- All SSH private keys and sudo passwords encrypted at rest (Fernet / AES-256)
- Temporary key files created with `0600` permissions and deleted after use
- Fernet key stored in environment variable — never in the database
- Traefik middleware enforces HSTS and security headers in k3s mode
- Control node protected from accidental auto-reboot
---
## API Reference
### Hosts
```
GET/POST /api/hosts List / Create
GET/PUT/DELETE /api/hosts/{id} Get / Update / Delete
```
### Checks & Patching
```
POST /api/check Full fleet check
POST /api/check/{hostname} Single-host check
POST /api/patch Patch selected hosts
GET /api/stats Dashboard statistics
```
### Updates
```
GET /api/updates/status Update status (cached)
POST /api/updates/check Force check
POST /api/updates/apply Apply update
GET /api/updates/progress Poll progress
```
### Backup & Restore
```
GET /api/backup/list List backups
POST /api/backup/create Create backup (license required)
POST /api/backup/restore/{f} Restore from backup (license required)
GET /api/backup/health Backup health info
```
### License
```
GET /api/license/status Trial/license status
POST /api/license/activate Activate license key (LemonSqueezy)
POST /api/license/deactivate Deactivate and free activation slot
POST /api/license/validate Manual re-validation
```
### WebSocket
```
WS /ws/patch-progress
```
---
## Troubleshooting
### Host shows "Unreachable"
Test SSH from the backend container: `docker exec -it patchpilot-backend-1 ssh user@host`
### Backend won't start
Check logs: `docker compose logs backend` or `kubectl logs -n patchpilot deploy/patchpilot-backend -c backend`
### TLS not issuing (k3s)
`kubectl describe cert patchpilot-tls -n patchpilot`
---
## Project Structure
```
patchpilot/
├── backend/ # FastAPI application
│ ├── app.py, ansible_runner.py, database.py, auth.py, rbac.py
│ ├── settings_api.py, schedules_api.py, setup_api.py
│ ├── backup_restore.py, uninstall_api.py, update_checker.py
│ ├── license.py # Trial/license management (LemonSqueezy)
│ └── encryption_utils.py, requirements.txt
├── frontend/ # Static HTML/JS/CSS dashboard
├── k8s/ # Kubernetes manifests + installer
├── webinstall/ # Web-based installer UI
├── scripts/ # Helper scripts
├── .github/workflows/ # CI/CD pipeline
├── Dockerfile, Dockerfile.frontend
├── docker-compose.yml, docker-compose.developer.yml
├── install.sh, nginx.conf, VERSION, LICENSE
└── database-schema.sql
```
---
*Built for sysadmins who patch first and ask questions never.*