Cloudron makes it easy to run web apps like WordPress, Nextcloud, GitLab on your server. Find out more or install now.


Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Bookmarks
  • Search
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Brand Logo

Cloudron Forum

Apps - Status | Demo | Docs | Install
  1. Cloudron Forum
  2. App Wishlist
  3. Hoarder (Now, Karakeep) - Mymind alternative - The Ultimate All-In-One Bookmark and Note Taking App

Hoarder (Now, Karakeep) - Mymind alternative - The Ultimate All-In-One Bookmark and Note Taking App

Scheduled Pinned Locked Moved App Wishlist
42 Posts 18 Posters 16.2k Views 20 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • L LoudLemur

    @andreasdueren

    Thank you for packaging this, and congratulations on getting it out. Karakeep has been sitting on the wishlist since June 2024 and it is good to finally see it on the store.

    I should explain why I am replying at such length. We were part way through scoping our own Karakeep package when we found yours. Separately, we have just finished packaging Meilisearch standalone for Cloudron and put it through a full acceptance ladder on a real rig, which means we spent an unreasonable number of hours on exactly the one component your package bundles. What follows is what that cost us, offered in the hope it saves you the same. Almost all of it is about the Meilisearch leg.

    Some of this is measured on our own package and some of it is inference about yours that we have not tested. I have tried to be explicit about which is which throughout, because I do not want to report a defect we have not actually reproduced.

    First, the things that are plainly right, because I read the whole tree and it would be rude not to say so. The secret handling in start.sh is properly idempotent: generated once, gated on non-emptiness, umask 077, atomic write through a temporary file, and the mode re-asserted on every boot rather than only on first run. That last detail is the one most packages get wrong, and a restore resets modes, so it matters. Running the migrations synchronously before supervisord so the port opens only once they are done is the right call. The OIDC redirect URI is /api/auth/callback/custom, which is the correct provider-id-derived path and not the intuitive wrong one. The scheduled CI that polls upstream releases and bumps the pin is better release machinery than most community packages have, including ours. And binding Meilisearch and Chromium to loopback is exactly right.

    The Meilisearch store and backups

    This is the substantive one, and it has two parts that are worth separating because they have different levels of confidence.

    The part I am confident about: a live LMDB store copied file by file is not a restorable artefact.

    Your Meilisearch runs with --db-path /app/data/meilisearch, which puts the live store inside the tree Cloudron copies. The platform takes that copy while the application is running, with no quiesce step and no post-backup hook. For a key file or a SQLite database at rest that is fine. For an LMDB store that is being written to, the copy has no defined consistency point, and Meilisearch does not consider a raw file copy restorable; a snapshot is what it considers restorable. So the index inside your backups may or may not come back cleanly, and nothing currently tells the operator which.

    The saving grace specific to Karakeep is that the index is derivable: Karakeep can rebuild it from SQLite through the admin "Reindex All Bookmarks" action. That turns a potentially corrupt restore from data loss into an inconvenience, which is a much better position than most applications are in. But it only works if the operator knows to do it, and at the moment POSTINSTALL.md does not mention it. The cheapest possible fix here is one paragraph of documentation saying that after a restore, search may be empty or stale until a reindex is run. That costs you nothing and closes the worst outcome.

    The part I am not confident about: whether this can abort the backup run itself.

    Cloudron's rsync syncer walks /app/data and is not resilient to concurrent mutation. Its tree walk lists a directory then recurses into it, and if a child directory disappears between the list and the recurse, the read returns null and a later sort throws. The exception is not caught per application. It aborts the entire server's backup run, so one application's temp-directory churn becomes every application's missed backup.

    I want to be careful here. That failure was earned against ClickHouse, whose merge process creates and atomically renames temporary directories continuously, and it generalises cleanly to Elasticsearch and RocksDB. Meilisearch is a much narrower case: mostly long-lived files under indexes/<uuid>/, and the obvious candidates for a vanishing directory are an index deletion landing mid-backup, or churn in the dump and snapshot directories. We have not reproduced this against Meilisearch, and we have not tested your package at all. Our own package moved the store out of the walked tree from the start, so we never ran the experiment that would have proved it either way. Treat it as a live question rather than a finding.

    If you want to settle it, the test is cheap: put a decent corpus in, start a large indexing batch, delete an index while it runs, and trigger a backup from the dashboard during the churn. Either the run completes or you get a syncer stack trace in the box logs. (It is cheap, but it does take a long time, especially if your server is busy!)

    The design that solves both parts, if you want it.

    minBoxVersion in your manifest is already 9.1.0, which is the version that unlocks the machinery, so this is available to you today. Move the live store onto a persistentDirs path outside /app/data, and add a backupCommand that asks the running instance for a snapshot over its own HTTP API and drops the artefact into /app/data for the platform to pick up.

    The one non-obvious part is that backupCommand runs in a temporary container, not the live one: no CLOUDRON_* environment variables, stdout discarded, read-only root filesystem, but attached to the cloudron network with /app/data and the persistent dirs mounted. So the backup script cannot learn its own application's address from the environment. The trick that makes it work is having the entrypoint write the container's address into a file under /app/data on every boot, which the backup script then reads over the shared mount and dials. Ours also exits 0 on every path deliberately, including failure, because a non-zero exit from a backup command takes the platform's whole backup run down, which is worse than a snapshot that is one cycle stale. It records success or failure to a log file under /app/data instead, since its stdout goes nowhere.

    We tested this under load and the result was better than we expected. A backup issued with sixteen tasks queued and isIndexing true completed cleanly in 314 seconds, and the snapshot did not merely capture a consistent view: it carried the pending write queue with it, so the restored instance resumed the indexing work that was in flight. Nothing accepted before the consistency point was dropped or half indexed.

    Three persistentDirs behaviours we confirmed on a real box, since they decide what your boot logic has to handle: an update preserves the directory, an in-place restore preserves it, and a clone starts it empty. That last one is the one that bites, because it means a cloned application must be able to rebuild its store from the snapshot in /app/data on first boot. In your case there is a second option, which is to let it rebuild from SQLite instead.

    The scripts are MIT licensed and public at https://github.com/OrcVole/meilisearch-cloudron if any of it is useful. Please lift whatever you want, no attribution needed.

    Memory sizing

    Your memoryLimit is 2 GiB shared between Next.js, the workers, Chromium and Meilisearch, with no absolute cap on any of them.

    Our package gives Meilisearch 4 GiB on its own, and it needs it. At 2 GiB it failed our memory gate outright: anonymous demand alone reached about 2.5 GiB against the 2 GiB limit while indexing a million documents.

    I want to be honest that this number does not transfer directly. A million documents is a far larger corpus than a typical bookmark collection, and your four processes will not all peak together in normal use. So this is not me telling you to ship 4 GiB.

    What does transfer is how to read the numbers, and this is the part I would genuinely have wanted to know earlier. memory.peak in the cgroup is useless for sizing a memory-mapped store. It sat at 100.0001 per cent of the limit in every one of our runs, at 2 GiB and at 4 GiB alike, because it counts page cache, and a store using mmap will use every page you give it and then reclaim. Reading that figure suggests a container in permanent distress when it is doing exactly what it should. The figure that means something is memory.stat anon plus swap: at 4 GiB that peaked at 1.74 GiB, or 43.5 per cent, which is a healthy result and the one the gate should have been testing all along.

    Also worth knowing: MEILI_MAX_INDEXING_MEMORY is not a ceiling on the process. It caps one specific arena. Ours was set to a third of the cgroup limit and the process still went well past it. Do not treat it as a safety bound. And Meilisearch sizes its defaults from host RAM rather than the cgroup limit, which is upstream issue 4686, so on a Cloudron box it will guess based on the whole machine unless you compute the value from /sys/fs/cgroup/memory.max yourself at boot.

    The Meilisearch version bump

    Your Meilisearch pin at v1.41.0 is manual and outside the CI that tracks Karakeep, so this may be some way off, but it is a trap worth knowing about before you hit it.

    Meilisearch will refuse to start against a store written by an older version. Handling it needs an explicit upgrade path: record the version that wrote the store in a marker file, compare it at boot, and run the upgrade when they differ. The rollback direction matters too, because a store written by a newer binary than the one now running cannot simply be opened, and that is exactly what a Cloudron rollback to a previous package version produces.

    Separately, 1.51.0 removed several flags that earlier versions accepted, including --experimental-no-snapshot-compaction, --experimental-replication-parameters and --experimental-no-edition-2024-for-dumps, and renamed the experimental spelling of the dumpless upgrade flag. You do not pass any of those today, so this only matters if you add one from an old blog post.

    Smaller things

    localstorage is declared as {} with no sqlite option, while start.sh sets DB_WAL_MODE=true. The addon's sqlite option exists to declare database paths so the backup captures them consistently, and a WAL-mode database is three files that need to agree with each other. I will be straight that our own doctrine has not settled what that option actually guarantees across the database, the WAL and the shared-memory file, so I am flagging it as worth a look rather than asserting it is broken.

    Supervisor priorities give you spawn order but not readiness. The web process and the workers can start talking to Meilisearch before it is accepting connections, and a worker that fails its initial connect can sit there doing nothing without saying so. A bounded wait on the dependency, sixty seconds or so, then proceeding degraded, is usually enough.

    OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING=true is set unconditionally. That silently links an OIDC identity to any existing local account with a matching email address, which is a reasonable convenience on a single-operator install and a real account-takeover path on a box where users can register with an email they do not control. Given DISABLE_SIGNUPS is left to the operator, both configurations exist in the wild. It may be worth making it opt-in through the env file, or at least calling it out in POSTINSTALL.md.

    OAUTH_WELLKNOWN_URL is hand-built from CLOUDRON_OIDC_ISSUER. Cloudron injects CLOUDRON_OIDC_DISCOVERY_URL directly, so using it removes an assumption about the issuer's URL shape.

    Finally, and this one is a preference rather than a defect: the final stage is the upstream Karakeep image with supervisor and Chromium layered on, rather than cloudron/base. The documented reason to prefer the base image is that the dashboard file manager, web terminal and log viewer depend on its userland. I do not know whether they actually break on a Debian-derived image, and I would be interested to hear if you have checked, because that is a question we would like answered for our own purposes too and I have not found it written down anywhere.

    Thanks again for the package, and apologies for the wall of text. Happy to expand on any of it, and happier still to be told I am wrong about the parts I have flagged as untested.

    andreasduerenA Offline
    andreasduerenA Offline
    andreasdueren
    App Dev
    wrote last edited by andreasdueren
    #41

    @LoudLemur Thanks, useful pointers. Shipped in 0.1.8:

    Meilisearch version bump: this was the real one. I reproduced it, a 1.40.0 store against the 1.41.0 binary refuses to open. start.sh now compares the store VERSION against the binary, passes --experimental-dumpless-upgrade when the store is older, and archives the store when it is newer, which is what a package rollback produces. Comparison uses sort -V, since string compare gets 1.9.0 against 1.41.0 backwards. Both paths tested on a live box.

    OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING now defaults to false and is opt-in from the env file.

    Switched to CLOUDRON_OIDC_DISCOVERY_URL.

    POSTINSTALL now documents that search may be stale after a restore and that Reindex All Bookmarks rebuilds it.

    Web and workers now wait for Meilisearch to accept connections, sixty second bound, then proceed without search.

    Memory raised to 3 GiB.

    Not adopting persistentDirs plus backupCommand. The index here is derivable from SQLite, so the worst case is one admin action, and the snapshot design adds a backup time HTTP dependency plus the clone starts empty behaviour. For your standalone package, where the index is the only copy, your conclusion makes sense.

    Backup abort: not reproduced here either. The package never enables dumps or snapshots and those dirs sit outside the db-path, so they stay empty.

    On cloudron/base: log viewer and web terminal both work on the Debian based upstream image. I used cloudron exec against it throughout,

    1 Reply Last reply
    2
    • necrevistonnezrN Offline
      necrevistonnezrN Offline
      necrevistonnezr
      wrote last edited by
      #42

      I let Claude built a sync / mirror script between Linkding and Karakeep, FYI: https://github.com/necrevistonnezr/linkding-karakeep-sync

      1 Reply Last reply
      3

      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

      With your input, this post could be even better 💗

      Register Login
      Reply
      • Reply as topic
      Log in to reply
      • Oldest to Newest
      • Newest to Oldest
      • Most Votes


      • Login

      • Don't have an account? Register

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Bookmarks
      • Search