22 Commits
Author SHA1 Message Date
Catubba d080731494 docs: collapse the 0.9 rollback into a details block
It is a page of instructions for something most readers will never do, sitting
in the middle of the upgrade section. Collapsed, the upgrade notes stay short
and the steps are one click away for the person who needs them.

The summary line carries the conclusion -- config rolls back by itself, the
database needs a step -- so a reader learns the important half without opening
it. First body sentence dropped as a duplicate of that summary.
2026-08-07 13:31:38 +02:00
Catubba 39932a2d31 docs: say how to go back to 0.9, since the database does not roll back on its own
The upgrade section promised "the .bak is your rollback" and never said what to
do with it. The config half is straightforward -- the backup is the file 0.9
wrote. The database is not: 1.0 widened guest_backups and datastore_stats with
NOT NULL columns 0.9 never writes, so 0.9 on a 1.0 database hits an
IntegrityError on both caches. The cycle swallows those (a WARN in the run log),
but resolve_datastore does not, so the dashboard 500s whenever a backup server
is awake -- exactly when someone is watching it.

Verified against 0.9's own code from the tag, on a 1.0 database: both cache
writes fail before the drop, both succeed after it, and the run history survives
either way. Dropping the two caches is enough because both versions rebuild them
from the backup server.

Also states what the failure looks like if the config step is skipped: 0.9's
model forbids unknown keys, so it refuses to start rather than coming up empty
and later saving over the 1.0 file.
2026-08-07 13:20:51 +02:00
Catubba 4c2fa4a77e docs: drop the power-lease limitation the per-machine key removed
The readme, the changelog and the architecture notes all told a first-time
reader that two datastores on one backup server carry independent power leases
and can therefore sleep and wake that machine once more than needed. Keying the
lease on the host removed it, so what shipped in three files was no longer true.

The architecture notes now describe the key itself, and what a sync route
between two datastores of one machine actually does: one lease, one wake, one
power-off.
2026-08-07 12:18:22 +02:00
Catubba 9820258fa8 docs: state the two limitations a first-time reader will not guess
Namespaces appeared nowhere in any shipped file. Joulenap lists a datastore's
root namespace, and a PBS namespace is configured on the Proxmox storage entry
where Joulenap never sees it, so a namespaced setup backs up, prunes and
collects garbage correctly while every one of its guests reads "never backed
up". Silence made that a wrong number with no explanation; for a backup tool
it is the worst direction for a false negative.

The power lease is also keyed per device rather than per machine, so two
datastores on one backup server can sleep and wake it once more than needed
between consecutive routes. Runs are serialised, so this is waste rather than
incorrectness.

Both are in the readme, the changelog, the example config and the architecture
notes -- the four places someone configuring this actually reads.
2026-08-05 19:47:47 +02:00
Catubba 9d65826278 fix: address the M15 pre-release review findings
Four review passes over the branch — backend, frontend, docs/packaging,
security — and the findings each one confirmed.

Config and secrets:

- omitting a device secret in `PUT /api/config` silently cleared it.
  `deep_merge` merges mappings key-by-key but replaces lists wholesale, and
  1.0 moved devices from mappings into lists, so deleting an
  `api_token_secret:` line in the YAML editor returned 200 and wiped the
  token. A stored secret is now carried over when the field is absent
  entirely; an explicit `""` still clears it.
- the pre-migration `.bak` was created by `copyfile` at 0644 and chmod'ed
  after, leaving a window in which every credential in it was world-readable.
  Opened 0600 up front, like every other secret-writing path.

Frontend:

- a failed SSH keygen put both wizards into an unbounded POST loop: the
  effect depended on the whole setup object, which is a fresh literal every
  render, and a failure clears the request cache so the retry fired from the
  next render instead of from a click.
- an expanded run's timeline froze at the finish, leaving `poweroff` stuck on
  "Running" forever, because polling stopped the moment the run did — and the
  power-off steps are recorded after the run reaches success.
- the route editor merged the PVE and PBS id namespaces, which are unique
  only per list. With a PVE and a PBS sharing a name, clicking the PVE chip
  lit the PBS one, flipped the badge to Sync and saved a route that synced a
  box to itself. The draft now carries kind-prefixed keys, as the topology
  already did.
- Add-PVE could not recover when the PBS landed but the PVE create then
  failed: each retry minted another orphan token on the PBS and re-posted a
  device that already existed. It now resumes instead of repeating.
- the route-enable and guest-include switches had no accessible name.
- upcoming occurrences drifted by an hour across a DST boundary — a fixed
  86_400_000 ms step is a UTC day, not a calendar day.
- a route whose last run aged out of the shared 50-run window claimed it had
  never run.
- the wizard's manual SSH-key step showed the unrestricted public key above
  the restricted `authorized_keys` line, inviting a copy of the wrong one.

Docs: the migration-failure fallback was documented backwards in three files
(the file on disk is preserved, but nothing in 1.0 reads the 0.9 sections, so
the app boots empty with the banner set); TLS pinning and SSH host-key
verification were both documented as unconditional when the runtime degrades
without a fingerprint and trusts on first use; the sync-route credential
propagation was documented nowhere; plus a dead Localization page, a wrong
endpoint for run steps, an omitted session-less endpoint, a self-contradiction
about which wizard calls write to disk, a password-change claim that
contradicted SECURITY.md, and a stale `[Unreleased]` link.
2026-08-05 09:44:32 +02:00
Catubba 5ed03db20c feat(demo): route-aware demo replay and 1.0 documentation
Rewrite demoTimeline.ts around the three-route scenario and rebuild the
scripted replay inside devStub.ts on top of the 1.0 endpoints, then bring
the shipped documentation in line with what actually ships.

Demo:
- demoTimeline.ts keys its online windows by PBS id, so one field covers a
  single-box backup and a sync route's two; steps carry the per-device
  names the backend really emits, plus their detail.
- The demo auto-plays: it opens mid-backup on Nightly and the queued Lab
  route starts by itself when that lands, with the target left awake
  between them and the skipped power-off recording why. The queue and the
  power lease are visible without a click.
- The clock ticks and the fixture calendar shifts by whole weeks, so
  weekdays and times survive and the schedules stay self-consistent. Dev
  stub mode keeps its frozen clock; every replay mutation sits behind the
  demo flag.
- Restore the "fake data" banner and make logout reload rather than strand
  the visitor on a login form.
- build:demo now type-checks first, which it never did.

Fix the expanded run history row refetching its detail only once, so a run
in flight showed a frozen step timeline while its task log kept streaming.

Docs:
- ARCHITECTURE: the route model, the queue and lease, a cycle per kind, the
  migration, and REST tables rebuilt from the shipped routers.
- CONFIG-WIZARD: the two device flows, and the /remote grant a sync route
  needs on a peer configured before 1.0.
- INTEGRATIONS: the new dashboard payload, snippets matching the ones the
  app generates, the labelled metric names, and a 0.9 mapping table.
- README, INSTALL: routes, the five settings tabs, upgrading from 0.9, and
  the Node version CI and the image actually build with.
- SECURITY: transport pinning, auth hardening, the two API-key endpoints
  outside the session, and what Joulenap deliberately does not do.
- CONTRIBUTING: npm test is a separate CI step, and the demo section now
  describes the demo that exists.
- CHANGELOG: the 1.0.0 entry, including the breaking dashboard and metrics
  shapes and the exclude guest mode widening to all.
2026-08-04 14:09:19 +02:00
Catubba 59a281ae4e feat: external schedules mode; bump to 0.9.0
Let PVE/PBS run their own scheduled jobs (backup, prune, GC, sync) and
use Joulenap purely as the power manager: at the scheduled time it wakes
the PBS, watches its tasks, and powers it off once they have been quiet
for a configurable window (issue #27).

- backup.external config block (enabled, first_task_wait, idle_wait);
  both knobs are timeouts, not fixed delays — watching starts as soon
  as the first task appears, and the quiet countdown restarts whenever
  a new task starts, so staggered/chained jobs are never cut short
- new monitor run kind + step: wake -> wait -> watch -> power off,
  recorded in run history with the number of tasks observed; a wake
  where no job ever appears still powers off and warns in the
  notification that the external schedule did not fire
- monitor runs count as the backup slot for last-backup status and the
  missed-backup startup check
- Schedule card mode switch (Scheduled by Joulenap / External
  schedules): external mode hides GC/retention/guest selection and
  shows the two timers with explanatory tooltips; manual panel runs
  the watch cycle instead of a backup and hides the GC button
- notification titles, body lines and duration phase for the watch
  cycle, in both languages
2026-07-29 09:54:52 +02:00
Catubba 6bff430cf8 feat: richer notification bodies, run id in history; bump to 0.8.0
Notifications now carry what triggered the run, a per-phase duration
breakdown, guests as ok/total with the names of the ones that failed,
the next scheduled run and the run number. Per-guest outcomes are read
off the vzdump task log as it streams, into a summary the caller owns,
so a failed task still reports which guest broke.

Also fixes two delivery bugs: bodies were collapsed onto a single line
on HTML channels (Apprise was never told the text was plain), and the
missed-backup alert mixed UTC with the configured timezone.

The run number is now the first column of the run history, so the id a
notification quotes can be looked up in the interface.
2026-07-25 22:35:48 +02:00
Catubba 5b3da78d20 feat: light theme with header toggle; bump to 0.7.0
The dark/light palettes now live as CSS variables in index.css, switched by a
data-theme attribute on <html>; theme.ts tokens reference the variables so the
whole UI (CodeMirror included) restyles without re-rendering. Scattered hex
literals are folded into shared tokens, and the four hex+alpha concatenations
are replaced with color-mix so they work on variable references.

The header gains a moon/sun toggle between Settings and Logout. The choice is
persisted to app.theme through the config API (the value existed since 0.1.0
but was never read) and mirrored to localStorage, which an inline script in
index.html applies before first paint so there is no flash of the wrong theme.
Switching cross-fades over 300ms, skipped under prefers-reduced-motion.

Also: header action buttons stretch to equal height (Logout was 3px short),
the dev stub persists PUT /config edits instead of silently reverting them,
and a parity test asserts both palettes define the identical token set.
2026-07-23 23:37:24 +02:00
Catubba 530cb5e9c3 release: 0.6.0 — run history, job cancellation, Prometheus metrics
Run history in the UI: the activity card gains a second tab listing every run
with job type, trigger, result, duration and guest count. Rows expand in place
to that run's steps and log lines. Backed by /api/runs, which had existed since
0.1 with no consumer; RunSummary gains guests_ok.

Job cancellation: Run backup / Run GC turn into Stop while a job is in flight,
behind a confirmation that can also power the PBS off afterwards. Cooperative
cancellation checked in the existing poll loops (task wait, PBS wake wait,
between steps), and the underlying PVE/PBS task is stopped, not abandoned, so a
cancelled backup does not keep running on the server. A running verify is
stoppable too. Previously a stuck job blocked every later run and manual
power-off until restart.

Prometheus /metrics for Grafana, protected by the existing dashboard API key.
Sixteen gauges including per-guest last-backup times, so a guest dropping out of
the backup set can be alerted on. Written directly in the text exposition format
rather than adding a dependency; a scrape never wakes the PBS.

Notifications now name the job that ran: a failed verify or GC no longer reports
"backup failed".

Removed the dead backup.guests.auto_include_new key. It was never read, while
its name and default implied new guests were picked up automatically. Existing
configs still load (the key is stripped) and the docs now state the real rule.

Documentation accuracy pass over README, ARCHITECTURE, INSTALL, INTEGRATIONS,
SECURITY and config.example: corrected the PVE and PBS token privilege lists,
the garbage-collection and guest-selection descriptions, the supported-versions
table and the API reference, and added a Settings walkthrough.

Toggle switches are announced as switches by screen readers and can no longer
submit a surrounding form.
2026-07-23 11:20:00 +02:00
Catubba d5f7132ee1 release: 0.5.0 — Advanced settings tab, config.yaml editor, update check
Closes re-review findings 11.8, 11.10 and 11.14.

- Advanced settings tab exposing the config-only knobs: backup mode,
  bwlimit, keep_last/keep_yearly, history retention, port, session
  lifetime and the HTTPS-only cookie flag.
- In-browser config.yaml editor (CodeMirror 6, lazy-loaded chunk) over
  new GET/PUT /api/config/yaml. The PUT path reuses the extracted
  _apply_config helper, so the editor gets the same redaction,
  deep-merge and validation as PUT /api/config; a Copy button yields a
  secret-free config for bug reports.
- Opt-in update check (app.update_check, default off): GET /api/update
  asks GitHub once a day behind a 24h cache and never touches
  /api/health, which the Docker healthcheck depends on.
2026-07-22 19:20:55 +02:00
Catubba 349d1b8789 release: 0.4.4 — UX + accessibility remediation
Frontend UX and accessibility fixes from the v0.3.1 re-review, plus
self-hosted fonts.

- Accessible confirmation dialog: role=alertdialog, aria-modal, focus
  trap, Escape to close, focus restored to the trigger on close
- Self-hosted IBM Plex fonts (latin subset) — no Google Fonts request;
  the UI renders offline / air-gapped
- Login and first-account screens are proper forms with password-manager
  autocomplete hints and a progress state on submit
- Header status label shows "GC running" / "Verify running" during those
  jobs instead of always "Backup running" (new /api/status running_kind)
- Block Selective backup mode with no guests selected, with an explanation
- Setup wizard: errors on lower steps scroll into view + role=alert;
  Detect MAC reports when auto-detection finds nothing
- Empty guest-list state in the guests panel
- Harden shared Dropdown buttons with type="button"

Also fixes the CHANGELOG compare links (the 0.4.3 release omitted them).
2026-07-12 23:18:00 +02:00
Catubba 344665126a release: 0.4.3 — frontend medium-finding remediation (FE-H4, FE-M1..M10)
Setup wizard validation (FE-H4): a step no longer completes and Save no longer
unlocks when a check fails (unreachable PBS, missing PBS token, empty WoL MAC).

Frontend robustness / UX (re-review FE-M cluster; FE-M3 skipped):
- FE-M1  client-side request-timeout backstop (45s), localized error
- FE-M2  unsaved-changes confirmation guard + beforeunload
- FE-M4  surface manual-action start failures inline
- FE-M5  surface scheduler-toggle failure next to the toggle
- FE-M6  fix useTaskLog overlapping-poll duplicate lines (guard + idempotent append)
- FE-M7  Localization saved-note only after save + draft resync
- FE-M8  keep last guest list on transient error + error note
- FE-M9  wizard re-save preserves hand-configured PVE port/TLS
- FE-M10 correct Integrations regenerate-key copy + read/refresh via ConfigContext

Version bumped to 0.4.3; CHANGELOG updated.
2026-07-12 17:51:06 +02:00
Catubba 5a958c0539 release: 0.4.2 2026-07-12 00:05:01 +02:00
Catubba c87b1e1c49 release: 0.4.1
Patch release: backend robustness fixes (SQLite WAL/busy_timeout/foreign_keys,
manual power-off race, worker-thread lock leak, prune-job timezone) and a
security hardening (config.yaml written owner-only). No feature or API changes.
2026-07-10 23:01:35 +02:00
Catubba 3c3d8d99ac release: 0.4.0
Bump every version reference to 0.4.0 and add the 0.4.0 CHANGELOG entry
(keep-PBS-on manual jobs + manual GC cycle; account current-password,
cron-validation, wizard-secret, and exclude-mode fixes).

Minor bump: new user-facing feature and an additive, backward-compatible
API field (keep_on defaults to false = prior behavior).
2026-07-10 21:58:48 +02:00
Catubba 1cf452217e release: align every version reference on 0.3.1
frontend/package.json had drifted at 0.2.0 since the 0.2.0 release, and the
README status heading was never updated for 0.3.0. The lockfile pins the root
version too, so it is synced here to keep `npm ci` happy.

The Docker references all use `:latest` and need no change.
2026-07-10 00:09:47 +02:00
Catubba 6572d5a950 release: v0.2.0 — dashboard integration + persistent datastore usage 2026-07-09 02:49:34 +02:00
Catubba 61cdf7268e docs: add dashboard integration guide 2026-07-09 00:20:40 +02:00
Catubba 48e4bc9b42 release: v0.1.1 — version bump + doc corrections 2026-07-08 22:48:57 +02:00
Catubba 37bfbbff16 docs: auth-hardening settings + first-run guidance (JN-009/010/011) 2026-07-08 16:57:56 +02:00
Catubba 28cc0f425d Initial commit 2026-07-02 20:46:57 +02:00