Skip to content

Rewrite Paperclip on the upstream PR-5298 layout (hooks-based)#9

Closed
raccommode wants to merge 1 commit intomasterfrom
claude/paperclip-upstream-structure
Closed

Rewrite Paperclip on the upstream PR-5298 layout (hooks-based)#9
raccommode wants to merge 1 commit intomasterfrom
claude/paperclip-upstream-structure

Conversation

@raccommode
Copy link
Copy Markdown
Owner

Summary

Adopt the file layout from getumbrel/umbrel-apps#5298 (the PR adding Paperclip to the official Umbrel store), adapted to this community store's id raccommode-paperclip. Replaces the in-compose shell-wrapper approach with Umbrel-native pre-start / post-start hooks and exports.sh.

Why

The previous setup (merged in #6 + #7 + #8) embedded the chown, secret bootstrap, allowed-hostname juggle, and bootstrap-CEO runner inside the container's CMD. It worked but:

  • forced exposure=public because the LAN IP wasn't known at compose time,
  • required generating the auth secret at runtime in a shared volume,
  • coupled bootstrap orchestration with the server lifecycle.

The upstream PR uses Umbrel's hook system instead: scripts run on the host, can docker exec, and have full access to host networking — so the LAN IPs can be discovered via hostname --all-ip-addresses and the bootstrap can be triggered after /api/health reports bootstrap_pending. Cleaner and validated by the upstream submitter against fresh installs, restart persistence, and uninstall/reinstall.

What changed

New files

  • raccommode-paperclip/exports.sh — exports APP_RACCOMMODE_PAPERCLIP_LOCAL_IPS (comma-separated host LAN IPs) so PAPERCLIP_ALLOWED_HOSTNAMES can include them.
  • raccommode-paperclip/hooks/pre-start — seeds instances/default/config.json with auth.baseUrlMode=explicit + publicBaseUrl=http://\${DEVICE_DOMAIN_NAME}:\${APP_PROXY_PORT}, then chowns the data dir to 1000:1000.
  • raccommode-paperclip/hooks/post-start — polls /api/health, parses bootstrapStatus + bootstrapInviteActive, runs npx paperclipai auth bootstrap-ceo once if needed, persists the invite URL to \${APP_DATA_DIR}/data/bootstrap-invite-url.txt and logs it.
  • raccommode-paperclip/data/.gitkeep — ensures the volume target exists in the package.

Rewritten

  • raccommode-paperclip/docker-compose.yml:
    • Image pinned by digest sha-a072377 (no more :latest).
    • user: \"1000:1000\" directly, removing the gosu / USER_UID / shell wrapper indirection.
    • entrypoint: node + bare command — docker-entrypoint.sh is no longer needed since chown moved to pre-start.
    • PROXY_AUTH_ADD: false on app_proxy → single Paperclip login instead of nested Umbrel + Paperclip auth.
    • PAPERCLIP_DEPLOYMENT_EXPOSURE reverts to private (safer default) with a populated PAPERCLIP_ALLOWED_HOSTNAMES: \${DEVICE_DOMAIN_NAME}, \${DEVICE_HOSTNAME}, \${APP_DOMAIN}, \${APP_HIDDEN_SERVICE}, the discovered LAN IPs, and the internal Docker hostname — with and without :\${APP_PROXY_PORT} suffix.
    • BETTER_AUTH_SECRET=\${APP_SEED} (Umbrel-derived stable secret) instead of the runtime-generated /paperclip/.better-auth-secret.
    • Telemetry off (PAPERCLIP_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1).

Updated

Test plan

  • Refresh community app store on Umbrel and reinstall the Paperclip app.
  • Confirm app log shows Paperclip bootstrap invite: http://umbrel.local:<port>/invite/pcp_bootstrap_* shortly after start.
  • Open the invite URL → claim CEO account → land on the dashboard.
  • Restart the app: log shows bootstrap not required (or already active), no duplicate invite.
  • Verify access works at http://<lan-ip>:\${APP_PROXY_PORT} without 403 (LAN IPs covered by allowed-hostnames via exports.sh).
  • Confirm \${APP_DATA_DIR}/data/bootstrap-invite-url.txt contains the URL on the host.

Credits

Layout and hook scripts adapted from @aidencole98's umbrel-apps#5298.

🤖 Generated with Claude Code

Adopt the structure validated in getumbrel/umbrel-apps#5298 (the PR
adding Paperclip to the official Umbrel store), adapted to this
community store's id `raccommode-paperclip`. The previous in-compose
shell-wrapper approach is replaced by Umbrel-native pre-start /
post-start hooks, which run on the host before/after the container
and have full access to the host filesystem and `docker exec`.

Changes:

- `hooks/pre-start`: seeds a complete `instances/default/config.json`
  with `auth.baseUrlMode=explicit` + `auth.publicBaseUrl=
  http://${DEVICE_DOMAIN_NAME}:${APP_PROXY_PORT}` so the server boots
  in `authenticated` + `private` mode without the previous startup
  errors. Also chowns the data dir to 1000:1000 (replaces the inline
  shell wrapper).

- `hooks/post-start`: polls `/api/health`, parses `bootstrapStatus`
  and `bootstrapInviteActive`, and runs
  `npx paperclipai auth bootstrap-ceo` exactly once. The generated
  invite URL is logged and persisted to
  `${APP_DATA_DIR}/data/bootstrap-invite-url.txt`. Idempotent because
  `bootstrap-ceo` itself short-circuits if an admin already exists,
  and `bootstrapInviteActive=true` means a previous invite is still
  valid.

- `exports.sh`: discovers the host's LAN IPs via
  `hostname --all-ip-addresses` and exports them as
  `APP_RACCOMMODE_PAPERCLIP_LOCAL_IPS`, so Paperclip's
  `PAPERCLIP_ALLOWED_HOSTNAMES` allowlist covers raw-IP access
  without falling back to `exposure=public`.

- `docker-compose.yml`:
    * Image pinned by digest (sha-a072377), no more `:latest`.
    * `user: 1000:1000` instead of the gosu / USER_UID dance.
    * `entrypoint: node` + bare command to skip
      `docker-entrypoint.sh` (no longer needed once chown is in
      pre-start).
    * `PROXY_AUTH_ADD: false` on `app_proxy` so users get a single
      Paperclip login instead of nested Umbrel + Paperclip auth.
    * `PAPERCLIP_DEPLOYMENT_EXPOSURE` back to `private` (the safer
      default) with a populated `PAPERCLIP_ALLOWED_HOSTNAMES`
      including `${DEVICE_DOMAIN_NAME}`, `${DEVICE_HOSTNAME}`,
      `${APP_DOMAIN}`, `${APP_HIDDEN_SERVICE}`, the discovered LAN
      IPs, and the internal Docker hostname — with and without the
      `:${APP_PROXY_PORT}` suffix.
    * `BETTER_AUTH_SECRET=${APP_SEED}` (Umbrel-derived stable
      secret) instead of the per-install random file.
    * Telemetry flags off.
    * `data/.gitkeep` so the volume mount target is present in the
      package.

- `umbrel-app.yml`: bump `version` to `2026.403.0` matching the
  pinned upstream image; release notes updated to reflect the new
  bootstrap mechanism.

Net effect: clean install on Umbrel from this store creates the
admin invite automatically, prints it to the app log viewer, and
reaches it via `http://umbrel.local:<port>` without manual SSH or
exposure-public workarounds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@raccommode
Copy link
Copy Markdown
Owner Author

Superseded — content committed directly to master in ebc0977.

@raccommode raccommode closed this Apr 25, 2026
@raccommode raccommode deleted the claude/paperclip-upstream-structure branch April 25, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant