Karakeep: the second new app through the checklist — template, record, fixture, first ladder step 0.33.1 -> 0.33.2
gates / gates (push) Successful in 2s
gates / gates (push) Successful in 2s
Bookmarks/articles/notes with a crawler (karakeep-chrome) and search (meilisearch v1.41.0). AI off unless the household enters a key; setup gate + sign-up closed twice; Chrome healthcheck over bash /dev/tcp; smtp_mapping (plaintext), mail-off boot measured; web memory 768M -> 1536M on measurements (bench watch, box crawl burst). onboarding/karakeep.md complete. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,3 +1,17 @@
|
||||
## Karakeep — the second app through the new-app checklist (2026-10-01, evening)
|
||||
|
||||
- **`templates/karakeep/`** — bookmarks, articles, notes (formerly Hoarder): `ghcr.io/karakeep-app/karakeep:0.33.2` +
|
||||
`karakeep-chrome:151.0.7922.47-r1` (the crawler's browser) + `getmeili/meilisearch:v1.41.0` (as upstream's compose).
|
||||
AI tagging OFF unless the household enters an OpenAI key at install (the field says what that sends where); release
|
||||
check off; meilisearch analytics off. First admin class 4: setup gate, then sign-up closed twice (`after_setup` →
|
||||
`DISABLE_SIGNUPS`, and a case-insensitive block of `/signup` + any tRPC call naming `users.create`, batched too).
|
||||
Chrome has no wget/curl, so its healthcheck asks DevTools over bash's /dev/tcp. App-email by `smtp_mapping`
|
||||
(plaintext :2526); mail OFF boots (measured).
|
||||
- **Memory measured into the definition:** web 768M → 79 % in the bench watch; 1024M → 51 %, but real crawls on the box
|
||||
reached 80.3 %; **1536M** → a 10-page burst peaked at 54 %. mem_limit 2816M.
|
||||
- First ladder step 0.33.1 -> 0.33.2 proven on the bench (swap 0) and on 9202; remove + restore read back.
|
||||
- `onboarding/karakeep.md`, fixture `Karakeep`, FIRST-ADMIN rows, README tables, Hungarian freeze.
|
||||
|
||||
## Radicale — the first app through the new-app checklist (2026-10-01, evening)
|
||||
|
||||
- **`templates/radicale/`** — calendar and contacts (CalDAV/CardDAV), the Radicale project's own image
|
||||
|
||||
@@ -55,6 +55,7 @@ asks the probe first where there is one. Open sign-up is closed by the box after
|
||||
| homepage | 5 | static start page | – | fine | R |
|
||||
| **immich** | 4 | first visitor admin sign-up | **setup gate**, probe `GET /api/server/config` → `isInitialized` | **GATED** — catalog, 2026-09-29 (decision 46) | **M 9202**: stranger 302→gate page / 401 during and after the install; household in 0.2 s; probe opened it 0–20 s after the sign-up; phone-app API (Bearer) 200 after (`felhom.eu/documentation/audits/login-gate-2026-09-29/C/`) |
|
||||
| **jellyfin** | 4 | startup wizard | **setup gate**, opened by probe `/System/Info/Public` → `StartupWizardCompleted`; the app itself refuses a stranger's second first-admin / sign-up call | **GATED** — catalog, 2026-09-29 (decisions 46–47) | **M 9202**: stranger → gate page / 401; household reached the first-setup screen; gate opened after the setup; the app answered after (`felhom.eu/documentation/audits/gate-rollout-2026-09-29/`) |
|
||||
| **karakeep** | 4 | the first account that signs up becomes the admin (`users.create`) | **setup gate**, opened by the household's press („Kész, beállítottam"); sign-up closed twice after the setup: `after_setup` → `DISABLE_SIGNUPS=true` (it also refuses the FIRST account, so never at install) + a case-insensitive block of `/signup` and any tRPC call naming `users.create` (batched too) | **NEW 2026-10-01** — catalog, `onboarding/karakeep.md` | **M 9202** (drill catalog): stranger 401 before the press; after it users.create / batched / upper-case / `//` / `/signup` all 403; 12 wrong key exchanges → the right one at once; after remove + restore the app's own switch still refuses, the route block is gone (R-773) (`felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/`) |
|
||||
| kimai | 1 | `ADMIN_PASSWORD` → `ADMINPASS` | – | fine | R |
|
||||
| **komga** | 4 | first visitor claims | **setup gate**, opened by probe `/api/v1/claim` → `isClaimed`; the app itself refuses a stranger's second first-admin / sign-up call | **GATED** — catalog, 2026-09-29 (decisions 46–47) | **M 9202**: stranger → gate page / 401; household reached the first-setup screen; gate opened after the setup; the app answered after (`felhom.eu/documentation/audits/gate-rollout-2026-09-29/`) |
|
||||
| **mealie** | 3 | `changeme@example.com / MyPassword` | (b) its own user repository (`update_password`), password as `sys.argv[1]` | **FIXED** — catalog, 2026-09-29 | **M 9202**: fresh install — default 401, generated 200, wrong 401 (`felhom.eu/documentation/audits/login-gate-2026-09-29/D/`) |
|
||||
@@ -105,6 +106,7 @@ Evidence: `felhom.eu/documentation/audits/signup-lock-2026-09-29/`.
|
||||
| sparkyfitness | `SPARKY_FITNESS_DISABLE_SIGNUP` | yes | `/api/auth/sign-up/*` | all refused |
|
||||
| termix | `ALLOW_REGISTRATION` | yes | `/users/create` — **its router ignores case**: `/users/CREATE` reached the sign-up before the block was made case-insensitive (its own switch refused it) | all refused |
|
||||
| vikunja | `VIKUNJA_SERVICE_ENABLEREGISTRATION` | yes (its register route answers 404) | `/api/v1/register` | all refused (its `/register` PAGE is only HTML; the API is blocked) |
|
||||
| karakeep | `DISABLE_SIGNUPS` | yes | `/signup`, tRPC `users.create` (any batch, any case) | all refused (2026-10-01) |
|
||||
| opengist | none reachable (an admin setting in its database) | — | `/-/register` | all refused (block only) |
|
||||
| wishlist | none reachable (`system_config.enableSignup` in its database) | — | `/signup` | all refused (block only) |
|
||||
| wanderer | `PUBLIC_DISABLE_SIGNUP` (web only — PocketBase's API ignores it) | — | `/register` + PocketBase `POST /api/collections/(users\|_pb_users_auth_)/records`, any case — **the collection id and a case change both got in before the fix** | all refused; the batch API is off by the app |
|
||||
|
||||
@@ -305,6 +305,7 @@ block + the matching compose `${VAR}` lines.
|
||||
| Homepage | None (file) | 50M / 256M | yes | -- | home.* |
|
||||
| Immich | PostgreSQL + Redis | 2048M / 4096M | no | `${HDD_PATH}/storage/immich/` | photos.* |
|
||||
| Jellyfin | None (file) | 512M / 2048M | no | `${HDD_PATH}/media/` | media.* |
|
||||
| Karakeep | SQLite + Meilisearch + Chrome | 700M / 2816M | no | -- | bookmarks.* |
|
||||
| Kimai | MariaDB | 100M / 384M | yes | -- | time.* |
|
||||
| Komga | None (file) | 200M / 512M | yes | `${HDD_PATH}/media/comics/` | comics.* |
|
||||
| Mealie | None (SQLite) | 200M / 1000M | yes | -- | recipes.* |
|
||||
@@ -364,6 +365,7 @@ block + the matching compose `${VAR}` lines.
|
||||
| Homepage | yes | -- | -- |
|
||||
| Immich | yes | yes | DB_PASSWORD |
|
||||
| Jellyfin | yes | yes | -- |
|
||||
| Karakeep | yes | -- | NEXTAUTH_SECRET, MEILI_MASTER_KEY, OPENAI_API_KEY (optional, secret_input) |
|
||||
| Kimai | yes | -- | DB_PASSWORD, ADMIN_EMAIL, ADMIN_PASSWORD |
|
||||
| Komga | yes | yes | -- |
|
||||
| Mealie | yes | -- | -- |
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Onboarding record — karakeep
|
||||
|
||||
app: karakeep
|
||||
opened: 2026-10-01
|
||||
template_at: the commit that publishes this record (karakeep 0.33.2 + karakeep-chrome 151.0.7922.47-r1 + meilisearch v1.41.0; web 1536M)
|
||||
|
||||
<!--
|
||||
Evidence root: felhom.eu/documentation/audits/new-apps-2026-10-01/. bench/karakeep-768M/ and box/karakeep-768M/ are the
|
||||
first definition (web 768M); bench/karakeep/ and box/karakeep/ the second (1024M, the proven ladder step);
|
||||
box/karakeep-final/ the final definition (1536M) and its crawl burst. The web limit went 768M -> 1024M -> 1536M on
|
||||
measurements (5.1, 5.2, 5.4); a limit that only rises cannot make a proven step fail.
|
||||
-->
|
||||
|
||||
0.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/A/A1-github-facts.txt — AGPL-3.0; upstream's own images, pulled
|
||||
0.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/A/A1-github-facts.txt — 0.33.2 on 2026-08-11; 5 releases in 2026; pushed 2026-09-29
|
||||
0.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/FIT.md — `0.33.1`, `0.33.2`, chrome `151.0.7922.47-r1`, meilisearch `v1.41.0`; amd64 + arm64
|
||||
0.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — AI tagging OFF (no OPENAI_API_KEY; the log says „No inference client configured"); release check off (`DISABLE_NEW_RELEASE_CHECK`); meilisearch analytics off; the crawler fetches each saved page and an ad-block list (the product); the deploy field says what turning AI on sends where; the official phone app has crash reporting built in (FIT.md, R-774 — not on the page yet)
|
||||
0.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep-768M/neg-and-crawl.txt — crawling needs the internet (example.com crawled with a screenshot); a private address is refused by Karakeep itself (felhom.eu resolves to the LAN here)
|
||||
0.6 | n/a | Karakeep serves HTTP on port 3000 only; Chrome and Meilisearch stay on the internal network
|
||||
0.7 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — the phone app's sign-in route (`apiKeys.exchange`) through traefik after the setup gate opened: right 200 + a key that reads the API, wrong 401
|
||||
0.8 | done | app-catalog-felhom.eu/templates/karakeep/.felhom.yml — tagline + use_cases
|
||||
0.9 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep/evidence/MV-karakeep/verdict.json — runs on the bench; NEXTAUTH_URL builds links only
|
||||
1.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-karakeep.txt — image-pins and image-resolvable exit 0
|
||||
1.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/A/compose/karakeep.yml — SQLite inside the app, meilisearch v1.41.0 exactly as upstream's compose
|
||||
1.3 | n/a | no MariaDB or PostgreSQL service in this template
|
||||
1.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep/evidence/MV-karakeep/verdict.json ; felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/step.txt — 0.33.1 seeded, stepped INTO 0.33.2; the image's s6 `init-db-migration` service ran; read back on both venues
|
||||
1.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — s6 supervises Next.js' production server and the workers (the image's own init)
|
||||
1.6 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-karakeep-reads.txt — NEXTAUTH_SECRET and MEILI_MASTER_KEY generated; OPENAI_API_KEY empty by default; the rest of the key-like settings belong to features off here (OAuth, Prometheus)
|
||||
1.7 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-karakeep-reads.txt — the entrypoint is s6 (`/init`) with fixed services; migrations run as its own `init-db-migration` service at every start; no switch to decide
|
||||
1.8 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — an unknown page redirects (308), no debug output
|
||||
1.9 | n/a | NEXTAUTH_SECRET signs sessions only (sign in again), MEILI_MASTER_KEY guards a rebuildable index; neither encrypts stored data
|
||||
2.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/cvp-karakeep-tail.txt — volume-persistence gate CLEAN
|
||||
2.2 | done | app-catalog-felhom.eu/templates/karakeep/docker-compose.yml — two named volumes (NVMe): the data (SQLite + assets) and the search index
|
||||
2.3 | n/a | needs_hdd false: no drive path to classify; the tier-1 unit holds both volumes
|
||||
2.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — the app wrote its database and crawled assets on first start
|
||||
2.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/restore.txt — the update's per-app backup, remove keeping backups, the household's restore button, the bookmark read back with the household's own email + password
|
||||
2.6 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/restore.txt ; felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep-768M/remove.txt — both remove choices delete both volumes; no drive data exists
|
||||
2.7 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — 732 KB + 512 KB after the seed and one crawl; each saved page adds its screenshot and content (MBs over a year)
|
||||
2.8 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep-768M/neg-and-crawl.txt — the crawled page's screenshot is stored as an asset and served back through the app (assets: 2); the UI shows it (shots/karakeep/1.png)
|
||||
3.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/install.txt — class 4: the first account became admin through the gate as the household
|
||||
3.2 | n/a | no known default login: the first account is the household's own
|
||||
3.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — `setup_gate`, opened by the household's press; before it a stranger's GET and sign-up answered 401; no probe (Karakeep has no public "set up" status)
|
||||
3.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — after the press: users.create, batched, upper-case, `//` and `/signup` all 403; the app's own switch DISABLE_SIGNUPS=true. After remove + restore the switch still refuses, the route block is gone (R-773)
|
||||
3.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/poll.txt — 93 stranger polls from the press: 404 until routed, then the gate's 401; never the app
|
||||
3.6 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — 12 wrong sign-ins for the household's email: 401 each, no lock; the right one at once
|
||||
3.7 | done | app-catalog-felhom.eu/templates/karakeep/.felhom.yml — `add_people`: the admin creates the family member's account (sign-up is closed); password change is in Karakeep's own settings
|
||||
3.8 | n/a | no after_install command: the household makes its own first account
|
||||
3.9 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt ; felhom.eu/documentation/audits/new-apps-2026-10-01/shots/karakeep/1.png — the API route (phone app, browser extension) and the browser sign-in over https through traefik (headless Chrome signed in)
|
||||
4.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-karakeep-reads.txt — web: the image's own wget check (127.0.0.1); chrome: bash + raw HTTP/1.1 to DevTools (no wget/curl in that image); meilisearch: curl/wget /health
|
||||
4.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-karakeep.txt — probe-matches-compose exit 0
|
||||
4.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/install.txt — all three healthy 83 s after the press (pulls included), 0 restarts
|
||||
4.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — the app container stopped: `degraded` within 10 s, front door 404; back in 15 s. (A paused container reads running — Docker's count; the probe that found no container recorded healthy, R-772)
|
||||
4.5 | done | app-catalog-felhom.eu/templates/karakeep/docker-compose.yml — `container_name: karakeep`; sidecars `karakeep-chrome`, `karakeep-meilisearch`
|
||||
5.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep/mem-karakeep.csv — bench, swap 0, from birth: web ~503 MiB, chrome 35, meili 31; 0 kills
|
||||
5.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep-final/burst.txt — at 1536M, 10 real pages saved at once: web 832 MiB = 54 %, chrome 28 %, meili 17 %, 0 kills, 0 restarts (at 1024M a crawl reached 80.3 %: box/karakeep/remove.txt)
|
||||
5.3 | done | app-catalog-felhom.eu/templates/karakeep/.felhom.yml — mem_limit 2816M = 1536 + 768 + 512
|
||||
5.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep-768M/evidence/MV-karakeep/verdict.json ; felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep/evidence/MV-karakeep/verdict.json — two watches at two limits: 79 % of 768M, 51 % of 1024M — it does not simply fill the limit
|
||||
5.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-karakeep-reads.txt — pi_compatible false (Chrome + Meilisearch); ~2 GB of images
|
||||
6.1 | done | app-catalog-felhom.eu/scripts/upgrade_fixtures_box.py — `Karakeep`: the first account, the phone app's key exchange, a bookmark over the REST API, three negative controls
|
||||
6.2 | done | app-catalog-felhom.eu/templates/karakeep/.felhom.yml — the first ladder step 0.33.1 -> 0.33.2, written by `upgrade-test.py --write-ladder`
|
||||
6.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/undo/box/radicale/step.txt — the box's own undo, proven on a real failed step this session (Radicale); Karakeep's step ran the same guarded Update (backing-up, verifying, done)
|
||||
6.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep/evidence/MV-karakeep/verdict.json — no mark
|
||||
6.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/FIT.md — plain `x.y.z` app tags; chrome tags follow Chrome's version; `release`/`latest` are not used
|
||||
7.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep-mail-off-boot.txt — `smtp_mapping` (plaintext :2526); a fresh install with mail OFF (empty SMTP_*) boots and signs up. Mail ON is not provable on 9202 (no hub) — R-774
|
||||
8.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-karakeep.txt — copy-i18n exit 0 (frozen with --add-app)
|
||||
8.2 | done | app-catalog-felhom.eu/templates/karakeep/.felhom.yml — tagline, use_cases, first_steps, add_people, the AI sentence on the deploy field; the first steps followed on 9202 (account, „Kész" press, API key)
|
||||
8.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S3-karakeep-assets.txt — logo + three screenshots answer 200; boxes get them with the next hub release (R-766)
|
||||
8.4 | done | app-catalog-felhom.eu/README.md — both tables; FIRST-ADMIN rows; category productivity; catalog_since
|
||||
8.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S3-karakeep-assets.txt — the website's count holds
|
||||
9.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-karakeep.txt — every gate exit 0, the runtime volume gate included
|
||||
9.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/install.txt ; felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/checks.txt — fresh installs from the drill catalog, as household and stranger
|
||||
9.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/README.md — every row done or n/a; findings are rows R-772, R-773, R-774
|
||||
9.4 | done | app-catalog-felhom.eu/onboarding/karakeep.md — published in one commit with this record (the onboarding gate)
|
||||
@@ -490,6 +490,27 @@
|
||||
"deploy_fields[SUBDOMAIN].label": "Aldomain",
|
||||
"description": "Ingyenes és nyílt forráskódú média szerver"
|
||||
},
|
||||
"karakeep": {
|
||||
"app_info.add_people": "A családtagodat te hozod létre: Beállítások, Admin, Felhasználók, Új felhasználó.",
|
||||
"app_info.first_steps[0]": "Nyisd meg a bookmarks.DOMAIN címet, és hozd létre a fiókodat (az első fiók lesz az admin)",
|
||||
"app_info.first_steps[1]": "Kattints a „Kész, beállítottam\" gombra az alkalmazás oldalán - ezután más már nem regisztrálhat",
|
||||
"app_info.first_steps[2]": "Telepítsd a böngészőbővítményt vagy a telefonos alkalmazást, és add meg: https://bookmarks.DOMAIN és egy API-kulcsot (Beállítások, API-kulcsok)",
|
||||
"app_info.first_steps[3]": "Mentsd el az első könyvjelződet",
|
||||
"app_info.tagline": "Könyvjelzők, cikkek és jegyzetek mentése - kereshető saját archívum",
|
||||
"app_info.use_cases[0]": "Weboldalak mentése: a Karakeep letölti és elteszi a tartalmukat, akkor is, ha az oldal később eltűnik",
|
||||
"app_info.use_cases[1]": "Jegyzetek, képek és PDF-ek egy helyen, teljes szöveges kereséssel",
|
||||
"app_info.use_cases[2]": "Böngészőbővítmény és telefonos alkalmazás a gyors mentéshez",
|
||||
"app_info.use_cases[3]": "AI-címkézés csak akkor, ha te megadsz hozzá egy OpenAI-kulcsot - alapból ki van kapcsolva",
|
||||
"deploy_fields[DOMAIN].description": "A szerver domain neve",
|
||||
"deploy_fields[DOMAIN].label": "Domain",
|
||||
"deploy_fields[MEILI_MASTER_KEY].label": "Keresőindex-kulcs",
|
||||
"deploy_fields[NEXTAUTH_SECRET].label": "Munkamenet-kulcs",
|
||||
"deploy_fields[OPENAI_API_KEY].description": "Üresen hagyva az automatikus AI-címkézés ki van kapcsolva, és semmi nem megy ki a szerveredről. Ha megadod, minden új könyvjelző szövege az OpenAI-hoz kerül címkézésre.",
|
||||
"deploy_fields[OPENAI_API_KEY].label": "OpenAI API-kulcs (nem kötelező)",
|
||||
"deploy_fields[SUBDOMAIN].description": "Az alkalmazás aldomainje",
|
||||
"deploy_fields[SUBDOMAIN].label": "Aldomain",
|
||||
"description": "Könyvjelzők, cikkek, jegyzetek és képek egy helyen, kereshetően"
|
||||
},
|
||||
"kimai": {
|
||||
"app_info.default_creds": "(telepítéskor megadott admin email és jelszó)",
|
||||
"app_info.first_steps[0]": "Nyisd meg a time.DOMAIN címet a böngészőben",
|
||||
@@ -1184,6 +1205,7 @@
|
||||
}
|
||||
},
|
||||
"reasons": {
|
||||
"karakeep": "new app 2026-10-01 through NEW-APP-CHECKLIST.md: te-form, no kérjük; reviewed by CC against the operator rules",
|
||||
"radicale": "new app 2026-10-01 through NEW-APP-CHECKLIST.md: te-form, no kérjük; reviewed by CC against the operator rules"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1930,6 +1930,79 @@ class Radicale:
|
||||
return ok
|
||||
|
||||
|
||||
# =============================================================================================
|
||||
class Karakeep:
|
||||
"""Karakeep (2026-10-01, new app through NEW-APP-CHECKLIST.md). THE FRONT DOOR is the app's own: the first account
|
||||
is made by its sign-up call (tRPC `users.create` — the first account becomes the admin; through the setup gate on the
|
||||
box, as the household), then the route its phone app uses to sign in — `apiKeys.exchange` (email + password -> an API
|
||||
key) — and the REST API: `POST /api/v1/bookmarks` (a text bookmark with a unique title), read back with
|
||||
`GET /api/v1/bookmarks/<id>`. Negative controls on every readback: no key 401, a wrong key 401, an id never made 404.
|
||||
A text bookmark, not a URL: the crawler's success depends on the internet, the read-back must not."""
|
||||
sub = "bookmarks"
|
||||
|
||||
@staticmethod
|
||||
def _trpc(w, sub, proc, payload):
|
||||
rc, code, out = w.app_curl(sub, f"/api/trpc/{proc}?batch=1", "-H", "Content-Type: application/json",
|
||||
data=json.dumps({"0": {"json": payload}}), method="POST")
|
||||
try:
|
||||
return code, json.loads(out)[0]
|
||||
except Exception:
|
||||
return code, {"_raw": (out or "")[:200]}
|
||||
|
||||
def _key(self, w, sub, t):
|
||||
code, r = self._trpc(w, sub, "apiKeys.exchange", {"keyName": "felhom-" + secrets.token_hex(3),
|
||||
"email": t["email"], "password": t["pw"]})
|
||||
return ((r.get("result") or {}).get("data") or {}).get("json", {}).get("key") if code == "200" else None
|
||||
|
||||
def seed(self, w, sub, say):
|
||||
if not w.wait_app(sub, "/api/health", want=("200",), tries=72):
|
||||
self.tried = "/api/health never answered 200"
|
||||
return None
|
||||
email = "admin-" + secrets.token_hex(3) + "@felhom.invalid"
|
||||
pw = "Kk-" + secrets.token_hex(10)
|
||||
code, r = self._trpc(w, sub, "users.create", {"name": "Felhom", "email": email, "password": pw, "confirmPassword": pw})
|
||||
role = ((r.get("result") or {}).get("data") or {}).get("json", {}).get("role")
|
||||
say(f" karakeep: users.create (the first account) http={code} role={role}")
|
||||
if code != "200":
|
||||
self.tried = f"users.create -> {code} {str(r)[:120]}"
|
||||
return None
|
||||
t = {"email": email, "pw": pw}
|
||||
key = self._key(w, sub, t)
|
||||
if not key:
|
||||
self.tried = "apiKeys.exchange gave no key"
|
||||
return None
|
||||
title = "felhom-seed-" + secrets.token_hex(4)
|
||||
rc, code, out = w.app_curl(sub, "/api/v1/bookmarks", "-H", f"Authorization: Bearer {key}", "-H", "Content-Type: application/json",
|
||||
data=json.dumps({"type": "text", "text": "Felhom teszt jegyzet " + title, "title": title}), method="POST")
|
||||
say(f" karakeep: POST /api/v1/bookmarks http={code}")
|
||||
try:
|
||||
bid = json.loads(out)["id"]
|
||||
except Exception:
|
||||
self.tried = f"POST bookmark -> {code} {(out or '')[:120]}"
|
||||
return None
|
||||
t.update({"id": bid, "title": title, "role": role})
|
||||
return t
|
||||
|
||||
def verify(self, w, sub, t, say):
|
||||
if not w.wait_app(sub, "/api/health", want=("200",), tries=72):
|
||||
say(" karakeep: /api/health never answered")
|
||||
return False
|
||||
key = self._key(w, sub, t)
|
||||
if not key:
|
||||
say(" karakeep: the household's email + password no longer give a key")
|
||||
return False
|
||||
rc, c_none, _ = w.app_curl(sub, f"/api/v1/bookmarks/{t['id']}")
|
||||
rc, c_wrong, _ = w.app_curl(sub, f"/api/v1/bookmarks/{t['id']}", "-H", "Authorization: Bearer ak1_felhom_wrong")
|
||||
rc, c_absent, _ = w.app_curl(sub, "/api/v1/bookmarks/never" + secrets.token_hex(4), "-H", f"Authorization: Bearer {key}")
|
||||
if c_none != "401" or c_wrong != "401" or c_absent != "404":
|
||||
say(f" karakeep: READBACK UNUSABLE — no key {c_none}, wrong key {c_wrong}, absent id {c_absent}")
|
||||
return False
|
||||
rc, code, out = w.app_curl(sub, f"/api/v1/bookmarks/{t['id']}", "-H", f"Authorization: Bearer {key}")
|
||||
ok = code == "200" and t["title"] in (out or "")
|
||||
say(f" karakeep: readback http={code} found={ok} (controls: no key {c_none}, wrong {c_wrong}, absent {c_absent})")
|
||||
return ok
|
||||
|
||||
|
||||
FIXTURES = {
|
||||
"uptime-kuma": UptimeKuma(),
|
||||
"crafty-controller": Crafty(),
|
||||
@@ -1967,4 +2040,5 @@ FIXTURES = {
|
||||
"claper": Claper(),
|
||||
"calcom": Calcom(),
|
||||
"radicale": Radicale(),
|
||||
"karakeep": Karakeep(),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# =============================================================================
|
||||
# .felhom.yml - App metadata for felhom-controller
|
||||
# =============================================================================
|
||||
# Karakeep (formerly Hoarder) — ghcr.io/karakeep-app/karakeep. Onboarding record: onboarding/karakeep.md.
|
||||
# First admin: CLASS 4 — the first account that signs up becomes the admin. So: the setup gate (decision 46) keeps a
|
||||
# fresh install closed to everyone but the household until it says „Kész, beállítottam"; then the box closes sign-up
|
||||
# twice (decision 47): the app's own switch (after_setup → DISABLE_SIGNUPS, which would refuse the FIRST account too, so
|
||||
# never at install) and a block of the sign-up routes.
|
||||
|
||||
# --- Display info (shown on dashboard) ---
|
||||
display_name: "Karakeep"
|
||||
description: "Könyvjelzők, cikkek, jegyzetek és képek egy helyen, kereshetően"
|
||||
category: "productivity"
|
||||
subdomain: "bookmarks"
|
||||
slug: "karakeep"
|
||||
# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that
|
||||
# changes an image: line must set this to the same day (see CLAUDE.md).
|
||||
catalog_since: "2026-10-01"
|
||||
|
||||
# --- Resource hints (displayed on deploy screen) ---
|
||||
resources:
|
||||
mem_request: "700M"
|
||||
mem_limit: "2816M" # karakeep 1536M + karakeep-chrome 768M + karakeep-meilisearch 512M = 2816M
|
||||
pi_compatible: false
|
||||
needs_hdd: false
|
||||
|
||||
# --- Deploy wizard fields ---
|
||||
deploy_fields:
|
||||
- env_var: DOMAIN
|
||||
label: "Domain"
|
||||
type: domain
|
||||
description: "A szerver domain neve"
|
||||
locked_after_deploy: true
|
||||
|
||||
- env_var: SUBDOMAIN
|
||||
label: "Aldomain"
|
||||
type: subdomain
|
||||
default: "bookmarks"
|
||||
required: true
|
||||
locked_after_deploy: true
|
||||
description: "Az alkalmazás aldomainje"
|
||||
|
||||
- env_var: NEXTAUTH_SECRET
|
||||
label: "Munkamenet-kulcs"
|
||||
type: secret
|
||||
generate: "hex:32"
|
||||
locked_after_deploy: true
|
||||
|
||||
- env_var: MEILI_MASTER_KEY
|
||||
label: "Keresőindex-kulcs"
|
||||
type: secret
|
||||
generate: "hex:32"
|
||||
locked_after_deploy: true
|
||||
|
||||
- env_var: OPENAI_API_KEY
|
||||
label: "OpenAI API-kulcs (nem kötelező)"
|
||||
type: secret_input
|
||||
required: false
|
||||
description: "Üresen hagyva az automatikus AI-címkézés ki van kapcsolva, és semmi nem megy ki a szerveredről. Ha megadod, minden új könyvjelző szövege az OpenAI-hoz kerül címkézésre."
|
||||
|
||||
setup_gate: true
|
||||
signup_block: "PathRegexp(`(?i)^/+(signup|api/trpc/[^?]*users\\.create)`)"
|
||||
after_setup:
|
||||
env:
|
||||
SIGNUP_CLOSED: "true"
|
||||
|
||||
# --- App-email (password reset) through the box's mail relay; plaintext :2526 (see the compose comment) ---
|
||||
smtp_mapping:
|
||||
tls_mode: plaintext
|
||||
host_var: SMTP_HOST
|
||||
port_var: SMTP_PORT
|
||||
from_var: SMTP_FROM
|
||||
from_local: karakeep
|
||||
|
||||
# --- Customer-facing info ---
|
||||
app_info:
|
||||
tagline: "Könyvjelzők, cikkek és jegyzetek mentése - kereshető saját archívum"
|
||||
add_people: "A családtagodat te hozod létre: Beállítások, Admin, Felhasználók, Új felhasználó."
|
||||
docs_url: "https://docs.karakeep.app/"
|
||||
use_cases:
|
||||
- 'Weboldalak mentése: a Karakeep letölti és elteszi a tartalmukat, akkor is, ha az oldal később eltűnik'
|
||||
- 'Jegyzetek, képek és PDF-ek egy helyen, teljes szöveges kereséssel'
|
||||
- 'Böngészőbővítmény és telefonos alkalmazás a gyors mentéshez'
|
||||
- 'AI-címkézés csak akkor, ha te megadsz hozzá egy OpenAI-kulcsot - alapból ki van kapcsolva'
|
||||
first_steps:
|
||||
- 'Nyisd meg a bookmarks.DOMAIN címet, és hozd létre a fiókodat (az első fiók lesz az admin)'
|
||||
- 'Kattints a „Kész, beállítottam" gombra az alkalmazás oldalán - ezután más már nem regisztrálhat'
|
||||
- 'Telepítsd a böngészőbővítményt vagy a telefonos alkalmazást, és add meg: https://bookmarks.DOMAIN és egy API-kulcsot (Beállítások, API-kulcsok)'
|
||||
- 'Mentsd el az első könyvjelződet'
|
||||
|
||||
# --- Controller health probe (dials what the compose healthcheck dials) ---
|
||||
healthcheck:
|
||||
checks:
|
||||
- type: api
|
||||
port: 3000
|
||||
path: "/api/health"
|
||||
expect:
|
||||
status: 200
|
||||
|
||||
i18n:
|
||||
en:
|
||||
description: 'Bookmarks, articles, notes and images in one place, searchable'
|
||||
app_info:
|
||||
tagline: 'Save bookmarks, articles and notes - your own searchable archive'
|
||||
add_people: "You create your family member's account: Settings, Admin, Users, New user."
|
||||
use_cases:
|
||||
- 'Save web pages: Karakeep downloads and keeps their content, even if the page disappears later'
|
||||
- 'Notes, images and PDFs in one place, with full-text search'
|
||||
- 'A browser extension and a phone app for quick saving'
|
||||
- 'AI tagging only if you give it an OpenAI key - off by default'
|
||||
first_steps:
|
||||
- 'Open bookmarks.DOMAIN and create your account (the first account becomes the admin)'
|
||||
- 'Press "Done, I set it up" on the app page - after that nobody else can sign up'
|
||||
- 'Install the browser extension or the phone app and enter: https://bookmarks.DOMAIN and an API key (Settings, API keys)'
|
||||
- 'Save your first bookmark'
|
||||
deploy_fields:
|
||||
- env_var: DOMAIN
|
||||
label: 'Domain'
|
||||
description: 'The server domain name'
|
||||
- env_var: SUBDOMAIN
|
||||
label: 'Subdomain'
|
||||
description: 'The subdomain this app answers on'
|
||||
- env_var: NEXTAUTH_SECRET
|
||||
label: 'Session key'
|
||||
- env_var: MEILI_MASTER_KEY
|
||||
label: 'Search index key'
|
||||
- env_var: OPENAI_API_KEY
|
||||
label: 'OpenAI API key (optional)'
|
||||
description: 'Left empty, automatic AI tagging is off and nothing leaves your server. If you enter one, the text of every new bookmark goes to OpenAI for tagging.'
|
||||
|
||||
# update_ladder — the test record: one tested step per line, oldest first (JSON flow mappings,
|
||||
# `09-update-architecture.md` §6.4 part 4). WRITTEN BY scripts/upgrade-test.py, never by hand;
|
||||
# gated by scripts/check-test-record.py. An image: move without a proven entry here is refused.
|
||||
update_ladder:
|
||||
- {"from": {"karakeep": "ghcr.io/karakeep-app/karakeep:0.33.1", "karakeep-chrome": "ghcr.io/karakeep-app/karakeep-chrome:151.0.7922.47-r1", "karakeep-meilisearch": "getmeili/meilisearch:v1.41.0"}, "to": {"karakeep": "ghcr.io/karakeep-app/karakeep:0.33.2", "karakeep-chrome": "ghcr.io/karakeep-app/karakeep-chrome:151.0.7922.47-r1", "karakeep-meilisearch": "getmeili/meilisearch:v1.41.0"}, "digest": {"karakeep": "sha256:b069e4307dec06ea06d16989c6861c30a1ff208568be44ed5fb5d422cd3e950c", "karakeep-chrome": "sha256:5b19bbb160e9ff60681a3abd97e1c4ec9f64212301410de658c3900ab7ef31e7", "karakeep-meilisearch": "sha256:860fa4baed04ae1c235de870edab0c8006227546dea1bbb6411fbfc5e27cf1db"}, "verdict": "proven", "tested_at": "2026-10-01T15:36:10Z", "harness_version": 4, "evidence": "felhom.eu/documentation/audits/new-apps-2026-10-01/bench/karakeep/evidence/MV-karakeep/verdict.json", "box_evidence": "felhom.eu/documentation/audits/new-apps-2026-10-01/box/karakeep/step.txt", "memory_peak_pct": 51.4, "marks": {"files_may_change": false, "needs_person": null, "memory_tight": false}, "memory_basis": "anon", "memory_cgroup_peak_pct": 55.4}
|
||||
@@ -0,0 +1,127 @@
|
||||
# Karakeep - Könyvjelzők, cikkek és jegyzetek mentése (a korábbi Hoarder)
|
||||
# Domain: ${SUBDOMAIN}.${DOMAIN}
|
||||
# Database: SQLite (in the data volume) + Meilisearch (search index) + a headless Chrome (the crawler)
|
||||
# RAM: ~700M (mem_limit: 2816M total — web 1536M + chrome 768M + meilisearch 512M) | Pi-compatible: No (Chrome + Meilisearch)
|
||||
#
|
||||
# Environment variables:
|
||||
# DOMAIN - Your domain (e.g., demo-felhom.eu)
|
||||
# NEXTAUTH_SECRET - Munkamenet-aláíró kulcs (generált)
|
||||
# MEILI_MASTER_KEY - A keresőindex kulcsa (generált; csak a belső hálón használt)
|
||||
# OPENAI_API_KEY - NEM KÖTELEZŐ. Üresen az AI-címkézés KI van kapcsolva (Karakeep: `isConfigured = !!OPENAI_API_KEY ||
|
||||
# !!OLLAMA_BASE_URL`). Ha a háztartás megadja, a könyvjelzők szövege az OpenAI-hoz kerül.
|
||||
# SIGNUP_CLOSED - after_setup (decision 47): the box sets it when the household's first account exists. Karakeep's
|
||||
# DISABLE_SIGNUPS also refuses the FIRST account, so the default here must stay OPEN.
|
||||
#
|
||||
# Mirrors upstream's docker/docker-compose.yml (web + karakeep-chrome + meilisearch v1.41.0, MEILI_NO_ANALYTICS),
|
||||
# pinned, with the new-release check off (it would call api.github.com) and the crawler's own defaults kept: it fetches
|
||||
# every bookmarked page — that IS the product — and an ad-block list at start (CRAWLER_ENABLE_ADBLOCKER).
|
||||
|
||||
services:
|
||||
karakeep:
|
||||
image: ghcr.io/karakeep-app/karakeep:0.33.2
|
||||
container_name: karakeep
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Europe/Budapest
|
||||
- DATA_DIR=/data
|
||||
- NEXTAUTH_URL=https://${SUBDOMAIN}.${DOMAIN}
|
||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||
- MEILI_ADDR=http://karakeep-meilisearch:7700
|
||||
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||
- BROWSER_WEB_URL=http://karakeep-chrome:9222
|
||||
- DISABLE_NEW_RELEASE_CHECK=true
|
||||
- DISABLE_SIGNUPS=${SIGNUP_CLOSED:-false}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
# App-email (smtp_mapping, tls_mode plaintext → the shim's :2526, which advertises no STARTTLS: Karakeep's mailer
|
||||
# has no switch to accept the shim's self-signed certificate). Empty SMTP_HOST = mail OFF; measured 2026-10-01 that a
|
||||
# fresh install with these EMPTY boots and signs up (checklist 7.1).
|
||||
- SMTP_HOST=${SMTP_HOST:-}
|
||||
- SMTP_PORT=${SMTP_PORT:-587}
|
||||
- SMTP_FROM=${SMTP_FROM:-}
|
||||
volumes:
|
||||
- karakeep_data:/data
|
||||
networks:
|
||||
- traefik-public
|
||||
- karakeep-internal
|
||||
depends_on:
|
||||
karakeep-meilisearch:
|
||||
condition: service_healthy
|
||||
# 1536M (measured 2026-10-01): the web container also runs the crawler workers. Bench 10-minute watch: 79 % of 768M,
|
||||
# 51 % of 1024M; on the box, real pages crawled (a Wikipedia article among them) took it to 822 MiB = 80.3 % of 1024M.
|
||||
# So the household's ordinary act — saving a heavy page — needs room; the burst is re-measured at 1536M (record 5.2).
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1536M
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1:3000/api/health || curl -fsS http://127.0.0.1:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.karakeep.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)"
|
||||
- "traefik.http.routers.karakeep.entrypoints=websecure"
|
||||
- "traefik.http.routers.karakeep.tls=true"
|
||||
- "traefik.http.routers.karakeep.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.karakeep.loadbalancer.server.port=3000"
|
||||
|
||||
karakeep-chrome:
|
||||
image: ghcr.io/karakeep-app/karakeep-chrome:151.0.7922.47-r1
|
||||
container_name: karakeep-chrome
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
command:
|
||||
- --disable-gpu
|
||||
- --disable-dev-shm-usage
|
||||
- --hide-scrollbars
|
||||
- --disable-blink-features=AutomationControlled
|
||||
- --window-size=1440,900
|
||||
networks:
|
||||
- karakeep-internal
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 768M
|
||||
# The image has no wget/curl/node (inspected one tool per run, 2026-10-01) but has bash: ask Chrome's own DevTools
|
||||
# endpoint (socat on 9222 → headless-shell on 9223) for its version over a raw HTTP/1.1 request (HTTP/1.0 got no
|
||||
# answer — measured 2026-10-01 on the bench).
|
||||
healthcheck:
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/9222 && printf 'GET /json/version HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n' >&3 && grep -q Browser <&3"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
karakeep-meilisearch:
|
||||
image: getmeili/meilisearch:v1.41.0
|
||||
container_name: karakeep-meilisearch
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Europe/Budapest
|
||||
- MEILI_NO_ANALYTICS=true
|
||||
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||
volumes:
|
||||
- karakeep_meili:/meili_data
|
||||
networks:
|
||||
- karakeep-internal
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:7700/health || wget -q -O - http://127.0.0.1:7700/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
volumes:
|
||||
karakeep_data:
|
||||
karakeep_meili:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
karakeep-internal:
|
||||
Reference in New Issue
Block a user