Catubba e9dc577ba4 feat(i18n): purge orphan keys, fix plurals, localize run errors
Close the frontend block. The locale packs go 727 -> 498 keys and the
strings that survive are now enforced to stay in step across languages.

Purge: delete 257 keys left over from the 0.9 UI (settings.setup/nav/
safety, the old dashboard panels, the manual-run dialogs). The nine
settings.setup.* strings the wizard still uses move into wizard.* rather
than keeping the tree alive for them.

Fix four defects a grep cannot see:
- the backup-mode dropdown rendered its options untranslated under a
  translated label; the strings already existed with no caller
- RouteStrip never called dashboard.onDays, so Italian lost the
  preposition around the day list
- six count strings interpolated {{n}}, which i18next cannot pluralise
  ("1 events"); they take {{count}} with _one/_other now
- the retention inputs built their accessible name by string surgery

Localize a run's failure message. It is shown in the notification body
and in the run-history row, so it is stored as a key plus parameters
(runs.error_key / runs.error_params, both nullable) and rendered on read
in the configured language. runs.error keeps the English rendering as the
fallback for pre-1.0 rows and for text from other software. CycleAbort
and PbsUnreachableError carry codes; text bubbling out of a connector
lands under a generic key with its message as a parameter.

Reword the missed-run notification, which said "missed scheduled backup"
while catchup fires for every route kind, and give the MONITOR step's
detail a single definition: the notifier parses the observed count back
out of it, which was an unpinned contract between two modules.

Add the en/it parity tests neither side had. The backend one covers
_KIND_LABEL and _ERRORS as well as _MESSAGES, because _pack() falls back
whole-pack and a key present in one language only raises at send time.
2026-08-04 11:55:06 +02:00
2026-07-02 20:46:57 +02:00
2026-07-02 20:46:57 +02:00
2026-07-02 20:46:57 +02:00
2026-07-02 20:46:57 +02:00

Joulenap

Your Proxmox backup server sleeps. Joulenap wakes it, runs the backup, and tucks it back in.

CI License: AGPL v3 Docker image

Joulenap is a small self-hosted web UI + scheduler that runs automated Proxmox VE backups to a Proxmox Backup Server (PBS) that stays powered off most of the time. At the scheduled hour it wakes the PBS over the network (Wake-on-LAN), runs the backup, applies retention and garbage collection, powers the PBS back down, and notifies you — so you get off-site, deduplicated backups without keeping a second machine running 24/7.

The name says it: a joule saved, while your backup server takes a nap. 💤


Why

A dedicated PBS box is the right way to keep backups on separate hardware (3-2-1 rule), but leaving it on 24/7 wastes power for a job that runs a few minutes a night. Proxmox's built-in scheduled backups assume the target is always reachable, so they can't drive a "wake → backup → sleep" cycle.

Joulenap fills that gap with a friendly UI: pick the time, pick which guests to back up, and forget it.

How it works

Joulenap owns the schedule itself (internal scheduler), so nothing on the Proxmox host needs to be modified. It talks to PVE and PBS through their APIs (scoped tokens) and uses a single SSH command only for the PBS power-off, which has no API.

Features

  • Web UI scheduler: choose backup time, enable/disable, see next/last run
  • 🔌 Wake-on-LAN of the PBS, with readiness wait and timeout
  • 🗂️ Per-guest selection: back up all guests, all except a list, or an explicit include list (new guests are covered automatically in the first two)
  • ♻️ Retention (daily/weekly/monthly/yearly), Garbage Collection after each backup, and a separately scheduled verify
  • 🔔 Notifications: Apprise, Telegram, ntfy, Discord, email — on success and/or failure
  • 📜 Live log viewer, run history with per-step detail, live PVE/PBS task output, and manual "Run backup now" / "Run GC now" (optionally leaving the PBS awake) — stoppable mid-run
  • ⚙️ Advanced settings tab with a built-in config.yaml editor, plus an opt-in update check
  • 📊 Integrations: backup status for Homepage, Homarr, Dashy or Glance, plus a Prometheus /metrics endpoint for Grafana (alert when a guest stops being backed up) — see docs/INTEGRATIONS.md
  • 🌍 Multi-language UI
  • 🔒 Login-protected; secrets kept out of the repo

Status

v0.9.0. Feature-complete: scheduler + Wake-on-LAN + vzdump + retention + GC + verify + notifications + setup wizard, packaged as a Docker image — with transport hardening (PBS TLS pinning + SSH host-key verification) and auth hardening (login rate-limit, session hardening). Includes an external-schedules mode (PVE/PBS run their own jobs; Joulenap wakes the PBS, watches the tasks and powers it off when they finish), run history with per-step detail, the ability to stop a job mid-run, integrations for dashboards (Homepage/Homarr/Dashy/Glance) and Prometheus, persistent datastore usage shown even while the PBS is powered off, a per-channel notification test report, and a responsive UI that works on a phone. See docs/ARCHITECTURE.md for the design and API.

Quick start (Docker)

One command — no files to download, no config to edit first. The container creates its own config on first run and you fill it in through the web wizard:

mkdir -p /opt/joulenap/data

docker run -d --name joulenap \
  --restart unless-stopped \
  --network host \
  -e TZ=Etc/UTC \
  -v /opt/joulenap/data:/app/data \
  catubba/joulenap:latest
# then open http://<host-ip>:8080

--network host lets Joulenap send the Wake-on-LAN magic packet on your LAN broadcast; the single data directory persists config, history, logs and the SSH key across updates. You pick your timezone on the first-run screen (pre-detected from your browser), so the TZ above is just a neutral default. Prefer Compose? See docker-compose.example.yml.

📖 Full guide: docs/INSTALL.md walks a Proxmox LXC install from scratch (create the container → install Docker → run Joulenap), plus Docker Compose and a native no-Docker install, timezone, and first-run setup. Every config field is documented in config.example.yaml.

Configuration

All settings live in config.yaml (see config.example.yaml for every field, grouped and commented). You normally never touch it by hand — the container creates it on first run inside the mounted data/ directory, and the setup wizard fills it in. Secrets (API tokens, SSH key, bot token) stay in that config.yaml; the repo's copy is git-ignored so it's never committed.

Security

Joulenap can trigger backups and power machines on/off, so treat it as privileged:

  • Use scoped API tokens for PVE and PBS, not root passwords — the exact privileges each one needs are listed in docs/ARCHITECTURE.md.
  • The SSH key to PBS should be dedicated and, ideally, restricted to the power-off command.
  • PBS API is TLS-pinned: calls to PBS are pinned to its certificate fingerprint (captured at setup), so a swapped/MITM cert is rejected; a legitimately renewed cert is accepted after you re-run PBS detection in the wizard.
  • PBS SSH host key is verified: confirmed once during setup and stored in data/known_hosts; later power-off/GC connections verify against it. Details in docs/CONFIG-WIZARD.md.
  • Keep the UI on your LAN/VPN and behind its login. Don't expose it to the internet.
  • config.yaml holds secrets — keep its file permissions tight and out of version control.
  • Login lockout: after 5 failed login attempts from an IP address, that IP is locked out for 5 minutes (protects against online brute-force attacks).
  • Password floor: admin passwords must be at least 8 characters.
  • Session cookie (app.session in config): set https_only: true when serving Joulenap over HTTPS or behind a TLS-terminating proxy; max_age_days controls session lifetime (default 14 days). Changing the admin password immediately invalidates all existing sessions.
  • First-run setup: complete the initial account setup promptly — the setup endpoint remains open until an account is created (and is rate-limited for security).

Roadmap

  • [] v0.1: scheduler + WoL + vzdump + retention + notifications + web UI
  • [] Garbage Collection after each backup, and scheduled verify jobs
  • [] Per-guest last-backup status from PBS
  • RTC-wake option (BIOS alarm) as an alternative to WoL
  • Multiple PBS targets / off-site sync

License

Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See LICENSE.

Disclaimer

Joulenap is an independent open-source project and is not affiliated with, sponsored by, or endorsed by Proxmox Server Solutions GmbH. "Proxmox" is a trademark of its respective owner; it is used here only to describe compatibility.

Languages
Python 59.1%
TypeScript 36.5%
CSS 4%
Dockerfile 0.2%
HTML 0.2%