{"id":47733357,"url":"https://github.com/linit01/patchpilot","last_synced_at":"2026-04-06T19:01:49.992Z","repository":{"id":348303971,"uuid":"1168663039","full_name":"linit01/patchpilot","owner":"linit01","description":"Self-hosted patch management for Linux \u0026 macOS — real-time dashboard, SSH patching, automated schedules","archived":false,"fork":false,"pushed_at":"2026-04-02T17:46:11.000Z","size":2552,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T06:49:45.782Z","etag":null,"topics":["ansible","docker","fastapi","homelab","k3s","kubernetes","linux","macos","patch-management","self-hosted"],"latest_commit_sha":null,"homepage":"https://getpatchpilot.app","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/linit01.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-27T16:48:33.000Z","updated_at":"2026-04-02T17:46:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/linit01/patchpilot","commit_stats":null,"previous_names":["linit01/patchpilot"],"tags_count":85,"template":false,"template_full_name":null,"purl":"pkg:github/linit01/patchpilot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linit01%2Fpatchpilot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linit01%2Fpatchpilot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linit01%2Fpatchpilot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linit01%2Fpatchpilot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/linit01","download_url":"https://codeload.github.com/linit01/patchpilot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linit01%2Fpatchpilot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31485516,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T17:22:55.647Z","status":"ssl_error","status_checked_at":"2026-04-06T17:22:54.741Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansible","docker","fastapi","homelab","k3s","kubernetes","linux","macos","patch-management","self-hosted"],"created_at":"2026-04-02T22:00:51.589Z","updated_at":"2026-04-06T19:01:49.972Z","avatar_url":"https://github.com/linit01.png","language":"Python","readme":"# PatchPilot — Patch Management for Linux \u0026 macOS\n\n# https://github.com/linit01/patchpilot\n\n# \u003cimg src=\"frontend/patchpilot-icon.jpeg\" alt=\"\" width=\"40\"\u003e PatchPilot\n\n**Automated patch management system for Linux and macOS hosts — real-time monitoring, secure SSH execution, and a dark-themed web dashboard.**\n\n![Version](https://img.shields.io/badge/version-0.12.4--alpha-blue)\n![License](https://img.shields.io/badge/license-Proprietary-blue)\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Architecture](#architecture)\n- [Installation](#installation)\n- [Licensing](#licensing)\n- [Configuration](#configuration)\n- [Usage](#usage)\n- [Updating](#updating)\n- [Security](#security)\n- [API Reference](#api-reference)\n- [Troubleshooting](#troubleshooting)\n\n---\n\n## Features\n\n### Core Functionality\n- **Multi-Platform Host Support** — Debian/Ubuntu (`apt`), RHEL/CentOS (`dnf`/`yum`), macOS (`brew` + `softwareupdate` + Mac App Store via `mas`)\n- **Real-Time Patching Progress** — WebSocket streaming of live Ansible task output, per-task timestamps\n- **Background Checks** — Configurable interval (default 5 min), with countdown timer in the UI\n- **Single-Host Checks** — Fast targeted scan (~30 s) via `/api/check/{hostname}` — auto-triggered on host creation\n- **Scheduled Patching** — Time-based patch windows with encrypted sudo-password storage\n- **In-App Updates** — Automatic update checking with one-click upgrades for both Docker Compose and Kubernetes deployments\n\n### Security \u0026 Authentication\n- **Multi-User RBAC** — Three-tier role model: Full Admin (app owner, sees/manages all), Admin (own resources only), Viewer (read-only across all resources)\n- **Resource Ownership** — Hosts, SSH keys, and schedules tracked by creator; Admin users see only their own resources\n- **Login Required** — Session-based auth before any dashboard access\n- **Fernet Encryption (AES-256)** — All SSH private keys and sudo passwords encrypted at rest in PostgreSQL\n- **Saved SSH Keys Library** — Store, reuse, upload, and set defaults per host; scoped per user\n- **Per-Host SSH Configuration** — Different key, user, and port per target\n- **Control Node Protection** — Detects when a managed host is also running PatchPilot; warns before patching, never auto-reboots it\n- **Debug Logging Toggle** — Runtime-switchable verbose logging via Settings → Advanced (no restart needed)\n\n### Infrastructure \u0026 Deployment\n- **Docker Compose** — Single-command local or LAN deployment\n- **K3s / Kubernetes** — Full manifest set with Traefik ingress, cert-manager, Let's Encrypt TLS (DNS-01 Cloudflare or HTTP-01)\n- **CI/CD** — GitHub Actions builds multi-arch images (amd64 + arm64) and auto-creates GitHub Releases on tag push\n- **PostgreSQL 15** — Persistent storage for hosts, packages, SSH keys, settings, schedules, and audit history\n- **Ansible** — Remote execution engine; playbook and inventory configurable per deployment\n\n### Backup \u0026 Restore\n- **Full application backup** — Database, settings, Ansible files, and optionally the encryption key\n- **Standalone encryption key export** — Key file written alongside the backup tarball for easy retrieval\n- **Smart retention policy** — Configurable retain count; uninstall backups excluded from count; companion key files cleaned up; at least one encryption-key-bearing backup preserved\n- **Web UI and CLI** — Create, download, upload (.tgz and .tar.gz), and restore backups from Settings → Backup \u0026 Restore\n- **License-gated** — Backup features require an active license (trial users see a lock overlay)\n\n### Licensing \u0026 Trial\n- **14-day free trial** — Starts automatically on first-run setup; full functionality except backup/restore\n- **LemonSqueezy integration** — License keys validated via LemonSqueezy License API with machine binding\n- **Activation limit** — Each key activates on one installation; deactivate to move to a new machine\n- **Periodic validation** — Background check every 7 days confirms license status with LemonSqueezy\n- **30-day grace period** — Offline installations continue working for 30 days between validations\n- **Subscription management** — Expiry, cancellation, and admin-disabled states detected automatically\n\n---\n\n## Architecture\n\n```\nBrowser (HTTPS / HTTP)\n        │\n        ▼\n  Traefik Ingress ─────────────────────────── (k3s only)\n  or Nginx (Docker)\n        │\n        ▼\n  patchpilot-frontend  (Nginx serving static HTML/JS/CSS)\n        │\n        │  /api/*  and  /ws/*\n        ▼\n  patchpilot-backend   (Python 3.11 · FastAPI · Uvicorn)\n        │\n   ┌────┴────┐\n   │         │\n   ▼         ▼\nPostgreSQL  Ansible Runner\n(port 5432) (SSH → managed hosts)\n```\n\n### Technology Stack\n\n| Layer | Technology |\n|-------|-----------|\n| Frontend | HTML5 · Vanilla JS · WebSocket API |\n| Backend | Python 3.11 · FastAPI · Uvicorn |\n| Database | PostgreSQL 15 |\n| Remote execution | Ansible (inside backend container) |\n| Encryption | `cryptography` (Fernet / AES-256) |\n| Web server | Nginx (Alpine) |\n| Container runtime | Docker / containerd (k3s) |\n| Ingress (k3s) | Traefik v3 |\n| TLS (k3s) | cert-manager + Let's Encrypt |\n| CI/CD | GitHub Actions · Docker Hub |\n\n---\n\n## Installation\n\nPatchPilot ships with a single installer that supports multiple deployment modes.\n\n### Prerequisites\n\n| Requirement | Notes |\n|-------------|-------|\n| Docker (Desktop or Engine) | Must be running |\n| Python 3.8+ | For config parsing and key generation |\n| Git | To clone the repo (or use the curl installer) |\n\n### Quick Install\n\n```bash\n# One-line installer (clone or tarball — auto-detected)\ncurl -fsSL https://getpatchpilot.app/install.sh | bash\n\n# Or clone and run manually\ngit clone https://github.com/linit01/patchpilot.git\ncd patchpilot\n./install.sh              # Web wizard (default)\n./install.sh --docker     # Docker Compose\n./install.sh --k3s        # K3s / Kubernetes\n```\n\nVisit **[getpatchpilot.app](https://getpatchpilot.app)** for screenshots and full details.\n\n### Docker Compose\n\nThe installer generates a Fernet encryption key, writes `.env`, pulls pre-built images from Docker Hub, and starts all services. Accessible at `http://\u003chost-ip\u003e:8080`.\n\n### K3s / Kubernetes\n\nSee **[KUBERNETES.md](KUBERNETES.md)** for the full step-by-step guide.\n\n---\n\n## Licensing\n\nPatchPilot includes a **14-day free trial** with full functionality (except backup/restore). After the trial, a license key is required.\n\n### Pricing\n\n| Plan | Price |\n|------|-------|\n| Monthly | $5.99/month |\n| Annual | $49/year (save 32%) |\n\nPurchase at **[getpatchpilot.app](https://getpatchpilot.app)** or via the PatchPilot store at `patchpilot.lemonsqueezy.com`.\n\n### Activating a License\n\n1. Purchase a subscription — you'll receive a license key (UUID format)\n2. Open **Settings → License** in PatchPilot\n3. Enter the key and click **Activate**\n4. PatchPilot validates the key with LemonSqueezy and binds it to your installation\n\nEach 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.\n\n---\n\n## Configuration\n\n### Environment Variables (Docker Compose — `.env`)\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `PATCHPILOT_ENCRYPTION_KEY` | ✅ | — | Fernet key (auto-generated by installer) |\n| `POSTGRES_USER` | | `patchpilot` | DB username |\n| `POSTGRES_PASSWORD` | ✅ | — | DB password |\n| `POSTGRES_DB` | | `patchpilot` | DB name |\n| `APP_BASE_URL` | | `http://localhost:8080` | Public URL (CORS + cookies) |\n| `ALLOWED_ORIGINS` | | `*` | Comma-separated CORS origins |\n| `AUTO_REFRESH_INTERVAL` | | `300` | Background check interval (seconds) |\n| `BACKUP_RETAIN_COUNT` | | `10` | Max backups to keep |\n| `INSTALL_DIR` | | — | Host path of PatchPilot directory (set by installer) |\n\n---\n\n## Usage\n\n### Dashboard\n\nStats 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.\n\n### Adding a Host\n\nSettings → 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.\n\n### Patching\n\nSelect hosts → Patch Selected → enter sudo password → watch real-time progress → dashboard auto-refreshes on completion.\n\n### Backup \u0026 Restore\n\nSee **[README_BACKUP_RESTORE.md](README_BACKUP_RESTORE.md)** for full instructions.\n\n---\n\n## Updating\n\nPatchPilot includes a built-in update checker and one-click update mechanism.\n\n### Automatic Checks\n\nThe 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.\n\n### One-Click Updates\n\nClick **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.\n\n### Manual Updates\n\n```bash\n# Docker Compose\ndocker compose pull \u0026\u0026 docker compose up -d\n\n# Kubernetes\nkubectl -n patchpilot set image deployment/patchpilot-backend backend=linit01/patchpilot:backend-\u003cversion\u003e\nkubectl -n patchpilot set image deployment/patchpilot-frontend frontend=linit01/patchpilot:frontend-\u003cversion\u003e\nkubectl -n patchpilot rollout restart deployment/patchpilot-backend deployment/patchpilot-frontend\n```\n\n---\n\n## Security\n\n- All SSH private keys and sudo passwords encrypted at rest (Fernet / AES-256)\n- Temporary key files created with `0600` permissions and deleted after use\n- Fernet key stored in environment variable — never in the database\n- Traefik middleware enforces HSTS and security headers in k3s mode\n- Control node protected from accidental auto-reboot\n\n---\n\n## API Reference\n\n### Hosts\n```\nGET/POST   /api/hosts              List / Create\nGET/PUT/DELETE /api/hosts/{id}     Get / Update / Delete\n```\n\n### Checks \u0026 Patching\n```\nPOST /api/check                Full fleet check\nPOST /api/check/{hostname}     Single-host check\nPOST /api/patch                Patch selected hosts\nGET  /api/stats                Dashboard statistics\n```\n\n### Updates\n```\nGET  /api/updates/status       Update status (cached)\nPOST /api/updates/check        Force check\nPOST /api/updates/apply        Apply update\nGET  /api/updates/progress     Poll progress\n```\n\n### Backup \u0026 Restore\n```\nGET  /api/backup/list          List backups\nPOST /api/backup/create        Create backup (license required)\nPOST /api/backup/restore/{f}   Restore from backup (license required)\nGET  /api/backup/health        Backup health info\n```\n\n### License\n```\nGET  /api/license/status       Trial/license status\nPOST /api/license/activate     Activate license key (LemonSqueezy)\nPOST /api/license/deactivate   Deactivate and free activation slot\nPOST /api/license/validate     Manual re-validation\n```\n\n### WebSocket\n```\nWS /ws/patch-progress\n```\n\n---\n\n## Troubleshooting\n\n### Host shows \"Unreachable\"\nTest SSH from the backend container: `docker exec -it patchpilot-backend-1 ssh user@host`\n\n### Backend won't start\nCheck logs: `docker compose logs backend` or `kubectl logs -n patchpilot deploy/patchpilot-backend -c backend`\n\n### TLS not issuing (k3s)\n`kubectl describe cert patchpilot-tls -n patchpilot`\n\n---\n\n## Project Structure\n\n```\npatchpilot/\n├── backend/                    # FastAPI application\n│   ├── app.py, ansible_runner.py, database.py, auth.py, rbac.py\n│   ├── settings_api.py, schedules_api.py, setup_api.py\n│   ├── backup_restore.py, uninstall_api.py, update_checker.py\n│   ├── license.py              # Trial/license management (LemonSqueezy)\n│   └── encryption_utils.py, requirements.txt\n├── frontend/                   # Static HTML/JS/CSS dashboard\n├── k8s/                        # Kubernetes manifests + installer\n├── webinstall/                 # Web-based installer UI\n├── scripts/                    # Helper scripts\n├── .github/workflows/          # CI/CD pipeline\n├── Dockerfile, Dockerfile.frontend\n├── docker-compose.yml, docker-compose.developer.yml\n├── install.sh, nginx.conf, VERSION, LICENSE\n└── database-schema.sql\n```\n\n---\n\n*Built for sysadmins who patch first and ask questions never.*\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinit01%2Fpatchpilot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flinit01%2Fpatchpilot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinit01%2Fpatchpilot/lists"}