Releases: Joulenap/joulenap
Release list
v1.2.1
Patch release for #51.
Fixed
- A sync route that pulls now tells PBS who owns the backups it fetches, namely the API token Joulenap uses on the receiving server. Without that field PBS falls back to
root@pam, and the run fails on every group with "owner check failed" as soon as the target datastore already holds backups owned by the token, which is the normal state of any datastore Joulenap itself backs up to (#51). Push routes were never affected: pushed backups belong to the account named in the remote configuration, whatever the job asks for.
Upgrading
A pull route that ran before this fix wrote its groups on the target as root@pam, so the first run afterwards reports the same owner mismatch the other way round. Reassign those groups once on the receiving server, with Change Owner in the datastore's Content tab or with proxmox-backup-client change-owner.
Docker
catubba/joulenap:1.2.1 (also :latest), digest sha256:e86063e8ae6648df1e7a8f621fbe7775976509153c153c0d19e98a5baf8248b4.
Full changelog: https://github.com/Joulenap/joulenap/blob/main/CHANGELOG.md
v1.2.0
Set-and-forget backup routes, and guest names back in the Notes column.
Added
- Backup routes gained an Exclude guest mode next to All and Selection: the route covers everything on its sources except the guests you tick, so a VM or container created later is backed up on the next run without touching the route. The exclusion is handed to
vzdumpitself (--allwith--exclude), so Proxmox keeps deciding which guests a run covers and a guest marked exclude from backup on the host is still honoured (#46). - Each of the three guest modes now explains itself under the switch, including the one that was missing before: Selection says outright that a guest created later has to be added by hand.
- Backups triggered by Joulenap now carry the guest name in their Notes, the way a backup job created in the Proxmox VE web interface does. Both the PVE and PBS backup lists show the name next to the ID again, so picking the right snapshot to restore no longer means matching VMIDs by hand (#48).
Changed
- A route that excludes every guest is refused at save time, the rule that already covered a Selection with nothing ticked. Either one wakes the backup server on schedule and aborts the run without ever writing a backup.
- A 0.9 config that used the old
excludeguest mode migrates straight through instead of widening toallwith a warning.
Upgrading
Nothing to do. Existing routes keep the mode they have, and the new one is opt-in per source.
Docker
catubba/joulenap:1.2.0 (also :latest), digest sha256:e906e9e4e6b6bb63602c46f1c8d132855d29cc53fbcb7173baf1f3b5dc844e77.
Full changelog: https://github.com/Joulenap/joulenap/blob/main/CHANGELOG.md
v1.1.3
Patch release for #44, and the second half of the fix started in 1.1.2.
Fixed
- The reachability probe now makes a real request instead of shaking hands and hanging up.
proxmox-backup-proxyprepares each connection, peer address and all, in the moment right after the handshake, and a client that disappears exactly then leftFailed to get api service: Transport endpoint is not connectedin the PBS syslog on every poll. The probe now asks forGET /and reads the answer to the end, which is one round trip and about 30 ms on a LAN (#44).
Docker
catubba/joulenap:1.1.3 (also :latest), digest sha256:271a9ca33a99996fa58b45c9a7acf0f86fabd1d3eb1360dea6bc4e5a802de643.
Full changelog: https://github.com/Joulenap/joulenap/blob/main/CHANGELOG.md
v1.1.2
Patch release for #44.
Fixed
- The reachability probe that tells an awake PBS from a sleeping one now completes the TLS handshake instead of opening a connection and closing it without speaking. The old probe made
proxmox-backup-proxywritefailed to check for TLS handshakeandFailed to get api serviceto the PBS syslog on every poll, roughly every 8 seconds while a dashboard was open (#44).
Docker
catubba/joulenap:1.1.2 (also :latest), digest sha256:dcf997a32a8144c9f9e2035d0030d66a36f028506692238ae262663d2f8d8ea0.
Full changelog: https://github.com/Joulenap/joulenap/blob/main/CHANGELOG.md
v1.1.1
Patch release for #38, follow-up to 1.1.0.
Fixed
- A run that could not create its own history row (SQLite
database is lockedpast the busy timeout, a full disk) used to vanish: no row, no notification, one line in the container log, so a scheduled sync looked as if it had never fired. It is now reported on the configured notification channels as a failed run (without a run number) and the single-run lock is released as usual (#38). - The SQLite database now uses
synchronous=NORMALwith WAL, so a commit no longer fsyncs on every task-log line, and the busy timeout is 30 s instead of 5 s. A disk that stalls for a few seconds under a running backup (a VM on spinning disks, for instance) no longer turns a task-log write intodatabase is lockedand a failed run. In WAL this remains safe across an application crash; a power loss can at worst lose the last few seconds of run history, never corrupt the file (#38).
Docker
catubba/joulenap:1.1.1 (also :latest), digest sha256:c6485c912ee51341f3d8958c7cb5d06656ad0b78d5c8ce16b34ef20867947d77.
Full changelog: https://github.com/Joulenap/joulenap/blob/main/CHANGELOG.md
v1.1.0
Docker image: catubba/joulenap:1.1.0 (also :latest) — digest sha256:338f9df08dcb4af806721edad90243aa45efecd96381a32d82082a77a5c0d0c5.
Added
- Sync routes can now keep an independent, tighter retention on the target: the route's retention (already shown in the form, but until now not applied to sync routes) is pruned on the target datastore after each sync, before GC. All-zero retention means no prune, and protected snapshots are never removed. Two new sync options in the route form:
transfer_last(copy only the newest N snapshots per group, PBStransfer-last) andremove_vanished(delete on the target what disappeared from the source, PBSremove-vanished; opt-in, off by default). Together they let an off-site or S3-backed copy stay smaller than its source instead of re-growing to it on every run (#36). - The PBS token grant for sync routes is now
RemoteAdmin+RemoteDatastoreAdmin(wasRemoteSyncPushOperator): a push job withremove_vanishedneedsRemote.DatastorePrune, which the old role lacks. A box granted by 1.0 keeps working as before; re-run Grant sync permissions on it (Settings → Devices → edit the PBS that creates the job: the source for push, the target for pull) before turningremove_vanishedon for a push route, or PBS refuses to create the job with "permission check failed".
Fixed
- Long-running tasks are no longer failed at 6 hours. The wait on a PVE/PBS task had a hard 6-hour cap, so a first full sync to an S3/Backblaze datastore was reported as
failed (unknown status)while the PBS worker went on and completed. The cap is now a no-progress timeout: a task that keeps writing to its log runs as long as it needs, one that goes silent for 6 hours fails withtimeout 6h(#37). - A run could stay "Running" forever after its worker died: if a database write failed at the wrong moment (a locked or slow SQLite at the peak of the log tail), the session was left unusable, the step and the run could not be closed out, and only a restart's sweep ended them, while Stop run answered "not the one in progress". Writes now recover from a failed commit, a run whose session died is still closed out on a fresh one, and Stop run on such an orphaned row marks it interrupted instead of refusing (#38).
- The task log of a backup no longer starts with PVE's "no content" placeholder, which was also swallowing the real first line of the vzdump output (#38).
Changed
- If you had a sync route configured before this release, its retention block was saved with the form defaults (keep-daily 7 / weekly 4 / monthly 6) and did nothing. It now prunes the target with those values on the next run: review it, or set every keep-* to 0 to keep the old "never prune" behaviour.
v1.0.0 Multi-PVE / Multi-PBS release
Joulenap is no longer built around one Proxmox host backing up to one backup server. It now models
devices — any number of PVEs and any number of PBSs — and routes between them. Existing
configurations are converted automatically on the first start; see Changed below for the one
conversion that is lossy, and for the two breaking changes outside the interface.
After upgrading, expect one alarming-looking display that is not a problem. Backup history is
tracked per route, and the conversion gives your old schedule new route ids, so Last backup per
guest reads "never" for every guest and the converted routes read "never run" — with all the old
runs still listed underneath. Nothing has been lost: the caches fill in again per guest as runs
happen, and the first run of each route restores its badge.
One limitation worth knowing before you configure. Joulenap reads the root namespace of a
backup datastore, so if your Proxmox storage entry writes into a PBS namespace the backups, the
retention and the garbage collection all work, but Last backup per guest reads "never" for those
guests.
Added
-
Routes. A route is one scheduled flow of backup data between devices: sources, a target, its
own time and weekdays, its own retention, its own options. Four kinds, inferred from the devices
you pick: backup (one or more Proxmox hosts into a backup server, including a fan-in from
several at once), sync (one backup server into another, pull or push, for a real off-site
second copy), external (Joulenap starts nothing and only watches the jobs PVE and PBS run on
their own schedules), and verify. Guests are selected per source, because vmids collide
between hosts. -
Multiple Proxmox hosts and multiple backup servers. Each is a device with its own address,
scoped token, TLS fingerprint and — for a backup server — its own wake-up and power-off settings.
A backup server you keep powered on all the time is supported: turnmanaged_poweroff and
Joulenap treats it as always available instead of trying to wake it. -
A run queue and a per-server power lease. One run is ever in flight; the rest wait their turn
instead of being dropped. Each backup server a run needs is leased: the first holder wakes it, the
last release powers it off — and only if nothing still queued needs it, the run succeeded, and you
did not ask to keep it on. So two routes an hour apart on the same box wake it once, and a sync
route wakes both boxes and releases them independently. The power-off step in the run timeline
says which of those happened. -
A rebuilt interface. The homepage is now an operations view: a live backup map of your hosts
and backup servers with the routes drawn between them, the route strip where routes are created,
edited, paused and run by hand, what is coming up next, and a run history whose rows expand into a
per-step timeline with the PVE/PBS task output streaming underneath. Settings became five tabs
(Devices, Account, Notifications, Integrations, Advanced) with a device card and edit modal for
every box, and a removal guard that names the routes still using one. -
Two guided wizards, replacing the single linear setup. Add a Proxmox VE connects and then
reads that host's storage configuration to discover the backup servers behind it, linking the ones
you already registered and offering to configure a new one inline. Add a Proxmox Backup Server
walks connection, wake-up (with a Test button that sends a real magic packet before you find out
at 04:00 that Wake-on-LAN was never armed) and power-off. Both work from pasted API tokens, or
provision everything themselves from a root login used once and never stored. Detect MAC now
works in the container: it used to shell out toping, which the image does not contain, so it
only ever found a machine something else had recently talked to. The Wake-on-LAN interface is
a list of the host's own NICs instead of a text box in which a typo silently fell back to
auto-detection. -
A backup server's API token is named after its datastore —
joulenap-backup,
joulenap-offsite— so one machine serving several datastores gets one token per device instead
of the setups fighting over a single name. Deleting an API token also drops the permissions
granted to it, so a shared name would have meant configuring the second datastore left the first
one both locked out and unrepairable by re-entering its secret. A Proxmox host is one device and
keeps the plainjoulenap. Tokens already in use are untouched; the name only applies to ones
Joulenap creates from now on. -
Ad-hoc maintenance per backup server. Run a garbage collection or a verification on one box
from the homepage, without a route. It queues and reports like any other run; only the route
column is empty. -
A route can be stopped and a
config.yamlcan be exported. Stopping a run also stops the
PVE/PBS task behind it and asks whether to power the server down. -
The header pill reports health, not just activity. While nothing is running it answers the
morning-after question directly: green "All OK" with the next fire when the last run succeeded,
red with the route's name and the time when it failed. A run you stopped yourself reads as plain
idle — a deliberate stop is not a failure. -
Every action sits on the thing it acts on. Each route card has its own Run button, and each
backup server card in the map has Run GC and Run verify; all three open a confirm dialog already
aimed at that route or box. The Manual run panel and its "which one?" dropdowns are gone, and
Upcoming runs uses the freed column to show about twice as much of the schedule. -
A guest that has never been backed up is called out — highlighted in the guest list, with a
count in the panel header that a search filter cannot hide.
Changed
-
GET /api/dashboardand/metricschanged shape, and this breaks existing widgets and
alerts. With several routes and several backup servers there is no single "next run" or "the
datastore" left to report. The dashboard payload is now{state, routes[], pbss[]}, so a widget
picks a list entry (routes.0.next_run) instead of a flat field. Prometheus series are labelled:
joulenap_next_run_timestamp_secondsbecame
joulenap_route_next_run_timestamp_seconds{route="..."}, thejoulenap_last_run_*family became
joulenap_route_last_run_*{route="..."}, every PBS and datastore series carriespbs=, and
per-guest freshness is now labelled{vmid, pve, pbs}because a vmid alone stopped being unique.
The field-by-field mapping is at the top ofdocs/INTEGRATIONS.md, and Settings → Integrations
always shows a snippet generated for the version you are running. -
Your
config.yamlis migrated automatically, with a parachute. Thepve:,pbs:and
backup:sections becomepves[],pbss[]androutes[]on the first start after the upgrade:
the backup job becomes a route named Backup, a scheduled verification becomes one named Verify,
and schedules, guest selections and retention come across with them. The original file is copied
toconfig.yaml.pre-overhaul.bakfirst. If the converted config fails validation the file on disk
is left untouched, but the app then starts with no devices and no routes — nothing is scheduled
until it is fixed — and says so in a banner rather than looking like a fresh install. -
The
excludeguest mode is gone, and a migrated route widens to "all guests". Inverting an
exclusion list needs a live guest list that is not available while the config is being read, so
such a route is converted to "all" and a warning is logged. It will back up more than before,
never less — but it is worth checking after the upgrade. -
Garbage collection and verification are per-route options, set in the route editor's Advanced
section, rather than global maintenance settings.maintenance:now holds only how long run
history is kept. -
Guest selection and retention moved out of Settings and into the route that uses them. So did
backup mode, the bandwidth cap and the minimum-free-space check. Wake timeout, Wake-on-LAN retries
and the external-watch timeouts belong to a backup server and live on its device card. -
GET /api/guestsnow requires a?pve=parameter and reports, per guest, which backup servers
hold a snapshot of it. Collapsing newest-per-vmid across every host would have shown the wrong
host's backup date once a second one existed. -
Notifications name the route and no longer describe every missed run as a missed backup. A run
that fails reports its error translated, in the interface as well as in the notification. A garbage
collection or verification started by hand has no route, so it names the backup server instead. -
Contrast, focus and keyboard behavior were retuned across the interface. Muted text meets
WCAG AA, input borders meet 3:1, keyboard focus is visible everywhere, dialogs start focused on
their first field, error banners scroll into view instead of appearing off-screen, Enter saves
the route editor, the language and timezone pickers are native selects, and a failed route
toggle reports why instead of silently snapping back. On phones the run history becomes cards,
the map stacks, and touch targets grow to 44px.
Removed
- The single
pve:/pbs:/backup:config sections, along withpve.node(cluster nodes
are discovered at runtime, which is also how a cluster is detected) andpve.storage_id(a host
now maps each backup server to the storage it uses for it). - External schedules as a global mode. It is a route kind now, so one backup server can be
watched while another is driven by Joulenap — which the global switch made impossible. - The endpoints the single-job model needed: `POST /api/backup...
v0.9.0 — External schedules mode
Added
- External schedules mode. For setups whose backup, prune, GC and sync jobs are already scheduled natively on PVE/PBS: Joulenap can now act purely as the power manager — at the scheduled time it wakes the PBS, watches the tasks the servers start on their own, and powers it off once they have been quiet for a configurable window. Selected with a new switch at the top of the Schedule card ("Scheduled by Joulenap" / "External schedules"); in external mode Joulenap starts no backup or GC of its own, and the run history records the watch as its own run kind with the number of tasks observed. A wake where no job ever appears still powers the PBS back off and says so in the notification, so a misfiring PVE/PBS schedule is noticed instead of silently missed. (#27)
- Live demo build.
npm run build:demoemits a self-contained copy of the UI that answers its own API calls from fixtures, so it can be hosted as static files (joulenap.com/demo). It opens on the dashboard with no login, carries an orange banner making clear the data is fake, and replays a scripted backup cycle — wake, vzdump, prune, GC, power off — when you press Run backup now. The regularnpm run buildthat the container image uses never includes any of it.
Docker
docker pull catubba/joulenap:0.9.0
Digest: sha256:416d7d00aa143188622feabc611cff352e3d6ecaee7bacd8471fc07b9c4fc380 (:0.9.0 and :latest)
Full changelog: v0.8.0...v0.9.0
v0.8.0 — Richer notifications
Added
- Notifications say a lot more. Every run notification now opens with what started the run
(scheduled or manual), breaks the duration down by phase
(12m 34s (backup 7m 10s · GC 1m 6s)), reports guests as a fraction with the names of the ones
that failed (Guests: 12/14 (failed: web01, db02)), and closes with the next scheduled run and
the run number. The alert for a run a restart interrupted also states how long the PBS has been
awake. All of it is translated in both languages. - Run history shows the run number. A new first column, so the
Run #128a notification
quotes can actually be looked up in the interface. - A failed backup names the guest that broke. Proxmox runs one vzdump task for all selected
guests, so a single guest failing used to fail the run with no indication of which one; the
outcome is now read off the task log as it streams, so the notification can name it.
Fixed
- Notification bodies keep their line breaks. Channels that deliver in HTML (email, Telegram)
collapsed the whole body onto a single line because Apprise was never told the text was plain
text. - Notification timestamps all use the configured timezone. In the missed-backup alert the
"Last backup run" line was rendered in UTC while the two lines around it usedapp.timezone,
so the same message could disagree with itself by hours.
Docker
docker pull catubba/joulenap:0.8.0
Image digest: sha256:3ae72d6220fd07bcb3680859a9324c53ac52508a28efb59a746513664bf1c003
(also tagged latest).
Full changelog: v0.7.0...v0.8.0
Joulenap 0.7.0
Added
- Light theme. The interface now ships the light theme that
app.themealways promised: a header toggle (moon/sun, between the Settings and Logout buttons) switches the whole interface — dashboard, settings, dialogs and the config.yaml editor included — with a short cross-fade, and remembers the choice inconfig.yamlso it survives restarts and follows you to other browsers. The login screen picks up the last theme used on that device. Dark remains the default, and existing installations that never touch the toggle look exactly as before. Users who have asked their system to reduce motion get an instant switch instead of the fade.
Fixed
- The
app.themeconfig value is honoured. It was accepted and stored since 0.1.0 but never read; setting it tolightnow actually applies the light theme at startup. - Header buttons render at the same height. The Logout button was a few pixels shorter than its neighbours.
Docker
catubba/joulenap:0.7.0 / catubba/joulenap:latest — digest sha256:651375718b7afb868e171770b80becbf16a18bf11e4b0b5e2e1945210bfbedf6