packaging(BEA-49): draft Umbrel/CasaOS/TrueNAS app-store manifests for the hub

Thin, per-store manifests wrapping the existing root Dockerfile so a
self-hosted BearDrive hub installs as a one-click app. All three share the
same static binary on distroless; none change the core image.

Runtime invocation validated by smoke-testing the built binary directly
(serve --remote file://... --addr :8080 --upload -c config.json): web UI
returns HTTP 200, /login + /signup reachable, sqlite hub.db + file storage
created on disk.

DRAFT — blocked on two prerequisites documented in packaging/README.md:
  1. no published multi-arch container image (image: is a placeholder)
  2. no real Umbrel/CasaOS/TrueNAS instance for the store-required install test

Install defaults (admin email via config-only auth.admins, port 8080/4173,
file:// storage, sqlite) documented for the listing copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow Lee
2026-08-18 08:54:49 -07:00
co-authored by Claude Opus 4.8
parent 6f0f474903
commit a3c101a31b
7 changed files with 421 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# Self-hosted app-store packaging (Umbrel / CasaOS / TrueNAS)
Thin, per-store manifests that wrap the repo's existing hub image (built from
the root [`Dockerfile`](../Dockerfile) — distroless, CGO-free) so a BearDrive
**hub** installs as a one-click app in the self-hosted app-store catalogs.
None of these change the core image.
| Dir | Store | Upstream PR target | Status |
|---|---|---|---|
| [`umbrel/`](umbrel/) | Umbrel App Store | `getumbrel/umbrel-apps` | **DRAFT — blocked** (see below) |
| [`casaos/`](casaos/) | CasaOS App Store | `IceWhaleTech/CasaOS-AppStore` | **DRAFT — blocked** |
| [`truenas/`](truenas/) | TrueNAS community | `truenas/apps` | **DRAFT — blocked** |
> `packaging/homebrew` is the existing sibling — the CLI distribution manifest.
## ⚠️ Two blockers before any of these can be PR'd upstream
These manifests are **NOT yet known-good**. Both stores' contributing guides
require a real-instance install test, and all three reference a published
container image that does not exist yet.
1. **No published container image.** Every store installs by pulling a public,
versioned, multi-arch image by tag. The repo publishes **none** — CI
(`.github/workflows/{ci,bump-cloud,docs}.yml`) has no image-publish job, and
the only build path is `deploy/gcp-cloudrun.sh` → a **private** GCP Artifact
Registry for Cloud Run. The `image:` fields below point at a **placeholder**
`ghcr.io/runbear-io/beardrive:<version>` that must first be built and pushed
(multi-arch `linux/amd64` + `linux/arm64` — Umbrel runs on Raspberry Pi).
Owner: **CTO/eng** (tracked as the child issue filed off BEA-49).
2. **No real-instance test environment.** Each store's guide requires the app
be installed + reach its web UI on an actual instance of that platform
(Umbrel OS / CasaOS / TrueNAS SCALE) before the PR is accepted. Eng has
Docker (the underlying image is smoke-tested — see below) but **no
Umbrel/CasaOS/TrueNAS instances and no local VM tooling**
(qemu/vagrant/multipass absent; Docker Desktop on macOS can't practically
nest those OSes). Owner: **CEO** — needs cloud-VM budget/credentials or a
hosted test env. Untested PRs get rejected and burn reviewer goodwill, so
we hold rather than ship blind (per BEA-49's "Known risk" instruction).
## What IS verified (Docker smoke test, not a store instance)
The underlying hub image was built from the root `Dockerfile` and run locally
under Docker with the self-host config below; the web UI was confirmed
reachable. This validates the **runtime invocation and defaults** the manifests
encode — it does **not** substitute for the per-store real-instance test.
## Install defaults chosen (for the listing copy — CMO)
The hub is configured by flags **plus** a JSON config file. Critically, the
**admin email can only be set via the config file's `auth.admins`** — there is
no `--admins` flag — and the image is **distroless (no shell)**, so each
manifest uses a tiny `busybox` init container to render `/data/config.json`
from the user-supplied admin email into the shared data volume before the hub
starts. The core image is untouched.
| Setting | Value | Notes |
|---|---|---|
| **Admin email** | user-supplied; placeholder `admin@example.com` | `auth.admins`. On a zero-account hub this email can self-sign-up first (invite-only afterward). |
| **Listen port (container)** | `8080` (`--addr :8080`) | Suggested host/web port `4173` (matches `docs/self-hosting.md`). |
| **Storage backend** | `file:///data/storage` | Local filesystem blob+journal store — the simplest self-host backend (no S3/GCS creds). |
| **Metadata DB** | sqlite at `/data/hub.db` | `database.driver = sqlite`. |
| **Users DB** | `/data/auth.json` | `auth.users_db`. |
| **Uploads** | enabled (`--upload`) | A hub must accept client uploads. |
| **Read analytics** | enabled (`reads.enabled`) | Powers the Insights dashboard. |
| **Device/home state** | `BDRIVE_HOME=/data/home` | Overrides the image default `/tmp/bdrive` (ephemeral) so identity persists. |
| **Persistent volume** | one dir mounted at `/data` | Holds `storage/`, `home/`, `hub.db`, `auth.json`, `config.json`. |
Rendered config (only the parts flags can't set — flags supply remote/addr/upload):
```json
{
"auth": { "admins": ["admin@example.com"], "users_db": "/data/auth.json" },
"reads": { "enabled": true },
"database": { "driver": "sqlite", "dsn": "/data/hub.db" }
}
```
Launch command: `serve --remote file:///data/storage --addr :8080 --upload -c /data/config.json`
## Remaining per-store work (needs a real instance)
- **UID/permissions:** distroless runs as nonroot UID **65532**; the init
container `chown`s `/data` to match. Each platform mounts app-data with its
own owner (Umbrel commonly 1000) — must be verified live.
- **TrueNAS:** `truenas/apps` expects the full `ix-dev` chart scaffold
(`app.yaml` + `questions.yaml` + `templates/`); the draft here is the core,
and the complete scaffold should be generated + validated with their tooling
on a real TrueNAS SCALE box.
- **Health checks, port-conflict defaults, gallery assets** per each store's
lint/CI.
+91
View File
@@ -0,0 +1,91 @@
# BearDrive hub — CasaOS App Store manifest (DRAFT, see ../README.md).
# PR target: IceWhaleTech/CasaOS-AppStore. Guide requires it be "well tested on
# your own CasaOS first" — BLOCKED on a published image + a real CasaOS test.
name: beardrive
services:
config-init:
# Distroless has no shell; render config + fix ownership, then exit.
image: busybox:1.36
restart: on-failure
volumes:
- type: bind
source: /DATA/AppData/beardrive
target: /data
command:
- sh
- -c
- |
set -e
mkdir -p /data/storage /data/home
if [ ! -f /data/config.json ]; then
cat > /data/config.json <<EOF
{
"auth": { "admins": ["${ADMIN_EMAIL:-admin@example.com}"], "users_db": "/data/auth.json" },
"reads": { "enabled": true },
"database": { "driver": "sqlite", "dsn": "/data/hub.db" }
}
EOF
fi
chown -R 65532:65532 /data
beardrive:
# PLACEHOLDER — image not published yet (see ../README.md, blocker 1).
image: ghcr.io/runbear-io/beardrive:0.1.0
container_name: beardrive
restart: unless-stopped
user: "65532:65532"
depends_on:
config-init:
condition: service_completed_successfully
environment:
BDRIVE_HOME: /data/home
ports:
- "4173:8080"
volumes:
- type: bind
source: /DATA/AppData/beardrive
target: /data
command:
- serve
- --remote
- file:///data/storage
- --addr
- :8080
- --upload
- -c
- /data/config.json
# CasaOS App Store metadata extension.
x-casaos:
architectures:
- amd64
- arm64
main: beardrive
author: Runbear
developer: Runbear
category: Cloud
icon: https://raw.githubusercontent.com/runbear-io/beardrive/main/docs/assets/icon.png
title:
en_us: BearDrive
tagline:
en_us: Google Drive for AI agents — self-hosted sync hub
description:
en_us: >-
Self-hosted BearDrive hub: sync any folder across your devices,
teammates, and AI agents with per-change provenance and read
analytics. Local filesystem storage + sqlite; clients connect with
`bdrive login`. Open source (AGPL-3.0).
port_map: "4173"
scheme: http
index: /
webui_port: 4173
envs:
- container: ADMIN_EMAIL
description:
en_us: >-
Email that becomes the hub admin. On a brand-new hub this address
can self-sign-up first; the hub is invite-only afterward.
volumes:
- container: /data
description:
en_us: BearDrive data — file storage, sqlite metadata, and config.
+35
View File
@@ -0,0 +1,35 @@
# BearDrive hub — TrueNAS Apps metadata (DRAFT, see ../README.md).
# PR target: truenas/apps (community train). BLOCKED on a published image and a
# real TrueNAS SCALE install test. The full ix-dev chart scaffold
# (questions.yaml + templates/) must be generated + validated with TrueNAS
# tooling on a real SCALE instance; questions.yaml and templates/ here are the
# core, not the complete lint-passing scaffold.
name: beardrive
title: BearDrive
train: community
categories:
- cloud
- productivity
app_version: "0.1.0"
version: 0.0.1
home: https://beardrive.ai
sources:
- https://github.com/runbear-io/beardrive
- https://docs.beardrive.ai
description: >-
Self-hosted BearDrive hub — Google Drive for AI agents. Sync any folder
across devices, teammates, and AI agents with per-change provenance and read
analytics. Local filesystem storage + sqlite; clients connect with
`bdrive login`. Open source (AGPL-3.0).
maintainers:
- name: Runbear
url: https://beardrive.ai
email: hello@runbear.io
run_as_context:
- description: BearDrive runs as the distroless nonroot user.
gid: 65532
group_name: nonroot
uid: 65532
user_name: nonroot
capabilities: []
host_mounts: []
+58
View File
@@ -0,0 +1,58 @@
# BearDrive hub — TrueNAS config questions (DRAFT core, see ../README.md).
# Minimal set; the full questions.yaml (resources, DNS, etc.) is generated by
# the ix-dev scaffold and must be validated on a real TrueNAS SCALE instance.
groups:
- name: BearDrive Configuration
description: Hub admin and web access.
- name: Storage Configuration
description: Where BearDrive keeps files, metadata, and config.
questions:
- variable: beardrive
label: ""
group: BearDrive Configuration
schema:
type: dict
attrs:
- variable: admin_email
label: Admin Email
description: >-
Email that becomes the hub admin. On a brand-new hub this address
can self-sign-up first; the hub is invite-only afterward.
schema:
type: string
required: true
default: admin@example.com
- variable: network
label: ""
group: BearDrive Configuration
schema:
type: dict
attrs:
- variable: web_port
label: Web Port
schema:
type: int
default: 4173
required: true
- variable: storage
label: ""
group: Storage Configuration
schema:
type: dict
attrs:
- variable: data
label: BearDrive Data
description: File storage, sqlite metadata, and config (mounted at /data).
schema:
type: dict
attrs:
- variable: type
label: Type
schema:
type: string
default: ix_volume
enum:
- value: ix_volume
description: ixVolume (dataset created by TrueNAS)
- value: host_path
description: Host Path (existing dataset)
@@ -0,0 +1,55 @@
# BearDrive hub — TrueNAS compose template (DRAFT, see ../../README.md).
# Shown resolved (concrete values) for readability; in the real ix-dev chart
# these become {{ .Values... }} template expressions bound to questions.yaml.
# BLOCKED on a published image + a real TrueNAS SCALE test.
services:
config-init:
# Distroless has no shell; render config + fix ownership, then exit.
image: busybox:1.36
restart: on-failure
volumes:
- type: bind
source: /mnt/.ix-apps/app_mounts/beardrive/data # ix_volume in the real chart
target: /data
command:
- sh
- -c
- |
set -e
mkdir -p /data/storage /data/home
if [ ! -f /data/config.json ]; then
cat > /data/config.json <<EOF
{
"auth": { "admins": ["admin@example.com"], "users_db": "/data/auth.json" },
"reads": { "enabled": true },
"database": { "driver": "sqlite", "dsn": "/data/hub.db" }
}
EOF
fi
chown -R 65532:65532 /data
beardrive:
# PLACEHOLDER — image not published yet (see ../../README.md, blocker 1).
image: ghcr.io/runbear-io/beardrive:0.1.0
restart: unless-stopped
user: "65532:65532"
depends_on:
config-init:
condition: service_completed_successfully
environment:
BDRIVE_HOME: /data/home
ports:
- "4173:8080"
volumes:
- type: bind
source: /mnt/.ix-apps/app_mounts/beardrive/data
target: /data
command:
- serve
- --remote
- file:///data/storage
- --addr
- :8080
- --upload
- -c
- /data/config.json
+61
View File
@@ -0,0 +1,61 @@
# BearDrive hub — Umbrel App Store manifest (DRAFT, see ../README.md).
# PR target: getumbrel/umbrel-apps. BLOCKED on a published multi-arch image
# and a real Umbrel-instance install test.
version: "3.7"
services:
# Umbrel's reverse proxy that fronts the app's web UI.
app_proxy:
environment:
APP_HOST: beardrive_hub_1
APP_PORT: 8080
# Distroless has no shell, so a tiny init container renders the hub config
# (the admin email lives only in the config file — there is no --admins flag)
# and fixes ownership for the nonroot (65532) hub user. Runs once, then exits.
config-init:
image: busybox:1.36
restart: on-failure
volumes:
- ${APP_DATA_DIR}/data:/data
command:
- sh
- -c
- |
set -e
mkdir -p /data/storage /data/home
if [ ! -f /data/config.json ]; then
cat > /data/config.json <<EOF
{
"auth": { "admins": ["${ADMIN_EMAIL:-admin@example.com}"], "users_db": "/data/auth.json" },
"reads": { "enabled": true },
"database": { "driver": "sqlite", "dsn": "/data/hub.db" }
}
EOF
fi
chown -R 65532:65532 /data
hub:
# PLACEHOLDER — image is not published yet (see ../README.md, blocker 1).
image: ghcr.io/runbear-io/beardrive:0.1.0
restart: on-failure
depends_on:
config-init:
condition: service_completed_successfully
user: "65532:65532"
environment:
# Persist device identity / projects.json (image default /tmp/bdrive is ephemeral).
BDRIVE_HOME: /data/home
volumes:
- ${APP_DATA_DIR}/data:/data
command:
- serve
- --remote
- file:///data/storage
- --addr
- :8080
- --upload
- -c
- /data/config.json
ports:
- "8080"
+30
View File
@@ -0,0 +1,30 @@
# BearDrive hub — Umbrel app metadata (DRAFT). Final id/gallery/port assigned
# at PR time per getumbrel/umbrel-apps conventions.
manifestVersion: 1
id: beardrive
category: files
name: BearDrive
version: "0.1.0"
tagline: Google Drive for AI agents — self-hosted sync hub
description: >-
BearDrive mounts any folder as a synced volume shared across your devices,
teammates, and AI agents. Every change is attributed to the human, agent, and
device that made it; any synced file can become a public page; and the
Dashboard shows what your agents actually read. This app runs your own
self-hosted hub (local filesystem storage + sqlite) — clients connect with
`bdrive login http://<your-umbrel>:<port>`. Open source (AGPL-3.0).
developer: Runbear
website: https://beardrive.ai
dependencies: []
repo: https://github.com/runbear-io/beardrive
support: https://github.com/runbear-io/beardrive/issues
port: 4173
gallery:
- 1.jpg
- 2.jpg
- 3.jpg
path: ""
defaultUsername: ""
defaultPassword: ""
submitter: Runbear
submission: https://github.com/getumbrel/umbrel-apps/pull/REPLACE_AT_PR_TIME