Skip to content

feat(catalog): add AppWeaver managed app - #397

Open
dhalsim wants to merge 2 commits into
LNVPS:masterfrom
dhalsim:catalog/appweaver
Open

feat(catalog): add AppWeaver managed app#397
dhalsim wants to merge 2 commits into
LNVPS:masterfrom
dhalsim:catalog/appweaver

Conversation

@dhalsim

@dhalsim dhalsim commented Aug 28, 2026

Copy link
Copy Markdown

What is AppWeaver?

AppWeaver is a self-hosted app hub for people who want AI-assisted tools and workflows in a private workspace they control. OpenCode provides the agent backend, while AppWeaver supplies the app/plugin runtime, tool and command registration, web UI, setup flow, scheduling, persistence, and Nostr DM interface. Available apps cover workflows such as todos, bookmarks, files, scheduled jobs, publishing, and Nostr monitoring.

The managed offering is for users who want that private workspace without administering Linux, Docker, TLS, or persistent storage themselves. First launch opens AppWeaver's web setup; configuration, credentials, installed apps, databases, and app data remain in the managed volume.

This reduced Alpine image intentionally excludes browser automation, Chromium, Cursor Agent, desktop, and VNC functionality.

Summary

  • add an AppWeaver managed app backed by the public Alpine image
  • persist the AppWeaver workspace and expose its setup UI through ingress
  • document included tools, excluded browser tooling, and supported OpenCode authentication flows

Validation

  • anonymously pulled ghcr.io/getappweaver/core:alpine at digest sha256:e36f3d29fc3cac7a7bc1f9ab5708e67ad6c52e800aaa751aade516cafe70e3fe
  • cargo test -p lnvps_compose -- --test-threads=1 (54 passed)
  • cargo run -q -p lnvps_compose --bin compose-validate -- catalog/*.yaml (all eight documents passed)
  • cargo test -p lnvps_operator catalog_documents_map_to_k8s -- --test-threads=1
  • HOST_PORT_OFFSET=11000 ./scripts/app-catalog-test.sh catalog/appweaver.yaml against the published GHCR image
  • retained rendered stack: root 200, /api/health 200, setup redirect 302, Piper ready, persistent workspace populated
  • restart test: health and Piper recovered, dependencies and state reused, zero crash restarts
  • verified rendered hardening: UID 1000, read-only root filesystem, all capabilities dropped, no-new-privileges
  • AppWeaver container publishing workflow: https://github.com/getappweaver/core/actions/runs/33177573601

@v0l

v0l commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Hi, thanks for the PR, can you explain a bit about the app, what is it, who is it for?

It looks to me like a web-based agent harness.

@dhalsim

dhalsim commented Aug 28, 2026

Copy link
Copy Markdown
Author

Thanks. It is partly a web-based agent harness, but the intended scope is a self-hosted app hub around that agent backend.

AppWeaver is for people who want a private workspace containing focused AI-assisted tools without building and operating separate services for each workflow. OpenCode supplies the agent execution layer; AppWeaver adds the plugin/app runtime, controlled tool registration, commands, web UI, setup, scheduler, persistent data, and Nostr DM access. Current apps include todos, bookmarks, file management, scheduled jobs, publishing tools, and Nostr monitoring.

The LNVPS managed version is aimed at users who want this workspace without managing a VPS, container runtime, TLS, or backups. They open the HTTPS setup page, select credentials/providers, and install the apps they need. Their configuration, credentials, databases, installed apps, and app data stay in the persistent managed volume.

This catalog image is deliberately the reduced server profile: no Chromium/browser automation, Cursor Agent, desktop, or VNC.

@v0l v0l left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on the image build, not on the compose document.

As published, nothing about what a deployment runs is fixed by this entry. Dockerfile.alpine builds FROM alpine:edge and installs Bun, OpenCode and ngit by piping three unpinned installers from three third-party domains into bash, with no checksums. :alpine is a moving tag. Then scripts/docker-entrypoint-alpine.sh clones APPWEAVER_GIT_REF=main into the workspace on first boot and runs bun install --frozen-lockfile on every start.

The consequence is that merging this would let the code running inside customer deployments change at any time, retroactively, with no change to this repository and no review on our side. Anyone who takes the ghcr namespace, the GitHub account, or any of bun.sh / opencode.ai / ngit.dev inherits the same reach. Every other entry in catalog/ pins a version (holgerhatgarkeinenode/haven-docker:v1.2.2, mariadb:11); this one pins nothing at any layer. That is not a risk we can carry for a paid, one-click deployment.

What we need before this can be considered:

  1. Compile the app into the image. The checkout and its resolved dependencies should be baked in at build time, so a deploy performs no git clone and no bun install. The workspace volume should hold customer state only (.env, .data/, databases, installed apps, credentials), not the application itself. Today a deployment cannot start without GitHub and the npm registry both being reachable, and a lockfile that drifts on main breaks every restart of every existing deployment at once.
  2. Pin the base and the toolchain. A specific Alpine release rather than edge, and Bun / OpenCode / ngit installed at pinned versions with verified checksums rather than curl | bash of a latest installer.
  3. Publish an immutable version tag, for example ghcr.io/getappweaver/core:v12.1.2-alpine, so the catalog entry can reference an exact build. The repository already carries 460 release tags, so the version to bake in is not in question. We will pin by digest in the entry.

Measured against the published digest sha256:e36f3d29fc3cac7a7bc1f9ab5708e67ad6c52e800aaa751aade516cafe70e3fe, run under the operator's hardening (read only rootfs, --cap-drop ALL, uid 1000, 1 CPU / 2Gi): first boot pulls roughly 120 MB, takes about 60 seconds to listen, and leaves a 450 MB workspace of which 316 MB is node_modules. Baking it in removes all of that from the deploy path.

Point 1 also fixes an upgrade problem that exists today regardless of the security question. The clone is guarded by if [ ! -f package.json ], so after first boot the checkout is never updated again. Pulling a new image would not upgrade the app, and two customers ordering a week apart would be on different code with no way for us to tell which.

Separately, and also blocking: the setup UI is unusable in a managed deployment. createSetupSecret() generates a fresh random secret on every boot and prints it only to the container log, and every /api/setup/* route requires it. Our customers have no access to container logs, and generated secrets are never returned to them, so there is no way to reach /setup. I confirmed the secret changes across a restart. Since SETUP_SECRET is already read from the environment, the fix on our side is a config: field the customer supplies at order time, which I can add once the image question above is resolved. Worth being aware that this puts an admin interface for an agent with arbitrary code execution behind one static string on a public URL, so it will need a strong required format.

Happy to re-test as soon as there is a tagged, self-contained image.

@dhalsim

dhalsim commented Aug 29, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed review. One architectural assumption needs clarification: AppWeaver is intentionally distributed as a source workspace rather than as an immutable compiled application.

AppWeaver Core is a Bun/TypeScript Git checkout. That checkout is the installation. I intentionally allow the owner to inspect and modify the Core source, including while developing a new AppWeaver app.

Each workspace is an independent AppWeaver instance. Every workspace can have its own Core version, installed apps, configuration, databases, app data, generated bindings, and OpenCode sessions. Updating one workspace does not update another.

Apps use the same source-based model. Each installed app is a Git repository under plugins/. AppWeaver generates the app's tool, CLI, and skill bindings into Core through scripts/generate-tools.ts. Apps also declare their required Core API version. When an app requires a newer Core API, the owner must update Core before that app can be installed.

Core updates are explicit owner actions, not automatic background mutations. The current updater performs git pull --ff-only and requests a process restart. I agree that dependency handling needs improvement: when bun.lockb changes, the update operation should run bun install --frozen-lockfile before restarting. An ordinary restart should skip installation when the installed lockfile is already current.

The Alpine image provides the native runtime required by a source workspace: Bun, OpenCode, ngit, Piper, Python/ONNX dependencies, Git, and SSH tooling. I agree that Alpine, these tools, release artifacts, and checksums should be pinned, and that the catalog should reference a versioned image digest.

I can also make initial provisioning deterministic without making the resulting workspace immutable. A versioned image can contain a prepared checkout at a reviewed AppWeaver commit with its frozen dependencies already installed. On first start, that checkout is copied into the empty persistent workspace. Deployment therefore needs neither GitHub nor npm access. The checkout remains a normal writable Git repository tracking origin/main, so its owner can later update or modify it explicitly. Subsequent container restarts reuse the persistent checkout and never overwrite local changes.

Setup access has already been addressed in AppWeaver Core commit 303ef531. When BOT_MASTER_PUBKEY is configured, /setup requires a valid NIP-98 signature from that key and exchanges it for an in-memory setup session. Secret-based setup remains available when no master pubkey is configured.

The matching catalog change is now in this PR as commit 5921984. The order form requires a 64-character lowercase hex master pubkey, injects it as BOT_MASTER_PUBKEY, and the catalog test fixture supplies a valid value. The customer can therefore authenticate to /setup without access to container logs.

The remaining policy question is whether LNVPS accepts a deterministic initial deployment whose authenticated owner can later update or modify the source inside their own persistent workspace. This mutability is an intentional AppWeaver capability. An edition that executes Core only from an immutable image layer would prevent source inspection, modification, Git updates, and workspace-local app development, so it would not represent the current AppWeaver model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants