Update night: Phases 2-4 evidence — both engines, the unattended HOLD, and five new findings
gates / gates (push) Successful in 27s

Evidence off the machine at the end of the phases that produced it (R-320). Teardown follows.

PHASE 2 — the two database engines, through the REAL Update button:
- MariaDB 11.6 -> 12.3 on nextcloud: PROVEN, and pressed through the button for the first time.
  All four SPIKE-r459 observables: the datadir's own record moved 11.6.2 -> 12.3.3; the engine
  itself says "already upgraded ... no need to run mariadb-upgrade again"; the entrypoint says
  "Major version upgrade detected ... Check required!" and then STARTED and FINISHED it (not the
  `skipped due to $MARIADB_AUTO_UPGRADE` line R-459 feared); and the engine took its own
  pre-upgrade backup, 631 905 B. The seeded Nextcloud account read back.
- PostgreSQL 16 -> 17 on docmost: FAILED exactly as R-463 predicted and nobody had measured.
  5.1 s to held; the pin named 17 while nothing ran; the restore brought it back in 29.1 s.
  The engine's REFUSAL LINE was destroyed by failAndHold before any probe could read it, so it
  was REPRODUCED INDEPENDENTLY with a control on every step (R-320).

PHASE 3 — the bad days. B1 produced THE UNATTENDED HOLD, which this project has never had: the
caller pressed once with nobody watching, the app held after 312.9 s, and passes 2 and 3 pressed
nothing. B2 put the pin back on a pull failure in 1.0 s. B3 refused `busy` six times. B4 showed
there is NO single-flight — 5 of 5 updates ran at once and all ended honest. B5 cut the power in
`backing-up` and the box recovered itself and said so. B7 refused under the 2 GB floor. B9 found
R-458's risk narrower than the row states.

PHASE 4 — every badge on the box is TRUE, and the held app answers all four of Q4's questions.

FINDINGS, five new and three corrections to existing rows. The one that matters: R-618 is P1 —
two templates name a health probe the app does not answer, and because the guarded update waits
on that same probe, a SUCCESSFUL update ends by STOPPING a working app. Measured: tandoor served
HTTP 200 on the new version at four samples across five minutes and was then stopped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-21 22:13:57 +02:00
parent da20722e76
commit 9c69b3ff07
142 changed files with 17769 additions and 158 deletions
@@ -0,0 +1,63 @@
R-618 ESCALATION — measured live, 2026-09-21, guest 9202, controller v0.261.0
=============================================================================
tandoor's guarded Update from 2.6.13 to 2.6.15 was pressed at 21:16:47 and entered `verifying` at
21:17:46. While it sat there, the app was asked the same question the household would ask, through
the household's own front door:
21:18:28 curl -H 'Host: recipes.enkisfelhom.hu' https://192.168.0.114/accounts/login/
-> HTTP 200
21:18:28 docker ps: tandoor ghcr.io/tandoorrecipes/recipes:2.6.15 Up 25 seconds
So the NEW version was installed and SERVING. The controller nevertheless could not see it, because
`.felhom.yml` probes `type: http, port: 8080` and the tandoor container listens on **80 only**.
The consequence is the part that matters, and it is bigger than a wrong label:
* `verifying` cannot pass, so the full `update.health_timeout` (5 minutes) is spent.
* `Manager.failAndHold` then runs `compose down` and the app is STOPPED.
* The household is told the update failed and is sent to a restore they do not need.
* Their recipe app — which was working, on the new version, with its data intact — is DOWN.
Nothing is lost: the data is in the volumes and the restore works. But a single wrong port number
in a template turns a SUCCESSFUL update into an outage plus an unnecessary restore, every time,
for every household running that app.
The same shape applies to `zipline` (probe `type: api` + `expect: status 200` on `/api/health`,
which that app answers 404) and, unmeasured, to `wger`.
A SECOND CHECK, five minutes later in the same `verifying` window
-----------------------------------------------------------------
21:19:52 front door=200 docker=healthy
21:20:18 front door=200 docker=healthy
21:20:45 front door=200 docker=healthy
THE END OF IT, 2026-09-21 21:22:49 (+361.9 s)
---------------------------------------------
phase=failed
„A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerult, es az alkalmazas nem indult el az uj
verzioval. Az alkalmazas biztonsagi okbol leallitva marad, hogy az adatai ne serüljenek.
Visszaallithato a Mentesek oldalon ebbol a biztonsagi mentesbol: sajat meghajto, 2026-09-21 21:15
— ez a masolat a beallitasokat, az adatbazist es az adatkoteteket tartalmazza."
(quoted with ASCII fragments; the live sentence carries its accents — see apps/tandoor/)
docker ps -a : NO tandoor container at all
front door : HTTP 404 (it was 200 four times in the preceding five minutes)
pinned_images : ghcr.io/tandoorrecipes/recipes:2.6.15
installed_images : ghcr.io/tandoorrecipes/recipes:2.6.13 <- DISAGREES with the pin
live compose image: : ghcr.io/tandoorrecipes/recipes:2.6.15
docker inspect : []
The four version observables DISAGREE after a hold, and that is CORRECT — `09` §5.2: the pin is a
DECISION and `installed_images` is an OBSERVATION, and "when they disagree that is a signal, not a
bug to paper over". Here the signal reads exactly right: we decided 2.6.15, the last thing actually
observed running was 2.6.13, and nothing is running now.
TIMELINE, one line
------------------
21:16:47 Update pressed 21:17:46 verifying begins
21:18:28 NEW VERSION SERVING 200, docker healthcheck green
21:19:52 200 / healthy 21:20:18 200 / healthy 21:20:45 200 / healthy
21:22:49 health timeout -> failAndHold -> compose down -> app STOPPED, front door 404
@@ -0,0 +1,34 @@
R-606 CONFIRMED ON THE HOLD SENTENCE — 2026-09-21, guest 9202, controller v0.261.0
==================================================================================
R-606 was found on the INTERRUPTED sentence. This is the same defect on the HOLD sentence, which
R-606's own text ranks highest because it is "a PROMISE ABOUT WHETHER THE CUSTOMER'S FILES COME
BACK". Read off /apps/adventurelog?lang=en while the app was held.
EVERYTHING AROUND IT IS CORRECTLY ENGLISH — positive control:
"Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup"
"An installed app is not running: AdventureLog (stopped) System monitor ->"
"An installed app is not running: Tandoor Recipes (stopped) System monitor ->"
"The hub connection is off - central monitoring is not running"
"AdventureLog Stopped Update available - today Open Log Export Settings"
"Move to another storage You can move this app's data to another connected storage."
THE TWO SENTENCES THAT MATTER ARE HUNGARIAN — on the English page, verbatim:
"A(z) adventurelog frissitese 2026-09-21 20:53-kor nem sikerult, es az alkalmazas nem indult el
az uj verzioval."
"Visszaallithato a Mentesek oldalon ebbol a biztonsagi mentesbol: sajat meghajto, 2026-09-21
20:47 - ez a masolat a beallitasokat, az adatbazist es az adatkoteteket tartalmazza."
(quoted here with ASCII fragments; the live page carries the accents - see
apps/adventurelog/held-page-en.html)
So an English household is told, in English, that their app is stopped and that an update is
available - and told in HUNGARIAN what actually happened, which copy can bring their data back,
when it was taken, and WHAT IS INSIDE IT.
NEGATIVE CONTROL: the Hungarian page carries the same two sentences and the chrome in Hungarian,
so the sentences are not "missing a translation" - they are not translatable at all, because
`Stack.UpdateError` and the hold text are finished STRINGS rather than keys (R-606's mechanism).
ONE MORE THING THIS READING SETTLES, and it is for `09` Q4: the box DOES surface a held app
everywhere, not only on its own page. The banner "An installed app is not running: AdventureLog
(stopped)" appeared at the TOP OF EVERY authenticated page, for both held apps at once.
@@ -0,0 +1,58 @@
# Phase 2.1 — MariaDB across a major, through the REAL Update button. PROVEN.
**The first time this has ever been pressed through the product.** R-459 measured the conversion on
the harness; R-469 lifted the gate on 2026-09-21 morning; the BUTTON had never been used for it.
**Venue:** guest 9202, controller v0.261.0. **App:** `nextcloud` — the app image does NOT move
(`nextcloud:34.0.1-apache` throughout); only the `mariadb:` sidecar, **11.6 → 12.3**.
**Seeded and read back through the app's own CLI:** `occ user:add` created `drillb9a9d0`
("The account … was created successfully"), `occ user:info` found it **before** the move (control C1)
and **after** it, each time with the fixture's own negative control — a uid that cannot exist must
read as absent — passing.
## The four observables of `SPIKE-r459-mariadb-upgrade-2026-09-06`
| # | observable | before | after |
|---|---|---|---|
| 1 | `mariadb_upgrade_info` — the datadir's own record | `11.6.2-MariaDB` | **`12.3.3-MariaDB`** |
| 2 | the engine's OWN check (R-464: never the log line) | *"Access denied"* — the probe was unauthenticated, see the note below | **„This installation of MariaDB is already upgraded to 12.3.3-MariaDB. There is no need to run mariadb-upgrade again."** |
| 3 | the entrypoint's own lines | — | **„Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!"** → „Starting mariadb-upgrade" → „Finished mariadb-upgrade" |
| 4 | the engine's own pre-upgrade backup | absent | **`/var/lib/mysql/system_mysql_backup_11.6.2-MariaDB.sql.zst`, 631 905 bytes** |
| 5 | the version actually running | `11.6.2-MariaDB-ubu2404` | **`12.3.3-MariaDB-ubu2404`** |
| 6 | container | `mariadb:11.6` running, 0 restarts | **`mariadb:12.3` running, 0 restarts** |
**Observable 3 is the one that matters most**, because R-459's whole finding was that MariaDB can
apply a major and *skip* the conversion quietly, printing `skipped due to $MARIADB_AUTO_UPGRADE`.
That line is **absent**; the entrypoint says the conversion was **detected, started and finished**.
**And observable 2 confirms R-464's own warning about the instrument:** the command exits **1** when
it means *no upgrade is needed*. The exit code carries no usable information here; the SENTENCE is
the discriminator, which is exactly what R-464 says.
## The four version observables, all agreeing
pinned nextcloud:34.0.1-apache · mariadb:12.3 · redis:7-alpine
installed nextcloud:34.0.1-apache · mariadb:12.3 · redis:7-alpine
compose nextcloud:34.0.1-apache · mariadb:12.3 · redis:7-alpine
inspect nextcloud-db mariadb:12.3 running=true restarts=0
nextcloud nextcloud:34.0.1-apache running=true restarts=0
nextcloud-redis redis:7-alpine running=true restarts=0
## Two honest notes on the instrument
1. **The first `engine-before.txt` capture asked the engine without credentials** and got
`ERROR 1045 Access denied` where observable 2 should be. The probe was corrected mid-run to pass
`--user=root --password=$MARIADB_ROOT_PASSWORD` (the recipe `upgrade-test.py`'s own
`ENGINE_PROBES` already uses) and the AFTER capture above was retaken with it. The BEFORE value
for observable 2 is therefore **not measured**, and is stated as such rather than inferred from
observable 1.
2. The `ls` in observable 4 printed `(no pre-upgrade backup file present)` **after** listing the
file, because it globs two patterns and only one matched. The file is there; the fallback text is
the shell's, not the engine's. Noted so nobody reads the line as a contradiction.
## What this settles
The MariaDB half of `09` §3 decision 5 and R-469's lift is now proven **through the button a
household presses**, not only on the harness — conversion detected, run, finished, the engine's own
pre-upgrade backup taken, and the customer's account still there afterwards.
@@ -0,0 +1,51 @@
*** THIS FILE IS THE FIRST ATTEMPT AND ITS MAIN RESULT IS WRONG — KEPT DELIBERATELY. ***
The volume lookup returned EMPTY, so the copy was empty, so postgres:17 initialised a FRESH 17
datadir and started happily. The line "PG_VERSION on the copy:" two lines below is blank and that
should have stopped the step; it did not. `17-...` is therefore NOT evidence that 17 accepts a 16
datadir — it never met one.
WHAT IT DID ACCIDENTALLY MEASURE, and this part IS real and useful: the MIRROR case. postgres:16
meeting a datadir written by 17 refuses in its own words —
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 17, which is not compatible
with this version 16.15.
which is the sentence a household would meet if a 17 pin were ever put BACK to 16. The correct
measurement — 17 meeting 16 — is in `18-postgres-refusal-reproduced.txt`.
================================================================================================
INDEPENDENT REPRODUCTION of the PostgreSQL 16 -> 17 refusal
R-320: the product destroyed the original evidence (R-621), so it is reproduced here rather
than inferred. Plain docker, beside the product, on a COPY of a real 16 datadir.
the live docmost 16 datadir volume:
PG_VERSION on the copy:
=== now start postgres:17-alpine on that 16 datadir — the household's case, exactly
--- container state:
running=true exit=0 restarts=0
--- THE REFUSAL, VERBATIM:
2026-09-21 19:45:47.148 UTC [41] LOG: database system is shut down
done
server stopped
PostgreSQL init process complete; ready for start up.
2026-09-21 19:45:47.239 UTC [1] LOG: starting PostgreSQL 17.11 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
2026-09-21 19:45:47.239 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2026-09-21 19:45:47.239 UTC [1] LOG: listening on IPv6 address "::", port 5432
2026-09-21 19:45:47.245 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2026-09-21 19:45:47.253 UTC [55] LOG: database system was shut down at 2026-09-21 19:45:47 UTC
2026-09-21 19:45:47.263 UTC [1] LOG: database system is ready to accept connections
--- positive control: the SAME copy under postgres:16-alpine must start
running=false exit=1
2026-09-21 19:45:53.852 UTC [1] FATAL: database files are incompatible with server
2026-09-21 19:45:53.852 UTC [1] DETAIL: The data directory was initialized by PostgreSQL version 17, which is not compatible with this version 16.15.
--- and the data is still there, asked of the engine:
Error response from daemon: container ea0332381c9771265f47fd08988836c38afb697496096074305ed154a3ac8f3e is not running
(throwaway containers and the copied volume removed by name)
@@ -0,0 +1,32 @@
=== 0. find the live docmost 16 datadir, and PROVE it is 16 before touching anything
mounts of docmost-postgres:
volume docmost_docmost_postgres_data /var/lib/docker/volumes/docmost_docmost_postgres_data/_data /var/lib/postgresql/data
volume: docmost_docmost_postgres_data
PG_VERSION of the SOURCE: '16'
=== 1. copy it, and PROVE the copy is 16 too
copied
PG_VERSION of the COPY: '16'
size of the copy: 49.0M
=== 2. THE MEASUREMENT: postgres:17-alpine on that 16 datadir — the household's exact case
container: running=false exit=1 restarts=0
--- POSTGRESQL 17's OWN WORDS, VERBATIM:
PostgreSQL Database directory appears to contain a database; Skipping initialization
2026-09-21 22:11:32.601 CEST [1] FATAL: database files are incompatible with server
2026-09-21 22:11:32.601 CEST [1] DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.11.
--- the copy's PG_VERSION AFTER 17 refused it (must still be 16 — nothing was migrated):
16
=== 3. POSITIVE CONTROL: the same copy under postgres:16-alpine must start AND hold the data
container: running=true exit=0
2026-09-21 22:11:43.497 CEST [33] LOG: checkpoint starting: end-of-recovery immediate wait
2026-09-21 22:11:43.583 CEST [33] LOG: checkpoint complete: wrote 600 buffers (3.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.014 s, sync=0.061 s, total=0.089 s; sync files=409, longest=0.025 s, average=0.001 s; distance=2838 kB, estimate=2838 kB; lsn=0/1BEB798, redo lsn=0/1BEB798
2026-09-21 22:11:43.592 CEST [1] LOG: database system is ready to accept connections
--- the DATA, asked of the engine itself:
tables in the public schema: 48
=== 4. teardown, BY NAME
removed: DRILL-pg17-refuse, DRILL-pg16-ok, volume DRILL-pg16-copy
@@ -0,0 +1,59 @@
# `09` §3b Q4 — the unattended HOLD. MEASURED 2026-09-21.
Q4 asks: *an automatic update that ends HELD happened while everyone was asleep — who is told, when,
and does the box try again?* Until tonight **the hold half had never been produced unattended**, and
the 2026-09-21 afternoon session recorded exactly why: the only failing edge available was
`vikunja → alpine:3.20`, which the within-a-major rule correctly refuses before the guarded update is
ever reached. *The rule that makes automatic updates safe is the same rule that refuses the obvious
way to break one.*
## What made it measurable
A throwaway image store on the scratch guest, and an edge the safety rule **cannot** filter out:
localhost:5000/drill/glance:1.0.0 the real glance image, retagged — it serves
localhost:5000/drill/glance:1.0.1 starts, stays up, and NEVER serves
Same repository, same major, plain version tags, so `stacks.CompareImageRefs` orders them and the
caller presses. Proven beforehand in isolation: the `1.0.1` container runs 14 s with **0 restarts**
and refuses every connection on 8080.
## The measurement, verbatim
`unattended-caller.py`, three passes 90 s apart. **Nobody pressed anything.**
2026-09-21T19:47:49Z === pass 1/3
2026-09-21T19:47:49Z glance BEHIND and within a major — pressing Update
2026-09-21T19:47:49Z glance: phase=safety-dump updating=True
2026-09-21T19:47:51Z glance: phase=verifying updating=True
2026-09-21T19:53:02Z glance: phase=failed updating=False
2026-09-21T19:53:02Z glance ENDED held after 312.9s
2026-09-21T19:54:32Z === pass 2/3
2026-09-21T19:56:03Z === pass 3/3
2026-09-21T19:56:03Z === summary outcomes={'glance': ('held', 312.9)} never_again=['glance']
## What Q4 now knows
| Q4's question | the answer, measured |
|---|---|
| does an unattended update ever produce a HOLD? | **Yes** — 312.9 s, the full `health_timeout` plus the phases |
| does the box try again? | **No.** Passes 2 and 3 pressed nothing at all; `never_again=['glance']` |
| is the household told? | **On the screen, yes** — the app page carries the sentence, and the banner „Telepített alkalmazás nem fut: …" appears at the top of **every** authenticated page |
| told what, exactly? | what happened, **when** (`21:53`), **which copy** („saját meghajtó, 2026-09-21 21:47"), and **what that copy holds** („a beállításokat, az adatbázist és az adatköteteket") |
| by MAIL? | **UNMEASURABLE on this venue** — `hub.enabled: false` and the notifier returns before it logs (R-620). Q4's mail half is still unmeasured |
| in ENGLISH? | **No.** The sentence is Hungarian on the English page (R-606, confirmed tonight on the hold sentence itself) |
## The one qualification, stated plainly
**This is the hold produced by a caller that presses the existing guarded Update.** It is not Slice 6,
which does not exist. What it proves is that the MECHANISM Q4's recommended option rests on — press
once, hold honestly, tell the household on the page, do not retry — **is already there and already
behaves that way**, and that the remaining work in Q4 is the MAIL and the ENGLISH, not the hold.
## And one thing that nearly went unmeasured
`unattended-caller.py`'s `follow()` read `update_phase` and `updating` off the API **envelope**, so
both were always `None`, and **every update it followed would have run to the 900 s timeout and been
recorded `timeout` rather than `held`** (R-623). It was found by reading the file before relying on
it, and fixed. The log above — with its real phase transitions — is what the fixed version produces.
The broken one would have written `phase=None` four times and then `timeout`.
@@ -0,0 +1,40 @@
WHICH UPDATE REFUSALS ACTUALLY REACH AN ENGLISH HOUSEHOLD IN ENGLISH
2026-09-21, guest 9202, controller v0.261.0. `langFor` DOES honour ?lang= (api/i18n_api.go:30).
glance hu http=409 reason=held :: A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva mar
glance en http=409 reason=held :: A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva mar
adventurelog hu http=409 reason=not_deployed :: Az alkalmazás nincs telepítve, ezért nem frissíthető.
adventurelog en http=409 reason=not_deployed :: Az alkalmazás nincs telepítve, ezért nem frissíthető.
nosuchapp hu http=404 reason=not_found :: stack "nosuchapp" not found
nosuchapp en http=404 reason=not_found :: stack "nosuchapp" not found
WHAT THIS CORRECTS
==================
R-606's own text records that controller v0.260.0 "routed the 409 REFUSAL through `errText` (so the
pre-flight refusals reach an English household in English)". **That is measured FALSE for at least
three refusals**, and the reason is mechanical rather than a regression:
* the ROUTING is there — `Router.langFor` honours `?lang=` (`api/i18n_api.go:30-40`) and
`errText` calls it, so an English request IS asked for in English;
* but the SENTENCES are not keys. `MsgUpdateDiskFmt`, `MsgUpdateNotDeployed`, `MsgUpdateBusy` and
their siblings (`stacks/update.go` L81-96) are **finished Hungarian string constants**, raised
with `fmt.Sprintf` into `refuseUpdate`. `errText` renders "its own text" for an error that
carries no bundle message — which is exactly right, and which means routing them through it
changes nothing at all.
So v0.260.0 built the pipe and the old sentences never entered it. Only the sentences BORN as keys
(v0.260.0's `downgrade`, v0.261.0's `self_updating`) actually translate.
**Why this matters more than a cosmetic:** the `held` refusal is the one that tells a household
which copy can bring their data back and what is inside it. An English household pressing Update on
a held app is told, in Hungarian, why they cannot.
MEASURED, all three with the Hungarian request as the control:
held -> identical Hungarian in both languages
not_deployed -> identical Hungarian in both languages
disk -> identical Hungarian in both languages ("Nincs eleg szabad hely a frissiteshez:
1.4 GB szabad, az uj verzio letolteshez legalabb 2 GB szukseges.")
not_found -> English in both, because it is a Go error and not ours (correct)
@@ -0,0 +1,41 @@
# B9 — a FROZEN app receives a newer `.felhom.yml` (R-458). MEASURED, and it REFINES the row.
**Setup.** `bentopdf` frozen: installed `ghcr.io/alam00000/bentopdf:v2.8.6`, catalog
`localhost:5000/drill/pdf:1.0.1`. A `.felhom.yml`-ONLY change was pushed to the drill catalog — a
health check for a path only a newer version would serve, `/__drill_only_in_the_newer_version__`.
No `image:` line was touched.
## §5.4's deliberate asymmetry, confirmed live
On the box, after one sync:
.felhom.yml checks: - type: http, port: 8080, path: /__drill_only_in_the_newer_version__
- type: http, port: 8080 <- the original
compose image: ghcr.io/alam00000/bentopdf:v2.8.6 <- UNCHANGED
So the `.felhom.yml` kept flowing to a frozen app while the compose file stayed frozen, exactly as
`09` §5.4 says and for the reason it gives (the file carries `catalog_since`, which the badge needs).
## R-458's claim holds — and the reason is NARROWER than the row states
Ten samples over two minutes, every one:
state = running front door = HTTP 200
**No false alarm was produced at all**, and the reason is the probe's own semantics rather than
luck. `healthprobe.go:258-261`: for `type: http`, **any response is healthy**. The bogus path
answers `404` — which is a response. So a health check written for a newer version **cannot** make
a `type: http` app read degraded, however wrong the path is.
**The refinement R-458 should carry:** its false-alarm risk is real only for `type: api` probes that
carry an `expect` block, where the status is compared. Of the 53 templates, that is the minority —
and it is the same subset R-618 convicts `zipline` in. For every `type: http` template and every
`type: api` WITHOUT `expect`, a path that a newer version would serve is simply invisible to the
probe.
**And the direction is confirmed, which is the row's actual claim:** the failure direction is a
false alarm, never data loss. Nothing touched the app's data; the app never stopped; the card showed
the previous pull failure's sentence („Az új verzió letöltése nem sikerült, ezért a frissítés
elmaradt.") and nothing new.
**Teardown:** the `.felhom.yml` was reverted in the drill catalog in the same leg.
@@ -0,0 +1,27 @@
B8 — THE FLOATING PIN, re-read cleanly (the first read caught docmost mid-update)
2026-09-21, guest 9202, controller v0.261.0
installed_images, as the box records them:
docmost ref=docmost/docmost:0.96.0
digest=sha256:b56947fcfd08aab8fae12a377e1792784786adbf8b96e4281f14ef4fc072685a
first seen at=2026-09-21T20:11:02Z
docmost-postgres ref=postgres:16-alpine
digest=sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
first seen at=2026-09-21T20:11:02Z
docmost-redis ref=redis:7-alpine
digest=sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499
first seen at=2026-09-21T20:11:02Z
catalog_images: {'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:16-alpine', 'docmost-redis': 'redis:7-alpine'}
badge HU: ['Naprakész']
badge EN: ['Up to date']
postgres:16-alpine
upstream digest, measured from DooPlex tonight : sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
the digest THIS BOX records for what it runs : sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
SAME? True -> the badge is TRUE for this box
redis:7-alpine
upstream digest, measured from DooPlex tonight : sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499
the digest THIS BOX records for what it runs : sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499
SAME? True -> the badge is TRUE for this box
@@ -0,0 +1,35 @@
# The alarm truth table for the update night, scored against `08-alarm-ladder.md`
**READ THE LIMIT FIRST, because it bounds every row.** Guest 9202 runs with `hub.enabled: false`.
Every notifier entry point returns **before it logs anything** (`notify/notifier.go:269, :359, :917,
:959, :1058`), so **no hub event and no customer mail could be produced or observed tonight**. The
hub was not enabled on 9202 to get around this: that would register an unclaimed host at the live
hub and could mail a real address, and the brief fences the hub. Filed as **R-620**.
So this table scores the surfaces that DO exist here — what the household READS (the app page, the
dashboard, the backups page) and what the box's own log records — and says "unmeasurable here" for
the send side rather than leaving a blank that could be read as "nothing fired".
| # | what happened | should it alarm, per `08` | did the box classify it that way | what the household could READ | verdict |
|---|---|---|---|---|---|
<!-- ROWS -->
## The three things this table is for
**1. Which alarm fired and was it true.** <FIRED>
**2. Which should have fired and did not.** <MISSING>
**3. The one that is structurally invisible on this venue.** Every row's "who was told" column is
answered only for the SCREEN. `08` §6.1's severity contract and §6.2's delivery grain were not
exercised at all, and no row below may be read as evidence about them.
## A note on `unhealthy`, because two of tonight's findings turn on it
`08` §4 puts `unhealthy` deliberately in the **NOT-down** set — *"a running container whose
healthcheck is failing. Folding it in reintroduces the flapping fix-3 was written to stop."* That
ruling is why tandoor and zipline reading `unhealthy` all night produced **no alarm at all**, and it
is the right ruling. **But R-618 shows the cost of the other half of that decision:** the same probe
result that is correctly ignored by the alarm ladder is NOT ignored by the guarded update's
`verifying` phase, which waits on it and then stops the app. One probe, two consumers, opposite
tolerances — and nothing says so in either document.
@@ -0,0 +1,39 @@
# Draft — the line to append to the capability map's UPDATE row (`00-capability-map.md` line 105)
*(Appended to the existing "Update is GUARDED …" row's evidence column. `<…>` filled at the end.)*
---
**WIDENED 2026-09-21 (the update night) from 3 apps to `<N>`, and NARROWED in one place by the same
run.** `audits/DRILL-update-night-2026-09-21.md`. On scratch guest 9202 (controller v0.261.0),
against a **private drill catalog** so the live catalog carried no test reference at any point,
`<N>` real within-a-major upstream edges were walked through the product's own guarded Update, each
app seeded and read back **through its own front door** (R-156) with a negative control on every
readback: **`<P>` proven, `<F>` failed, `<I>` inconclusive.**
**What the PROVEN edges prove, precisely:** the app moved, the four version observables agreed, and
the data the app itself was given came back through the app's own interface afterwards. `<MIG>` of
them printed a verbatim migration line.
**What the FAILED edges prove, and they are the more valuable half.** `<FAILED_LIST>`.
`adventurelog v0.12.1 → v0.13.0` applied **nine database migrations successfully** and then never
bound its port; the update held after the full health wait, the hold sentence named the tier, the
date and what the copy holds, and the restore the sentence names brought the app back. **That is
this row's own promise, exercised on a real upstream edge rather than a staged one.**
**AND THE NARROWING, which this row must carry because it is the same mechanism:** the `verifying`
phase trusts the `.felhom.yml` probe absolutely, and **two of the 53 templates name a probe the app
does not answer** — `tandoor` (port 8080; it listens on 80) and `zipline` (`/api/health`; it answers
404 there, while the compose healthcheck in the same file uses `/api/healthcheck` and is green).
For those apps a **successful** update is stopped by its own health wait: tandoor was measured
**serving HTTP 200 on the new version at four samples across five minutes**, with docker's own
healthcheck green, and was then stopped by `failAndHold` and the household sent to a restore they
did not need. **R-618, P1.** No data was lost and the restore works — but "the update is guarded"
must not be read as "the guard is right about whether the app came up".
**Still true and unchanged:** no automatic rollback (by measurement); the route back is the restore;
a multi-major jump ends held honestly.
**Not measured on this venue, and named rather than assumed:** every event and every customer mail.
Guest 9202 runs `hub.enabled: false` and the notifier returns before it logs (**R-620**), so the
whole "who was told" half of `08` was structurally unobservable tonight.
File diff suppressed because one or more lines are too long
@@ -26,3 +26,26 @@ A resuming session reads THIS FILE FIRST and never repeats a finished step.
| 21:08 | CI check for the catalog push | **GREEN** — job **id=830**, `name='gates'`, `status='completed'`, `conclusion='success'`, matched on `head_sha=4463243f2e09`. Confirms CLAUDE.md's warning: page 1's newest id was 52, the real newest was 830 — job ids are NOT page-ordered | scratchpad/ci.sh |
| 21:12 | **Observation, NOT a new row** — an app with an `HDD_PATH` cannot have its DATA removed on 9202 | R-442's guard working as designed: `/api/disks` answers `agent not configured` on this guest, so the drive path cannot be RESOLVED and the removal is **refused with the app kept** rather than half-deleted. The household's other choice — remove the app, KEEP the data — is accepted. The harness now takes that route and tidies its own directories by name at teardown | apps/navidrome/, apps/romm/ |
| 21:13 | **FINDING R-623** — the unattended caller turned every SUCCESS into a `timeout` | Read before use, not after: `follow()` read `update_phase`/`updating` off the API ENVELOPE, so both were always `None`, every followed update hit the 900 s timeout and was then marked never-press-again. **Fixed in that file** before B1 relied on it. The earlier night missed it because its only `follow()` pass was the one whose log was lost | update-arc-gaps-2026-09-21/unattended-caller.py |
| 21:18 | **Interim commit pushed** — felhom.eu `da20722e76a7` | Phase 0 + Phase 1 evidence off the machine before Phases 2-5 (R-320). repo_gates.py --fast: all 15 OK. Includes the two instrument fixes (api-recipe route, unattended-caller `follow()`) | felhom.eu@da20722e76a7 |
| 21:18 | **R-618 RAISED TO P1 by a live measurement** | tandoor 2.6.13→2.6.15 was **serving HTTP 200 on the NEW version** at 21:18:28 while the update sat in `verifying`, because the probe names port 8080 and the app listens on 80. The health wait then times out and `failAndHold` STOPS the working app. A wrong port turns every successful update into an outage plus a needless restore | 14-tandoor-serving-while-verifying.txt |
| 21:22 | **EDGE tandoor 2.6.13 -> 2.6.15** (db-postgres) | **FAILED — and the failure is OURS, not the app's.** The new version was SERVING 200 with a green docker healthcheck at four samples across five minutes; the controller's probe names port 8080 and the app listens on 80, so `verifying` timed out and `failAndHold` STOPPED a working app. The four observables disagree exactly as `09` §5.2 predicts. **R-618 is now P1** | 14-tandoor-serving-while-verifying.txt, apps/tandoor/ |
| 21:35 | **failwalk adventurelog — the household's WAY OUT, on a real failed edge** | **PROVEN** — the restore the hold sentence names („helyi", 1 copy offered) ran in **75 s**; app back on v0.12.1, all three containers running, hold CLEARED, state `running`. R-606 CONFIRMED on the hold sentence itself (English page, Hungarian sentence) with positive and negative controls | apps/adventurelog/failwalk.json, 15-r606-*.txt |
| 21:36 | Observation after the restore | The hold sentence is GONE from the card (R-480 working) and the app reads „Fut". The badge briefly read „Frissítés elérhető — **65 napja**" rather than „ma", because the restore brought back the recovery unit's own `.felhom.yml` with its pre-drill `catalog_since`; the syncer copies that file verbatim every cycle, so it heals within one sync. Recorded as an observation, not a row | apps/adventurelog/failwalk.json |
| 21:37 | **failwalk tandoor — the way out of the WRONG-PROBE hold** | **PROVEN** — restore in **32.4 s**, hold cleared, app running, data reads back. Note what this means for R-618: the household's route back works, so nothing is LOST — but they had to walk it for an update that had actually succeeded | apps/tandoor/failwalk.json |
| 21:40 | **PHASE 2.1 — MariaDB 11.6 -> 12.3 through the REAL Update button** | **PROVEN, a first.** All four SPIKE-r459 observables: datadir `11.6.2`->`12.3.3`; the engine's own check says „already upgraded … no need to run mariadb-upgrade again"; the entrypoint says „Major version upgrade detected … Check required!" then started and **finished** (NOT the `skipped due to $MARIADB_AUTO_UPGRADE` line R-459 feared); the engine's own pre-upgrade backup `system_mysql_backup_11.6.2-MariaDB.sql.zst` 631 905 B. Seeded Nextcloud account read back. All four version observables agree | 16-phase2.1-mariadb-major.md, apps/nextcloud-engine-mariadb/ |
| 21:45 | **PHASE 2.2 — PostgreSQL 16 -> 17 through the real Update button** | **FAILED, exactly as R-463 predicted and nobody had measured.** The update ended in **5.1 s**, the app was stopped and held, and **`pinned` named `postgres:17-alpine` while `installed` still said 16 and nothing was running** — the brief's own question answered. The restore then brought it back in **29.1 s** (hold CLEARED, health probe 200) | apps/docmost-engine-postgres/ |
| 21:45 | **R-621 demonstrated itself on this leg's KEY artefact** | The engine's refusal line was GONE before any probe could read it — `failAndHold` had removed the container, and the controller log does not carry it either. Reproduced independently instead (R-320) | 18-postgres-refusal-reproduced.txt |
| 21:45 | **My own first reproduction was WRONG, and is kept labelled** | The volume lookup returned empty -> the copy was empty -> postgres:17 initialised a FRESH datadir and started happily. A blank `PG_VERSION` should have stopped the step and did not. Rewritten so every step proves itself first. The accidental result (16 refusing a 17 datadir, verbatim) is real and kept | 17-postgres-refusal-reproduced.txt |
| 21:56 | **B1 — THE UNATTENDED HOLD. MEASURED, and it is the thing `09` Q4 has never had.** | The caller pressed ONCE with nobody watching; the drill image started, stayed up and never served; `safety-dump` -> `verifying` -> **`held after 312.9 s`**; then **pass 2 and pass 3 pressed NOTHING** — `outcomes={'glance': ('held', 312.9)} never_again=['glance']`. The hold sentence names the tier, the date and what the copy holds. **My `follow()` fix (R-623) was load-bearing: without it this would have read `timeout` after 900 s** | bad-days/B1-unattended-hold/ |
| 21:57 | **B2 — the new tag cannot be pulled** | **Exactly §6.1 Scenario E.** `phase=failed` in **1.0 s**, `hold=None`, „Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt. Az alkalmazás a korábbi verzióval fut tovább." Nothing held; the app keeps running the old version | bad-days/B2-pull-fails/ |
| 21:57 | **B3 — Update pressed while a backup runs** | `409 reason='busy'` on six consecutive presses with „A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött." — the transient reason an unattended caller needs (R-609) | bad-days/B3-busy-during-backup/ |
| 21:58 | **B7 — the 2 GB disk floor** | **REFUSED correctly**: filled to 1.4 GB free, `409 reason='disk'`, „Nincs elég szabad hely a frissítéshez: 1.4 GB szabad… legalább 2 GB szükséges." Nothing moved. Space released immediately after | bad-days/B7-disk-and-memory-refusals/ |
| 21:58 | **CORRECTION to R-606** — the refusals are NOT English | Three refusals requested with `?lang=en`, Hungarian request as control: `held`, `not_deployed` and `disk` all come back **identical Hungarian**. The pipe exists (`langFor` honours `?lang=`) but the sentences are frozen string constants, so routing them through `errText` changes nothing | 20-refusals-in-english.txt |
| 22:00 | **B9 — a frozen app gets a newer `.felhom.yml`** | §5.4's asymmetry confirmed live; **no false alarm** — 10 samples, all `running`/200. The reason NARROWS R-458: `type: http` treats any response as healthy, so a wrong path is invisible; the risk is real only for `type: api` + `expect` | 21-B9-frozen-app-newer-felhomyml.md |
| 22:00 | B8 skipped here — docmost was swept by B1's precondition | re-run in `phase2_redo.sh` with a freshly deployed docmost | phase2_redo.sh |
| 22:02 | **B4 (two) — two Updates within one second** | **THERE IS NO SINGLE-FLIGHT.** Both accepted `202` within **0.268 s**, and **2 of 2 ran in flight at the same time**. Both ended `done`, no error, no hold, both pins advanced. That is a real Slice-6 design input: an unattended caller pressing N apps would run N updates at once | bad-days/B4-concurrent-updates/two.json |
| 22:06 | **B4 (five) — five Updates within half a second** | **5 of 5 ran concurrently and ALL ended honest**: accepted within **0.448 s**, every one `done`, no error, no hold, every pin advanced, whole batch ~30 s. So the box does not serialise updates at all — and on this box it coped. Memory samples in the record | bad-days/B4-concurrent-updates/five.json |
| 22:08 | **B5 (backing-up cut) — the phase nobody had cut in** | **BEHAVED.** Cut at `backing-up` +0.027 s (`pct stop` returned 9.65 s later — the usual instrument limit, stated). After boot the box said so ITSELF: „crash recovery: an app-data backup (volume dump) … was interrupted and left 1 app(s) stopped — restarting them: [privatebin]" and „update recovery: privatebin was interrupted in backing-up". **The pin did NOT move**, the app runs, and a fresh paste seeded+read back | bad-days/B5-backing-up/ |
| 22:08 | **B5 (safety-dump cut) — MISSED, recorded as a miss** | The phases went `backing-up` -> `pulling` -> `failed` in **0.473 s** and `safety-dump` was never observed, so the plug was never pulled. Recorded as a MISS, not as a pass. To be retried with a genuine pending edge | bad-days/B5-safety-dump/ |
| 22:09 | **Phase 4 — the morning after** | Every one of the **10 badges is TRUE** (refs equal <-> „Naprakész", refs differ <-> „Frissítés elérhető"). Q4's four promises all scored True on the held app's page. `zipline` shows the household „Nem egészséges — URL nem elérhető" while running — R-618 in the household's own words | bad-days/P4-morning-after/ |
| 22:09 | **B6 — the way out FORWARDS: REFUSED** | A held app met a FIXED newer version. Badge: „Frissítés elérhető — ma" in both languages, button offered; press -> **`409 reason='held'`**. Correct per §6.1 (only a restore lifts a hold) but **the page invites what the button refuses** — the exact inconsistency R-524 removed for the Ahead case. Filed as **R-625** | bad-days/B6-way-out-forwards/ |
@@ -0,0 +1,23 @@
21:34:35 ==== FAILWALK adventurelog: the household's way out of a held update
21:34:37 [1] held state: phase=failed hold='A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.'
21:34:37 update_error = 'A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.'
21:34:37 pinned = {'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.13.0', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'}
21:34:37 installed = {'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.12.1', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'}
21:34:37 compose = ['image: ghcr.io/seanmorley15/adventurelog-backend:v0.13.0', 'image: postgis/postgis:16-3.5-alpine', 'image: ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0']
21:34:37 inspect = []
21:34:37 [2] hold sentence HU: ['AdventureLog — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: AdventureLog (stopped) Rendszermonitor → Telepített alkalmazás nem fut: Tandoor Recipes (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások AdventureLog Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.', 'Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.', 'Mentések Utazási napló - utazások dokumentálása térképpel és fotókkal ~100M RAM travel Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.', 'Utazások dokumentálása képekkel és jegyzetekkel Térképes megjelenítés a meglátogatott helyekről Úti tervek készítése és szervezése Statisztikák - meglátogatott országok, városok Kalandok megosztása családdal és barátokkal Első lépések Nyisd meg a travel.DOMAIN címet a böngészőben Hozd létre a fiókodat Add hozzá az első utazásodat Jelöld meg a meglátogatott helyeket a térképen Dokumentáció Hivatalos dokumentáció ↗']
21:34:37 [2] hold sentence EN: ['AdventureLog — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ An installed app is not running: AdventureLog (stopped) System monitor → An installed app is not running: Tandoor Recipes (stopped) System monitor → The hub connection is off — central monitoring is not running System monitor → ← Apps AdventureLog Stopped Update available — today Open ↗ Log Export Settings A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.', 'Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.', 'Backups Travel journal - keep your trips with a map and photos ~100M RAM travel Runs on Pi Move to another storage You can move this app’s data to another connected storage.', 'Keep your trips with pictures and notes See everywhere you have been on a map Plan and organise trips before you go Statistics - the countries and cities you have visited Share your adventures with family and friends First steps Open travel.DOMAIN in your browser Create your account Add your first trip Mark the places you have been on the map Documentation Official documentation ↗']
21:34:37 [3] the last migration line the app printed, verbatim: None
21:34:37 [R] restoring adventurelog from snapshot 'helyi' (of 1 offered)
21:34:38 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
21:34:38 + 0.0s restore (True, None, None)
21:35:52 + 74.9s restore (False, None, None)
21:35:52 [R] after restore: state=running hold=None phase=None
21:35:55 [4] restore: HTTP/2 302 in 75.0s; state=running hold=None
21:35:55 pinned after = {'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.12.1', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'}
21:35:55 inspect after = ['adventurelog-frontend ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1 running=true restarts=0', 'adventurelog ghcr.io/seanmorley15/adventurelog-backend:v0.12.1 running=true restarts=0', 'adventurelog-postgres postgis/postgis:16-3.5-alpine running=true restarts=0']
21:36:00 adventurelog: createsuperuser :: Superuser created successfully.
21:36:04 adventurelog: seeded superuser drill4471b3
21:36:12 adventurelog: readback of the seeded account found=True
21:36:12 [5] the app answers its own front door again and holds data: True
21:36:12 [6] sentence after the restore HU: ['AdventureLog — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: Tandoor Recipes (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások AdventureLog Fut Frissítés elérhető — 65 napja Megnyitás ↗ Napló Exportálás Beállítások Utazási napló - utazások dokumentálása térképpel és fotókkal ~100M RAM travel Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.', 'Utazások dokumentálása képekkel és jegyzetekkel Térképes megjelenítés a meglátogatott helyekről Úti tervek készítése és szervezése Statisztikák - meglátogatott országok, városok Kalandok megosztása családdal és barátokkal Első lépések Nyisd meg a travel.DOMAIN címet a böngészőben Hozd létre a fiókodat Add hozzá az első utazásodat Jelöld meg a meglátogatott helyeket a térképen Dokumentáció Hivatalos dokumentáció ↗']
@@ -0,0 +1,111 @@
{
"leg": "failwalk",
"app": "adventurelog",
"held": {
"state": "stopped",
"updating": false,
"update_phase": "failed",
"update_phase_label": "A frissítés nem sikerült",
"update_error": "A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold_reason": "A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"observables": {
"pinned_images": {
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.13.0",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
},
"installed_images": {
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.12.1",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
},
"catalog_images": {
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.13.0",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
},
"live_compose_image_lines": [
"image: ghcr.io/seanmorley15/adventurelog-backend:v0.13.0",
"image: postgis/postgis:16-3.5-alpine",
"image: ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0"
],
"docker_inspect": []
}
},
"hold_sentences": {
"hu": [
"AdventureLog — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: AdventureLog (stopped) Rendszermonitor → Telepített alkalmazás nem fut: Tandoor Recipes (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások AdventureLog Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Mentések Utazási napló - utazások dokumentálása térképpel és fotókkal ~100M RAM travel Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Utazások dokumentálása képekkel és jegyzetekkel Térképes megjelenítés a meglátogatott helyekről Úti tervek készítése és szervezése Statisztikák - meglátogatott országok, városok Kalandok megosztása családdal és barátokkal Első lépések Nyisd meg a travel.DOMAIN címet a böngészőben Hozd létre a fiókodat Add hozzá az első utazásodat Jelöld meg a meglátogatott helyeket a térképen Dokumentáció Hivatalos dokumentáció ↗"
],
"en": [
"AdventureLog — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ An installed app is not running: AdventureLog (stopped) System monitor → An installed app is not running: Tandoor Recipes (stopped) System monitor → The hub connection is off — central monitoring is not running System monitor → ← Apps AdventureLog Stopped Update available — today Open ↗ Log Export Settings A(z) adventurelog frissítése 2026-09-21 20:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 20:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Backups Travel journal - keep your trips with a map and photos ~100M RAM travel Runs on Pi Move to another storage You can move this app’s data to another connected storage.",
"Keep your trips with pictures and notes See everywhere you have been on a map Plan and organise trips before you go Statistics - the countries and cities you have visited Share your adventures with family and friends First steps Open travel.DOMAIN in your browser Create your account Add your first trip Mark the places you have been on the map Documentation Official documentation ↗"
]
},
"last_migration_line": null,
"way_out": {
"ok": true,
"snapshot_id": "helyi",
"snapshots": [
{
"time": "2026-09-21T18:48:09Z",
"short_id": "helyi",
"tier": 1,
"drive_label": "Belső SSD (rendszer)"
}
],
"http": "HTTP/2 302",
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 75.0,
"state_after": "running",
"hold_after": null,
"observables_after": {
"pinned_images": {
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.12.1",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
},
"installed_images": {
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.12.1",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
},
"catalog_images": {
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.13.0",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
},
"live_compose_image_lines": [
"image: ghcr.io/seanmorley15/adventurelog-backend:v0.12.1",
"image: postgis/postgis:16-3.5-alpine",
"image: ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1"
],
"docker_inspect": [
"adventurelog-frontend ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1 running=true restarts=0",
"adventurelog ghcr.io/seanmorley15/adventurelog-backend:v0.12.1 running=true restarts=0",
"adventurelog-postgres postgis/postgis:16-3.5-alpine running=true restarts=0"
]
}
},
"data_after_restore": {
"route": "re-seeded after the restore",
"ok": true,
"note": "the pre-update seed token did not survive the failed run's process, so this re-seeds AFTER the restore: it proves the app WORKS again on the old version, not that the specific pre-update row survived. Stated rather than glossed."
},
"sentences_after_restore": {
"hu": [
"AdventureLog — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: Tandoor Recipes (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások AdventureLog Fut Frissítés elérhető — 65 napja Megnyitás ↗ Napló Exportálás Beállítások Utazási napló - utazások dokumentálása térképpel és fotókkal ~100M RAM travel Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Utazások dokumentálása képekkel és jegyzetekkel Térképes megjelenítés a meglátogatott helyekről Úti tervek készítése és szervezése Statisztikák - meglátogatott országok, városok Kalandok megosztása családdal és barátokkal Első lépések Nyisd meg a travel.DOMAIN címet a böngészőben Hozd létre a fiókodat Add hozzá az első utazásodat Jelöld meg a meglátogatott helyeket a térképen Dokumentáció Hivatalos dokumentáció ↗"
],
"en": [
"AdventureLog — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ An installed app is not running: Tandoor Recipes (stopped) System monitor → The hub connection is off — central monitoring is not running System monitor → ← Apps AdventureLog Running Update available — 65 days ago Open ↗ Log Export Settings Travel journal - keep your trips with a map and photos ~100M RAM travel Runs on Pi Move to another storage You can move this app’s data to another connected storage.",
"Keep your trips with pictures and notes See everywhere you have been on a map Plan and organise trips before you go Statistics - the countries and cities you have visited Share your adventures with family and friends First steps Open travel.DOMAIN in your browser Create your account Add your first trip Mark the places you have been on the map Documentation Official documentation ↗"
]
}
}
@@ -0,0 +1,662 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AdventureLog — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.261.0">
<link rel="stylesheet" href="/static/style.css?v=0.261.0">
<meta name="csrf-token" content="f1b38af3981c5aa0730617a1677adcfa060cef61805c532aa5f440be5feef25a">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Message</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menu">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Launcher</a></li>
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Dashboard</a></li>
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Apps</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Storage<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Drives</a></li>
<li><a href="/storage/network" class="">Network storage</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Backup<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Overview</a></li>
<li><a href="/backups/remote" class="">Remote backup</a></li>
<li><a href="/backups/apps" class="">Apps</a></li>
<li><a href="/backups/restore" class="">Restore</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Sharing<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Network sharing</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>System monitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Settings</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>System</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Notifications</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Security and access</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.261.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Language" title="Language"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="f1b38af3981c5aa0730617a1677adcfa060cef61805c532aa5f440be5feef25a"><input type="hidden" name="back" value="/apps/adventurelog"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="f1b38af3981c5aa0730617a1677adcfa060cef61805c532aa5f440be5feef25a"><input type="hidden" name="back" value="/apps/adventurelog"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item current" aria-current="true">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Sign out ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="alerts-container">
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">An installed app is not running: Tandoor Recipes (stopped)</span>
<a href="/monitoring" class="alert-link">System monitor →</a>
</div>
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">The hub connection is off — central monitoring is not running</span>
<a href="/monitoring" class="alert-link">System monitor →</a>
</div>
</div>
<div class="page-header">
<div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">← Apps</a>
<h2>AdventureLog</h2>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<span class="stack-state-badge state-run">Running</span>
<span class="tag tag-warn" title="A newer version of this app is available. Select the Update button to start it.">Update available — 65 days ago</span>
<a href="https://travel.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Open ↗</a>
<a href="/stacks/adventurelog/logs" class="btn btn-sm btn-outline">Log</a>
<a href="/stacks/adventurelog/export" class="btn btn-sm btn-outline">Export</a>
<a href="/stacks/adventurelog/deploy" class="btn btn-sm btn-outline">Settings</a>
</div>
</div>
<div class="app-info-hero">
<img class="app-info-logo" src="/static/assets/adventurelog-logo.svg"
alt="AdventureLog" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/adventurelog-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-info-hero-text">
<p class="app-info-tagline">Travel journal - keep your trips with a map and photos</p>
<div class="stack-meta-badges">
<span class="meta-badge">~100M RAM</span>
<span class="meta-badge">travel</span>
<span class="meta-badge meta-badge-ok">Runs on Pi</span>
</div>
</div>
</div>
<div class="app-screenshots" id="screenshots">
<img src="/static/assets/adventurelog-screenshot-1.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/adventurelog-screenshot-2.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/adventurelog-screenshot-3.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
</div>
<div class="app-info-card" style="margin-top:1rem">
<h3>Move to another storage</h3>
<p class="form-hint">You can move this app’s data to another connected storage. The app stops briefly during the move, and the data is deleted from the old place only after the check and a successful restart.</p>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
<select id="app-migrate-target" class="btn btn-sm btn-outline">
<option value="">Choose a target storage…</option>
<option value="/mnt/felhom-drives/scratch_hdd">SCRATCH HDD (/mnt/felhom-drives/scratch_hdd)</option>
</select>
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'adventurelog','AdventureLog')">Move</button>
</div>
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
</div>
<script>
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
function appMigRender(job){
var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'};
var s=names[job.phase]||job.phase;
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
return s+'…';
}
function appMigWatch(){
var panel=document.getElementById('app-migrate-progress');
if(panel) panel.style.display='block';
function tick(){
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
var job=d.data&&d.data.job;
if(!job){ if(panel) panel.textContent='No move in progress.'; return; }
if(panel) panel.innerHTML=appMigRender(job);
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Done</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Stopped: '+(job.error||'')+'</strong><br>The old data is untouched.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
}
tick();
}
function appMigrate(btn,app,label){
var sel=document.getElementById('app-migrate-target');
var target=sel?sel.value:'';
if(!target){ showAlert('Choose a target storage.'); return; }
felhomConfirm(btn,'Move the '+label+' data to: '+target+'? The app stops briefly. The old data is deleted only after a successful move.',function(){
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } })
.catch(function(e){ showAlert('Error: '+e); });
});
}
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
</script>
<div class="app-info-grid">
<div class="app-info-card">
<h3>What is it for?</h3>
<ul class="app-info-list">
<li>Keep your trips with pictures and notes</li><li>See everywhere you have been on a map</li><li>Plan and organise trips before you go</li><li>Statistics - the countries and cities you have visited</li><li>Share your adventures with family and friends</li>
</ul>
</div>
<div class="app-info-card">
<h3>First steps</h3>
<ol class="app-info-list">
<li>Open travel.DOMAIN in your browser</li><li>Create your account</li><li>Add your first trip</li><li>Mark the places you have been on the map</li>
</ol>
</div>
<div class="app-info-card">
<h3>Documentation</h3>
<p><a href="https://adventurelog.app/docs/" target="_blank" class="app-info-link">Official documentation ↗</a></p>
</div>
</div>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('This app is already installed.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Refreshing...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Network error: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'In progress...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Network error: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Loading...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Your data on the hard drive:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Delete your data from the hard drive</label>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Delete app: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">This removes the containers, the volumes and the configuration files.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Cancel</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Delete</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Close</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Deleting...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted data: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Kept data: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Deleted!</h3>' +
'<p style="color:var(--text-2)">The app (' + name + ') was deleted.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
} else {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.disabled = false;
btn.textContent = 'Delete';
}
} catch (err) {
showAlert('Network error: ' + err.message);
btn.disabled = false;
btn.textContent = 'Delete';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Loading...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
sections += '<div class="modal-section">' +
'<strong>Always deleted:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker volumes (database, app configuration)</li>' +
'<li>Install configuration (app.yaml)</li>' +
'<li>Secondary backup schedule</li>' +
'</ul></div>';
var hddCheckbox = '';
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Your data on the hard drive:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Delete your data</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'If you reinstall the app, you need to import the data again, because the database is deleted. Data you keep is NOT backed up automatically any more.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Backup data:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Delete backup data</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' +
'</div></div>';
}
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Remove app: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">The app goes back to "Not installed". The template stays, and you can install it again.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Cancel</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Remove</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Close</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Removing...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted data: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted backups: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Kept data: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Backups not deleted: ' + data.data.backup_paths_refused.join('; ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Removed!</h3>' +
'<p style="color:var(--text-2)">The app (' + name + ') was removed. You can install it again with the Install button.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
} else {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.disabled = false;
btn.textContent = 'Remove';
}
} catch (err) {
showAlert('Network error: ' + err.message);
btn.disabled = false;
btn.textContent = 'Remove';
}
}
</script>
</body>
</html>
@@ -0,0 +1,662 @@
<!DOCTYPE html>
<html lang="hu" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AdventureLog — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.261.0">
<link rel="stylesheet" href="/static/style.css?v=0.261.0">
<meta name="csrf-token" content="f1b38af3981c5aa0730617a1677adcfa060cef61805c532aa5f440be5feef25a">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menü">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Indítópult</a></li>
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Meghajtók</a></li>
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Áttekintés</a></li>
<li><a href="/backups/remote" class="">Távoli mentés</a></li>
<li><a href="/backups/apps" class="">Alkalmazások</a></li>
<li><a href="/backups/restore" class="">Visszaállítás</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Megosztás<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Hálózati megosztás</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Beállítások</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.261.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="f1b38af3981c5aa0730617a1677adcfa060cef61805c532aa5f440be5feef25a"><input type="hidden" name="back" value="/apps/adventurelog"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item current" aria-current="true">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="f1b38af3981c5aa0730617a1677adcfa060cef61805c532aa5f440be5feef25a"><input type="hidden" name="back" value="/apps/adventurelog"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="alerts-container">
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">Telepített alkalmazás nem fut: Tandoor Recipes (stopped)</span>
<a href="/monitoring" class="alert-link">Rendszermonitor →</a>
</div>
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">Hub kapcsolat kikapcsolva — a központi monitoring nem aktív</span>
<a href="/monitoring" class="alert-link">Rendszermonitor →</a>
</div>
</div>
<div class="page-header">
<div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
<h2>AdventureLog</h2>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<span class="stack-state-badge state-run">Fut</span>
<span class="tag tag-warn" title="Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.">Frissítés elérhető — 65 napja</span>
<a href="https://travel.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
<a href="/stacks/adventurelog/logs" class="btn btn-sm btn-outline">Napló</a>
<a href="/stacks/adventurelog/export" class="btn btn-sm btn-outline">Exportálás</a>
<a href="/stacks/adventurelog/deploy" class="btn btn-sm btn-outline">Beállítások</a>
</div>
</div>
<div class="app-info-hero">
<img class="app-info-logo" src="/static/assets/adventurelog-logo.svg"
alt="AdventureLog" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/adventurelog-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-info-hero-text">
<p class="app-info-tagline">Utazási napló - utazások dokumentálása térképpel és fotókkal</p>
<div class="stack-meta-badges">
<span class="meta-badge">~100M RAM</span>
<span class="meta-badge">travel</span>
<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>
</div>
</div>
</div>
<div class="app-screenshots" id="screenshots">
<img src="/static/assets/adventurelog-screenshot-1.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/adventurelog-screenshot-2.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/adventurelog-screenshot-3.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
</div>
<div class="app-info-card" style="margin-top:1rem">
<h3>Áthelyezés másik tárhelyre</h3>
<p class="form-hint">Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át. Az alkalmazás az áthelyezés alatt rövid időre leáll, az adatok pedig csak az ellenőrzés és a sikeres újraindítás után törlődnek a régi helyről.</p>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
<select id="app-migrate-target" class="btn btn-sm btn-outline">
<option value="">Válassz céltárhelyet…</option>
<option value="/mnt/felhom-drives/scratch_hdd">SCRATCH HDD (/mnt/felhom-drives/scratch_hdd)</option>
</select>
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'adventurelog','AdventureLog')">Áthelyezés</button>
</div>
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
</div>
<script>
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
function appMigRender(job){
var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'};
var s=names[job.phase]||job.phase;
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
return s+'…';
}
function appMigWatch(){
var panel=document.getElementById('app-migrate-progress');
if(panel) panel.style.display='block';
function tick(){
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
var job=d.data&&d.data.job;
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
if(panel) panel.innerHTML=appMigRender(job);
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
}
tick();
}
function appMigrate(btn,app,label){
var sel=document.getElementById('app-migrate-target');
var target=sel?sel.value:'';
if(!target){ showAlert('Válassz céltárhelyet.'); return; }
felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.',function(){
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } })
.catch(function(e){ showAlert('Hiba: '+e); });
});
}
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
</script>
<div class="app-info-grid">
<div class="app-info-card">
<h3>Mire használható?</h3>
<ul class="app-info-list">
<li>Utazások dokumentálása képekkel és jegyzetekkel</li><li>Térképes megjelenítés a meglátogatott helyekről</li><li>Úti tervek készítése és szervezése</li><li>Statisztikák - meglátogatott országok, városok</li><li>Kalandok megosztása családdal és barátokkal</li>
</ul>
</div>
<div class="app-info-card">
<h3>Első lépések</h3>
<ol class="app-info-list">
<li>Nyisd meg a travel.DOMAIN címet a böngészőben</li><li>Hozd létre a fiókodat</li><li>Add hozzá az első utazásodat</li><li>Jelöld meg a meglátogatott helyeket a térképen</li>
</ol>
</div>
<div class="app-info-card">
<h3>Dokumentáció</h3>
<p><a href="https://adventurelog.app/docs/" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
</div>
</div>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('Ez az alkalmazás már telepítve van.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Frissítés...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Hálózati hiba: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Folyamatban...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Törlés folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen törölve!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Törlés';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Törlés';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
sections += '<div class="modal-section">' +
'<strong>Mindig törlődik:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
'<li>Telepítési konfiguráció (app.yaml)</li>' +
'<li>Másodlagos mentés ütemezése</li>' +
'</ul></div>';
var hddCheckbox = '';
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Mentési adatok:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
'</div></div>';
}
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Eltávolítás folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Nem törölt mentések: ' + data.data.backup_paths_refused.join('; ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen eltávolítva!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
}
</script>
</body>
</html>
@@ -0,0 +1,14 @@
{
"hu": [
{
"title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.",
"text": "Frissítés elérhető — ma"
}
],
"en": [
{
"title": "A newer version of this app is available. Select the Update button to start it.",
"text": "Update available — today"
}
]
}
@@ -0,0 +1,17 @@
=== [1] PG_VERSION — the datadir's own record of its major
16
16
=== [2] the engine's REFUSAL, verbatim (the whole point of this leg)
PostgreSQL Database directory appears to contain a database; Skipping initialization
2026-09-21 21:44:28.155 CEST [1] LOG: starting PostgreSQL 16.15 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
2026-09-21 21:44:28.155 CEST [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2026-09-21 21:44:28.155 CEST [1] LOG: listening on IPv6 address "::", port 5432
2026-09-21 21:44:28.161 CEST [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2026-09-21 21:44:28.169 CEST [29] LOG: database system was shut down at 2026-09-21 21:42:44 CEST
2026-09-21 21:44:28.183 CEST [1] LOG: database system is ready to accept connections
=== [3] container state
image=postgres:16-alpine running=true restarts=0 exit=0
=== [4] the version binary in the image
postgres (PostgreSQL) 16.15
@@ -0,0 +1,13 @@
=== [1] PG_VERSION — the datadir's own record of its major
Error response from daemon: No such container: docmost-postgres
docker: invalid spec: :/d: empty section between colons
Run 'docker run --help' for more information
=== [2] the engine's REFUSAL, verbatim (the whole point of this leg)
Error response from daemon: No such container: docmost-postgres
=== [3] container state
error: no such object: docmost-postgres
=== [4] the version binary in the image
Error response from daemon: No such container: docmost-postgres
(cannot exec — not running)
@@ -0,0 +1,38 @@
=== [1] PG_VERSION — the datadir's own record of its major
16
Unable to find image 'alpine:3.20' locally
3.20: Pulling from library/alpine
25f1d6b1951a: Already exists
Digest: sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc
Status: Downloaded newer image for alpine:3.20
16
=== [2] the engine's REFUSAL, verbatim (the whole point of this leg)
done
server started
CREATE DATABASE
/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
waiting for server to shut down....2026-09-21 21:42:03.450 CEST [41] LOG: received fast shutdown request
2026-09-21 21:42:03.453 CEST [41] LOG: aborting any active transactions
2026-09-21 21:42:03.456 CEST [41] LOG: background worker "logical replication launcher" (PID 47) exited with exit code 1
2026-09-21 21:42:03.457 CEST [42] LOG: shutting down
2026-09-21 21:42:03.459 CEST [42] LOG: checkpoint starting: shutdown immediate
2026-09-21 21:42:03.544 CEST [42] LOG: checkpoint complete: wrote 926 buffers (5.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.022 s, sync=0.051 s, total=0.088 s; sync files=301, longest=0.022 s, average=0.001 s; distance=4283 kB, estimate=4283 kB; lsn=0/1925D58, redo lsn=0/1925D58
2026-09-21 21:42:03.558 CEST [41] LOG: database system is shut down
done
server stopped
PostgreSQL init process complete; ready for start up.
2026-09-21 21:42:03.687 CEST [1] LOG: starting PostgreSQL 16.15 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
2026-09-21 21:42:03.687 CEST [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2026-09-21 21:42:03.687 CEST [1] LOG: listening on IPv6 address "::", port 5432
2026-09-21 21:42:03.692 CEST [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2026-09-21 21:42:03.701 CEST [57] LOG: database system was shut down at 2026-09-21 21:42:03 CEST
2026-09-21 21:42:03.710 CEST [1] LOG: database system is ready to accept connections
=== [3] container state
image=postgres:16-alpine running=true restarts=0 exit=0
=== [4] the version binary in the image
postgres (PostgreSQL) 16.15
@@ -0,0 +1,34 @@
{
"hu": [
"Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Üzenet '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Docmost Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Mentések Modern wiki és dokumentáció platform Notion-szerű szerkesztővel ~200M RAM productivity Csak x86 Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Válassz céltárhelyet… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Áthelyezés function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Kész '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Megszakadt: '+(job.error||'')+' A régi adatok érintetlenek.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Válassz céltárhelyet.'); return; } felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'?",
"A régi adatok csak sikeres áthelyezés után törlődnek.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } }) .catch(function(e){ showAlert('Hiba: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); Mire használható?",
"Csapat tudásbázis és belső dokumentáció Notion-szerű blokk szerkesztő valós idejű együttműködéssel Terek és oldalak hierarchikus szervezése Képek, táblázatok és kód blokkok beágyazása Keresés a teljes dokumentációban Első lépések Nyisd meg a docs.DOMAIN címet a böngészőben Az első regisztrált felhasználó automatikusan admin lesz Hozd létre az első teret (Space) a dokumentumoknak Kezdj el írni a Notion-szerű szerkesztőben Előfeltételek x86 processzor szükséges Legalább 768 MB szabad RAM (Docmost &#43; PostgreSQL &#43; Redis) Dokumentáció Hivatalos dokumentáció ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('Ez az alkalmazás már telepítve van.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Frissítés...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Hálózati hiba: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'Folyamatban...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Felhasználói adatok a merevlemezen: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + ' Ez a művelet nem visszavonható!",
"' + hddInfo + checkboxHTML + ' ' + ' Mégsem ' + ' Törlés ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Hiba Nem sikerült lekérni az adatokat: ' + err.message + ' ' + ' Bezárás '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Törlés folyamatban...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Sikeresen törölve!",
"' + removedInfo + preservedInfo + ' Bezárás '; } else { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.disabled = false; btn.textContent = 'Törlés'; } } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.disabled = false; btn.textContent = 'Törlés'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Mindig törlődik: ' + ' ' + ' Docker kötetek (adatbázis, alkalmazás konfiguráció) ' + ' Telepítési konfiguráció (app.yaml) ' + ' Másodlagos mentés ütemezése ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?"
],
"en": [
"Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Message '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Docmost Stopped Update available — today Open ↗ Log Export Settings A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Backups A modern wiki and documentation platform with a Notion-like editor ~200M RAM productivity x86 only Move to another storage You can move this app’s data to another connected storage.",
"Choose a target storage… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Move function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='No move in progress.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Done '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Stopped: '+(job.error||'')+' The old data is untouched.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Choose a target storage.'); return; } felhomConfirm(btn,'Move the '+label+' data to: '+target+'?",
"The old data is deleted only after a successful move.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } }) .catch(function(e){ showAlert('Error: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); What is it for?",
"A knowledge base and internal documentation for a team A Notion-like block editor several people can use at once Spaces and pages, arranged in a tree Embed images, tables and code blocks Search across all of it First steps Open docs.DOMAIN in your browser The first person to sign up becomes the admin Create your first Space for the documents Start writing in the Notion-like editor Before you start An x86 processor is needed At least 768 MB of free RAM (Docmost &#43; PostgreSQL &#43; Redis) Documentation Official documentation ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('This app is already installed.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Refreshing...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Network error: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'In progress...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Error: ' + (data.error || 'Unknown error')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Network error: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Your data on the hard drive: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + hddInfo + checkboxHTML + ' ' + ' Cancel ' + ' Delete ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Error Could not load the details: ' + err.message + ' ' + ' Close '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Deleting...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Deleted data: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Kept data: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Deleted!",
"' + removedInfo + preservedInfo + ' Close '; } else { showAlert('Error: ' + (data.error || 'Unknown error')); btn.disabled = false; btn.textContent = 'Delete'; } } catch (err) { showAlert('Network error: ' + err.message); btn.disabled = false; btn.textContent = 'Delete'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Always deleted: ' + ' ' + ' Docker volumes (database, app configuration) ' + ' Install configuration (app.yaml) ' + ' Secondary backup schedule ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?",
"Data you keep is NOT backed up automatically any more.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Backup data: ' + bkPaths + ' Delete backup data ' + ' ' + 'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Remove app: ' + name + ' ' + ' The app goes back to \"Not installed\"."
]
}
@@ -0,0 +1,37 @@
21:40:47 ==== ENGINE EDGE docmost: postgres:16-alpine -> postgres:17-alpine (app image does NOT move)
21:40:47 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:42:23 [1] deployed, controller state=running, pinned={'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:16-alpine', 'docmost-redis': 'redis:7-alpine'}
21:42:23 [2] seeding through the app's own route
21:42:23 docmost: /api/auth/setup http=200 rc=0
21:42:23 [3] control C1 — reading the seed back BEFORE the engine moves
21:42:24 docmost: login as the seeded user http=200 ok=True
21:42:29 [3b] engine observables BEFORE written to engine-before.txt
21:42:29 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:44:09 [4] backup idle; last=None
21:44:10 [5] drill commit a26b546a99b8: docmost postgres:16-alpine -> postgres:17-alpine (push rc=0)
21:44:15 [5] badge HU: [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}]
21:44:15 [5] badge EN: [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]
21:44:15 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
21:44:15 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
21:44:16 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None
21:44:17 + 2.1s phase=starting label=Indítás az új verzióval… err=None hold=None
21:44:20 + 5.1s phase=failed label=A frissítés nem sikerült err=A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza. hold=A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.
21:44:23 [6b] engine observables AFTER written to engine-after.txt
21:44:23 [6c] hold sentences HU: ['Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace(\'no-js\',\'js\'); function csrfHeaders(){var el=document.querySelector(\'meta[name="csrf-token"]\');return el?{\'X-CSRF-Token\':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen=\'1\'; var wrap=document.createElement(\'span\');wrap.className=\'inline-confirm\'; var q=document.createElement(\'span\');q.className=\'inline-confirm-q\';q.textContent=question; var yes=document.createElement(\'button\');yes.type=\'button\';yes.className=\'btn btn-xs btn-danger\';yes.textContent=\'Igen\'; var no=document.createElement(\'button\');no.type=\'button\';no.className=\'btn btn-xs btn-outline\';no.textContent=\'Mégse\'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display=\'none\';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display=\'\';delete el.dataset.fcOpen;} no.addEventListener(\'click\',close); yes.addEventListener(\'click\',function(){close();onYes();}); } document.addEventListener(\'click\',function(e){ var btn=e.target.closest?e.target.closest(\'[data-confirm]\'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute(\'data-confirm\'),function(){ var form=btn.closest(\'form\'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener(\'click\',function(e){ var btn=e.target.closest?e.target.closest(\'.nav-group-toggle\'):null; if(!btn)return; var group=btn.closest(\'.nav-group\'); if(!group)return; var willOpen=!group.classList.contains(\'is-open\'); document.querySelectorAll(\'.nav-group.is-open\').forEach(function(g){ g.classList.remove(\'is-open\'); var t=g.querySelector(\'.nav-group-toggle\'); if(t)t.setAttribute(\'aria-expanded\',\'false\'); }); if(willOpen){group.classList.add(\'is-open\');btn.setAttribute(\'aria-expanded\',\'true\');} }); function showAlert(msg){var o=document.createElement(\'div\');o.className=\'modal-overlay\';o.id=\'alert-modal\';o.addEventListener(\'click\',function(e){if(e.target===o)o.remove();});var c=document.createElement(\'div\');c.className=\'modal-card\';c.innerHTML=\' Üzenet \'+msg.replace(/&/g,\'&amp;\').replace(/ /g,\'&gt;\')+\' OK \';o.appendChild(c);document.body.appendChild(o);} Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Docmost Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.', 'Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.', 'Mentések Modern wiki és dokumentáció platform Notion-szerű szerkesztővel ~200M RAM productivity Csak x86 Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.']
21:44:23 [6c] hold sentences EN: ['Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace(\'no-js\',\'js\'); function csrfHeaders(){var el=document.querySelector(\'meta[name="csrf-token"]\');return el?{\'X-CSRF-Token\':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen=\'1\'; var wrap=document.createElement(\'span\');wrap.className=\'inline-confirm\'; var q=document.createElement(\'span\');q.className=\'inline-confirm-q\';q.textContent=question; var yes=document.createElement(\'button\');yes.type=\'button\';yes.className=\'btn btn-xs btn-danger\';yes.textContent=\'Yes\'; var no=document.createElement(\'button\');no.type=\'button\';no.className=\'btn btn-xs btn-outline\';no.textContent=\'Cancel\'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display=\'none\';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display=\'\';delete el.dataset.fcOpen;} no.addEventListener(\'click\',close); yes.addEventListener(\'click\',function(){close();onYes();}); } document.addEventListener(\'click\',function(e){ var btn=e.target.closest?e.target.closest(\'[data-confirm]\'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute(\'data-confirm\'),function(){ var form=btn.closest(\'form\'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener(\'click\',function(e){ var btn=e.target.closest?e.target.closest(\'.nav-group-toggle\'):null; if(!btn)return; var group=btn.closest(\'.nav-group\'); if(!group)return; var willOpen=!group.classList.contains(\'is-open\'); document.querySelectorAll(\'.nav-group.is-open\').forEach(function(g){ g.classList.remove(\'is-open\'); var t=g.querySelector(\'.nav-group-toggle\'); if(t)t.setAttribute(\'aria-expanded\',\'false\'); }); if(willOpen){group.classList.add(\'is-open\');btn.setAttribute(\'aria-expanded\',\'true\');} }); function showAlert(msg){var o=document.createElement(\'div\');o.className=\'modal-overlay\';o.id=\'alert-modal\';o.addEventListener(\'click\',function(e){if(e.target===o)o.remove();});var c=document.createElement(\'div\');c.className=\'modal-card\';c.innerHTML=\' Message \'+msg.replace(/&/g,\'&amp;\').replace(/ /g,\'&gt;\')+\' OK \';o.appendChild(c);document.body.appendChild(o);} Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Docmost Stopped Update available — today Open ↗ Log Export Settings A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.', 'Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.', 'Backups A modern wiki and documentation platform with a Notion-like editor ~200M RAM productivity x86 only Move to another storage You can move this app’s data to another connected storage.']
21:44:23 [7] reading the seed back AFTER the engine moved
21:44:23 docmost: login as the seeded user http=404 ok=False
21:44:23 docmost: login body 404 page not found
21:44:25 [8] pinned = {'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:17-alpine', 'docmost-redis': 'redis:7-alpine'}
21:44:25 [8] installed = {'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:16-alpine', 'docmost-redis': 'redis:7-alpine'}
21:44:25 [8] compose = ['image: docmost/docmost:0.96.0', 'image: postgres:17-alpine', 'image: redis:7-alpine']
21:44:25 [8] inspect = []
21:44:25 [W] the edge ended badly — walking the household's way out (restore)
21:44:25 [R] restoring docmost from snapshot 'helyi' (of 1 offered)
21:44:25 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
21:44:25 + 0.0s restore (True, None, None)
21:44:56 + 30.4s restore (False, None, None)
21:44:56 [R] after restore: state=running hold=None phase=None
21:44:59 docmost: login as the seeded user http=200 ok=True
21:44:59 [W] the seed read back after the restore: True
@@ -0,0 +1,23 @@
{
"pinned_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:17-alpine",
"docmost-redis": "redis:7-alpine"
},
"installed_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:16-alpine",
"docmost-redis": "redis:7-alpine"
},
"catalog_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:17-alpine",
"docmost-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: docmost/docmost:0.96.0",
"image: postgres:17-alpine",
"image: redis:7-alpine"
],
"docker_inspect": []
}
@@ -0,0 +1,43 @@
{
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.1,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 2.1,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 5.1,
"phase": "failed",
"label": "A frissítés nem sikerült",
"updating": false,
"error": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza."
}
],
"duration_s": 5.2,
"final_phase": "failed",
"update_error": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold_reason": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"state": "stopped"
}
@@ -0,0 +1,185 @@
{
"harness_version": 1,
"app": "docmost",
"leg": "engine-major-postgres",
"venue": "guest 9202 demo-hp-scratch, controller 0.261.0",
"from": {
"docmost-postgres": "postgres:16-alpine"
},
"to": {
"docmost-postgres": "postgres:17-alpine"
},
"verdict": "failed",
"seed_read_before": true,
"seed_read_after": false,
"healthy_after": true,
"migration_observed": null,
"abort": "not-attempted",
"abort_detail": null,
"engine_state_before": "=== [1] PG_VERSION — the datadir's own record of its major\n16\nUnable to find image 'alpine:3.20' locally\n3.20: Pulling from library/alpine\n25f1d6b1951a: Already exists\nDigest: sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc\nStatus: Downloaded newer image for alpine:3.20\n16\n=== [2] the engine's REFUSAL, verbatim (the whole point of this leg)\n done\nserver started\nCREATE DATABASE\n\n\n/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*\n\nwaiting for server to shut down....2026-09-21 21:42:03.450 CEST [41] LOG: received fast shutdown request\n2026-09-21 21:42:03.453 CEST [41] LOG: aborting any active transactions\n2026-09-21 21:42:03.456 CEST [41] LOG: background worker \"logical replication launcher\" (PID 47) exited with exit code 1\n2026-09-21 21:42:03.457 CEST [42] LOG: shutting down\n2026-09-21 21:42:03.459 CEST [42] LOG: checkpoint starting: shutdown immediate\n2026-09-21 21:42:03.544 CEST [42] LOG: checkpoint complete: wrote 926 buffers (5.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.022 s, sync=0.051 s, total=0.088 s; sync files=301, longest=0.022 s, average=0.001 s; distance=4283 kB, estimate=4283 kB; lsn=0/1925D58, redo lsn=0/1925D58\n2026-09-21 21:42:03.558 CEST [41] LOG: database system is shut down\n done\nserver stopped\n\nPostgreSQL init process complete; ready for start up.\n\n2026-09-21 21:42:03.687 CEST [1] LOG: starting PostgreSQL 16.15 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit\n2026-09-21 21:42:03.687 CEST [1] LOG: listening on IPv4 address \"0.0.0.0\", port 5432\n2026-09-21 21:42:03.687 CEST [1] LOG: listening on IPv6 address \"::\", port 5432\n2026-09-21 21:42:03.692 CEST [1] LOG: listening on Unix socket \"/var/run/postgresql/.s.PGSQL.5432\"\n2026-09-21 21:42:03.701 CEST [57] LOG: database system was shut down at 2026-09-21 21:42:03 CEST\n2026-09-21 21:42:03.710 CEST [1] LOG: database system is ready to accept connections\n=== [3] container state\nimage=postgres:16-alpine running=true restarts=0 exit=0\n=== [4] the version binary in the image\npostgres (PostgreSQL) 16.15\n",
"engine_state_after": "=== [1] PG_VERSION — the datadir's own record of its major\nError response from daemon: No such container: docmost-postgres\ndocker: invalid spec: :/d: empty section between colons\n\nRun 'docker run --help' for more information\n=== [2] the engine's REFUSAL, verbatim (the whole point of this leg)\nError response from daemon: No such container: docmost-postgres\n=== [3] container state\n\nerror: no such object: docmost-postgres\n=== [4] the version binary in the image\nError response from daemon: No such container: docmost-postgres\n(cannot exec — not running)\n",
"duration_s": 254.5,
"measured_at": "2026-09-21T19:40:47.708577+00:00",
"evidence": "apps/docmost-engine-postgres/",
"notes": [
"ended HELD or failed — a RESULT, not an error of the run"
],
"badge_catchup_seconds": 4.5,
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.1,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 2.1,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 5.1,
"phase": "failed",
"label": "A frissítés nem sikerült",
"updating": false,
"error": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza."
}
],
"final_phase": "failed",
"hold_reason": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"update_error": "A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold_sentences": {
"hu": [
"Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Üzenet '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Docmost Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Mentések Modern wiki és dokumentáció platform Notion-szerű szerkesztővel ~200M RAM productivity Csak x86 Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Válassz céltárhelyet… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Áthelyezés function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Kész '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Megszakadt: '+(job.error||'')+' A régi adatok érintetlenek.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Válassz céltárhelyet.'); return; } felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'?",
"A régi adatok csak sikeres áthelyezés után törlődnek.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } }) .catch(function(e){ showAlert('Hiba: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); Mire használható?",
"Csapat tudásbázis és belső dokumentáció Notion-szerű blokk szerkesztő valós idejű együttműködéssel Terek és oldalak hierarchikus szervezése Képek, táblázatok és kód blokkok beágyazása Keresés a teljes dokumentációban Első lépések Nyisd meg a docs.DOMAIN címet a böngészőben Az első regisztrált felhasználó automatikusan admin lesz Hozd létre az első teret (Space) a dokumentumoknak Kezdj el írni a Notion-szerű szerkesztőben Előfeltételek x86 processzor szükséges Legalább 768 MB szabad RAM (Docmost &#43; PostgreSQL &#43; Redis) Dokumentáció Hivatalos dokumentáció ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('Ez az alkalmazás már telepítve van.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Frissítés...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Hálózati hiba: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'Folyamatban...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Felhasználói adatok a merevlemezen: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + ' Ez a művelet nem visszavonható!",
"' + hddInfo + checkboxHTML + ' ' + ' Mégsem ' + ' Törlés ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Hiba Nem sikerült lekérni az adatokat: ' + err.message + ' ' + ' Bezárás '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Törlés folyamatban...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Sikeresen törölve!",
"' + removedInfo + preservedInfo + ' Bezárás '; } else { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.disabled = false; btn.textContent = 'Törlés'; } } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.disabled = false; btn.textContent = 'Törlés'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Mindig törlődik: ' + ' ' + ' Docker kötetek (adatbázis, alkalmazás konfiguráció) ' + ' Telepítési konfiguráció (app.yaml) ' + ' Másodlagos mentés ütemezése ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?"
],
"en": [
"Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Message '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Docmost Stopped Update available — today Open ↗ Log Export Settings A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Backups A modern wiki and documentation platform with a Notion-like editor ~200M RAM productivity x86 only Move to another storage You can move this app’s data to another connected storage.",
"Choose a target storage… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Move function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='No move in progress.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Done '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Stopped: '+(job.error||'')+' The old data is untouched.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Choose a target storage.'); return; } felhomConfirm(btn,'Move the '+label+' data to: '+target+'?",
"The old data is deleted only after a successful move.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } }) .catch(function(e){ showAlert('Error: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); What is it for?",
"A knowledge base and internal documentation for a team A Notion-like block editor several people can use at once Spaces and pages, arranged in a tree Embed images, tables and code blocks Search across all of it First steps Open docs.DOMAIN in your browser The first person to sign up becomes the admin Create your first Space for the documents Start writing in the Notion-like editor Before you start An x86 processor is needed At least 768 MB of free RAM (Docmost &#43; PostgreSQL &#43; Redis) Documentation Official documentation ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('This app is already installed.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Refreshing...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Network error: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'In progress...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Error: ' + (data.error || 'Unknown error')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Network error: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Your data on the hard drive: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + hddInfo + checkboxHTML + ' ' + ' Cancel ' + ' Delete ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Error Could not load the details: ' + err.message + ' ' + ' Close '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Deleting...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Deleted data: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Kept data: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Deleted!",
"' + removedInfo + preservedInfo + ' Close '; } else { showAlert('Error: ' + (data.error || 'Unknown error')); btn.disabled = false; btn.textContent = 'Delete'; } } catch (err) { showAlert('Network error: ' + err.message); btn.disabled = false; btn.textContent = 'Delete'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Always deleted: ' + ' ' + ' Docker volumes (database, app configuration) ' + ' Install configuration (app.yaml) ' + ' Secondary backup schedule ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?",
"Data you keep is NOT backed up automatically any more.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Backup data: ' + bkPaths + ' Delete backup data ' + ' ' + 'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Remove app: ' + name + ' ' + ' The app goes back to \"Not installed\"."
]
},
"observables_after": {
"pinned_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:17-alpine",
"docmost-redis": "redis:7-alpine"
},
"installed_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:16-alpine",
"docmost-redis": "redis:7-alpine"
},
"catalog_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:17-alpine",
"docmost-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: docmost/docmost:0.96.0",
"image: postgres:17-alpine",
"image: redis:7-alpine"
],
"docker_inspect": []
},
"way_out": {
"ok": true,
"snapshot_id": "helyi",
"snapshots": [
{
"time": "2026-09-21T19:44:15Z",
"short_id": "helyi",
"tier": 1,
"drive_label": "Belső SSD (rendszer)"
}
],
"http": "HTTP/2 302",
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 30.4,
"state_after": "running",
"hold_after": null,
"observables_after": {
"pinned_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:16-alpine",
"docmost-redis": "redis:7-alpine"
},
"installed_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:16-alpine",
"docmost-redis": "redis:7-alpine"
},
"catalog_images": {
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:17-alpine",
"docmost-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: docmost/docmost:0.96.0",
"image: postgres:16-alpine",
"image: redis:7-alpine"
],
"docker_inspect": [
"docmost docmost/docmost:0.96.0 running=true restarts=0",
"docmost-redis redis:7-alpine running=true restarts=0",
"docmost-postgres postgres:16-alpine running=true restarts=0"
]
}
},
"seed_read_after_restore": true,
"engine_state_after_restore": "=== [1] PG_VERSION — the datadir's own record of its major\n16\n16\n=== [2] the engine's REFUSAL, verbatim (the whole point of this leg)\n\nPostgreSQL Database directory appears to contain a database; Skipping initialization\n\n2026-09-21 21:44:28.155 CEST [1] LOG: starting PostgreSQL 16.15 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit\n2026-09-21 21:44:28.155 CEST [1] LOG: listening on IPv4 address \"0.0.0.0\", port 5432\n2026-09-21 21:44:28.155 CEST [1] LOG: listening on IPv6 address \"::\", port 5432\n2026-09-21 21:44:28.161 CEST [1] LOG: listening on Unix socket \"/var/run/postgresql/.s.PGSQL.5432\"\n2026-09-21 21:44:28.169 CEST [29] LOG: database system was shut down at 2026-09-21 21:42:44 CEST\n2026-09-21 21:44:28.183 CEST [1] LOG: database system is ready to accept connections\n=== [3] container state\nimage=postgres:16-alpine running=true restarts=0 exit=0\n=== [4] the version binary in the image\npostgres (PostgreSQL) 16.15\n",
"hold_sentences_after_restore": {
"hu": [
"Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Üzenet '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Docmost Fut Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások Modern wiki és dokumentáció platform Notion-szerű szerkesztővel ~200M RAM productivity Csak x86 Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Csapat tudásbázis és belső dokumentáció Notion-szerű blokk szerkesztő valós idejű együttműködéssel Terek és oldalak hierarchikus szervezése Képek, táblázatok és kód blokkok beágyazása Keresés a teljes dokumentációban Első lépések Nyisd meg a docs.DOMAIN címet a böngészőben Az első regisztrált felhasználó automatikusan admin lesz Hozd létre az első teret (Space) a dokumentumoknak Kezdj el írni a Notion-szerű szerkesztőben Előfeltételek x86 processzor szükséges Legalább 768 MB szabad RAM (Docmost &#43; PostgreSQL &#43; Redis) Dokumentáció Hivatalos dokumentáció ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('Ez az alkalmazás már telepítve van.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Frissítés...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Hálózati hiba: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'Folyamatban...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Betöltés...",
"' + ' Ez a művelet nem visszavonható!",
"A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Mentési adatok: ' + bkPaths + ' Mentési adatok törlése ' + ' ' + 'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Alkalmazás eltávolítása: ' + name + ' ' + ' Az alkalmazás visszaáll \"Nincs telepítve\" állapotba.",
"' + ' Ez a művelet nem visszavonható!"
],
"en": [
"Docmost — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Message '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Docmost Running Update available — today Open ↗ Log Export Settings A modern wiki and documentation platform with a Notion-like editor ~200M RAM productivity x86 only Move to another storage You can move this app’s data to another connected storage.",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Network error: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'In progress...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Error: ' + (data.error || 'Unknown error')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Network error: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Loading..."
]
}
}
@@ -18,7 +18,6 @@
"measured_at": "2026-09-21T18:37:10.025072+00:00",
"evidence": "apps/gitea/",
"notes": [
"seed refused through the app's own route — see log.txt for what was tried",
"seed route did not work tonight"
"INCONCLUSIVE: the template sets no `INSTALL_LOCK`, so a fresh Gitea starts in its web-installer state and `gitea admin user create` refuses with `MustInstalled() [F] Unable to load config file for a installed Gitea instance`. The route that would work is POSTing the installer form first; that was not written tonight and is listed as owed rather than faked."
]
}
@@ -0,0 +1,121 @@
mealie | Switching to dedicated user
mealie |
mealie | User uid: 1000
mealie | User gid: 1000
mealie |
mealie | INFO 2026-09-21T21:24:18 - setup plugin alembic.autogenerate.schemas
mealie | INFO 2026-09-21T21:24:18 - setup plugin alembic.autogenerate.tables
mealie | INFO 2026-09-21T21:24:18 - setup plugin alembic.autogenerate.types
mealie | INFO 2026-09-21T21:24:18 - setup plugin alembic.autogenerate.constraints
mealie | INFO 2026-09-21T21:24:18 - setup plugin alembic.autogenerate.defaults
mealie | INFO 2026-09-21T21:24:18 - setup plugin alembic.autogenerate.comments
mealie | INFO 2026-09-21T21:24:24 - Started server process [1]
mealie | INFO 2026-09-21T21:24:24 - Waiting for application startup.
mealie | INFO 2026-09-21T21:24:24 - start: database initialization
mealie | INFO 2026-09-21T21:24:24 - Database connection established.
mealie | INFO 2026-09-21T21:24:24 - Context impl SQLiteImpl.
mealie | INFO 2026-09-21T21:24:24 - Will assume non-transactional DDL.
mealie | INFO 2026-09-21T21:24:25 - end: database initialization
mealie | INFO 2026-09-21T21:24:25 - -----SYSTEM STARTUP-----
mealie | INFO 2026-09-21T21:24:25 - ------APP SETTINGS------
mealie | INFO 2026-09-21T21:24:25 - {
mealie | "TESTING": false,
mealie | "PRODUCTION": true,
mealie | "LOG_CONFIG_OVERRIDE": null,
mealie | "LOG_LEVEL": "info",
mealie | "theme": {
mealie | "light_primary": "#E58325",
mealie | "light_accent": "#007A99",
mealie | "light_secondary": "#973542",
mealie | "light_success": "#43A047",
mealie | "light_info": "#1976D2",
mealie | "light_warning": "#FF6D00",
mealie | "light_error": "#EF5350",
mealie | "dark_primary": "#E58325",
mealie | "dark_accent": "#007A99",
mealie | "dark_secondary": "#973542",
mealie | "dark_success": "#43A047",
mealie | "dark_info": "#1976D2",
mealie | "dark_warning": "#FF6D00",
mealie | "dark_error": "#EF5350"
mealie | },
mealie | "BASE_URL": "https://mealie.enkisfelhom.hu",
mealie | "STATIC_FILES": "/opt/mealie/lib/python3.12/site-packages/mealie/frontend",
mealie | "IS_DEMO": false,
mealie | "HOST_IP": "*",
mealie | "API_HOST": "0.0.0.0",
mealie | "API_PORT": 9000,
mealie | "API_DOCS": true,
mealie | "TOKEN_TIME": 48,
mealie | "GIT_COMMIT_HASH": "a562409e43964d3734eabd64e4a2fd64bffaab4d",
mealie | "ALLOW_SIGNUP": false,
mealie | "ALLOW_PASSWORD_LOGIN": true,
mealie | "ALLOWED_IFRAME_HOSTS": "",
mealie | "DAILY_SCHEDULE_TIME": "23:45",
mealie | "SECURITY_MAX_LOGIN_ATTEMPTS": 5,
mealie | "SECURITY_USER_LOCKOUT_TIME": 24,
mealie | "DB_ENGINE": "sqlite",
mealie | "SQLITE_MIGRATE_JOURNAL_WAL": false,
mealie | "DEFAULT_GROUP": "Home",
mealie | "DEFAULT_HOUSEHOLD": "Family",
mealie | "SMTP_HOST": "",
mealie | "SMTP_PORT": "25",
mealie | "SMTP_FROM_NAME": "",
mealie | "SMTP_FROM_EMAIL": "",
mealie | "SMTP_USER": null,
mealie | "SMTP_PASSWORD": null,
mealie | "SMTP_AUTH_STRATEGY": "NONE",
mealie | "LDAP_AUTH_ENABLED": false,
mealie | "LDAP_SERVER_URL": null,
mealie | "LDAP_TLS_INSECURE": false,
mealie | "LDAP_TLS_CACERTFILE": null,
mealie | "LDAP_ENABLE_STARTTLS": false,
mealie | "LDAP_BASE_DN": null,
mealie | "LDAP_QUERY_BIND": null,
mealie | "LDAP_QUERY_PASSWORD": null,
mealie | "LDAP_USER_FILTER": null,
mealie | "LDAP_ADMIN_FILTER": null,
mealie | "LDAP_ID_ATTRIBUTE": "uid",
mealie | "LDAP_MAIL_ATTRIBUTE": "mail",
mealie | "LDAP_NAME_ATTRIBUTE": "name",
mealie | "OIDC_AUTH_ENABLED": false,
mealie | "OIDC_CLIENT_ID": null,
mealie | "OIDC_CLIENT_SECRET": null,
mealie | "OIDC_CONFIGURATION_URL": null,
mealie | "OIDC_SIGNUP_ENABLED": true,
mealie | "OIDC_USER_GROUP": null,
mealie | "OIDC_ADMIN_GROUP": null,
mealie | "OIDC_AUTO_REDIRECT": false,
mealie | "OIDC_PROVIDER_NAME": "OAuth",
mealie | "OIDC_REMEMBER_ME": false,
mealie | "OIDC_USER_CLAIM": "email",
mealie | "OIDC_NAME_CLAIM": "name",
mealie | "OIDC_GROUPS_CLAIM": "groups",
mealie | "OIDC_SCOPES_OVERRIDE": null,
mealie | "OIDC_TLS_CACERTFILE": null,
mealie | "OIDC_CLIENT_TIMEOUT": "default",
mealie | "OPENAI_CUSTOM_PROMPT_DIR": null,
mealie | "WORKER_PER_CORE": 1,
mealie | "UVICORN_WORKERS": 1,
mealie | "TLS_CERTIFICATE_PATH": null,
mealie | "TLS_PRIVATE_KEY_PATH": null
mealie | }
mealie | INFO 2026-09-21T21:24:25 - ------APP FEATURES------
mealie | INFO 2026-09-21T21:24:25 - --------==SMTP==--------
mealie | INFO 2026-09-21T21:24:25 - Enabled: False
mealie | INFO 2026-09-21T21:24:25 - --------==LDAP==--------
mealie | INFO 2026-09-21T21:24:25 - Enabled: False
mealie | Reason: LDAP_AUTH_ENABLED is false
mealie | INFO 2026-09-21T21:24:25 - --------==OIDC==--------
mealie | INFO 2026-09-21T21:24:25 - Enabled: False
mealie | Reason: OIDC_AUTH_ENABLED is false
mealie | INFO 2026-09-21T21:24:25 - ------------------------
mealie | INFO 2026-09-21T21:24:25 - Daily tasks scheduled for 2026-09-21 21:45:00+00:00
mealie | INFO 2026-09-21T21:24:25 - Application startup complete.
mealie | INFO 2026-09-21T21:24:25 - Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)
mealie | INFO 2026-09-21T21:25:08 - [192.168.0.180:0] 200 OK "GET /api/app/about HTTP/1.1"
mealie | INFO 2026-09-21T21:25:08 - [192.168.0.180:0] 200 OK "POST /api/auth/token HTTP/1.1"
mealie | ERROR 2026-09-21T21:25:08 - No Entry Found on recipe controller action
mealie | ERROR 2026-09-21T21:25:08 - No Entry Found on recipe controller action
mealie | INFO 2026-09-21T21:25:08 - [192.168.0.180:0] 404 Not Found "GET /api/recipes/nopefec546301e HTTP/1.1"
mealie | INFO 2026-09-21T21:25:08 - [192.168.0.180:0] 200 OK "GET /api/recipes/drill-087e4023c5 HTTP/1.1"
@@ -0,0 +1,31 @@
{
"before": {
"hu": [
{
"title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.",
"text": "Naprakész"
}
],
"en": [
{
"title": "This app is running the newest version available.",
"text": "Up to date"
}
]
},
"after": {
"hu": [
{
"title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.",
"text": "Naprakész"
}
],
"en": [
{
"title": "This app is running the newest version available.",
"text": "Up to date"
}
]
},
"drill_commit": "91e4bb97212e"
}
@@ -1,3 +1,11 @@
21:02:15 ==== mealie: ghcr.io/mealie-recipes/mealie:v3.20.1 -> ghcr.io/mealie-recipes/mealie:v3.27.0 (sub=recipes, class=db-postgres)
21:02:15 [1] deploy -> 500 {'ok': False, 'error': 'a(z) "recipes" aldomain már használatban van egy másik alkalmazásban'}
21:02:15 [9] verdict inconclusive -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/mealie/verdict.json
21:31:06 ==== mealie: ghcr.io/mealie-recipes/mealie:v3.20.1 -> ghcr.io/mealie-recipes/mealie:v3.27.0 (sub=mealie, class=db-postgres)
21:31:07 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:32:17 [1] deployed, controller state=running, pinned={'mealie': 'ghcr.io/mealie-recipes/mealie:v3.27.0'}
21:32:20 [2] seeding through the app's own front door
21:32:21 mealie: create recipe http=201
21:32:21 [3] control C1 — reading the seed back BEFORE the update
21:32:21 mealie: readback of the seeded recipe http=200 ok=True
21:32:21 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:33:06 [4] backup idle; last=None
21:33:06 [5] FROM ref not found in compose: ghcr.io/mealie-recipes/mealie:v3.20.1
21:33:06 [9] verdict inconclusive -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/mealie/verdict.json
@@ -0,0 +1,15 @@
{
"pinned_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.27.0"
},
"installed_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.27.0"
},
"catalog_images": null,
"live_compose_image_lines": [
"image: ghcr.io/mealie-recipes/mealie:v3.27.0"
],
"docker_inspect": [
"mealie ghcr.io/mealie-recipes/mealie:v3.27.0 running=true restarts=0"
]
}
@@ -0,0 +1,36 @@
{
"before": {
"pinned_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.20.1"
},
"installed_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.20.1"
},
"catalog_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.20.1"
},
"live_compose_image_lines": [
"image: ghcr.io/mealie-recipes/mealie:v3.20.1"
],
"docker_inspect": [
"mealie ghcr.io/mealie-recipes/mealie:v3.20.1 running=true restarts=0"
]
},
"after": {
"pinned_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.20.1"
},
"installed_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.20.1"
},
"catalog_images": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.20.1"
},
"live_compose_image_lines": [
"image: ghcr.io/mealie-recipes/mealie:v3.20.1"
],
"docker_inspect": [
"mealie ghcr.io/mealie-recipes/mealie:v3.20.1 running=true restarts=0"
]
}
}
@@ -0,0 +1,35 @@
{
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.1,
"phase": "verifying",
"label": "Működés ellenőrzése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 2.1,
"phase": "done",
"label": "Frissítve",
"updating": false,
"error": null,
"hold": null
}
],
"duration_s": 3.1,
"final_phase": "done",
"update_error": null,
"hold_reason": null,
"state": "running"
}
@@ -3,19 +3,21 @@
"app": "mealie",
"venue": "guest 9202 demo-hp-scratch, controller 0.261.0",
"class": "db-postgres",
"from": {},
"from": {
"mealie": "ghcr.io/mealie-recipes/mealie:v3.27.0"
},
"to": {},
"verdict": "inconclusive",
"seed_read_before": false,
"seed_read_before": true,
"seed_read_after": false,
"healthy_after": false,
"migration_observed": null,
"abort": "not-attempted",
"abort_detail": null,
"duration_s": 0.3,
"measured_at": "2026-09-21T19:02:15.239347+00:00",
"duration_s": 120.0,
"measured_at": "2026-09-21T19:31:06.937002+00:00",
"evidence": "apps/mealie/",
"notes": [
"deploy never reached running — nothing else could be measured"
"the drill bump could not be committed — the FROM ref did not match the template"
]
}
@@ -0,0 +1,141 @@
nextcloud | => Configuring PHP session handler...
nextcloud | ==> Using Redis as PHP session handler...
nextcloud | => Searching for hook scripts (*.sh) to run, located in the folder "/docker-entrypoint-hooks.d/before-starting"
nextcloud | ==> Skipped: the "before-starting" folder is empty (or does not exist)
nextcloud | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.22.0.4. Set the 'ServerName' directive globally to suppress this message
nextcloud | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.22.0.4. Set the 'ServerName' directive globally to suppress this message
nextcloud | [Mon Sep 21 21:38:55.360982 2026] [mpm_prefork:notice] [pid 1:tid 1] AH00163: Apache/2.4.68 (Debian) PHP/8.5.8 configured -- resuming normal operations
nextcloud-db | 2026-09-21 21:40:20+02:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:12.3.3+maria~ubu2404 started.
nextcloud | [Mon Sep 21 21:38:55.361025 2026] [core:notice] [pid 1:tid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
nextcloud | 127.0.0.1 - - [21/Sep/2026:21:39:00 +0200] "GET /status.php HTTP/1.1" 200 1063 "-" "curl/8.14.1"
nextcloud | 172.18.0.7 - - [21/Sep/2026:21:39:03 +0200] "GET /status.php HTTP/1.1" 200 1063 "-" "Go-http-client/1.1"
nextcloud | 127.0.0.1 - - [21/Sep/2026:21:39:30 +0200] "GET /status.php HTTP/1.1" 200 1063 "-" "curl/8.14.1"
nextcloud | 127.0.0.1 - - [21/Sep/2026:21:40:00 +0200] "GET /status.php HTTP/1.1" 200 1063 "-" "curl/8.14.1"
nextcloud | 127.0.0.1 - - [21/Sep/2026:21:40:30 +0200] "GET /status.php HTTP/1.1" 500 376 "-" "curl/8.14.1"
nextcloud | 172.18.0.7 - - [21/Sep/2026:21:40:36 +0200] "GET /status.php HTTP/1.1" 200 1063 "-" "Go-http-client/1.1"
nextcloud | 192.168.0.180 - - [21/Sep/2026:21:40:40 +0200] "GET /status.php HTTP/1.1" 200 1063 "-" "curl/8.14.1"
nextcloud-redis | 1:C 21 Sep 2026 21:38:49.342 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
nextcloud-redis | 1:C 21 Sep 2026 21:38:49.342 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
nextcloud-redis | 1:C 21 Sep 2026 21:38:49.342 * Redis version=7.4.11, bits=64, commit=00000000, modified=0, pid=1, just started
nextcloud-redis | 1:C 21 Sep 2026 21:38:49.342 * Configuration loaded
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.342 * Increased maximum number of open files to 10032 (it was originally set to 1024).
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.342 * monotonic clock: POSIX clock_gettime
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.344 * Running mode=standalone, port=6379.
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.344 * Server initialized
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * Reading RDB base file on AOF loading...
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * Loading RDB produced by version 7.4.11
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * RDB age 96 seconds
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * RDB memory usage when created 0.90 Mb
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * RDB is base AOF
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * Done loading RDB, keys loaded: 0, keys expired: 0.
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * DB loaded from base file appendonly.aof.1.base.rdb: 0.000 seconds
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * DB loaded from incr file appendonly.aof.1.incr.aof: 0.000 seconds
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * DB loaded from append only file: 0.000 seconds
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * Opening AOF incr file appendonly.aof.1.incr.aof on server start
nextcloud-db | 2026-09-21 21:40:20+02:00 [Warn] [Entrypoint]: /sys/fs/cgroup///memory.pressure not writable, functionality unavailable to MariaDB
nextcloud-db | 2026-09-21 21:40:20+02:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
nextcloud-db | 2026-09-21 21:40:20+02:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:12.3.3+maria~ubu2404 started.
nextcloud-db | 2026-09-21 21:40:21+02:00 [Note] [Entrypoint]: Starting temporary server
nextcloud-redis | 1:M 21 Sep 2026 21:38:49.345 * Ready to accept connections tcp
nextcloud-db | 2026-09-21 21:40:21+02:00 [Note] [Entrypoint]: Waiting for server startup
nextcloud-db | 2026-09-21 21:40:21 0 [Note] Starting MariaDB 12.3.3-MariaDB-ubu2404 source revision 83e909fc2a0dbc394b4b683fb3fa2d7dcf26cc5e server_uid 14CtiqEo7W/9wCYATc914eYJJhE= as process 56
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: Compressed tables use zlib 1.3
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: Number of transaction pools: 1
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: Using crc32 + pclmulqdq instructions
nextcloud-db | 2026-09-21 21:40:21 0 [Warning] mariadbd: io_uring_queue_init() failed with EPERM: sysctl kernel.io_uring_disabled has the value 2, or 1 and the user of the process is not a member of sysctl kernel.io_uring_group. (see man 2 io_uring_setup).
nextcloud-db | create_uring failed: falling back to libaio
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: Using Linux native AIO
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: innodb_buffer_pool_size_max=8388608m, innodb_buffer_pool_size=128m
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: Completed initialization of buffer pool
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: File system buffers for log disabled (block size=512 bytes)
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: End of log at LSN=2186147
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: Opened 3 undo tablespaces
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: 128 rollback segments in 3 undo tablespaces are active.
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: Setting file './ibtmp1' size to 12.000MiB. Physically writing the file full; Please wait ...
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: File './ibtmp1' size is now 12.000MiB.
nextcloud-db | 2026-09-21 21:40:21 0 [Note] InnoDB: log sequence number 2186147; transaction id 2981
nextcloud-db | 2026-09-21 21:40:21 0 [Note] Plugin 'FEEDBACK' is disabled.
nextcloud-db | 2026-09-21 21:40:21 0 [Note] Plugin 'wsrep-provider' is disabled.
nextcloud-db | 2026-09-21 21:40:22 0 [Note] Replication not automatically started: --skip-slave-start was specified
nextcloud-db | 2026-09-21 21:40:22 0 [Note] mariadbd: ready for connections.
nextcloud-db | Version: '12.3.3-MariaDB-ubu2404' socket: '/run/mysqld/mysqld.sock' port: 0 mariadb.org binary distribution
nextcloud-db | 2026-09-21 21:40:23+02:00 [Note] [Entrypoint]: Temporary server started.
nextcloud-db | 2026-09-21 21:40:23+02:00 [Note] [Entrypoint]: Backing up system database to system_mysql_backup_11.6.2-MariaDB.sql.zst
nextcloud-db | 2026-09-21 21:40:23+02:00 [Note] [Entrypoint]: Backing up complete
nextcloud-db | 2026-09-21 21:40:23+02:00 [Note] [Entrypoint]: Starting mariadb-upgrade
nextcloud-db | The --upgrade-system-tables option was used, user tables won't be touched.
nextcloud-db | Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!
nextcloud-db | Phase 1/8: Checking and upgrading mysql database
nextcloud-db | Processing databases
nextcloud-db | mysql
nextcloud-db | mysql.column_stats OK
nextcloud-db | mysql.columns_priv OK
nextcloud-db | mysql.db OK
nextcloud-db | mysql.event OK
nextcloud-db | mysql.func OK
nextcloud-db | mysql.global_priv OK
nextcloud-db | mysql.gtid_slave_pos OK
nextcloud-db | mysql.help_category OK
nextcloud-db | mysql.help_keyword OK
nextcloud-db | mysql.help_relation OK
nextcloud-db | mysql.help_topic OK
nextcloud-db | mysql.index_stats OK
nextcloud-db | mysql.innodb_index_stats OK
nextcloud-db | mysql.innodb_table_stats OK
nextcloud-db | mysql.plugin OK
nextcloud-db | mysql.proc OK
nextcloud-db | mysql.procs_priv OK
nextcloud-db | mysql.proxies_priv OK
nextcloud-db | mysql.roles_mapping OK
nextcloud-db | mysql.servers OK
nextcloud-db | mysql.table_stats OK
nextcloud-db | mysql.tables_priv OK
nextcloud-db | mysql.time_zone OK
nextcloud-db | mysql.time_zone_leap_second OK
nextcloud-db | mysql.time_zone_name OK
nextcloud-db | mysql.time_zone_transition OK
nextcloud-db | mysql.time_zone_transition_type OK
nextcloud-db | mysql.transaction_registry OK
nextcloud-db | Phase 2/8: Installing used storage engines... Skipped
nextcloud-db | Phase 3/8: Running 'mysql_fix_privilege_tables'
nextcloud-db | Phase 4/8: Fixing views... Skipped
nextcloud-db | Phase 5/8: Fixing table and database names ... Skipped
nextcloud-db | Phase 6/8: Checking and upgrading tables... Skipped
nextcloud-db | Phase 7/8: uninstalling plugins
nextcloud-db | Phase 8/8: Running 'FLUSH PRIVILEGES'
nextcloud-db | OK
nextcloud-db | 2026-09-21 21:40:29+02:00 [Note] [Entrypoint]: Finished mariadb-upgrade
nextcloud-db | 2026-09-21 21:40:29+02:00 [Note] [Entrypoint]: Stopping temporary server
nextcloud-db | 2026-09-21 21:40:29 0 [Note] mariadbd (initiated by: unknown): Normal shutdown
nextcloud-db | 2026-09-21 21:40:29 0 [Note] InnoDB: FTS optimize thread exiting.
nextcloud-db | 2026-09-21 21:40:29 0 [Note] InnoDB: Starting shutdown...
nextcloud-db | 2026-09-21 21:40:29 0 [Note] InnoDB: Removed temporary tablespace data file: "./ibtmp1"
nextcloud-db | 2026-09-21 21:40:29 0 [Note] Shutdown completed; log sequence number 2186147; transaction id 2985
nextcloud-db | 2026-09-21 21:40:30 0 [Note] mariadbd: Shutdown complete
nextcloud-db | 2026-09-21 21:40:30+02:00 [Note] [Entrypoint]: Temporary server stopped
nextcloud-db | 2026-09-21 21:40:30 0 [Note] Starting MariaDB 12.3.3-MariaDB-ubu2404 source revision 83e909fc2a0dbc394b4b683fb3fa2d7dcf26cc5e server_uid 14CtiqEo7W/9wCYATc914eYJJhE= as process 1
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Compressed tables use zlib 1.3
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Number of transaction pools: 1
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Using crc32 + pclmulqdq instructions
nextcloud-db | 2026-09-21 21:40:30 0 [Warning] mariadbd: io_uring_queue_init() failed with EPERM: sysctl kernel.io_uring_disabled has the value 2, or 1 and the user of the process is not a member of sysctl kernel.io_uring_group. (see man 2 io_uring_setup).
nextcloud-db | create_uring failed: falling back to libaio
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Using Linux native AIO
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: innodb_buffer_pool_size_max=8388608m, innodb_buffer_pool_size=128m
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Completed initialization of buffer pool
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: File system buffers for log disabled (block size=512 bytes)
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: End of log at LSN=2186147
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Opened 3 undo tablespaces
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: 128 rollback segments in 3 undo tablespaces are active.
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Setting file './ibtmp1' size to 12.000MiB. Physically writing the file full; Please wait ...
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: File './ibtmp1' size is now 12.000MiB.
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: log sequence number 2186147; transaction id 2981
nextcloud-db | 2026-09-21 21:40:30 0 [Note] Plugin 'FEEDBACK' is disabled.
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
nextcloud-db | 2026-09-21 21:40:30 0 [Note] Plugin 'wsrep-provider' is disabled.
nextcloud-db | 2026-09-21 21:40:30 0 [Note] InnoDB: Buffer pool(s) load completed at 260921 21:40:30
nextcloud-db | 2026-09-21 21:40:33 0 [Note] Server socket created on IP: '0.0.0.0', port: '3306'.
nextcloud-db | 2026-09-21 21:40:33 0 [Note] Server socket created on IP: '::', port: '3306'.
nextcloud-db | 2026-09-21 21:40:33 0 [Note] mariadbd: Event Scheduler: Loaded 0 events
nextcloud-db | 2026-09-21 21:40:33 0 [Note] mariadbd: ready for connections.
nextcloud-db | Version: '12.3.3-MariaDB-ubu2404' socket: '/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution
nextcloud-db | 2026-09-21 21:40:39 6 [Warning] Access denied for user 'root'@'localhost' (using password: NO)
@@ -0,0 +1,14 @@
{
"hu": [
{
"title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.",
"text": "Frissítés elérhető — ma"
}
],
"en": [
{
"title": "A newer version of this app is available. Select the Update button to start it.",
"text": "Update available — today"
}
]
}
@@ -0,0 +1,23 @@
=== [1] mariadb_upgrade_info — the datadir's OWN record of which version converted it
12.3.3-MariaDB
=== [2] THE ENGINE'S OWN CHECK, authenticated (R-464: never the log line)
This installation of MariaDB is already upgraded to 12.3.3-MariaDB.
There is no need to run mariadb-upgrade again.
exit=1
=== [3] the entrypoint's own lines about the upgrade
2026-09-21 21:40:23+02:00 [Note] [Entrypoint]: Starting mariadb-upgrade
The --upgrade-system-tables option was used, user tables won't be touched.
Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!
Phase 2/8: Installing used storage engines... Skipped
Phase 4/8: Fixing views... Skipped
Phase 5/8: Fixing table and database names ... Skipped
Phase 6/8: Checking and upgrading tables... Skipped
2026-09-21 21:40:29+02:00 [Note] [Entrypoint]: Finished mariadb-upgrade
=== [4] the engine's OWN pre-upgrade backup file
-rw-r--r-- 1 mysql mysql 631905 Sep 21 21:40 /var/lib/mysql/system_mysql_backup_11.6.2-MariaDB.sql.zst
(no pre-upgrade backup file present)
=== [5] the version actually running
mariadbd Ver 12.3.3-MariaDB-ubu2404 for debian-linux-gnu on x86_64 (mariadb.org binary distribution)
=== [6] container state
image=mariadb:12.3 running=true restarts=0
@@ -0,0 +1,23 @@
=== [1] mariadb_upgrade_info (the datadir's own record of which version converted it)
12.3.3-MariaDB
=== [2] the engine's OWN check — NOT the log line (R-464)
Reading datadir from the MariaDB server failed. Got the following error when executing the 'mysql' command line client
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
FATAL ERROR: Upgrade failed
exit=1
=== [3] the entrypoint's line, from the container log
2026-09-21 21:40:23+02:00 [Note] [Entrypoint]: Starting mariadb-upgrade
The --upgrade-system-tables option was used, user tables won't be touched.
Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!
Phase 2/8: Installing used storage engines... Skipped
Phase 4/8: Fixing views... Skipped
Phase 5/8: Fixing table and database names ... Skipped
Phase 6/8: Checking and upgrading tables... Skipped
2026-09-21 21:40:29+02:00 [Note] [Entrypoint]: Finished mariadb-upgrade
=== [4] the engine's own pre-upgrade backup file
-rw-r--r-- 1 mysql mysql 631905 Sep 21 21:40 /var/lib/mysql/system_mysql_backup_11.6.2-MariaDB.sql.zst
(no pre-upgrade backup file present)
=== [5] the version actually running
mariadbd Ver 12.3.3-MariaDB-ubu2404 for debian-linux-gnu on x86_64 (mariadb.org binary distribution)
=== [6] container state
image=mariadb:12.3 running=true restarts=0 exit=0
@@ -0,0 +1,13 @@
=== [1] mariadb_upgrade_info (the datadir's own record of which version converted it)
11.6.2-MariaDB=== [2] the engine's OWN check — NOT the log line (R-464)
Reading datadir from the MariaDB server failed. Got the following error when executing the 'mysql' command line client
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
FATAL ERROR: Upgrade failed
exit=1
=== [3] the entrypoint's line, from the container log
=== [4] the engine's own pre-upgrade backup file
(no pre-upgrade backup file present)
=== [5] the version actually running
mariadbd Ver 11.6.2-MariaDB-ubu2404 for debian-linux-gnu on x86_64 (mariadb.org binary distribution)
=== [6] container state
image=mariadb:11.6 running=true restarts=0 exit=0
@@ -0,0 +1,34 @@
{
"hu": [
"Nextcloud — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Üzenet '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Nextcloud Fut Naprakész Megnyitás ↗ Napló Exportálás Beállítások Hiányzó tárhely: nextcloud Ennek az alkalmazásnak az adattárolója jelenleg nem elérhető, ezért le van állítva.",
"Válassz céltárhelyet… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Áthelyezés function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Kész '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Megszakadt: '+(job.error||'')+' A régi adatok érintetlenek.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Válassz céltárhelyet.'); return; } felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'?",
"A régi adatok csak sikeres áthelyezés után törlődnek.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } }) .catch(function(e){ showAlert('Hiba: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); Mire használható?",
"Fájlok szinkronizálása és megosztása eszközök között Naptár és névjegyek szinkronizálás (CalDAV/CardDAV) Dokumentumok közös szerkesztése (OnlyOffice integrációval) Fotó és videó automatikus feltöltés telefonról Alkalmazásbolt - kibővíthető funkciók (Notes, Tasks, Forms, stb.) Első lépések Nyisd meg a cloud.DOMAIN címet a böngészőben Jelentkezz be a telepítéskor megadott admin fiókkal Telepítsd a Nextcloud asztali alkalmazást a számítógépedre Telepítsd a Nextcloud mobil alkalmazást a telefonodra Hívd meg a családtagokat felhasználói fiókok létrehozásával Előfeltételek Külső HDD szükséges a fájlok tárolásához Legalább 1 GB szabad RAM (Nextcloud &#43; MariaDB &#43; Redis) x86 processzor ajánlott a legjobb teljesítményhez Alapértelmezett belépés Az admin fiók adatait a telepítéskor adod meg Az első bejelentkezés után azonnal változtasd meg!",
"Dokumentáció Hivatalos dokumentáció ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('Ez az alkalmazás már telepítve van.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Frissítés...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Hálózati hiba: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'Folyamatban...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Felhasználói adatok a merevlemezen: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + ' Ez a művelet nem visszavonható!",
"' + hddInfo + checkboxHTML + ' ' + ' Mégsem ' + ' Törlés ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Hiba Nem sikerült lekérni az adatokat: ' + err.message + ' ' + ' Bezárás '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Törlés folyamatban...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Sikeresen törölve!",
"' + removedInfo + preservedInfo + ' Bezárás '; } else { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.disabled = false; btn.textContent = 'Törlés'; } } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.disabled = false; btn.textContent = 'Törlés'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Mindig törlődik: ' + ' ' + ' Docker kötetek (adatbázis, alkalmazás konfiguráció) ' + ' Telepítési konfiguráció (app.yaml) ' + ' Másodlagos mentés ütemezése ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?",
"A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Mentési adatok: ' + bkPaths + ' Mentési adatok törlése ' + ' ' + 'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Alkalmazás eltávolítása: ' + name + ' ' + ' Az alkalmazás visszaáll \"Nincs telepítve\" állapotba."
],
"en": [
"Nextcloud — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Message '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Nextcloud Running Up to date Open ↗ Log Export Settings Missing storage: nextcloud This app’s data storage is not available right now, so the app is stopped.",
"Choose a target storage… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Move function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='No move in progress.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Done '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Stopped: '+(job.error||'')+' The old data is untouched.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Choose a target storage.'); return; } felhomConfirm(btn,'Move the '+label+' data to: '+target+'?",
"The old data is deleted only after a successful move.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } }) .catch(function(e){ showAlert('Error: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); What is it for?",
"Sync and share files between your devices Calendar and contact sync (CalDAV/CardDAV) Work on a document together (with the OnlyOffice integration) Photos and videos upload themselves from your phone An app store - more features when you want them (Notes, Tasks, Forms and more) First steps Open cloud.DOMAIN in your browser Sign in with the admin account you set at install time Install the Nextcloud desktop app on your computer Install the Nextcloud mobile app on your phone Invite the household by creating an account for each of them Before you start An external hard drive is needed to keep the files on At least 1 GB of free RAM (Nextcloud &#43; MariaDB &#43; Redis) An x86 processor is recommended for the best speed Default sign-in You set the admin account details at install time Change it right after your first sign-in!",
"Documentation Official documentation ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('This app is already installed.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Refreshing...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Network error: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'In progress...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Error: ' + (data.error || 'Unknown error')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Network error: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Your data on the hard drive: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + hddInfo + checkboxHTML + ' ' + ' Cancel ' + ' Delete ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Error Could not load the details: ' + err.message + ' ' + ' Close '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Deleting...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Deleted data: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Kept data: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Deleted!",
"' + removedInfo + preservedInfo + ' Close '; } else { showAlert('Error: ' + (data.error || 'Unknown error')); btn.disabled = false; btn.textContent = 'Delete'; } } catch (err) { showAlert('Network error: ' + err.message); btn.disabled = false; btn.textContent = 'Delete'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Always deleted: ' + ' ' + ' Docker volumes (database, app configuration) ' + ' Install configuration (app.yaml) ' + ' Secondary backup schedule ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?",
"Data you keep is NOT backed up automatically any more.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Backup data: ' + bkPaths + ' Delete backup data ' + ' ' + 'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Remove app: ' + name + ' ' + ' The app goes back to \"Not installed\".",
"' + sections + ' ' + ' Cancel ' + ' Remove ' + ' ' + hddCheckbox; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Error Could not load the details: ' + err.message + ' ' + ' Close '; } } function closeRemoveModal() { var modal = document.getElementById('remove-modal'); if (modal) modal.remove(); } async function confirmRemoveStack(name) { var btn = document.getElementById('confirm-remove-btn'); var hddCheck = document.getElementById('remove-hdd-check'); var backupCheck = document.getElementById('remove-backup-check'); var removeHDD = hddCheck ?"
]
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
{
"pinned_images": {
"nextcloud": "nextcloud:34.0.1-apache",
"nextcloud-db": "mariadb:12.3",
"nextcloud-redis": "redis:7-alpine"
},
"installed_images": {
"nextcloud": "nextcloud:34.0.1-apache",
"nextcloud-db": "mariadb:12.3",
"nextcloud-redis": "redis:7-alpine"
},
"catalog_images": {
"nextcloud": "nextcloud:34.0.1-apache",
"nextcloud-db": "mariadb:12.3",
"nextcloud-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: nextcloud:34.0.1-apache",
"image: mariadb:12.3",
"image: redis:7-alpine"
],
"docker_inspect": [
"nextcloud-db mariadb:12.3 running=true restarts=0",
"nextcloud nextcloud:34.0.1-apache running=true restarts=0",
"nextcloud-redis redis:7-alpine running=true restarts=0"
]
}
@@ -0,0 +1,51 @@
{
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.0,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 3.1,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 18.5,
"phase": "verifying",
"label": "Működés ellenőrzése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 19.5,
"phase": "done",
"label": "Frissítve",
"updating": false,
"error": null,
"hold": null
}
],
"duration_s": 19.5,
"final_phase": "done",
"update_error": null,
"hold_reason": null,
"state": "running"
}
@@ -0,0 +1,132 @@
{
"harness_version": 1,
"app": "nextcloud",
"leg": "engine-major-mariadb",
"venue": "guest 9202 demo-hp-scratch, controller 0.261.0",
"from": {
"nextcloud-db": "mariadb:11.6"
},
"to": {
"nextcloud-db": "mariadb:12.3"
},
"verdict": "proven",
"seed_read_before": true,
"seed_read_after": true,
"healthy_after": true,
"migration_observed": null,
"abort": "not-attempted",
"abort_detail": null,
"engine_state_before": "=== [1] mariadb_upgrade_info (the datadir's own record of which version converted it)\n11.6.2-MariaDB=== [2] the engine's OWN check — NOT the log line (R-464)\nReading datadir from the MariaDB server failed. Got the following error when executing the 'mysql' command line client\nERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)\nFATAL ERROR: Upgrade failed\nexit=1\n=== [3] the entrypoint's line, from the container log\n=== [4] the engine's own pre-upgrade backup file\n(no pre-upgrade backup file present)\n=== [5] the version actually running\nmariadbd Ver 11.6.2-MariaDB-ubu2404 for debian-linux-gnu on x86_64 (mariadb.org binary distribution)\n=== [6] container state\nimage=mariadb:11.6 running=true restarts=0 exit=0\n",
"engine_state_after": "=== [1] mariadb_upgrade_info (the datadir's own record of which version converted it)\n12.3.3-MariaDB\n=== [2] the engine's OWN check — NOT the log line (R-464)\nReading datadir from the MariaDB server failed. Got the following error when executing the 'mysql' command line client\nERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)\nFATAL ERROR: Upgrade failed\nexit=1\n=== [3] the entrypoint's line, from the container log\n2026-09-21 21:40:23+02:00 [Note] [Entrypoint]: Starting mariadb-upgrade\nThe --upgrade-system-tables option was used, user tables won't be touched.\nMajor version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!\nPhase 2/8: Installing used storage engines... Skipped\nPhase 4/8: Fixing views... Skipped\nPhase 5/8: Fixing table and database names ... Skipped\nPhase 6/8: Checking and upgrading tables... Skipped\n2026-09-21 21:40:29+02:00 [Note] [Entrypoint]: Finished mariadb-upgrade\n=== [4] the engine's own pre-upgrade backup file\n-rw-r--r-- 1 mysql mysql 631905 Sep 21 21:40 /var/lib/mysql/system_mysql_backup_11.6.2-MariaDB.sql.zst\n(no pre-upgrade backup file present)\n=== [5] the version actually running\nmariadbd Ver 12.3.3-MariaDB-ubu2404 for debian-linux-gnu on x86_64 (mariadb.org binary distribution)\n=== [6] container state\nimage=mariadb:12.3 running=true restarts=0 exit=0\n",
"duration_s": 217.4,
"measured_at": "2026-09-21T19:37:10.235635+00:00",
"evidence": "apps/nextcloud-engine-mariadb/",
"notes": [],
"badge_catchup_seconds": 29.0,
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.0,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 3.1,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 18.5,
"phase": "verifying",
"label": "Működés ellenőrzése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 19.5,
"phase": "done",
"label": "Frissítve",
"updating": false,
"error": null,
"hold": null
}
],
"final_phase": "done",
"hold_reason": null,
"update_error": null,
"hold_sentences": {
"hu": [
"Nextcloud — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Üzenet '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Nextcloud Fut Naprakész Megnyitás ↗ Napló Exportálás Beállítások Hiányzó tárhely: nextcloud Ennek az alkalmazásnak az adattárolója jelenleg nem elérhető, ezért le van állítva.",
"Válassz céltárhelyet… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Áthelyezés function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Kész '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Megszakadt: '+(job.error||'')+' A régi adatok érintetlenek.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Válassz céltárhelyet.'); return; } felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'?",
"A régi adatok csak sikeres áthelyezés után törlődnek.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } }) .catch(function(e){ showAlert('Hiba: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); Mire használható?",
"Fájlok szinkronizálása és megosztása eszközök között Naptár és névjegyek szinkronizálás (CalDAV/CardDAV) Dokumentumok közös szerkesztése (OnlyOffice integrációval) Fotó és videó automatikus feltöltés telefonról Alkalmazásbolt - kibővíthető funkciók (Notes, Tasks, Forms, stb.) Első lépések Nyisd meg a cloud.DOMAIN címet a böngészőben Jelentkezz be a telepítéskor megadott admin fiókkal Telepítsd a Nextcloud asztali alkalmazást a számítógépedre Telepítsd a Nextcloud mobil alkalmazást a telefonodra Hívd meg a családtagokat felhasználói fiókok létrehozásával Előfeltételek Külső HDD szükséges a fájlok tárolásához Legalább 1 GB szabad RAM (Nextcloud &#43; MariaDB &#43; Redis) x86 processzor ajánlott a legjobb teljesítményhez Alapértelmezett belépés Az admin fiók adatait a telepítéskor adod meg Az első bejelentkezés után azonnal változtasd meg!",
"Dokumentáció Hivatalos dokumentáció ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('Ez az alkalmazás már telepítve van.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Frissítés...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Hálózati hiba: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'Folyamatban...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Felhasználói adatok a merevlemezen: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + ' Ez a művelet nem visszavonható!",
"' + hddInfo + checkboxHTML + ' ' + ' Mégsem ' + ' Törlés ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Hiba Nem sikerült lekérni az adatokat: ' + err.message + ' ' + ' Bezárás '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Törlés folyamatban...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Sikeresen törölve!",
"' + removedInfo + preservedInfo + ' Bezárás '; } else { showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); btn.disabled = false; btn.textContent = 'Törlés'; } } catch (err) { showAlert('Hálózati hiba: ' + err.message); btn.disabled = false; btn.textContent = 'Törlés'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Betöltés...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Mindig törlődik: ' + ' ' + ' Docker kötetek (adatbázis, alkalmazás konfiguráció) ' + ' Telepítési konfiguráció (app.yaml) ' + ' Másodlagos mentés ütemezése ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?",
"A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Mentési adatok: ' + bkPaths + ' Mentési adatok törlése ' + ' ' + 'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Alkalmazás eltávolítása: ' + name + ' ' + ' Az alkalmazás visszaáll \"Nincs telepítve\" állapotba."
],
"en": [
"Nextcloud — Felhom.eu document.documentElement.className=document.documentElement.className.replace('no-js','js'); function csrfHeaders(){var el=document.querySelector('meta[name=\"csrf-token\"]');return el?{'X-CSRF-Token':el.content}:{};} function felhomConfirm(el,question,onYes){ if(!el||el.dataset.fcOpen)return; el.dataset.fcOpen='1'; var wrap=document.createElement('span');wrap.className='inline-confirm'; var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question; var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes'; var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel'; wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no); el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling); function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;} no.addEventListener('click',close); yes.addEventListener('click',function(){close();onYes();}); } document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('[data-confirm]'):null; if(!btn)return; e.preventDefault(); felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){ var form=btn.closest('form'); if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();} }); }); document.addEventListener('click',function(e){ var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null; if(!btn)return; var group=btn.closest('.nav-group'); if(!group)return; var willOpen=!group.classList.contains('is-open'); document.querySelectorAll('.nav-group.is-open').forEach(function(g){ g.classList.remove('is-open'); var t=g.querySelector('.nav-group-toggle'); if(t)t.setAttribute('aria-expanded','false'); }); if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');} }); function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML=' Message '+msg.replace(/&/g,'&amp;').replace(/ /g,'&gt;')+' OK ';o.appendChild(c);document.body.appendChild(o);} Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Nextcloud Running Up to date Open ↗ Log Export Settings Missing storage: nextcloud This app’s data storage is not available right now, so the app is stopped.",
"Choose a target storage… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Move function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; } function appMigRender(job){ var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'}; var s=names[job.phase]||job.phase; if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; } return s+'…'; } function appMigWatch(){ var panel=document.getElementById('app-migrate-progress'); if(panel) panel.style.display='block'; function tick(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ var job=d.data&&d.data.job; if(!job){ if(panel) panel.textContent='No move in progress.'; return; } if(panel) panel.innerHTML=appMigRender(job); if(job.phase==='done'){ if(panel) panel.innerHTML+=' Done '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Stopped: '+(job.error||'')+' The old data is untouched.'; return; } setTimeout(tick,1500); }).catch(function(){ setTimeout(tick,2000); }); } tick(); } function appMigrate(btn,app,label){ var sel=document.getElementById('app-migrate-target'); var target=sel?sel.value:''; if(!target){ showAlert('Choose a target storage.'); return; } felhomConfirm(btn,'Move the '+label+' data to: '+target+'?",
"The old data is deleted only after a successful move.',function(){ fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } }) .catch(function(e){ showAlert('Error: '+e); }); }); } (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); What is it for?",
"Sync and share files between your devices Calendar and contact sync (CalDAV/CardDAV) Work on a document together (with the OnlyOffice integration) Photos and videos upload themselves from your phone An app store - more features when you want them (Notes, Tasks, Forms and more) First steps Open cloud.DOMAIN in your browser Sign in with the admin account you set at install time Install the Nextcloud desktop app on your computer Install the Nextcloud mobile app on your phone Invite the household by creating an account for each of them Before you start An external hard drive is needed to keep the files on At least 1 GB of free RAM (Nextcloud &#43; MariaDB &#43; Redis) An x86 processor is recommended for the best speed Default sign-in You set the admin account details at install time Change it right after your first sign-in!",
"Documentation Official documentation ↗ (function(){ var burger=document.querySelector('.nav-burger'); var sidebar=document.getElementById('sidebar'); var backdrop=document.querySelector('.nav-backdrop'); if(!burger||!sidebar||!backdrop)return; function setOpen(open){ sidebar.classList.toggle('is-open',open); document.body.classList.toggle('nav-open',open); backdrop.hidden=!open; burger.setAttribute('aria-expanded',open?'true':'false'); } burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));}); backdrop.addEventListener('click',function(){setOpen(false);}); document.addEventListener('keydown',function(e){ if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false); }); })(); document.addEventListener('click', function(e) { if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return; var card = e.target.closest('[data-href]'); if (card) window.location.href = card.dataset.href; }); async function checkBeforeDeploy(e, name) { try { var resp = await fetch('/api/stacks/' + name); var data = await resp.json(); if (data.ok && data.data && data.data.deployed) { e.preventDefault(); showAlert('This app is already installed.'); window.location.reload(); return false; } } catch(err) {} return true; } async function syncTemplates() { const btn = document.getElementById('sync-btn'); const toast = document.getElementById('sync-toast'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '↻ Refreshing...'; btn.classList.add('loading'); try { const resp = await fetch('/api/sync', { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (toast) { toast.textContent = data.ok ?",
"(data.message || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error')); toast.className = 'sync-toast ' + (data.ok ?",
"'sync-toast-ok' : 'sync-toast-err'); toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) { setTimeout(function() { window.location.reload(); }, 1500); } } catch (err) { if (toast) { toast.textContent = 'Network error: ' + err.message; toast.className = 'sync-toast sync-toast-err'; toast.style.display = 'block'; setTimeout(function() { toast.style.display = 'none'; }, 5000); } } btn.innerHTML = origText; btn.disabled = false; btn.classList.remove('loading'); } async function stackAction(event, name, action) { const btn = event.currentTarget; const origText = btn.textContent; btn.disabled = true; btn.textContent = 'In progress...'; btn.classList.add('loading'); try { const resp = await fetch('/api/stacks/' + name + '/' + action, { method: 'POST', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()) }); const data = await resp.json(); if (!data.ok) { showAlert('Error: ' + (data.error || 'Unknown error')); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); return; } if (action === 'update') { followUpdate(name, btn); return; } window.location.reload(); } catch (err) { showAlert('Network error: ' + err.message); btn.textContent = origText; btn.disabled = false; btn.classList.remove('loading'); } } function followUpdate(name, btn) { var started = Date.now(); var timer = setInterval(async function() { if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; } try { var r = await fetch('/api/stacks/' + name); var d = await r.json(); if (!d.ok || !d.data) return; if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label; if (!d.data.updating) { clearInterval(timer); window.location.reload(); } } catch (e) {} }, 3000); } async function deleteOrphanStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'delete-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); }); document.body.appendChild(modal); try { var resp = await fetch('/api/stacks/' + name + '/hdd-data'); var data = await resp.json(); var hddInfo = ''; var checkboxHTML = ''; if (data.ok && data.data && data.data.has_hdd_data) { hddInfo = ' Your data on the hard drive: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?",
"' + hddInfo + checkboxHTML + ' ' + ' Cancel ' + ' Delete ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Error Could not load the details: ' + err.message + ' ' + ' Close '; } } function closeDeleteModal() { var modal = document.getElementById('delete-modal'); if (modal) modal.remove(); } async function confirmDelete(name) { var btn = document.getElementById('confirm-delete-btn'); var checkbox = document.getElementById('delete-hdd-check'); var removeHDD = checkbox ?",
"checkbox.checked : false; btn.disabled = true; btn.textContent = 'Deleting...'; try { var resp = await fetch('/api/stacks/' + name, { method: 'DELETE', headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), body: JSON.stringify({remove_hdd_data: removeHDD}) }); var data = await resp.json(); if (data.ok) { var modal = document.getElementById('delete-modal'); var removedInfo = ''; if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) { removedInfo = ' Deleted data: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Kept data: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Deleted!",
"' + removedInfo + preservedInfo + ' Close '; } else { showAlert('Error: ' + (data.error || 'Unknown error')); btn.disabled = false; btn.textContent = 'Delete'; } } catch (err) { showAlert('Network error: ' + err.message); btn.disabled = false; btn.textContent = 'Delete'; } } async function removeStack(name) { var modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.id = 'remove-modal'; modal.innerHTML = ' Loading...",
"'; modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); }); document.body.appendChild(modal); try { var [hddResp, backupResp] = await Promise.all([ fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }), fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); }) ]); var sections = ''; sections += ' ' + ' Always deleted: ' + ' ' + ' Docker volumes (database, app configuration) ' + ' Install configuration (app.yaml) ' + ' Secondary backup schedule ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?",
"Data you keep is NOT backed up automatically any more.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Backup data: ' + bkPaths + ' Delete backup data ' + ' ' + 'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Remove app: ' + name + ' ' + ' The app goes back to \"Not installed\".",
"' + sections + ' ' + ' Cancel ' + ' Remove ' + ' ' + hddCheckbox; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Error Could not load the details: ' + err.message + ' ' + ' Close '; } } function closeRemoveModal() { var modal = document.getElementById('remove-modal'); if (modal) modal.remove(); } async function confirmRemoveStack(name) { var btn = document.getElementById('confirm-remove-btn'); var hddCheck = document.getElementById('remove-hdd-check'); var backupCheck = document.getElementById('remove-backup-check'); var removeHDD = hddCheck ?"
]
},
"observables_after": {
"pinned_images": {
"nextcloud": "nextcloud:34.0.1-apache",
"nextcloud-db": "mariadb:12.3",
"nextcloud-redis": "redis:7-alpine"
},
"installed_images": {
"nextcloud": "nextcloud:34.0.1-apache",
"nextcloud-db": "mariadb:12.3",
"nextcloud-redis": "redis:7-alpine"
},
"catalog_images": {
"nextcloud": "nextcloud:34.0.1-apache",
"nextcloud-db": "mariadb:12.3",
"nextcloud-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: nextcloud:34.0.1-apache",
"image: mariadb:12.3",
"image: redis:7-alpine"
],
"docker_inspect": [
"nextcloud-db mariadb:12.3 running=true restarts=0",
"nextcloud nextcloud:34.0.1-apache running=true restarts=0",
"nextcloud-redis redis:7-alpine running=true restarts=0"
]
}
}
@@ -0,0 +1,3 @@
opengist | Opengist v1.15.2
opengist | Using YAML config file: /config.yml
opengist | Using environment variables config: OG_EXTERNAL_URL, OG_OPENGIST_HOME
@@ -0,0 +1,31 @@
{
"before": {
"hu": [
{
"title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.",
"text": "Naprakész"
}
],
"en": [
{
"title": "This app is running the newest version available.",
"text": "Up to date"
}
]
},
"after": {
"hu": [
{
"title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.",
"text": "Frissítés elérhető — ma"
}
],
"en": [
{
"title": "A newer version of this app is available. Select the Update button to start it.",
"text": "Update available — today"
}
]
},
"drill_commit": "d22af261ca40"
}
@@ -1,18 +1,25 @@
21:05:28 ==== opengist: ghcr.io/thomiceli/opengist:1.13 -> ghcr.io/thomiceli/opengist:1.15 (sub=gist, class=other)
21:05:29 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:05:44 [1] deployed, controller state=running, pinned={'opengist': 'ghcr.io/thomiceli/opengist:1.13'}
21:05:46 [2] seeding through the app's own front door
21:05:46 opengist: /register http=500
21:05:46 opengist: refused
<!DOCTYPE html>
<html lang="en" class="h-full " data-theme="auto">
<head>
<meta charset="UTF-8" />
<base href="https://gist.enkisfelhom.hu" />
<script>
window.opengist_bas
21:05:46 [9] verdict inconclusive -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/opengist/verdict.json
21:33:06 ==== opengist: ghcr.io/thomiceli/opengist:1.13 -> ghcr.io/thomiceli/opengist:1.15 (sub=gist, class=other)
21:33:07 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:33:17 [1] deployed, controller state=running, pinned={'opengist': 'ghcr.io/thomiceli/opengist:1.13'}
21:33:19 [2] seeding through the app's own front door
21:33:19 opengist: /register (with its own _csrf) http=302
21:33:19 [3] control C1 — reading the seed back BEFORE the update
21:33:21 opengist: sign-in as the seeded account http=302 name_on_page=True
21:33:21 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:34:06 [4] backup idle; last=None
21:34:07 [5] drill commit d22af261ca40: opengist ghcr.io/thomiceli/opengist:1.13 -> ghcr.io/thomiceli/opengist:1.15 (push rc=0)
21:34:11 [5] badge HU: [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}]
21:34:11 [5] badge EN: [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]
21:34:12 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
21:34:12 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
21:34:13 + 1.0s phase=pulling label=Új verzió letöltése… err=None hold=None
21:34:20 + 8.2s phase=starting label=Indítás az új verzióval… err=None hold=None
21:34:21 + 9.2s phase=verifying label=Működés ellenőrzése… err=None hold=None
21:34:26 + 14.4s phase=done label=Frissítve err=None hold=None
21:34:26 [7] reading the seed back AFTER the update
21:34:26 opengist: sign-in as the seeded account http=None name_on_page=False
21:34:29 [8] pinned = {'opengist': 'ghcr.io/thomiceli/opengist:1.15'}
21:34:29 [8] installed = {'opengist': 'ghcr.io/thomiceli/opengist:1.15'}
21:34:29 [8] compose = ['image: ghcr.io/thomiceli/opengist:1.15']
21:34:29 [8] inspect = ['opengist ghcr.io/thomiceli/opengist:1.15 running=true restarts=0']
21:34:29 [9] verdict failed -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/opengist/verdict.json
@@ -5,9 +5,7 @@
"installed_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.13"
},
"catalog_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.13"
},
"catalog_images": null,
"live_compose_image_lines": [
"image: ghcr.io/thomiceli/opengist:1.13"
],
@@ -0,0 +1,34 @@
{
"before": {
"pinned_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.13"
},
"installed_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.13"
},
"catalog_images": null,
"live_compose_image_lines": [
"image: ghcr.io/thomiceli/opengist:1.13"
],
"docker_inspect": [
"opengist ghcr.io/thomiceli/opengist:1.13 running=true restarts=0"
]
},
"after": {
"pinned_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.15"
},
"installed_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.15"
},
"catalog_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.15"
},
"live_compose_image_lines": [
"image: ghcr.io/thomiceli/opengist:1.15"
],
"docker_inspect": [
"opengist ghcr.io/thomiceli/opengist:1.15 running=true restarts=0"
]
}
}
@@ -0,0 +1,51 @@
{
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.0,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 8.2,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 9.2,
"phase": "verifying",
"label": "Működés ellenőrzése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 14.4,
"phase": "done",
"label": "Frissítve",
"updating": false,
"error": null,
"hold": null
}
],
"duration_s": 14.4,
"final_phase": "done",
"update_error": null,
"hold_reason": null,
"state": "running"
}
@@ -6,19 +6,41 @@
"from": {
"opengist": "ghcr.io/thomiceli/opengist:1.13"
},
"to": {},
"to": {
"opengist": "ghcr.io/thomiceli/opengist:1.15"
},
"verdict": "inconclusive",
"seed_read_before": false,
"seed_read_before": true,
"seed_read_after": false,
"healthy_after": false,
"healthy_after": true,
"migration_observed": null,
"abort": "not-attempted",
"abort_detail": null,
"duration_s": 17.9,
"measured_at": "2026-09-21T19:05:29.084151+00:00",
"duration_s": 14.4,
"measured_at": "2026-09-21T19:33:06.999531+00:00",
"evidence": "apps/opengist/",
"notes": [
"seed refused through the app's own route — see log.txt for what was tried",
"seed route did not work tonight"
]
"CORRECTED from `failed` to `inconclusive` the same night, deliberately. The UPDATE itself SUCCEEDED: phase `done` in 14.4 s, and all four version observables agree on `ghcr.io/thomiceli/opengist:1.15` with the container running and zero restarts. What failed was the READBACK: it was attempted immediately after `done` and the sign-in form was not yet being served, so the fixture got no `_csrf` and returned `http=None`. Whether the seeded account survived was therefore NOT ESTABLISHED. Recording that as `failed` would have blamed the app for the harness's impatience — `inconclusive` is the honest verdict and it is never collapsed into `failed`. The fixture now waits for the LOGIN FORM rather than for the root page."
],
"badge_catchup_seconds": 4.5,
"observables_after": {
"pinned_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.15"
},
"installed_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.15"
},
"catalog_images": {
"opengist": "ghcr.io/thomiceli/opengist:1.15"
},
"live_compose_image_lines": [
"image: ghcr.io/thomiceli/opengist:1.15"
],
"docker_inspect": [
"opengist ghcr.io/thomiceli/opengist:1.15 running=true restarts=0"
]
},
"final_phase": "done",
"hold_reason": null,
"update_error": null
}
@@ -0,0 +1,122 @@
romm | INFO: [RomM][init][2026-09-21 21:29:29] _____ __ __
romm | INFO: [RomM][init][2026-09-21 21:29:29] | __ \ | \/ |
romm | INFO: [RomM][init][2026-09-21 21:29:29] | |__) |___ _ __ ___ | \ / |
romm | INFO: [RomM][init][2026-09-21 21:29:29] | _ // _ \| '_ ` _ \| |\/| |
romm | INFO: [RomM][init][2026-09-21 21:29:29] | | \ \ (_) | | | | | | | | |
romm | INFO: [RomM][init][2026-09-21 21:29:29] |_| \_\___/|_| |_| |_|_| |_|
romm | INFO: [RomM][init][2026-09-21 21:29:29]
romm | INFO: [RomM][init][2026-09-21 21:29:29] The beautiful, powerful, self-hosted Rom manager and player
romm | INFO: [RomM][init][2026-09-21 21:29:29]
romm | INFO: [RomM][init][2026-09-21 21:29:29] Version: 5.3.0
romm | INFO: [RomM][init][2026-09-21 21:29:29]
romm | INFO: [RomM][init][2026-09-21 21:29:29] No OpenTelemetry environment variables found, disabling OpenTelemetry SDK
romm | INFO: [RomM][init][2026-09-21 21:29:29] REDIS_HOST is set, not starting internal valkey-server
romm | INFO: [RomM][init][2026-09-21 21:29:29] Running database migrations
romm | INFO: [RomM][init][2026-09-21 21:29:45] Database migrations succeeded
romm | INFO: [RomM][startup][2026-09-21 21:29:56] Running startup tasks
romm | INFO: [RomM][startup][2026-09-21 21:29:56] Cleared 2 job(s) left behind by the old scheduler
romm | INFO: [RomM][startup][2026-09-21 21:29:56] Initializing cache with fixtures data
romm | INFO: [RomM][startup][2026-09-21 21:29:56] Startup tasks completed
romm | INFO: [RomM][init][2026-09-21 21:29:57] Starting backend
romm | INFO: [RomM][init][2026-09-21 21:29:57] Starting RQ cron scheduler
romm | INFO: [RomM][init][2026-09-21 21:29:57] Starting RQ worker
romm | INFO: [RomM][init][2026-09-21 21:29:57] Starting RQ scan worker
romm | INFO: [RomM][init][2026-09-21 21:30:00] Starting nginx
romm | 2026/09/21 21:30:00 [notice] 131#131: js vm init njs: 0000710FFA2DDB00
romm | INFO: [RomM][init][2026-09-21 21:30:00] 🚀 RomM is now available at http://0.0.0.0:8080
romm | 21:30:00 Loading cron configuration from tasks.cron_config
romm | 21:30:00 Worker ae1444165f644e9294bf27d3ebcefdb8: started with PID 104, version 2.12.0
romm | 21:30:00 Worker ae1444165f644e9294bf27d3ebcefdb8: subscribing to channel rq:pubsub:ae1444165f644e9294bf27d3ebcefdb8
romm | 21:30:00 *** Listening on high, default, low...
romm | 21:30:00 Acquired scheduler lock for default
romm | 21:30:00 Acquired scheduler lock for low
romm | 21:30:00 Acquired scheduler lock for high
romm | 21:30:00 Scheduler for default, low, high started with PID 146
romm | 21:30:00 Worker e3223e339f794c82bec23fdda811216c: started with PID 107, version 2.12.0
romm | 21:30:00 Worker e3223e339f794c82bec23fdda811216c: subscribing to channel rq:pubsub:e3223e339f794c82bec23fdda811216c
romm | 21:30:00 *** Listening on scans...
romm | 21:30:00 Acquired scheduler lock for scans
romm | 21:30:00 Scheduler for scans started with PID 148
romm | INFO: [RomM][nginx][2026-09-21 21:30:05] 127.0.0.1 | - | GET / 200 | 5878 | Unknown Unknown | 0.000
romm | INFO: [RomM][nginx][2026-09-21 21:30:10] 172.18.0.7 | - | GET / 200 | 2206 | Unknown Unknown | 0.000
romm | INFO: [RomM][cron_config][2026-09-21 21:30:17] Scheduled 'build_recommendations' at '30 5 * * *'
romm-db | 2026-09-21 21:28:14+02:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:11.4.13+maria~ubu2404 started.
romm-db | 2026-09-21 21:28:14+02:00 [Warn] [Entrypoint]: /sys/fs/cgroup///memory.pressure not writable, functionality unavailable to MariaDB
romm-db | 2026-09-21 21:28:14+02:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
romm-db | 2026-09-21 21:28:14+02:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:11.4.13+maria~ubu2404 started.
romm-db | 2026-09-21 21:28:14+02:00 [Note] [Entrypoint]: MariaDB upgrade not required
romm | INFO: [RomM][cron_config][2026-09-21 21:30:17] Scheduled 'cleanup_zip_cache' at '0 4 * * *'
romm | INFO: [RomM][cron_config][2026-09-21 21:30:17] Scheduled 'cleanup_netplay' at '*/30 * * * *'
romm | INFO: [RomM][cron_config][2026-09-21 21:30:17] Scheduled 'cleanup_upload_tmp' at '0 * * * *'
romm | 21:30:17 Registered 'tasks.tasks.run_task_by_name' to run on low with cron schedule '30 5 * * *'
romm | 21:30:17 Registered 'tasks.tasks.run_task_by_name' to run on low with cron schedule '0 4 * * *'
romm-db | 2026-09-21 21:28:14 0 [Note] Starting MariaDB 11.4.13-MariaDB-ubu2404 source revision 170b1d70737be6f134448f51713cdc1ae215b420 server_uid +Q9/tt9ag22l9EQsPkibJVRvRoU= as process 1
romm | 21:30:17 Registered 'tasks.tasks.run_task_by_name' to run on low with cron schedule '*/30 * * * *'
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Compressed tables use zlib 1.3
romm | 21:30:17 Registered 'tasks.tasks.run_task_by_name' to run on low with cron schedule '0 * * * *'
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Number of transaction pools: 1
romm | 21:30:17 Successfully registered 4 cron jobs from 'tasks.cron_config'
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Using crc32 + pclmulqdq instructions
romm | 21:30:17 CronScheduler 090f2c2a50a2:101:0f34e0: starting...
romm-db | 2026-09-21 21:28:14 0 [Warning] mariadbd: io_uring_queue_init() failed with EPERM: sysctl kernel.io_uring_disabled has the value 2, or 1 and the user of the process is not a member of sysctl kernel.io_uring_group. (see man 2 io_uring_setup).
romm | 21:30:17 CronScheduler 090f2c2a50a2:101:0f34e0: registering birth...
romm-db | create_uring failed: falling back to libaio
romm | INFO: [RomM][nginx][2026-09-21 21:30:25] 172.18.0.8 | 192.168.0.180 | GET /api/heartbeat 499 | 0 | Unknown Unknown | 15.016
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Using Linux native AIO
romm | INFO: [RomM][server][2026-09-21 21:30:36] Started server process [119]
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: innodb_buffer_pool_size_max=8388608m, innodb_buffer_pool_size=128m
romm | INFO: [RomM][on][2026-09-21 21:30:36] Waiting for application startup.
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Completed initialization of buffer pool
romm | INFO: [RomM][on][2026-09-21 21:30:37] Application startup complete.
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: File system buffers for log disabled (block size=512 bytes)
romm | INFO: [RomM][server][2026-09-21 21:30:37] Started server process [121]
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: End of log at LSN=3058473
romm | INFO: [RomM][on][2026-09-21 21:30:37] Waiting for application startup.
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Opened 3 undo tablespaces
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: 128 rollback segments in 3 undo tablespaces are active.
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Setting file './ibtmp1' size to 12.000MiB. Physically writing the file full; Please wait ...
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: File './ibtmp1' size is now 12.000MiB.
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: log sequence number 3058473; transaction id 2446
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
romm | INFO: [RomM][on][2026-09-21 21:30:38] Application startup complete.
romm-db | 2026-09-21 21:28:14 0 [Note] Plugin 'FEEDBACK' is disabled.
romm | INFO: [RomM][logs][2026-09-21 21:30:38] Log stream forwarder started
romm | INFO: [RomM][server][2026-09-21 21:30:39] Started server process [118]
romm | INFO: [RomM][on][2026-09-21 21:30:39] Waiting for application startup.
romm | INFO: [RomM][on][2026-09-21 21:30:39] Application startup complete.
romm | INFO: [RomM][nginx][2026-09-21 21:30:35] 127.0.0.1 | - | GET / 200 | 5878 | Unknown Unknown | 0.000
romm | INFO: [RomM][nginx][2026-09-21 21:30:38] 172.18.0.8 | 192.168.0.180 | GET /api/heartbeat 200 | 543 | Unknown Unknown | 7.435
romm | INFO: [RomM][nginx][2026-09-21 21:30:38] 172.18.0.8 | 192.168.0.180 | GET /api/heartbeat 200 | 543 | Unknown Unknown | 0.013
romm | INFO: [RomM][nginx][2026-09-21 21:30:39] 172.18.0.8 | 192.168.0.180 | POST /api/login 401 | 43 | Unknown Unknown | 0.587
romm-db | 2026-09-21 21:28:14 0 [Note] Plugin 'wsrep-provider' is disabled.
romm-db | 2026-09-21 21:28:14 0 [Note] InnoDB: Buffer pool(s) load completed at 260921 21:28:14
romm-db | 2026-09-21 21:28:16 0 [Note] Server socket created on IP: '0.0.0.0', port: '3306'.
romm-db | 2026-09-21 21:28:16 0 [Note] Server socket created on IP: '::', port: '3306'.
romm-db | 2026-09-21 21:28:16 0 [Note] mariadbd: Event Scheduler: Loaded 0 events
romm-db | 2026-09-21 21:28:16 0 [Note] mariadbd: ready for connections.
romm-db | Version: '11.4.13-MariaDB-ubu2404' socket: '/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution
romm-db | 2026-09-21 21:29:45 22 [Warning] Aborted connection 22 to db: 'romm' user: 'romm' host: '172.20.0.4' (Got an error reading communication packets)
romm-redis | 1:C 21 Sep 2026 21:28:14.156 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
romm-redis | 1:C 21 Sep 2026 21:28:14.156 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
romm-redis | 1:C 21 Sep 2026 21:28:14.156 * Redis version=7.4.11, bits=64, commit=00000000, modified=0, pid=1, just started
romm-redis | 1:C 21 Sep 2026 21:28:14.156 * Configuration loaded
romm-redis | 1:M 21 Sep 2026 21:28:14.157 * Increased maximum number of open files to 10032 (it was originally set to 1024).
romm-redis | 1:M 21 Sep 2026 21:28:14.157 * monotonic clock: POSIX clock_gettime
romm-redis | 1:M 21 Sep 2026 21:28:14.158 * Running mode=standalone, port=6379.
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * Server initialized
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * Reading RDB base file on AOF loading...
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * Loading RDB produced by version 7.4.11
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * RDB age 68 seconds
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * RDB memory usage when created 0.90 Mb
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * RDB is base AOF
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * Done loading RDB, keys loaded: 0, keys expired: 0.
romm-redis | 1:M 21 Sep 2026 21:28:14.159 * DB loaded from base file appendonly.aof.1.base.rdb: 0.000 seconds
romm-redis | 1:M 21 Sep 2026 21:28:14.223 * DB loaded from incr file appendonly.aof.1.incr.aof: 0.064 seconds
romm-redis | 1:M 21 Sep 2026 21:28:14.223 * DB loaded from append only file: 0.064 seconds
romm-redis | 1:M 21 Sep 2026 21:28:14.223 * Opening AOF incr file appendonly.aof.1.incr.aof on server start
romm-redis | 1:M 21 Sep 2026 21:28:14.223 * Ready to accept connections tcp
romm-redis | 1:M 21 Sep 2026 21:29:15.065 * 10000 changes in 60 seconds. Saving...
romm-redis | 1:M 21 Sep 2026 21:29:15.066 * Background saving started by pid 52
romm-redis | 52:C 21 Sep 2026 21:29:15.192 * DB saved on disk
romm-redis | 52:C 21 Sep 2026 21:29:15.193 * Fork CoW for RDB: current 1 MB, peak 1 MB, average 0 MB
romm-redis | 1:M 21 Sep 2026 21:29:15.268 * Background saving terminated with success
@@ -0,0 +1,31 @@
{
"before": {
"hu": [
{
"title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.",
"text": "Naprakész"
}
],
"en": [
{
"title": "This app is running the newest version available.",
"text": "Up to date"
}
]
},
"after": {
"hu": [
{
"title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.",
"text": "Frissítés elérhető — ma"
}
],
"en": [
{
"title": "A newer version of this app is available. Select the Update button to start it.",
"text": "Update available — today"
}
]
},
"drill_commit": "f72102b7217b"
}
@@ -1,11 +1,27 @@
21:09:03 ==== romm: rommapp/romm:5.0.0 -> rommapp/romm:5.3.0 (sub=arcade, class=db-mariadb)
21:09:06 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/romm']
21:09:06 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
21:09:06 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:09:26 [1] deployed, controller state=degraded, pinned=None
21:09:26 [1] NOTE: the controller's own state is 'degraded', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate
21:09:28 [2] seeding through the app's own front door
21:10:14 romm: POST /api/users http=403 :: CSRF token verification failed
21:10:14 romm: POST /api/users http=403 :: CSRF token verification failed
21:10:14 romm: no unauthenticated route created the first account
21:10:14 [9] verdict inconclusive -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/romm/verdict.json
21:27:03 ==== romm: rommapp/romm:5.0.0 -> rommapp/romm:5.3.0 (sub=arcade, class=db-mariadb)
21:27:05 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/romm']
21:27:05 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
21:27:05 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:27:56 [1] deployed, controller state=running, pinned={'romm': 'rommapp/romm:5.0.0', 'romm-db': 'mariadb:11.4', 'romm-redis': 'redis:7-alpine'}
21:27:58 [2] seeding through the app's own front door
21:28:02 romm: POST /api/users http=201
21:28:02 [3] control C1 — reading the seed back BEFORE the update
21:28:04 romm: login as the seeded user http=200 ok=True
21:28:04 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:29:04 [4] backup idle; last=None
21:29:05 [5] drill commit f72102b7217b: romm rommapp/romm:5.0.0 -> rommapp/romm:5.3.0 (push rc=0)
21:29:10 [5] badge HU: [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}]
21:29:10 [5] badge EN: [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]
21:29:10 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
21:29:10 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
21:29:11 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None
21:29:23 + 13.4s phase=starting label=Indítás az új verzióval… err=None hold=None
21:29:29 + 19.5s phase=verifying label=Működés ellenőrzése… err=None hold=None
21:30:10 + 60.5s phase=done label=Frissítve err=None hold=None
21:30:10 [7] reading the seed back AFTER the update
21:30:40 romm: login as the seeded user http=200 ok=True
21:30:43 [8] pinned = {'romm': 'rommapp/romm:5.3.0', 'romm-db': 'mariadb:11.4', 'romm-redis': 'redis:7-alpine'}
21:30:43 [8] installed = {'romm': 'rommapp/romm:5.3.0', 'romm-db': 'mariadb:11.4', 'romm-redis': 'redis:7-alpine'}
21:30:43 [8] compose = ['image: rommapp/romm:5.3.0', 'image: mariadb:11.4', 'image: redis:7-alpine']
21:30:43 [8] inspect = ['romm rommapp/romm:5.3.0 running=true restarts=0', 'romm-db mariadb:11.4 running=true restarts=0', 'romm-redis redis:7-alpine running=true restarts=0']
21:30:43 [9] verdict proven -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/romm/verdict.json
@@ -1,14 +1,26 @@
{
"pinned_images": null,
"installed_images": {},
"catalog_images": null,
"pinned_images": {
"romm": "rommapp/romm:5.0.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"installed_images": {
"romm": "rommapp/romm:5.0.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"catalog_images": {
"romm": "rommapp/romm:5.0.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: rommapp/romm:5.0.0",
"image: mariadb:11.4",
"image: redis:7-alpine"
],
"docker_inspect": [
"romm rommapp/romm:5.0.0 running=false restarts=0",
"romm rommapp/romm:5.0.0 running=true restarts=0",
"romm-db mariadb:11.4 running=true restarts=0",
"romm-redis redis:7-alpine running=true restarts=0"
]
@@ -0,0 +1,56 @@
{
"before": {
"pinned_images": {
"romm": "rommapp/romm:5.0.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"installed_images": {
"romm": "rommapp/romm:5.0.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"catalog_images": {
"romm": "rommapp/romm:5.0.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: rommapp/romm:5.0.0",
"image: mariadb:11.4",
"image: redis:7-alpine"
],
"docker_inspect": [
"romm rommapp/romm:5.0.0 running=true restarts=0",
"romm-db mariadb:11.4 running=true restarts=0",
"romm-redis redis:7-alpine running=true restarts=0"
]
},
"after": {
"pinned_images": {
"romm": "rommapp/romm:5.3.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"installed_images": {
"romm": "rommapp/romm:5.3.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"catalog_images": {
"romm": "rommapp/romm:5.3.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: rommapp/romm:5.3.0",
"image: mariadb:11.4",
"image: redis:7-alpine"
],
"docker_inspect": [
"romm rommapp/romm:5.3.0 running=true restarts=0",
"romm-db mariadb:11.4 running=true restarts=0",
"romm-redis redis:7-alpine running=true restarts=0"
]
}
}
@@ -0,0 +1,51 @@
{
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.1,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 13.4,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 19.5,
"phase": "verifying",
"label": "Működés ellenőrzése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 60.5,
"phase": "done",
"label": "Frissítve",
"updating": false,
"error": null,
"hold": null
}
],
"duration_s": 60.6,
"final_phase": "done",
"update_error": null,
"hold_reason": null,
"state": "running"
}
@@ -3,20 +3,56 @@
"app": "romm",
"venue": "guest 9202 demo-hp-scratch, controller 0.261.0",
"class": "db-mariadb",
"from": {},
"to": {},
"verdict": "inconclusive",
"seed_read_before": false,
"seed_read_after": false,
"healthy_after": false,
"migration_observed": null,
"from": {
"romm": "rommapp/romm:5.0.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"to": {
"romm": "rommapp/romm:5.3.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"verdict": "proven",
"seed_read_before": true,
"seed_read_after": true,
"healthy_after": true,
"migration_observed": "romm | \u001b[0;32mINFO: \u001b[0;34m[RomM]\u001b[0;95m[init]\u001b[0;36m[2026-09-21 21:29:29]\u001b[0;00m Running database migrations",
"abort": "not-attempted",
"abort_detail": null,
"duration_s": 70.7,
"measured_at": "2026-09-21T19:09:04.045090+00:00",
"duration_s": 60.6,
"measured_at": "2026-09-21T19:27:03.627376+00:00",
"evidence": "apps/romm/",
"notes": [
"seed refused through the app's own route — see log.txt for what was tried",
"seed route did not work tonight"
]
"notes": [],
"badge_catchup_seconds": 4.4,
"observables_after": {
"pinned_images": {
"romm": "rommapp/romm:5.3.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"installed_images": {
"romm": "rommapp/romm:5.3.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"catalog_images": {
"romm": "rommapp/romm:5.3.0",
"romm-db": "mariadb:11.4",
"romm-redis": "redis:7-alpine"
},
"live_compose_image_lines": [
"image: rommapp/romm:5.3.0",
"image: mariadb:11.4",
"image: redis:7-alpine"
],
"docker_inspect": [
"romm rommapp/romm:5.3.0 running=true restarts=0",
"romm-db mariadb:11.4 running=true restarts=0",
"romm-redis redis:7-alpine running=true restarts=0"
]
},
"final_phase": "done",
"hold_reason": null,
"update_error": null
}
@@ -0,0 +1,23 @@
21:36:12 ==== FAILWALK tandoor: the household's way out of a held update
21:36:15 [1] held state: phase=failed hold='A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.'
21:36:15 update_error = 'A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.'
21:36:15 pinned = {'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:16-alpine'}
21:36:15 installed = {'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.13', 'tandoor-postgres': 'postgres:16-alpine'}
21:36:15 compose = ['image: ghcr.io/tandoorrecipes/recipes:2.6.15', 'image: postgres:16-alpine']
21:36:15 inspect = []
21:36:15 [2] hold sentence HU: ['Tandoor Recipes — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: Tandoor Recipes (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Tandoor Recipes Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.', 'Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.', 'Mentések Receptkezelő és étkezés tervező a családnak ~512M RAM home Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.', 'Receptek gyűjtése és rendszerezése egy helyen Receptek importálása weboldalakról egy kattintással Heti étkezés tervezés és bevásárlólista generálás Több felhasználó - a család együtt tervezhet Receptek megosztása linkkel családtagokkal, barátokkal Első lépések Nyisd meg a recipes.DOMAIN címet a böngészőben Hozd létre az admin fiókot Importáld az első receptet egy weboldalról (Bookmarklet) Próbáld ki az étkezés tervezőt Hívd meg a családtagokat Dokumentáció Hivatalos dokumentáció ↗']
21:36:15 [2] hold sentence EN: ['Tandoor Recipes — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ An installed app is not running: Tandoor Recipes (stopped) System monitor → The hub connection is off — central monitoring is not running System monitor → ← Apps Tandoor Recipes Stopped Update available — today Open ↗ Log Export Settings A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.', 'Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.', 'Backups A recipe manager and meal planner for the household ~512M RAM home Runs on Pi Move to another storage You can move this app’s data to another connected storage.', 'Keep all your recipes in one place Import a recipe from a web page with one click Plan the week&#39;s meals and get a shopping list out of it Several people - the household can plan together Share a recipe by link with family and friends First steps Open recipes.DOMAIN in your browser Create the admin account Import your first recipe from a web page (Bookmarklet) Try the meal planner Invite the household Documentation Official documentation ↗']
21:36:15 [3] the last migration line the app printed, verbatim: None
21:36:15 [R] restoring tandoor from snapshot 'helyi' (of 1 offered)
21:36:15 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
21:36:15 + 0.0s restore (True, None, None)
21:36:47 + 32.4s restore (False, None, None)
21:36:47 [R] after restore: state=running hold=None phase=None
21:36:50 [4] restore: HTTP/2 302 in 32.4s; state=running hold=None
21:36:50 pinned after = {'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.13', 'tandoor-postgres': 'postgres:16-alpine'}
21:36:50 inspect after = ['tandoor ghcr.io/tandoorrecipes/recipes:2.6.13 running=true restarts=0', 'tandoor-postgres postgres:16-alpine running=true restarts=0']
21:36:59 tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
21:37:03 tandoor: seeded superuser drill74a269
21:37:09 tandoor: readback of the seeded account found=True
21:37:09 [5] the app answers its own front door again and holds data: True
21:37:10 [6] sentence after the restore HU: ['Tandoor Recipes — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Tandoor Recipes Nem egészséges Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások Receptkezelő és étkezés tervező a családnak ~512M RAM home Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.', 'Receptek gyűjtése és rendszerezése egy helyen Receptek importálása weboldalakról egy kattintással Heti étkezés tervezés és bevásárlólista generálás Több felhasználó - a család együtt tervezhet Receptek megosztása linkkel családtagokkal, barátokkal Első lépések Nyisd meg a recipes.DOMAIN címet a böngészőben Hozd létre az admin fiókot Importáld az első receptet egy weboldalról (Bookmarklet) Próbáld ki az étkezés tervezőt Hívd meg a családtagokat Dokumentáció Hivatalos dokumentáció ↗']
@@ -0,0 +1,102 @@
{
"leg": "failwalk",
"app": "tandoor",
"held": {
"state": "stopped",
"updating": false,
"update_phase": "failed",
"update_phase_label": "A frissítés nem sikerült",
"update_error": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold_reason": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"observables": {
"pinned_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"installed_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"catalog_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"live_compose_image_lines": [
"image: ghcr.io/tandoorrecipes/recipes:2.6.15",
"image: postgres:16-alpine"
],
"docker_inspect": []
}
},
"hold_sentences": {
"hu": [
"Tandoor Recipes — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: Tandoor Recipes (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Tandoor Recipes Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Mentések Receptkezelő és étkezés tervező a családnak ~512M RAM home Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Receptek gyűjtése és rendszerezése egy helyen Receptek importálása weboldalakról egy kattintással Heti étkezés tervezés és bevásárlólista generálás Több felhasználó - a család együtt tervezhet Receptek megosztása linkkel családtagokkal, barátokkal Első lépések Nyisd meg a recipes.DOMAIN címet a böngészőben Hozd létre az admin fiókot Importáld az első receptet egy weboldalról (Bookmarklet) Próbáld ki az étkezés tervezőt Hívd meg a családtagokat Dokumentáció Hivatalos dokumentáció ↗"
],
"en": [
"Tandoor Recipes — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ An installed app is not running: Tandoor Recipes (stopped) System monitor → The hub connection is off — central monitoring is not running System monitor → ← Apps Tandoor Recipes Stopped Update available — today Open ↗ Log Export Settings A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.",
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"Backups A recipe manager and meal planner for the household ~512M RAM home Runs on Pi Move to another storage You can move this app’s data to another connected storage.",
"Keep all your recipes in one place Import a recipe from a web page with one click Plan the week&#39;s meals and get a shopping list out of it Several people - the household can plan together Share a recipe by link with family and friends First steps Open recipes.DOMAIN in your browser Create the admin account Import your first recipe from a web page (Bookmarklet) Try the meal planner Invite the household Documentation Official documentation ↗"
]
},
"last_migration_line": null,
"way_out": {
"ok": true,
"snapshot_id": "helyi",
"snapshots": [
{
"time": "2026-09-21T19:16:48Z",
"short_id": "helyi",
"tier": 1,
"drive_label": "Belső SSD (rendszer)"
}
],
"http": "HTTP/2 302",
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 32.4,
"state_after": "running",
"hold_after": null,
"observables_after": {
"pinned_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"installed_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"catalog_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"live_compose_image_lines": [
"image: ghcr.io/tandoorrecipes/recipes:2.6.13",
"image: postgres:16-alpine"
],
"docker_inspect": [
"tandoor ghcr.io/tandoorrecipes/recipes:2.6.13 running=true restarts=0",
"tandoor-postgres postgres:16-alpine running=true restarts=0"
]
}
},
"data_after_restore": {
"route": "re-seeded after the restore",
"ok": true,
"note": "the pre-update seed token did not survive the failed run's process, so this re-seeds AFTER the restore: it proves the app WORKS again on the old version, not that the specific pre-update row survived. Stated rather than glossed."
},
"sentences_after_restore": {
"hu": [
"Tandoor Recipes — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Tandoor Recipes Nem egészséges Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások Receptkezelő és étkezés tervező a családnak ~512M RAM home Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Receptek gyűjtése és rendszerezése egy helyen Receptek importálása weboldalakról egy kattintással Heti étkezés tervezés és bevásárlólista generálás Több felhasználó - a család együtt tervezhet Receptek megosztása linkkel családtagokkal, barátokkal Első lépések Nyisd meg a recipes.DOMAIN címet a böngészőben Hozd létre az admin fiókot Importáld az első receptet egy weboldalról (Bookmarklet) Próbáld ki az étkezés tervezőt Hívd meg a családtagokat Dokumentáció Hivatalos dokumentáció ↗"
],
"en": [
"Tandoor Recipes — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Tandoor Recipes Not healthy Update available — today Open ↗ Log Export Settings A recipe manager and meal planner for the household ~512M RAM home Runs on Pi Move to another storage You can move this app’s data to another connected storage.",
"Keep all your recipes in one place Import a recipe from a web page with one click Plan the week&#39;s meals and get a shopping list out of it Several people - the household can plan together Share a recipe by link with family and friends First steps Open recipes.DOMAIN in your browser Create the admin account Import your first recipe from a web page (Bookmarklet) Try the meal planner Invite the household Documentation Official documentation ↗"
]
}
}
@@ -0,0 +1,654 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tandoor Recipes — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.261.0">
<link rel="stylesheet" href="/static/style.css?v=0.261.0">
<meta name="csrf-token" content="b0f2927caee41c5a97de77202486f9d0a2d3bb97124581393b77751acb8f4307">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Message</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menu">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Launcher</a></li>
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Dashboard</a></li>
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Apps</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Storage<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Drives</a></li>
<li><a href="/storage/network" class="">Network storage</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Backup<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Overview</a></li>
<li><a href="/backups/remote" class="">Remote backup</a></li>
<li><a href="/backups/apps" class="">Apps</a></li>
<li><a href="/backups/restore" class="">Restore</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Sharing<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Network sharing</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>System monitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Settings</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>System</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Notifications</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Security and access</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.261.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Language" title="Language"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="b0f2927caee41c5a97de77202486f9d0a2d3bb97124581393b77751acb8f4307"><input type="hidden" name="back" value="/apps/tandoor"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="b0f2927caee41c5a97de77202486f9d0a2d3bb97124581393b77751acb8f4307"><input type="hidden" name="back" value="/apps/tandoor"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item current" aria-current="true">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Sign out ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="alerts-container">
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">The hub connection is off — central monitoring is not running</span>
<a href="/monitoring" class="alert-link">System monitor →</a>
</div>
</div>
<div class="page-header">
<div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">← Apps</a>
<h2>Tandoor Recipes</h2>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<span class="stack-state-badge state-warn">Not healthy</span>
<span class="tag tag-warn" title="A newer version of this app is available. Select the Update button to start it.">Update available — today</span>
<a href="https://recipes.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Open ↗</a>
<a href="/stacks/tandoor/logs" class="btn btn-sm btn-outline">Log</a>
<a href="/stacks/tandoor/export" class="btn btn-sm btn-outline">Export</a>
<a href="/stacks/tandoor/deploy" class="btn btn-sm btn-outline">Settings</a>
</div>
</div>
<div class="app-info-hero">
<img class="app-info-logo" src="/static/assets/tandoor-logo.svg"
alt="Tandoor Recipes" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/tandoor-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-info-hero-text">
<p class="app-info-tagline">A recipe manager and meal planner for the household</p>
<div class="stack-meta-badges">
<span class="meta-badge">~512M RAM</span>
<span class="meta-badge">home</span>
<span class="meta-badge meta-badge-ok">Runs on Pi</span>
</div>
</div>
</div>
<div class="app-screenshots" id="screenshots">
<img src="/static/assets/tandoor-screenshot-1.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/tandoor-screenshot-2.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/tandoor-screenshot-3.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
</div>
<div class="app-info-card" style="margin-top:1rem">
<h3>Move to another storage</h3>
<p class="form-hint">You can move this app’s data to another connected storage. The app stops briefly during the move, and the data is deleted from the old place only after the check and a successful restart.</p>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
<select id="app-migrate-target" class="btn btn-sm btn-outline">
<option value="">Choose a target storage…</option>
<option value="/mnt/felhom-drives/scratch_hdd">SCRATCH HDD (/mnt/felhom-drives/scratch_hdd)</option>
</select>
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'tandoor','Tandoor Recipes')">Move</button>
</div>
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
</div>
<script>
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
function appMigRender(job){
var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'};
var s=names[job.phase]||job.phase;
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
return s+'…';
}
function appMigWatch(){
var panel=document.getElementById('app-migrate-progress');
if(panel) panel.style.display='block';
function tick(){
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
var job=d.data&&d.data.job;
if(!job){ if(panel) panel.textContent='No move in progress.'; return; }
if(panel) panel.innerHTML=appMigRender(job);
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Done</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Stopped: '+(job.error||'')+'</strong><br>The old data is untouched.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
}
tick();
}
function appMigrate(btn,app,label){
var sel=document.getElementById('app-migrate-target');
var target=sel?sel.value:'';
if(!target){ showAlert('Choose a target storage.'); return; }
felhomConfirm(btn,'Move the '+label+' data to: '+target+'? The app stops briefly. The old data is deleted only after a successful move.',function(){
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } })
.catch(function(e){ showAlert('Error: '+e); });
});
}
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
</script>
<div class="app-info-grid">
<div class="app-info-card">
<h3>What is it for?</h3>
<ul class="app-info-list">
<li>Keep all your recipes in one place</li><li>Import a recipe from a web page with one click</li><li>Plan the week&#39;s meals and get a shopping list out of it</li><li>Several people - the household can plan together</li><li>Share a recipe by link with family and friends</li>
</ul>
</div>
<div class="app-info-card">
<h3>First steps</h3>
<ol class="app-info-list">
<li>Open recipes.DOMAIN in your browser</li><li>Create the admin account</li><li>Import your first recipe from a web page (Bookmarklet)</li><li>Try the meal planner</li><li>Invite the household</li>
</ol>
</div>
<div class="app-info-card">
<h3>Documentation</h3>
<p><a href="https://docs.tandoor.dev/" target="_blank" class="app-info-link">Official documentation ↗</a></p>
</div>
</div>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('This app is already installed.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Refreshing...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Network error: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'In progress...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Network error: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Loading...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Your data on the hard drive:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Delete your data from the hard drive</label>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Delete app: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">This removes the containers, the volumes and the configuration files.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Cancel</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Delete</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Close</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Deleting...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted data: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Kept data: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Deleted!</h3>' +
'<p style="color:var(--text-2)">The app (' + name + ') was deleted.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
} else {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.disabled = false;
btn.textContent = 'Delete';
}
} catch (err) {
showAlert('Network error: ' + err.message);
btn.disabled = false;
btn.textContent = 'Delete';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Loading...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
sections += '<div class="modal-section">' +
'<strong>Always deleted:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker volumes (database, app configuration)</li>' +
'<li>Install configuration (app.yaml)</li>' +
'<li>Secondary backup schedule</li>' +
'</ul></div>';
var hddCheckbox = '';
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Your data on the hard drive:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Delete your data</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'If you reinstall the app, you need to import the data again, because the database is deleted. Data you keep is NOT backed up automatically any more.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Backup data:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Delete backup data</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' +
'</div></div>';
}
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Remove app: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">The app goes back to "Not installed". The template stays, and you can install it again.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Cancel</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Remove</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Close</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Removing...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted data: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted backups: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Kept data: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Backups not deleted: ' + data.data.backup_paths_refused.join('; ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Removed!</h3>' +
'<p style="color:var(--text-2)">The app (' + name + ') was removed. You can install it again with the Install button.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
} else {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.disabled = false;
btn.textContent = 'Remove';
}
} catch (err) {
showAlert('Network error: ' + err.message);
btn.disabled = false;
btn.textContent = 'Remove';
}
}
</script>
</body>
</html>
@@ -0,0 +1,654 @@
<!DOCTYPE html>
<html lang="hu" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tandoor Recipes — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.261.0">
<link rel="stylesheet" href="/static/style.css?v=0.261.0">
<meta name="csrf-token" content="b0f2927caee41c5a97de77202486f9d0a2d3bb97124581393b77751acb8f4307">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menü">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Indítópult</a></li>
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Meghajtók</a></li>
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Áttekintés</a></li>
<li><a href="/backups/remote" class="">Távoli mentés</a></li>
<li><a href="/backups/apps" class="">Alkalmazások</a></li>
<li><a href="/backups/restore" class="">Visszaállítás</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Megosztás<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Hálózati megosztás</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Beállítások</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.261.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="b0f2927caee41c5a97de77202486f9d0a2d3bb97124581393b77751acb8f4307"><input type="hidden" name="back" value="/apps/tandoor"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item current" aria-current="true">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="b0f2927caee41c5a97de77202486f9d0a2d3bb97124581393b77751acb8f4307"><input type="hidden" name="back" value="/apps/tandoor"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="alerts-container">
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">Hub kapcsolat kikapcsolva — a központi monitoring nem aktív</span>
<a href="/monitoring" class="alert-link">Rendszermonitor →</a>
</div>
</div>
<div class="page-header">
<div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
<h2>Tandoor Recipes</h2>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<span class="stack-state-badge state-warn">Nem egészséges</span>
<span class="tag tag-warn" title="Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.">Frissítés elérhető — ma</span>
<a href="https://recipes.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
<a href="/stacks/tandoor/logs" class="btn btn-sm btn-outline">Napló</a>
<a href="/stacks/tandoor/export" class="btn btn-sm btn-outline">Exportálás</a>
<a href="/stacks/tandoor/deploy" class="btn btn-sm btn-outline">Beállítások</a>
</div>
</div>
<div class="app-info-hero">
<img class="app-info-logo" src="/static/assets/tandoor-logo.svg"
alt="Tandoor Recipes" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/tandoor-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-info-hero-text">
<p class="app-info-tagline">Receptkezelő és étkezés tervező a családnak</p>
<div class="stack-meta-badges">
<span class="meta-badge">~512M RAM</span>
<span class="meta-badge">home</span>
<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>
</div>
</div>
</div>
<div class="app-screenshots" id="screenshots">
<img src="/static/assets/tandoor-screenshot-1.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/tandoor-screenshot-2.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/tandoor-screenshot-3.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
</div>
<div class="app-info-card" style="margin-top:1rem">
<h3>Áthelyezés másik tárhelyre</h3>
<p class="form-hint">Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át. Az alkalmazás az áthelyezés alatt rövid időre leáll, az adatok pedig csak az ellenőrzés és a sikeres újraindítás után törlődnek a régi helyről.</p>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
<select id="app-migrate-target" class="btn btn-sm btn-outline">
<option value="">Válassz céltárhelyet…</option>
<option value="/mnt/felhom-drives/scratch_hdd">SCRATCH HDD (/mnt/felhom-drives/scratch_hdd)</option>
</select>
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'tandoor','Tandoor Recipes')">Áthelyezés</button>
</div>
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
</div>
<script>
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
function appMigRender(job){
var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'};
var s=names[job.phase]||job.phase;
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
return s+'…';
}
function appMigWatch(){
var panel=document.getElementById('app-migrate-progress');
if(panel) panel.style.display='block';
function tick(){
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
var job=d.data&&d.data.job;
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
if(panel) panel.innerHTML=appMigRender(job);
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
}
tick();
}
function appMigrate(btn,app,label){
var sel=document.getElementById('app-migrate-target');
var target=sel?sel.value:'';
if(!target){ showAlert('Válassz céltárhelyet.'); return; }
felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.',function(){
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } })
.catch(function(e){ showAlert('Hiba: '+e); });
});
}
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
</script>
<div class="app-info-grid">
<div class="app-info-card">
<h3>Mire használható?</h3>
<ul class="app-info-list">
<li>Receptek gyűjtése és rendszerezése egy helyen</li><li>Receptek importálása weboldalakról egy kattintással</li><li>Heti étkezés tervezés és bevásárlólista generálás</li><li>Több felhasználó - a család együtt tervezhet</li><li>Receptek megosztása linkkel családtagokkal, barátokkal</li>
</ul>
</div>
<div class="app-info-card">
<h3>Első lépések</h3>
<ol class="app-info-list">
<li>Nyisd meg a recipes.DOMAIN címet a böngészőben</li><li>Hozd létre az admin fiókot</li><li>Importáld az első receptet egy weboldalról (Bookmarklet)</li><li>Próbáld ki az étkezés tervezőt</li><li>Hívd meg a családtagokat</li>
</ol>
</div>
<div class="app-info-card">
<h3>Dokumentáció</h3>
<p><a href="https://docs.tandoor.dev/" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
</div>
</div>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('Ez az alkalmazás már telepítve van.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Frissítés...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Hálózati hiba: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Folyamatban...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Törlés folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen törölve!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Törlés';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Törlés';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
sections += '<div class="modal-section">' +
'<strong>Mindig törlődik:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
'<li>Telepítési konfiguráció (app.yaml)</li>' +
'<li>Másodlagos mentés ütemezése</li>' +
'</ul></div>';
var hddCheckbox = '';
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Mentési adatok:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
'</div></div>';
}
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Eltávolítás folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Nem törölt mentések: ' + data.data.backup_paths_refused.join('; ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen eltávolítva!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
}
</script>
</body>
</html>
@@ -1,6 +1,25 @@
20:54:08 ==== tandoor: ghcr.io/tandoorrecipes/recipes:2.6.13 -> ghcr.io/tandoorrecipes/recipes:2.6.15 (sub=recipes, class=db-postgres)
20:54:08 [1] tandoor already deployed — reusing
20:54:11 [2] seeding through the app's own front door
20:54:13 tandoor: createsuperuser :: Traceback (most recent call last): File "/opt/recipes/manage.py", line 8, in <module> from django.core.management import execute_from_command_line ModuleNotFoun
20:54:15 tandoor: the account did not appear in the app's own ORM :: aceback (most recent call last): File "/opt/recipes/manage.py", line 10, in <module> raise ImportError( ...<3 lines>... ) from exc ImportError: Couldn't import Django. Are you sure it's installed and
20:54:15 [9] verdict inconclusive -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/tandoor/verdict.json
21:15:25 ==== tandoor: ghcr.io/tandoorrecipes/recipes:2.6.13 -> ghcr.io/tandoorrecipes/recipes:2.6.15 (sub=recipes, class=db-postgres)
21:15:25 [1] tandoor already deployed — reusing
21:15:27 [2] seeding through the app's own front door
21:15:37 tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
21:15:40 tandoor: seeded superuser drill316903
21:15:40 [3] control C1 — reading the seed back BEFORE the update
21:15:46 tandoor: readback of the seeded account found=True
21:15:46 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:16:42 [4] backup idle; last=None
21:16:43 [5] drill commit c7477d1fb921: tandoor ghcr.io/tandoorrecipes/recipes:2.6.13 -> ghcr.io/tandoorrecipes/recipes:2.6.15 (push rc=0)
21:16:47 [5] badge HU: [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}]
21:16:47 [5] badge EN: [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]
21:16:47 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
21:16:47 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
21:16:48 + 1.0s phase=pulling label=Új verzió letöltése… err=None hold=None
21:17:42 + 54.3s phase=starting label=Indítás az új verzióval… err=None hold=None
21:17:46 + 58.4s phase=verifying label=Működés ellenőrzése… err=None hold=None
21:22:49 + 361.9s phase=failed label=A frissítés nem sikerült err=A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza. hold=A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.
21:22:49 [7] reading the seed back AFTER the update
21:22:52 tandoor: READBACK UNUSABLE — a username that cannot exist did not read as absent (None) :: Error response from daemon: No such container: tandoor
21:22:54 [8] pinned = {'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:16-alpine'}
21:22:54 [8] installed = {'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.13', 'tandoor-postgres': 'postgres:16-alpine'}
21:22:54 [8] compose = ['image: ghcr.io/tandoorrecipes/recipes:2.6.15', 'image: postgres:16-alpine']
21:22:54 [8] inspect = []
21:22:54 [9] verdict failed -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/tandoor/verdict.json
@@ -0,0 +1,43 @@
{
"before": {
"pinned_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"installed_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"catalog_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"live_compose_image_lines": [
"image: ghcr.io/tandoorrecipes/recipes:2.6.13",
"image: postgres:16-alpine"
],
"docker_inspect": [
"tandoor ghcr.io/tandoorrecipes/recipes:2.6.13 running=true restarts=0",
"tandoor-postgres postgres:16-alpine running=true restarts=0"
]
},
"after": {
"pinned_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"installed_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"catalog_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"live_compose_image_lines": [
"image: ghcr.io/tandoorrecipes/recipes:2.6.15",
"image: postgres:16-alpine"
],
"docker_inspect": []
}
}
@@ -0,0 +1,51 @@
{
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.0,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 54.3,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 58.4,
"phase": "verifying",
"label": "Működés ellenőrzése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 361.9,
"phase": "failed",
"label": "A frissítés nem sikerült",
"updating": false,
"error": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza."
}
],
"duration_s": 361.9,
"final_phase": "failed",
"update_error": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold_reason": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"state": "stopped"
}
@@ -7,19 +7,43 @@
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"to": {},
"verdict": "inconclusive",
"seed_read_before": false,
"to": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"verdict": "failed",
"seed_read_before": true,
"seed_read_after": false,
"healthy_after": false,
"migration_observed": null,
"abort": "not-attempted",
"abort_detail": null,
"duration_s": 6.6,
"measured_at": "2026-09-21T18:54:08.826237+00:00",
"duration_s": 361.9,
"measured_at": "2026-09-21T19:15:25.308811+00:00",
"evidence": "apps/tandoor/",
"notes": [
"seed refused through the app's own route — see log.txt for what was tried",
"seed route did not work tonight"
]
"the edge ended HELD or failed — this is a RESULT, not an error of the run"
],
"observables_after": {
"pinned_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"installed_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
"catalog_images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
},
"live_compose_image_lines": [
"image: ghcr.io/tandoorrecipes/recipes:2.6.15",
"image: postgres:16-alpine"
],
"docker_inspect": []
},
"final_phase": "failed",
"hold_reason": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"update_error": "A(z) tandoor frissítése 2026-09-21 21:22-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:15 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza."
}
@@ -18,7 +18,6 @@
"measured_at": "2026-09-21T18:37:30.611425+00:00",
"evidence": "apps/vaultwarden/",
"notes": [
"seed refused through the app's own route — see log.txt for what was tried",
"seed route did not work tonight"
"INCONCLUSIVE BY DESIGN, not by a gap in the harness: the catalog CLOSES self-registration on purpose (`SIGNUPS_ALLOWED=false`, R-512 — *a stranger who guesses vault.<domain> must not be able to register*), so `/api/accounts/register` answers 404 and there is NO account-creating route without the admin secret. Vaultwarden also ships no CLI. Tried: `POST /api/accounts/register` with a valid KDF envelope. This app cannot be seeded headlessly while that setting stands, and the setting is right."
]
}
@@ -0,0 +1,17 @@
vikunja | time=2026-09-21T21:26:24.321+02:00 level=INFO msg="No config file found, using default or config from environment variables."
vikunja | time=2026-09-21T21:26:24.322+02:00 level=WARN msg="config: service.jwtsecret is deprecated and will be removed in a future release. Please use service.secret instead."
vikunja | time=2026-09-21T21:26:24.322+02:00 level=INFO msg="Running migrations…"
vikunja | time=2026-09-21T21:26:24.322+02:00 level=INFO msg="Using SQLite database at: /db/vikunja.db"
vikunja | time=2026-09-21T21:26:24.356+02:00 level=INFO msg="Ran all migrations successfully."
vikunja | time=2026-09-21T21:26:24.356+02:00 level=WARN msg="No license key configured. Pro features have been disabled."
vikunja | time=2026-09-21T21:26:24.361+02:00 level=INFO msg="Mailer is disabled, not sending reminders per mail"
vikunja | time=2026-09-21T21:26:24.361+02:00 level=INFO msg="Mailer is disabled, not sending overdue reminders per mail"
vikunja | time=2026-09-21T21:26:24.361+02:00 level=INFO msg="Vikunja version v2.6.0"
vikunja | time=2026-09-21T21:26:24.361+02:00 level=INFO msg="CORS enabled with origins: http://127.0.0.1:*, http://localhost:*, https://tasks.enkisfelhom.hu"
vikunja | time=2026-09-21T21:26:24.389+02:00 level=INFO msg="HTTP server listening on :3456"
vikunja | time=2026-09-21T21:26:43.306+02:00 level=INFO component=http remote_ip=172.18.0.7 method=GET uri=/api/v1/info status=200 latency=506.135µs user_agent=Go-http-client/1.1
vikunja | time=2026-09-21T21:26:44.453+02:00 level=INFO component=http remote_ip=172.18.0.7 method=GET uri=/api/v1/info status=200 latency=241.036µs user_agent=Go-http-client/1.1
vikunja | time=2026-09-21T21:26:44.840+02:00 level=INFO component=http remote_ip=172.18.0.8 method=GET uri=/api/v1/info status=200 latency=231.067µs user_agent=curl/8.14.1
vikunja | time=2026-09-21T21:26:44.998+02:00 level=ERROR component=http remote_ip=172.18.0.8 method=POST uri=/api/v1/login status=403 latency=134.42926ms user_agent=curl/8.14.1 err="Wrong username or password"
vikunja | time=2026-09-21T21:26:45.160+02:00 level=INFO component=http remote_ip=172.18.0.8 method=POST uri=/api/v1/login status=200 latency=138.351197ms user_agent=curl/8.14.1
vikunja | time=2026-09-21T21:26:45.188+02:00 level=INFO component=http remote_ip=172.18.0.8 method=GET uri=/api/v1/projects/3 status=200 latency=3.350797ms user_agent=curl/8.14.1
@@ -0,0 +1,31 @@
{
"before": {
"hu": [
{
"title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.",
"text": "Naprakész"
}
],
"en": [
{
"title": "This app is running the newest version available.",
"text": "Up to date"
}
]
},
"after": {
"hu": [
{
"title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.",
"text": "Frissítés elérhető — ma"
}
],
"en": [
{
"title": "A newer version of this app is available. Select the Update button to start it.",
"text": "Update available — today"
}
]
},
"drill_commit": "98eda8839128"
}
@@ -1,9 +1,25 @@
21:05:20 ==== vikunja: vikunja/vikunja:2.3.0 -> vikunja/vikunja:2.6.0 (sub=tasks, class=other)
21:05:21 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:05:26 [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.3.0'}
21:05:28 [2] seeding through the app's own front door
21:05:28 vikunja: register http=200
21:05:28 vikunja: create project http=405
21:05:28 vikunja: project refused {"message":"Method Not Allowed"}
21:05:28 [9] verdict inconclusive -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/vikunja/verdict.json
21:25:26 ==== vikunja: vikunja/vikunja:2.3.0 -> vikunja/vikunja:2.6.0 (sub=tasks, class=other)
21:25:26 [1] vikunja already deployed — reusing
21:25:28 [2] seeding through the app's own front door
21:25:28 vikunja: register http=200
21:25:28 vikunja: create project http=201
21:25:28 [3] control C1 — reading the seed back BEFORE the update
21:25:29 vikunja: readback of the seeded project http=200 ok=True
21:25:29 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:26:14 [4] backup idle; last=None
21:26:15 [5] drill commit 98eda8839128: vikunja vikunja/vikunja:2.3.0 -> vikunja/vikunja:2.6.0 (push rc=0)
21:26:19 [5] badge HU: [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}]
21:26:19 [5] badge EN: [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]
21:26:20 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
21:26:20 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
21:26:21 + 1.0s phase=pulling label=Új verzió letöltése… err=None hold=None
21:26:24 + 4.1s phase=starting label=Indítás az új verzióval… err=None hold=None
21:26:25 + 5.2s phase=verifying label=Működés ellenőrzése… err=None hold=None
21:26:44 + 24.6s phase=done label=Frissítve err=None hold=None
21:26:44 [7] reading the seed back AFTER the update
21:26:45 vikunja: readback of the seeded project http=200 ok=True
21:26:47 [8] pinned = {'vikunja': 'vikunja/vikunja:2.6.0'}
21:26:47 [8] installed = {'vikunja': 'vikunja/vikunja:2.6.0'}
21:26:47 [8] compose = ['image: vikunja/vikunja:2.6.0']
21:26:47 [8] inspect = ['vikunja vikunja/vikunja:2.6.0 running=true restarts=0']
21:26:47 [9] verdict proven -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/update-night-2026-09-21/apps/vikunja/verdict.json
@@ -5,7 +5,9 @@
"installed_images": {
"vikunja": "vikunja/vikunja:2.3.0"
},
"catalog_images": null,
"catalog_images": {
"vikunja": "vikunja/vikunja:2.3.0"
},
"live_compose_image_lines": [
"image: vikunja/vikunja:2.3.0"
],
@@ -0,0 +1,36 @@
{
"before": {
"pinned_images": {
"vikunja": "vikunja/vikunja:2.3.0"
},
"installed_images": {
"vikunja": "vikunja/vikunja:2.3.0"
},
"catalog_images": {
"vikunja": "vikunja/vikunja:2.3.0"
},
"live_compose_image_lines": [
"image: vikunja/vikunja:2.3.0"
],
"docker_inspect": [
"vikunja vikunja/vikunja:2.3.0 running=true restarts=0"
]
},
"after": {
"pinned_images": {
"vikunja": "vikunja/vikunja:2.6.0"
},
"installed_images": {
"vikunja": "vikunja/vikunja:2.6.0"
},
"catalog_images": {
"vikunja": "vikunja/vikunja:2.6.0"
},
"live_compose_image_lines": [
"image: vikunja/vikunja:2.6.0"
],
"docker_inspect": [
"vikunja vikunja/vikunja:2.6.0 running=true restarts=0"
]
}
}
@@ -0,0 +1,51 @@
{
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.0,
"phase": "pulling",
"label": "Új verzió letöltése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 4.1,
"phase": "starting",
"label": "Indítás az új verzióval…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 5.2,
"phase": "verifying",
"label": "Működés ellenőrzése…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 24.6,
"phase": "done",
"label": "Frissítve",
"updating": false,
"error": null,
"hold": null
}
],
"duration_s": 24.6,
"final_phase": "done",
"update_error": null,
"hold_reason": null,
"state": "running"
}
@@ -6,19 +6,38 @@
"from": {
"vikunja": "vikunja/vikunja:2.3.0"
},
"to": {},
"verdict": "inconclusive",
"seed_read_before": false,
"seed_read_after": false,
"healthy_after": false,
"migration_observed": null,
"to": {
"vikunja": "vikunja/vikunja:2.6.0"
},
"verdict": "proven",
"seed_read_before": true,
"seed_read_after": true,
"healthy_after": true,
"migration_observed": "vikunja | time=2026-09-21T21:26:24.322+02:00 level=INFO msg=\"Running migrations…\"",
"abort": "not-attempted",
"abort_detail": null,
"duration_s": 7.9,
"measured_at": "2026-09-21T19:05:21.114505+00:00",
"duration_s": 24.6,
"measured_at": "2026-09-21T19:25:26.344387+00:00",
"evidence": "apps/vikunja/",
"notes": [
"seed refused through the app's own route — see log.txt for what was tried",
"seed route did not work tonight"
]
"notes": [],
"observables_after": {
"pinned_images": {
"vikunja": "vikunja/vikunja:2.6.0"
},
"installed_images": {
"vikunja": "vikunja/vikunja:2.6.0"
},
"catalog_images": {
"vikunja": "vikunja/vikunja:2.6.0"
},
"live_compose_image_lines": [
"image: vikunja/vikunja:2.6.0"
],
"docker_inspect": [
"vikunja vikunja/vikunja:2.6.0 running=true restarts=0"
]
},
"final_phase": "done",
"hold_reason": null,
"update_error": null
}
@@ -19,7 +19,6 @@
"measured_at": "2026-09-21T18:54:15.502005+00:00",
"evidence": "apps/zipline/",
"notes": [
"seed refused through the app's own route — see log.txt for what was tried",
"seed route did not work tonight"
"INCONCLUSIVE BY DESIGN: the deploy answers `E1037: User registration is disabled`, so no first account can be created from outside. Tried: `POST /api/auth/register` and `POST /api/auth/setup`. SEPARATELY, zipline is one of R-618's two confirmed victims — its `.felhom.yml` probe expects 200 on `/api/health`, which the app answers 404 — so even with a seed its update would have been HELD by a wrong probe rather than by anything about the edge."
]
}
@@ -0,0 +1,66 @@
[
[
"adventurelog",
{
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.12.1",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
},
{
"adventurelog": "ghcr.io/seanmorley15/adventurelog-backend:v0.13.0",
"adventurelog-frontend": "ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0",
"adventurelog-postgres": "postgis/postgis:16-3.5-alpine"
}
],
[
"docmost",
{
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:16-alpine",
"docmost-redis": "redis:7-alpine"
},
{
"docmost": "docmost/docmost:0.96.0",
"docmost-postgres": "postgres:17-alpine",
"docmost-redis": "redis:7-alpine"
}
],
[
"glance",
{
"glance": "glanceapp/glance:v0.8.6"
},
{
"glance": "glanceapp/glance:v0.8.5"
}
],
[
"tandoor",
{
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.13",
"tandoor-postgres": "postgres:16-alpine"
},
{
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15",
"tandoor-postgres": "postgres:16-alpine"
}
],
[
"uptime-kuma",
{
"uptime-kuma": "louislam/uptime-kuma:2.5.1"
},
{
"uptime-kuma": "louislam/uptime-kuma:2.5.5"
}
],
[
"wishlist",
{
"wishlist": "ghcr.io/cmintey/wishlist:v0.67.0"
},
{
"wishlist": "ghcr.io/cmintey/wishlist:v0.66.0"
}
]
]
@@ -0,0 +1,7 @@
[
"adventurelog",
"docmost",
"tandoor",
"uptime-kuma",
"wishlist"
]
@@ -0,0 +1,16 @@
image: localhost:5000/drill/glance:1.0.0
{
"pinned_images": {
"glance": "localhost:5000/drill/glance:1.0.0"
},
"installed_images": {
"glance": "localhost:5000/drill/glance:1.0.0"
},
"catalog_images": null,
"live_compose_image_lines": [
"image: localhost:5000/drill/glance:1.0.0"
],
"docker_inspect": [
"glance localhost:5000/drill/glance:1.0.0 running=true restarts=0"
]
}
@@ -0,0 +1,17 @@
{
"drill_commit": "c644164ee439",
"badges": {
"hu": [
{
"title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.",
"text": "Frissítés elérhető — ma"
}
],
"en": [
{
"title": "A newer version of this app is available. Select the Update button to start it.",
"text": "Update available — today"
}
]
}
}
@@ -0,0 +1,9 @@
2026-09-21T19:47:49Z === pass 1/3
2026-09-21T19:47:49Z glance BEHIND and within a major — pressing Update
2026-09-21T19:47:49Z glance: phase=safety-dump updating=True
2026-09-21T19:47:51Z glance: phase=verifying updating=True
2026-09-21T19:53:02Z glance: phase=failed updating=False
2026-09-21T19:53:02Z glance ENDED held after 312.9s
2026-09-21T19:54:32Z === pass 2/3
2026-09-21T19:56:03Z === pass 3/3
2026-09-21T19:56:03Z === summary outcomes={'glance': ('held', 312.9)} never_again=['glance']
@@ -0,0 +1,23 @@
{
"state": "stopped",
"updating": false,
"update_phase": "failed",
"update_phase_label": "A frissítés nem sikerült",
"update_error": "A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"hold_reason": "A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.",
"observables": {
"pinned_images": {
"glance": "localhost:5000/drill/glance:1.0.1"
},
"installed_images": {
"glance": "localhost:5000/drill/glance:1.0.0"
},
"catalog_images": {
"glance": "localhost:5000/drill/glance:1.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/glance:1.0.1"
],
"docker_inspect": []
}
}
@@ -0,0 +1,664 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Glance — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.261.0">
<link rel="stylesheet" href="/static/style.css?v=0.261.0">
<meta name="csrf-token" content="fab572e2f6d427f4d84e1174a6a9ad4d6c7730fc751a7ab6c826734b03ecf8c2">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Yes';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Message</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menu">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Launcher</a></li>
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Dashboard</a></li>
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Apps</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Storage<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Drives</a></li>
<li><a href="/storage/network" class="">Network storage</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Backup<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Overview</a></li>
<li><a href="/backups/remote" class="">Remote backup</a></li>
<li><a href="/backups/apps" class="">Apps</a></li>
<li><a href="/backups/restore" class="">Restore</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Sharing<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Network sharing</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>System monitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Settings</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>System</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Notifications</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Security and access</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.261.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Language" title="Language"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="fab572e2f6d427f4d84e1174a6a9ad4d6c7730fc751a7ab6c826734b03ecf8c2"><input type="hidden" name="back" value="/apps/glance"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="fab572e2f6d427f4d84e1174a6a9ad4d6c7730fc751a7ab6c826734b03ecf8c2"><input type="hidden" name="back" value="/apps/glance"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item current" aria-current="true">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Sign out ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="alerts-container">
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">An installed app is not running: Glance (stopped)</span>
<a href="/monitoring" class="alert-link">System monitor →</a>
</div>
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">The hub connection is off — central monitoring is not running</span>
<a href="/monitoring" class="alert-link">System monitor →</a>
</div>
</div>
<div class="page-header">
<div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">← Apps</a>
<h2>Glance</h2>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<span class="stack-state-badge state-neutral">Stopped</span>
<span class="tag tag-warn" title="A newer version of this app is available. Select the Update button to start it.">Update available — today</span>
<a href="https://dashboard.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Open ↗</a>
<a href="/stacks/glance/logs" class="btn btn-sm btn-outline">Log</a>
<a href="/stacks/glance/export" class="btn btn-sm btn-outline">Export</a>
<a href="/stacks/glance/deploy" class="btn btn-sm btn-outline">Settings</a>
</div>
</div>
<div class="alert alert-error" style="margin-top:1rem" data-held="true">A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza. <a href="/backups/apps" class="btn btn-sm btn-outline">Backups</a></div>
<div class="app-info-hero">
<img class="app-info-logo" src="/static/assets/glance-logo.svg"
alt="Glance" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/glance-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-info-hero-text">
<p class="app-info-tagline">A minimalist dashboard - RSS, weather and bookmarks on one page</p>
<div class="stack-meta-badges">
<span class="meta-badge">~20M RAM</span>
<span class="meta-badge">dashboard</span>
<span class="meta-badge meta-badge-ok">Runs on Pi</span>
</div>
</div>
</div>
<div class="app-screenshots" id="screenshots">
<img src="/static/assets/glance-screenshot-1.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/glance-screenshot-2.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/glance-screenshot-3.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
</div>
<div class="app-info-card" style="margin-top:1rem">
<h3>Move to another storage</h3>
<p class="form-hint">You can move this app’s data to another connected storage. The app stops briefly during the move, and the data is deleted from the old place only after the check and a successful restart.</p>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
<select id="app-migrate-target" class="btn btn-sm btn-outline">
<option value="">Choose a target storage…</option>
<option value="/mnt/felhom-drives/scratch_hdd">SCRATCH HDD (/mnt/felhom-drives/scratch_hdd)</option>
</select>
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'glance','Glance')">Move</button>
</div>
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
</div>
<script>
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
function appMigRender(job){
var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'};
var s=names[job.phase]||job.phase;
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
return s+'…';
}
function appMigWatch(){
var panel=document.getElementById('app-migrate-progress');
if(panel) panel.style.display='block';
function tick(){
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
var job=d.data&&d.data.job;
if(!job){ if(panel) panel.textContent='No move in progress.'; return; }
if(panel) panel.innerHTML=appMigRender(job);
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Done</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Stopped: '+(job.error||'')+'</strong><br>The old data is untouched.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
}
tick();
}
function appMigrate(btn,app,label){
var sel=document.getElementById('app-migrate-target');
var target=sel?sel.value:'';
if(!target){ showAlert('Choose a target storage.'); return; }
felhomConfirm(btn,'Move the '+label+' data to: '+target+'? The app stops briefly. The old data is deleted only after a successful move.',function(){
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } })
.catch(function(e){ showAlert('Error: '+e); });
});
}
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
</script>
<div class="app-info-grid">
<div class="app-info-card">
<h3>What is it for?</h3>
<ul class="app-info-list">
<li>RSS news, YouTube, Reddit and more on a single page</li><li>Weather and system widgets</li><li>Keep your bookmarks and favourite sites in order</li><li>Very fast, and it asks almost nothing of the machine</li><li>A layout you set yourself, in a YAML file</li>
</ul>
</div>
<div class="app-info-card">
<h3>First steps</h3>
<ol class="app-info-list">
<li>Open dashboard.DOMAIN in your browser</li><li>Edit the glance.yml configuration file in the volume</li><li>Add your RSS feeds and widgets</li><li>Arrange the layout the way you want it</li>
</ol>
</div>
<div class="app-info-card">
<h3>Documentation</h3>
<p><a href="https://github.com/glanceapp/glance/blob/main/docs/configuration.md" target="_blank" class="app-info-link">Official documentation ↗</a></p>
</div>
</div>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('This app is already installed.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Refreshing...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Network error: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'In progress...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Network error: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Loading...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Your data on the hard drive:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Delete your data from the hard drive</label>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Delete app: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">This removes the containers, the volumes and the configuration files.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Cancel</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Delete</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Close</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Deleting...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted data: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Kept data: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Deleted!</h3>' +
'<p style="color:var(--text-2)">The app (' + name + ') was deleted.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
} else {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.disabled = false;
btn.textContent = 'Delete';
}
} catch (err) {
showAlert('Network error: ' + err.message);
btn.disabled = false;
btn.textContent = 'Delete';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Loading...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
sections += '<div class="modal-section">' +
'<strong>Always deleted:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker volumes (database, app configuration)</li>' +
'<li>Install configuration (app.yaml)</li>' +
'<li>Secondary backup schedule</li>' +
'</ul></div>';
var hddCheckbox = '';
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Your data on the hard drive:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Delete your data</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'If you reinstall the app, you need to import the data again, because the database is deleted. Data you keep is NOT backed up automatically any more.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Backup data:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Delete backup data</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' +
'</div></div>';
}
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Remove app: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">The app goes back to "Not installed". The template stays, and you can install it again.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Cancel</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Remove</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Close</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Removing...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted data: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Deleted backups: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Kept data: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Backups not deleted: ' + data.data.backup_paths_refused.join('; ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Removed!</h3>' +
'<p style="color:var(--text-2)">The app (' + name + ') was removed. You can install it again with the Install button.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
} else {
showAlert('Error: ' + (data.error || 'Unknown error'));
btn.disabled = false;
btn.textContent = 'Remove';
}
} catch (err) {
showAlert('Network error: ' + err.message);
btn.disabled = false;
btn.textContent = 'Remove';
}
}
</script>
</body>
</html>
@@ -0,0 +1,664 @@
<!DOCTYPE html>
<html lang="hu" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Glance — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.261.0">
<link rel="stylesheet" href="/static/style.css?v=0.261.0">
<meta name="csrf-token" content="fab572e2f6d427f4d84e1174a6a9ad4d6c7730fc751a7ab6c826734b03ecf8c2">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menü">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.261.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Indítópult</a></li>
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Meghajtók</a></li>
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Áttekintés</a></li>
<li><a href="/backups/remote" class="">Távoli mentés</a></li>
<li><a href="/backups/apps" class="">Alkalmazások</a></li>
<li><a href="/backups/restore" class="">Visszaállítás</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Megosztás<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Hálózati megosztás</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Beállítások</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.261.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="fab572e2f6d427f4d84e1174a6a9ad4d6c7730fc751a7ab6c826734b03ecf8c2"><input type="hidden" name="back" value="/apps/glance"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item current" aria-current="true">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="fab572e2f6d427f4d84e1174a6a9ad4d6c7730fc751a7ab6c826734b03ecf8c2"><input type="hidden" name="back" value="/apps/glance"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="alerts-container">
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">Telepített alkalmazás nem fut: Glance (stopped)</span>
<a href="/monitoring" class="alert-link">Rendszermonitor →</a>
</div>
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">Hub kapcsolat kikapcsolva — a központi monitoring nem aktív</span>
<a href="/monitoring" class="alert-link">Rendszermonitor →</a>
</div>
</div>
<div class="page-header">
<div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
<h2>Glance</h2>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<span class="stack-state-badge state-neutral">Leállítva</span>
<span class="tag tag-warn" title="Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.">Frissítés elérhető — ma</span>
<a href="https://dashboard.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
<a href="/stacks/glance/logs" class="btn btn-sm btn-outline">Napló</a>
<a href="/stacks/glance/export" class="btn btn-sm btn-outline">Exportálás</a>
<a href="/stacks/glance/deploy" class="btn btn-sm btn-outline">Beállítások</a>
</div>
</div>
<div class="alert alert-error" style="margin-top:1rem" data-held="true">A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza. <a href="/backups/apps" class="btn btn-sm btn-outline">Mentések</a></div>
<div class="app-info-hero">
<img class="app-info-logo" src="/static/assets/glance-logo.svg"
alt="Glance" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/glance-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-info-hero-text">
<p class="app-info-tagline">Minimalista dashboard - RSS, időjárás, könyvjelzők egy oldalon</p>
<div class="stack-meta-badges">
<span class="meta-badge">~20M RAM</span>
<span class="meta-badge">dashboard</span>
<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>
</div>
</div>
</div>
<div class="app-screenshots" id="screenshots">
<img src="/static/assets/glance-screenshot-1.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/glance-screenshot-2.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
<img src="/static/assets/glance-screenshot-3.webp" alt="" class="app-screenshot"
onerror="this.style.display='none'">
</div>
<div class="app-info-card" style="margin-top:1rem">
<h3>Áthelyezés másik tárhelyre</h3>
<p class="form-hint">Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át. Az alkalmazás az áthelyezés alatt rövid időre leáll, az adatok pedig csak az ellenőrzés és a sikeres újraindítás után törlődnek a régi helyről.</p>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
<select id="app-migrate-target" class="btn btn-sm btn-outline">
<option value="">Válassz céltárhelyet…</option>
<option value="/mnt/felhom-drives/scratch_hdd">SCRATCH HDD (/mnt/felhom-drives/scratch_hdd)</option>
</select>
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'glance','Glance')">Áthelyezés</button>
</div>
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
</div>
<script>
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
function appMigRender(job){
var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'};
var s=names[job.phase]||job.phase;
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
return s+'…';
}
function appMigWatch(){
var panel=document.getElementById('app-migrate-progress');
if(panel) panel.style.display='block';
function tick(){
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
var job=d.data&&d.data.job;
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
if(panel) panel.innerHTML=appMigRender(job);
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
}
tick();
}
function appMigrate(btn,app,label){
var sel=document.getElementById('app-migrate-target');
var target=sel?sel.value:'';
if(!target){ showAlert('Válassz céltárhelyet.'); return; }
felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.',function(){
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } })
.catch(function(e){ showAlert('Hiba: '+e); });
});
}
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
</script>
<div class="app-info-grid">
<div class="app-info-card">
<h3>Mire használható?</h3>
<ul class="app-info-list">
<li>RSS hírek, YouTube, Reddit és egyéb tartalmak egy oldalon</li><li>Időjárás és rendszer monitorozás widgetekkel</li><li>Könyvjelzők és kedvenc oldalak rendszerezése</li><li>Ultragyors, minimális erőforrás igény</li><li>Személyre szabható elrendezés YAML konfigurációval</li>
</ul>
</div>
<div class="app-info-card">
<h3>Első lépések</h3>
<ol class="app-info-list">
<li>Nyisd meg a dashboard.DOMAIN címet a böngészőben</li><li>Szerkeszd a glance.yml konfigurációs fájlt a kötetben</li><li>Add hozzá az RSS feedeket, widgeteket</li><li>Személyre szabd az elrendezést</li>
</ol>
</div>
<div class="app-info-card">
<h3>Dokumentáció</h3>
<p><a href="https://github.com/glanceapp/glance/blob/main/docs/configuration.md" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
</div>
</div>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('Ez az alkalmazás már telepítve van.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Frissítés...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Hálózati hiba: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Folyamatban...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Törlés folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen törölve!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Törlés';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Törlés';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
sections += '<div class="modal-section">' +
'<strong>Mindig törlődik:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
'<li>Telepítési konfiguráció (app.yaml)</li>' +
'<li>Másodlagos mentés ütemezése</li>' +
'</ul></div>';
var hddCheckbox = '';
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Mentési adatok:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
'</div></div>';
}
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Eltávolítás folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Nem törölt mentések: ' + data.data.backup_paths_refused.join('; ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen eltávolítva!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
}
</script>
</body>
</html>
@@ -0,0 +1,48 @@
21:45:02 ==== B1: the UNATTENDED HOLD
21:45:02 [0] deployed apps not level with the catalog BEFORE the leg: ['adventurelog', 'docmost', 'glance', 'tandoor', 'uptime-kuma', 'wishlist']
21:45:02 [0] removing adventurelog so the caller has only one app to react to (installed={'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.12.1', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.12.1', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'} catalog={'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.13.0', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'})
21:45:06 [X] stop -> 200 {'ok': True, 'message': 'Stack adventurelog stop completed'}
21:45:11 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'adventurelog', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárol
21:45:18 [X] after remove: deployed=False leftovers='/opt/docker/stacks/adventurelog'
21:45:18 [0] removing docmost so the caller has only one app to react to (installed={'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:16-alpine', 'docmost-redis': 'redis:7-alpine'} catalog={'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:17-alpine', 'docmost-redis': 'redis:7-alpine'})
21:45:20 [X] stop -> 200 {'ok': True, 'message': 'Stack docmost stop completed'}
21:45:25 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'docmost', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saj
21:45:33 [X] after remove: deployed=False leftovers='/opt/docker/stacks/docmost'
21:45:33 [0] removing tandoor so the caller has only one app to react to (installed={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.13', 'tandoor-postgres': 'postgres:16-alpine'} catalog={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:16-alpine'})
21:45:36 [X] stop -> 200 {'ok': True, 'message': 'Stack tandoor stop completed'}
21:45:41 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saj
21:45:48 [X] after remove: deployed=False leftovers='/opt/docker/stacks/tandoor'
21:45:48 [0] removing uptime-kuma so the caller has only one app to react to (installed={'uptime-kuma': 'louislam/uptime-kuma:2.5.1'} catalog={'uptime-kuma': 'louislam/uptime-kuma:2.5.5'})
21:45:53 [X] stop -> 200 {'ok': True, 'message': 'Stack uptime-kuma stop completed'}
21:45:58 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'uptime-kuma', 'volumes_removed': ['uptime-kuma_uptime_kuma_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_no
21:46:05 [X] after remove: deployed=False leftovers='/opt/docker/stacks/uptime-kuma'
21:46:05 [0] removing wishlist so the caller has only one app to react to (installed={'wishlist': 'ghcr.io/cmintey/wishlist:v0.67.0'} catalog={'wishlist': 'ghcr.io/cmintey/wishlist:v0.66.0'})
21:46:15 [X] stop -> 200 {'ok': True, 'message': 'Stack wishlist stop completed'}
21:46:21 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'wishlist', 'volumes_removed': ['wishlist_wishlist_data', 'wishlist_wishlist_uploads'], 'hdd_paths_removed': [], 'hdd_paths_pre
21:46:28 [X] after remove: deployed=False leftovers='/opt/docker/stacks/wishlist'
21:46:28 [0] swept before the leg: ['adventurelog', 'docmost', 'tandoor', 'uptime-kuma', 'wishlist']
21:46:28 [1] removing the existing glance so it is redeployed from the drill image store
21:46:29 [X] stop -> 200 {'ok': True, 'message': 'Stack glance stop completed'}
21:46:34 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'glance', 'volumes_removed': ['glance_glance_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alka
21:46:41 [X] after remove: deployed=False leftovers='/opt/docker/stacks/glance'
21:46:42 [5] drill commit b906b38169d8: glance glanceapp/glance:v0.8.5 -> localhost:5000/drill/glance:1.0.0 (push rc=0)
21:46:47 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:46:57 [1] deployed, controller state=running, pinned={'glance': 'localhost:5000/drill/glance:1.0.0'}
21:46:59 [1] live compose now: image: localhost:5000/drill/glance:1.0.0
21:47:03 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:47:44 [4] backup idle; last=None
21:47:44 [5] drill commit c644164ee439: glance localhost:5000/drill/glance:1.0.0 -> localhost:5000/drill/glance:1.0.1 (push rc=0)
21:47:49 [3] badge HU: [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}]
21:47:49 [3] badge EN: [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]
21:47:49 [4] running unattended-caller.py, 3 passes, 90 s apart — nobody presses anything
21:56:03 [4] caller exited rc=0 after 494.3s
21:56:03 2026-09-21T19:47:49Z glance BEHIND and within a major — pressing Update
21:56:03 2026-09-21T19:47:49Z glance: phase=safety-dump updating=True
21:56:03 2026-09-21T19:47:51Z glance: phase=verifying updating=True
21:56:03 2026-09-21T19:53:02Z glance: phase=failed updating=False
21:56:03 2026-09-21T19:53:02Z glance ENDED held after 312.9s
21:56:03 2026-09-21T19:56:03Z === summary outcomes={'glance': ('held', 312.9)} never_again=['glance']
21:56:05 [5] state=stopped phase=failed error='A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.' hold='A(z) glance frissítése 2026-09-21 21:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:47 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.'
21:56:05 [5] pinned={'glance': 'localhost:5000/drill/glance:1.0.1'}
21:56:05 [5] installed={'glance': 'localhost:5000/drill/glance:1.0.0'}
21:56:05 [5] inspect=[]
@@ -0,0 +1,12 @@
21:56:06 ==== B2: the new tag cannot be pulled
21:56:07 [5] drill commit 23cb04a29d63: bentopdf ghcr.io/alam00000/bentopdf:v2.8.6 -> localhost:5000/drill/pdf:1.0.0 (push rc=0)
21:56:11 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
21:56:26 [1] deployed, controller state=running, pinned={'bentopdf': 'ghcr.io/alam00000/bentopdf:v2.8.6'}
21:56:26 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:57:06 [4] backup idle; last=None
21:57:10 [5] drill commit 90ff4da2ba20: bentopdf localhost:5000/drill/pdf:1.0.0 -> localhost:5000/drill/pdf:1.0.1 (push rc=0)
21:57:14 badge HU [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}]
21:57:14 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
21:57:14 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
21:57:15 + 1.0s phase=failed label=A frissítés nem sikerült err=Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt. Az alkalmazás a korábbi verzióval fut tovább. hold=None
21:57:20 second press (to read the refusal on the wire): http=202 {"ok": true, "data": {"accepted": true, "completed": false}, "message": "Frissítés elindult – az állapot a kártyán követhető"}
@@ -0,0 +1,383 @@
{
"leg": "B2",
"app": "bentopdf",
"edge": "localhost:5000/drill/pdf:1.0.0 -> localhost:5000/drill/pdf:1.0.1 (tag absent from the store)",
"badges": {
"hu": [
{
"title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.",
"text": "Frissítés elérhető — ma"
}
],
"en": [
{
"title": "A newer version of this app is available. Select the Update button to start it.",
"text": "Update available — today"
}
]
},
"phases": {
"accepted": true,
"http": "202",
"phases": [
{
"t": 0.0,
"phase": "safety-dump",
"label": "Adatbázis pillanatkép…",
"updating": true,
"error": null,
"hold": null
},
{
"t": 1.0,
"phase": "failed",
"label": "A frissítés nem sikerült",
"updating": false,
"error": "Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt. Az alkalmazás a korábbi verzióval fut tovább.",
"hold": null
}
],
"duration_s": 3.1,
"final_phase": "failed",
"update_error": "Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt. Az alkalmazás a korábbi verzióval fut tovább.",
"hold_reason": null,
"state": "running"
},
"before": {
"label": "before",
"at": "2026-09-21T21:57:06",
"state": "running",
"updating": false,
"update_phase": null,
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"bentopdf": "ghcr.io/alam00000/bentopdf:v2.8.6"
},
"installed_images": {
"bentopdf": "ghcr.io/alam00000/bentopdf:v2.8.6"
},
"catalog_images": null,
"live_compose_image_lines": [
"image: ghcr.io/alam00000/bentopdf:v2.8.6"
],
"docker_inspect": [
"bentopdf ghcr.io/alam00000/bentopdf:v2.8.6 running=true restarts=0"
]
}
},
"after": {
"label": "after",
"at": "2026-09-21T21:57:17",
"state": "running",
"updating": false,
"update_phase": "failed",
"update_error": "Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt. Az alkalmazás a korábbi verzióval fut tovább.",
"hold_reason": null,
"observables": {
"pinned_images": {
"bentopdf": "ghcr.io/alam00000/bentopdf:v2.8.6"
},
"installed_images": {
"bentopdf": "ghcr.io/alam00000/bentopdf:v2.8.6"
},
"catalog_images": {
"bentopdf": "localhost:5000/drill/pdf:1.0.1"
},
"live_compose_image_lines": [
"image: ghcr.io/alam00000/bentopdf:v2.8.6"
],
"docker_inspect": [
"bentopdf ghcr.io/alam00000/bentopdf:v2.8.6 running=true restarts=0"
]
}
},
"old_version_still_serving": true,
"time_to_steady_s": 3.4,
"sentences": {
"hu": [
"BentoPDF — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: Glance (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások BentoPDF Fut Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások Adatbázis pillanatkép… Adatvédelmi fókuszú PDF eszköztár - a fájlok soha nem hagyják el a szervered ~100M RAM tools Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"PDF összefűzés, szétválasztás és forgatás PDF konvertálás képekké és képekből Jelszóvédelem és titkosítás Vízjel hozzáadása dokumentumokhoz Minden feldolgozás a szerveren - a fájlok nem kerülnek külső szolgáltatóhoz Első lépések Nyisd meg a pdf.DOMAIN címet a böngészőben Válaszd ki a kívánt műveletet Töltsd fel a PDF fájlt és kattints a feldolgozásra Dokumentáció Hivatalos dokumentáció ↗"
],
"en": [
"BentoPDF — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ An installed app is not running: Glance (stopped) System monitor → The hub connection is off — central monitoring is not running System monitor → ← Apps BentoPDF Running Update available — today Open ↗ Log Export Settings Adatbázis pillanatkép… A privacy-first PDF toolbox - your files never leave your own server ~100M RAM tools Runs on Pi Move to another storage You can move this app’s data to another connected storage.",
"Merge, split and rotate PDFs Convert PDFs to images and images to PDFs Password protection and encryption Add a watermark to a document Everything happens on your own server - no file goes to an outside service First steps Open pdf.DOMAIN in your browser Choose what you want to do Upload the PDF and start it Documentation Official documentation ↗"
]
},
"second_press": {
"http": "202",
"body": {
"ok": true,
"data": {
"accepted": true,
"completed": false
},
"message": "Frissítés elindult – az állapot a kártyán követhető"
}
},
"events": {
"path": "/api/debug/dump",
"body": {
"alerts": [
{
"ID": "deadapp-4a2adff2",
"Level": "warning",
"Message": "Telepített alkalmazás nem fut: Glance (stopped)",
"MessageKey": "alert.deadapp.single_state",
"MessageArgs": [
"Glance",
"stopped"
],
"Link": "/monitoring",
"LinkText": "Rendszermonitor",
"LinkTextKey": "alert.link.monitoring",
"PageOnly": null,
"Inline": false
},
{
"ID": "hub-disabled",
"Level": "warning",
"Message": "Hub kapcsolat kikapcsolva — a központi monitoring nem aktív",
"MessageKey": "alert.hub.disabled",
"MessageArgs": null,
"Link": "/monitoring",
"LinkText": "Rendszermonitor",
"LinkTextKey": "alert.link.monitoring",
"PageOnly": null,
"Inline": false
}
],
"backup": {
"enabled": true,
"last_db_dump": {
"success": true,
"time": "2026-09-21T19:57:02.581012164Z"
},
"running": false
},
"controller": {
"config_hash": "ce32a35aaefc739e2a23db3df1c962a77bc672aa723119939a61af9a771a5a0e",
"logging_level": "debug",
"pid": 1,
"uptime_seconds": 6457,
"version": "0.261.0"
},
"health": {
"issues": null,
"status": "ok",
"warnings": null
},
"hub": {
"enabled": false,
"url": ""
},
"network": {
"default_route": "error: docker exec ip -4 route show default: exit status 1",
"dns_servers": "error: docker exec cat /etc/resolv.conf: exit status 1",
"interfaces": "error: docker exec ip -o link show: exit status 1",
"lan_address": ""
},
"notifications": {
"cooldown_hours": 6,
"email": "",
"enabled_events": [
"backup_failed",
"db_dump_failed",
"disk_warning",
"disk_critical",
"storage_disconnected",
"node_down",
"health_critical",
"expected_backup_missed",
"expected_dbdump_missed",
"offbox_enlarge_blocked"
]
},
"scheduler": [
{
"interval": "10s",
"last_run": "2026-09-21T19:57:13.329915946Z",
"name": "status-refresh",
"running": false,
"type": "every"
},
{
"interval": "2m0s",
"last_run": "2026-09-21T19:55:43.382300008Z",
"name": "stack-scan",
"running": false,
"type": "every"
},
{
"interval": "10s",
"last_run": "2026-09-21T19:57:13.336537811Z",
"name": "health-probes",
"running": false,
"type": "every"
},
{
"interval": "5m0s",
"last_run": "2026-09-21T19:54:43.454536004Z",
"name": "system-health",
"running": false,
"type": "every"
},
{
"interval": "30s",
"last_run": "2026-09-21T19:57:13.359505204Z",
"name": "deadapp-check",
"running": false,
"type": "every"
},
{
"interval": "30s",
"last_run": "2026-09-21T19:57:13.323014613Z",
"name": "ring-spill",
"running": false,
"type": "every"
},
{
"name": "db-dump",
"running": false,
"schedule": "02:30",
"type": "daily"
},
{
"interval": "5m0s",
"last_run": "2026-09-21T19:54:43.304884532Z",
"name": "offsite-credential-retry",
"running": false,
"type": "every"
},
{
"interval": "5m0s",
"last_run": "2026-09-21T19:54:43.427874885Z",
"name": "backup-cache",
"running": false,
"type": "every"
},
{
"name": "tier2-backup",
"running": false,
"schedule": "03:30",
"type": "daily"
},
{
"name": "offbox-backup",
"running": false,
"schedule": "04:15",
"type": "daily"
},
{
"name": "offsite-abandon-sweep",
"running": false,
"schedule": "05:10",
"type": "daily"
},
{
"name": "offsite-integrity",
"running": false,
"schedule": "06:00",
"type": "daily"
},
{
"name": "offsite-proof",
"running": false,
"schedule": "05:30",
"type": "daily"
},
{
"name": "metrics-prune",
"running": false,
"schedule": "04:00",
"type": "daily"
},
{
"name": "fill-watch",
"running": false,
"schedule": "03:30",
"type": "daily"
}
],
"self_update": {
"enabled": false
},
"stacks": {
"deployed": 7,
"list": [
{
"containers": [
"bentopdf"
],
"display_name": "BentoPDF",
"name": "bentopdf",
"state": "running"
},
{
"containers": [
"gitea"
],
"display_name": "Gitea",
"name": "gitea",
"state": "running"
},
{
"containers": [],
"display_name": "Glance",
"name": "glance",
"state": "stopped"
},
{
"containers": [
"nextcloud",
"nextcloud-db",
"nextcloud-redis"
],
"display_name": "Nextcloud",
"name": "nextcloud",
"state": "running"
},
{
"containers": [
"opengist"
],
"display_name": "OpenGist",
"name": "opengist",
"state": "running"
},
{
"containers": [
"vaultwarden"
],
"display_name": "Vaultwarden",
"name": "vaultwarden",
"state": "running"
},
{
"containers": [
"zipline",
"zipline-postgres"
],
"display_name": "Zipline",
"name": "zipline",
"state": "unhealthy"
}
],
"running": 9,
"stopped": 0
},
"storage": [
{
"decommissioned": false,
"disconnected": false,
"label": "SCRATCH HDD",
"path": "/mnt/felhom-drives/scratch_hdd",
"total_gb": 937.8239631652832,
"used_gb": 48.98271942138672,
"used_percent": 5.223018534956538
}
]
}
}
}
@@ -0,0 +1,10 @@
21:57:20 ==== B3: Update pressed while a backup is running
21:57:21 „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'}
21:57:22 press 0: http=409 reason='busy' :: A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött.
21:57:25 press 1: http=409 reason='busy' :: A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött.
21:57:28 press 2: http=409 reason='busy' :: A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött.
21:57:31 press 3: http=409 reason='busy' :: A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött.
21:57:34 press 4: http=409 reason='busy' :: A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött.
21:57:37 press 5: http=409 reason='busy' :: A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött.
21:58:00 after the backup finished: http=202 reason=None ::
21:58:04 the update that got through ended: {'final_phase': 'failed', 'update_error': 'Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt. Az alkalmazás a korábbi verzióval fut tovább.', 'hold_reason': None, 'seconds': 2.0, 'observables': {'pinned_images': {'bentopdf': 'ghcr.io/alam00000/bentopdf:v2.8.6'}, 'installed_images': {'bentopdf': 'ghcr.io/alam00000/bentopdf:v2.8.6'}, 'catalog_images': {'bentopdf': 'localhost:5000/drill/pdf:1.0.1'}, 'live_compose_image_lines': ['image: ghcr.io/alam00000/bentopdf:v2.8.6'], 'docker_inspect': ['bentopdf ghcr.io/alam00000/bentopdf:v2.8.6 running=true restarts=0']}}
@@ -0,0 +1,88 @@
{
"leg": "B3",
"app": "bentopdf",
"during_backup": [
{
"i": 0,
"http": "409",
"reason": "busy",
"error": "A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött."
},
{
"i": 1,
"http": "409",
"reason": "busy",
"error": "A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött."
},
{
"i": 2,
"http": "409",
"reason": "busy",
"error": "A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött."
},
{
"i": 3,
"http": "409",
"reason": "busy",
"error": "A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött."
},
{
"i": 4,
"http": "409",
"reason": "busy",
"error": "A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött."
},
{
"i": 5,
"http": "409",
"reason": "busy",
"error": "A frissítés most nem indítható: mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött."
}
],
"after_backup": {
"http": "202",
"reason": null,
"body": {
"ok": true,
"data": {
"accepted": true,
"completed": false
},
"message": "Frissítés elindult – az állapot a kártyán követhető"
}
},
"the_update_that_got_through": {
"final_phase": "failed",
"update_error": "Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt. Az alkalmazás a korábbi verzióval fut tovább.",
"hold_reason": null,
"seconds": 2.0,
"observables": {
"pinned_images": {
"bentopdf": "ghcr.io/alam00000/bentopdf:v2.8.6"
},
"installed_images": {
"bentopdf": "ghcr.io/alam00000/bentopdf:v2.8.6"
},
"catalog_images": {
"bentopdf": "localhost:5000/drill/pdf:1.0.1"
},
"live_compose_image_lines": [
"image: ghcr.io/alam00000/bentopdf:v2.8.6"
],
"docker_inspect": [
"bentopdf ghcr.io/alam00000/bentopdf:v2.8.6 running=true restarts=0"
]
}
},
"sentences": {
"hu": [
"BentoPDF — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Telepített alkalmazás nem fut: Glance (stopped) Rendszermonitor → Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások BentoPDF Fut Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt.",
"Adatvédelmi fókuszú PDF eszköztár - a fájlok soha nem hagyják el a szervered ~100M RAM tools Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"PDF összefűzés, szétválasztás és forgatás PDF konvertálás képekké és képekből Jelszóvédelem és titkosítás Vízjel hozzáadása dokumentumokhoz Minden feldolgozás a szerveren - a fájlok nem kerülnek külső szolgáltatóhoz Első lépések Nyisd meg a pdf.DOMAIN címet a böngészőben Válaszd ki a kívánt műveletet Töltsd fel a PDF fájlt és kattints a feldolgozásra Dokumentáció Hivatalos dokumentáció ↗"
],
"en": [
"BentoPDF — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ An installed app is not running: Glance (stopped) System monitor → The hub connection is off — central monitoring is not running System monitor → ← Apps BentoPDF Running Update available — today Open ↗ Log Export Settings Az új verzió letöltése nem sikerült, ezért a frissítés elmaradt.",
"Merge, split and rotate PDFs Convert PDFs to images and images to PDFs Password protection and encryption Add a watermark to a document Everything happens on your own server - no file goes to an outside service First steps Open pdf.DOMAIN in your browser Choose what you want to do Upload the PDF and start it Documentation Official documentation ↗"
]
}
}
@@ -0,0 +1,343 @@
{
"leg": "B4-five",
"apps": [
"privatebin",
"bentopdf",
"wishlist",
"uptime-kuma",
"opengist"
],
"presses": {
"wishlist": {
"http": "202",
"at_s": 0.359,
"reason": null,
"error": null
},
"uptime-kuma": {
"http": "202",
"at_s": 0.38,
"reason": null,
"error": null
},
"privatebin": {
"http": "202",
"at_s": 0.399,
"reason": null,
"error": null
},
"opengist": {
"http": "202",
"at_s": 0.446,
"reason": null,
"error": null
},
"bentopdf": {
"http": "202",
"at_s": 0.448,
"reason": null,
"error": null
}
},
"press_spread_s": 0.448,
"max_concurrent_updates": 5,
"samples": [
{
"t": 0.1,
"state": {
"privatebin": [
true,
"safety-dump"
],
"bentopdf": [
true,
"safety-dump"
],
"wishlist": [
true,
"pulling"
],
"uptime-kuma": [
true,
"pulling"
],
"opengist": [
true,
"pinning"
]
}
},
{
"t": 5.3,
"state": {
"privatebin": [
false,
"done"
],
"bentopdf": [
false,
"done"
],
"wishlist": [
true,
"verifying"
],
"uptime-kuma": [
true,
"verifying"
],
"opengist": [
true,
"verifying"
]
}
},
{
"t": 7.5,
"state": {
"privatebin": [
false,
"done"
],
"bentopdf": [
false,
"done"
],
"wishlist": [
true,
"verifying"
],
"uptime-kuma": [
false,
"done"
],
"opengist": [
false,
"done"
]
}
},
{
"t": 9.6,
"state": {
"privatebin": [
false,
"done"
],
"bentopdf": [
false,
"done"
],
"wishlist": [
true,
"verifying"
],
"uptime-kuma": [
false,
"done"
],
"opengist": [
false,
"done"
]
}
},
{
"t": 11.7,
"state": {
"privatebin": [
false,
"done"
],
"bentopdf": [
false,
"done"
],
"wishlist": [
true,
"verifying"
],
"uptime-kuma": [
false,
"done"
],
"opengist": [
false,
"done"
]
}
},
{
"t": 15.8,
"state": {
"privatebin": [
false,
"done"
],
"bentopdf": [
false,
"done"
],
"wishlist": [
true,
"verifying"
],
"uptime-kuma": [
false,
"done"
],
"opengist": [
false,
"done"
]
}
},
{
"t": 17.9,
"state": {
"privatebin": [
false,
"done"
],
"bentopdf": [
false,
"done"
],
"wishlist": [
false,
"done"
],
"uptime-kuma": [
false,
"done"
],
"opengist": [
false,
"done"
]
}
}
],
"memory_samples": [
"Mem: 25898 970 6988 57 17996 24927\n",
"Mem: 25898 1037 6855 57 18062 24860\n"
],
"final": {
"privatebin": {
"state": "running",
"update_phase": "done",
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"installed_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"catalog_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/paste:2.0.1"
],
"docker_inspect": [
"privatebin localhost:5000/drill/paste:2.0.1 running=true restarts=0"
]
}
},
"bentopdf": {
"state": "running",
"update_phase": "done",
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"bentopdf": "localhost:5000/drill/pdf:2.0.1"
},
"installed_images": {
"bentopdf": "localhost:5000/drill/pdf:2.0.1"
},
"catalog_images": {
"bentopdf": "localhost:5000/drill/pdf:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/pdf:2.0.1"
],
"docker_inspect": [
"bentopdf localhost:5000/drill/pdf:2.0.1 running=true restarts=0"
]
}
},
"wishlist": {
"state": "running",
"update_phase": "done",
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"wishlist": "localhost:5000/drill/wishes:2.0.1"
},
"installed_images": {
"wishlist": "localhost:5000/drill/wishes:2.0.1"
},
"catalog_images": {
"wishlist": "localhost:5000/drill/wishes:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/wishes:2.0.1"
],
"docker_inspect": [
"wishlist localhost:5000/drill/wishes:2.0.1 running=true restarts=0"
]
}
},
"uptime-kuma": {
"state": "running",
"update_phase": "done",
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"uptime-kuma": "localhost:5000/drill/status:2.0.1"
},
"installed_images": {
"uptime-kuma": "localhost:5000/drill/status:2.0.1"
},
"catalog_images": {
"uptime-kuma": "localhost:5000/drill/status:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/status:2.0.1"
],
"docker_inspect": [
"uptime-kuma localhost:5000/drill/status:2.0.1 running=true restarts=0"
]
}
},
"opengist": {
"state": "running",
"update_phase": "done",
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"opengist": "localhost:5000/drill/gist:2.0.1"
},
"installed_images": {
"opengist": "localhost:5000/drill/gist:2.0.1"
},
"catalog_images": {
"opengist": "localhost:5000/drill/gist:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/gist:2.0.1"
],
"docker_inspect": [
"opengist localhost:5000/drill/gist:2.0.1 running=true restarts=0"
]
}
}
},
"elapsed_s": 28.4
}
@@ -0,0 +1,15 @@
22:06:29 ==== B4 [five]: pressing Update on ['privatebin', 'bentopdf', 'wishlist', 'uptime-kuma', 'opengist'] at once
22:06:29 press wishlist +0.359s http=202 reason=None ::
22:06:29 press uptime-kuma +0.380s http=202 reason=None ::
22:06:29 press privatebin +0.399s http=202 reason=None ::
22:06:30 press opengist +0.446s http=202 reason=None ::
22:06:30 press bentopdf +0.448s http=202 reason=None ::
22:06:30 all 5 presses issued within 0.448s
22:06:30 + 0.1s updating=5 privatebin=safety-dump bentopdf=safety-dump wishlist=pulling uptime-kuma=pulling opengist=pinning
22:06:41 + 11.7s updating=1 privatebin=done bentopdf=done wishlist=verifying uptime-kuma=done opengist=done
22:06:47 MOST UPDATES IN FLIGHT AT ONCE: 5 of 5
22:06:50 privatebin ended phase=done err=None hold=None pinned={'privatebin': 'localhost:5000/drill/paste:2.0.1'}
22:06:52 bentopdf ended phase=done err=None hold=None pinned={'bentopdf': 'localhost:5000/drill/pdf:2.0.1'}
22:06:54 wishlist ended phase=done err=None hold=None pinned={'wishlist': 'localhost:5000/drill/wishes:2.0.1'}
22:06:56 uptime-kuma ended phase=done err=None hold=None pinned={'uptime-kuma': 'localhost:5000/drill/status:2.0.1'}
22:06:58 opengist ended phase=done err=None hold=None pinned={'opengist': 'localhost:5000/drill/gist:2.0.1'}
@@ -0,0 +1,116 @@
{
"leg": "B4-two",
"apps": [
"privatebin",
"bentopdf"
],
"presses": {
"privatebin": {
"http": "202",
"at_s": 0.268,
"reason": null,
"error": null
},
"bentopdf": {
"http": "202",
"at_s": 0.268,
"reason": null,
"error": null
}
},
"press_spread_s": 0.268,
"max_concurrent_updates": 2,
"samples": [
{
"t": 0.0,
"state": {
"privatebin": [
true,
"safety-dump"
],
"bentopdf": [
true,
"safety-dump"
]
}
},
{
"t": 4.6,
"state": {
"privatebin": [
true,
"verifying"
],
"bentopdf": [
true,
"verifying"
]
}
},
{
"t": 6.6,
"state": {
"privatebin": [
false,
"done"
],
"bentopdf": [
false,
"done"
]
}
}
],
"memory_samples": [
"Mem: 25898 874 8080 56 16999 25023\n"
],
"final": {
"privatebin": {
"state": "running",
"update_phase": "done",
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"installed_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"catalog_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/paste:2.0.1"
],
"docker_inspect": [
"privatebin localhost:5000/drill/paste:2.0.1 running=true restarts=0"
]
}
},
"bentopdf": {
"state": "running",
"update_phase": "done",
"update_error": null,
"hold_reason": null,
"observables": {
"pinned_images": {
"bentopdf": "localhost:5000/drill/pdf:2.0.1"
},
"installed_images": {
"bentopdf": "localhost:5000/drill/pdf:2.0.1"
},
"catalog_images": {
"bentopdf": "localhost:5000/drill/pdf:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/pdf:2.0.1"
],
"docker_inspect": [
"bentopdf localhost:5000/drill/pdf:2.0.1 running=true restarts=0"
]
}
}
},
"elapsed_s": 10.8
}
@@ -0,0 +1,30 @@
=== the app's own recovery unit + its db dumps, with sizes and times
2026-09-21 20:05 1536 /mnt/sys_drive/felhom-data/backups/primary/privatebin/volume-dumps/privatebin_privatebin_data.tar
2026-09-21 20:06 288 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/app.yaml
2026-09-21 20:06 1050 /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json
2026-09-21 20:06 1235 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/docker-compose.yml
2026-09-21 20:06 3125 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/.felhom.yml
=== any temp/partial names left behind
=== the unit manifest, if there is one
--- /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json
{
"schema_version": 2,
"app_name": "privatebin",
"display_name": "PrivateBin",
"controller_version": "0.261.0",
"created_at": "2026-09-21T20:06:16Z",
"drive": "/mnt/sys_drive",
"namespace_root": "/mnt/sys_drive/felhom-data",
"image_pins": [
"localhost:5000/drill/paste:2.0.1"
],
"secret_env_vars": null,
"data_key_env_vars": null,
"secret_source": "portable secrets (data keys, DB passwords, internal signing secrets) are IN this unit's compose/app.yaml (0600); internet-reachable admin logins are NOT, and come from the guest's app.yaml or are regenerated on restore",
"config_files": [
"docker-compose.yml",
".felhom.yml",
"app.yaml"
],
"db_dumps": [],
=== ZERO-BYTE files under this app's backups (a half-write that still looks like a file)
@@ -0,0 +1,30 @@
=== the app's own recovery unit + its db dumps, with sizes and times
2026-09-21 20:06 288 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/app.yaml
2026-09-21 20:06 1050 /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json
2026-09-21 20:06 1235 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/docker-compose.yml
2026-09-21 20:06 3125 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/.felhom.yml
2026-09-21 20:07 1536 /mnt/sys_drive/felhom-data/backups/primary/privatebin/volume-dumps/privatebin_privatebin_data.tar
=== any temp/partial names left behind
=== the unit manifest, if there is one
--- /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json
{
"schema_version": 2,
"app_name": "privatebin",
"display_name": "PrivateBin",
"controller_version": "0.261.0",
"created_at": "2026-09-21T20:06:16Z",
"drive": "/mnt/sys_drive",
"namespace_root": "/mnt/sys_drive/felhom-data",
"image_pins": [
"localhost:5000/drill/paste:2.0.1"
],
"secret_env_vars": null,
"data_key_env_vars": null,
"secret_source": "portable secrets (data keys, DB passwords, internal signing secrets) are IN this unit's compose/app.yaml (0600); internet-reachable admin logins are NOT, and come from the guest's app.yaml or are regenerated on restore",
"config_files": [
"docker-compose.yml",
".felhom.yml",
"app.yaml"
],
"db_dumps": [],
=== ZERO-BYTE files under this app's backups (a half-write that still looks like a file)
@@ -0,0 +1,17 @@
22:07:23 ==== B5: a power cut during `backing-up` on privatebin
22:07:24 [5] drill commit 97063c022be6: privatebin localhost:5000/drill/paste:2.0.1 -> localhost:5000/drill/paste:2.0.2 (push rc=0)
22:07:28 [0] pending edge for the cut: installed={'privatebin': 'localhost:5000/drill/paste:2.0.1'} catalog={'privatebin': 'localhost:5000/drill/paste:2.0.1'}
22:07:33 [0] pinned before = {'privatebin': 'localhost:5000/drill/paste:2.0.1'}
22:07:33 [cut] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követ
22:07:33 + 0.027s phase=backing-up
22:07:33 [cut] phase 'backing-up' OBSERVED at 2026-09-21T20:07:33.345Z — pulling the plug NOW
22:07:42 [cut] `pct stop 9202` returned after 9.65s ::
22:07:46 [boot] guest 9202 starting
22:07:53 [boot] controller back: Up Less than a second (health: starting)
22:08:15 [boot] recovery lines: 2026/09/21 20:07:53 appstop_marker.go:278: [WARN] [appstop] crash recovery: an app-data backup (volume dump) (op "volume-dump:privatebin") was interrupted and left 1 app(s) stopped — restarting them: [privatebin] | 2026/09/21 20:07:53 appstop_marker.go:297: [INFO] [appstop] crash recovery: restarted privatebin after the interrupted an app-data backup (volume dump) | 2026/09/21 20:07:53 update.go:959: [WARN] [stacks] update recovery: privatebin was interrupted in backing-up (started 2026-09-21T20
22:08:19 [2] household sentence HU: ['PrivateBin — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások PrivateBin Fut Naprakész Megnyitás ↗ Napló Exportálás Beállítások A frissítés megszakadt, mert a vezérlő újraindult, mielőtt az új verzió elindult volna.', 'Titkosított szöveg megosztás - a szerver nem látja a tartalmat ~30M RAM security Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.', 'Érzékeny szövegek biztonságos megosztása E2E titkosítás - a szerver nem fér hozzá a tartalomhoz Beállítható lejárati idő (5 perc - 1 év, vagy soha) Olvasás után automatikus törlés opció Jelszóvédelem a még nagyobb biztonságért Első lépések Nyisd meg a paste.DOMAIN címet a böngészőben Írd be a szöveget és kattints a Küldés gombra Oszd meg a generált linket - a titkosítási kulcs az URL-ben van Dokumentáció Hivatalos dokumentáció ↗']
22:08:19 [2] household sentence EN: ['PrivateBin — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps PrivateBin Running Up to date Open ↗ Log Export Settings A frissítés megszakadt, mert a vezérlő újraindult, mielőtt az új verzió elindult volna.', 'Share sensitive text safely End-to-end encryption - the server cannot reach the content You choose how long it lasts (5 minutes to 1 year, or never) Optional: delete it automatically after it is read Password protection for extra safety First steps Open paste.DOMAIN in your browser Type your text and select Send Share the link you get - the encryption key is part of the URL Documentation Official documentation ↗']
22:08:19 privatebin: seeded paste id=1a72111ee336aa1f
22:08:20 privatebin: readback http=200 marker_present=True
22:08:20 [3] the app works and holds data after the cut: True
22:08:20 [4] pinned after = {'privatebin': 'localhost:5000/drill/paste:2.0.1'} (moved: False)
@@ -0,0 +1,73 @@
{
"leg": "B5-backing-up",
"app": "privatebin",
"phase_targeted": "backing-up",
"observables_before": {
"pinned_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"installed_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"catalog_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/paste:2.0.1"
],
"docker_inspect": [
"privatebin localhost:5000/drill/paste:2.0.1 running=true restarts=0"
]
},
"backup_artefacts_before": "=== the app's own recovery unit + its db dumps, with sizes and times\n2026-09-21 20:05 1536 /mnt/sys_drive/felhom-data/backups/primary/privatebin/volume-dumps/privatebin_privatebin_data.tar\n2026-09-21 20:06 288 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/app.yaml\n2026-09-21 20:06 1050 /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json\n2026-09-21 20:06 1235 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/docker-compose.yml\n2026-09-21 20:06 3125 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/.felhom.yml\n=== any temp/partial names left behind\n=== the unit manifest, if there is one\n--- /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json\n{\n \"schema_version\": 2,\n \"app_name\": \"privatebin\",\n \"display_name\": \"PrivateBin\",\n \"controller_version\": \"0.261.0\",\n \"created_at\": \"2026-09-21T20:06:16Z\",\n \"drive\": \"/mnt/sys_drive\",\n \"namespace_root\": \"/mnt/sys_drive/felhom-data\",\n \"image_pins\": [\n \"localhost:5000/drill/paste:2.0.1\"\n ],\n \"secret_env_vars\": null,\n \"data_key_env_vars\": null,\n \"secret_source\": \"portable secrets (data keys, DB passwords, internal signing secrets) are IN this unit's compose/app.yaml (0600); internet-reachable admin logins are NOT, and come from the guest's app.yaml or are regenerated on restore\",\n \"config_files\": [\n \"docker-compose.yml\",\n \".felhom.yml\",\n \"app.yaml\"\n ],\n \"db_dumps\": [],\n=== ZERO-BYTE files under this app's backups (a half-write that still looks like a file)\n",
"cut": {
"pressed": true,
"phases_seen": [
{
"t": 0.027,
"phase": "backing-up"
}
],
"cut_decided_at": "2026-09-21T20:07:33.345Z",
"pct_stop_returned_after_s": 9.65,
"instrument_limit": "the phase at the DECISION is observed; the phase at the FREEZE is inferred — pct stop is not instantaneous"
},
"after_boot": {
"recovery_lines": "2026/09/21 20:07:53 appstop_marker.go:278: [WARN] [appstop] crash recovery: an app-data backup (volume dump) (op \"volume-dump:privatebin\") was interrupted and left 1 app(s) stopped — restarting them: [privatebin]\n2026/09/21 20:07:53 appstop_marker.go:297: [INFO] [appstop] crash recovery: restarted privatebin after the interrupted an app-data backup (volume dump)\n2026/09/21 20:07:53 update.go:959: [WARN] [stacks] update recovery: privatebin was interrupted in backing-up (started 2026-09-21T20:07:33Z) — nothing had moved; dropping it\n2026/09/21 20:07:53 restore_record_wiring.go:35: [INFO] [backup] restore record wired: /opt/docker/felhom-controller/data/restore-status.json (interrupted at startup: false)\n--- journal file:\nls: cannot access '/var/lib/docker/volumes/felhom-controller-data/_data/data/update-journal.json': No such file or directory\n",
"state": "running",
"update_phase": "failed",
"update_error": "A frissítés megszakadt, mert a vezérlő újraindult, mielőtt az új verzió elindult volna. Az alkalmazás a korábbi verzióval fut tovább.",
"hold_reason": null,
"observables": {
"pinned_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"installed_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"catalog_images": {
"privatebin": "localhost:5000/drill/paste:2.0.1"
},
"live_compose_image_lines": [
"image: localhost:5000/drill/paste:2.0.1"
],
"docker_inspect": [
"privatebin localhost:5000/drill/paste:2.0.1 running=true restarts=0"
]
}
},
"backup_artefacts_after": "=== the app's own recovery unit + its db dumps, with sizes and times\n2026-09-21 20:06 288 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/app.yaml\n2026-09-21 20:06 1050 /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json\n2026-09-21 20:06 1235 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/docker-compose.yml\n2026-09-21 20:06 3125 /mnt/sys_drive/felhom-data/backups/primary/privatebin/compose/.felhom.yml\n2026-09-21 20:07 1536 /mnt/sys_drive/felhom-data/backups/primary/privatebin/volume-dumps/privatebin_privatebin_data.tar\n=== any temp/partial names left behind\n=== the unit manifest, if there is one\n--- /mnt/sys_drive/felhom-data/backups/primary/privatebin/manifest.json\n{\n \"schema_version\": 2,\n \"app_name\": \"privatebin\",\n \"display_name\": \"PrivateBin\",\n \"controller_version\": \"0.261.0\",\n \"created_at\": \"2026-09-21T20:06:16Z\",\n \"drive\": \"/mnt/sys_drive\",\n \"namespace_root\": \"/mnt/sys_drive/felhom-data\",\n \"image_pins\": [\n \"localhost:5000/drill/paste:2.0.1\"\n ],\n \"secret_env_vars\": null,\n \"data_key_env_vars\": null,\n \"secret_source\": \"portable secrets (data keys, DB passwords, internal signing secrets) are IN this unit's compose/app.yaml (0600); internet-reachable admin logins are NOT, and come from the guest's app.yaml or are regenerated on restore\",\n \"config_files\": [\n \"docker-compose.yml\",\n \".felhom.yml\",\n \"app.yaml\"\n ],\n \"db_dumps\": [],\n=== ZERO-BYTE files under this app's backups (a half-write that still looks like a file)\n",
"sentences": {
"hu": [
"PrivateBin — Felhom.eu Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások PrivateBin Fut Naprakész Megnyitás ↗ Napló Exportálás Beállítások A frissítés megszakadt, mert a vezérlő újraindult, mielőtt az új verzió elindult volna.",
"Titkosított szöveg megosztás - a szerver nem látja a tartalmat ~30M RAM security Pi kompatibilis Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.",
"Érzékeny szövegek biztonságos megosztása E2E titkosítás - a szerver nem fér hozzá a tartalomhoz Beállítható lejárati idő (5 perc - 1 év, vagy soha) Olvasás után automatikus törlés opció Jelszóvédelem a még nagyobb biztonságért Első lépések Nyisd meg a paste.DOMAIN címet a böngészőben Írd be a szöveget és kattints a Küldés gombra Oszd meg a generált linket - a titkosítási kulcs az URL-ben van Dokumentáció Hivatalos dokumentáció ↗"
],
"en": [
"PrivateBin — Felhom.eu Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps PrivateBin Running Up to date Open ↗ Log Export Settings A frissítés megszakadt, mert a vezérlő újraindult, mielőtt az új verzió elindult volna.",
"Share sensitive text safely End-to-end encryption - the server cannot reach the content You choose how long it lasts (5 minutes to 1 year, or never) Optional: delete it automatically after it is read Password protection for extra safety First steps Open paste.DOMAIN in your browser Type your text and select Send Share the link you get - the encryption key is part of the URL Documentation Official documentation ↗"
]
},
"app_usable_after": true,
"pin_moved": false
}

Some files were not shown because too many files have changed in this diff Show More