FUTO Backups is a multi-tenant backup service: users sign in with OIDC and get S3-backed restic repositories, with first-class support for backing up Immich.
End-user documentation is published at https://docs.futo.cloud from packages/docs.
This repository holds the application, its container images and Helm charts, and the local development stack. The infrastructure FUTO runs it on is managed separately.
Ensure you have prerequisites installed:
If necessary, copy .env.example to .env and customise.
Then use mise:
mise dev # install deps, prep environment, start servers (compose-based)
mise check # lint, format check, svelte check, unit tests
mise test # unit tests
mise test:integration # integration tests
mise test:e2e # e2e tests
mise test:e2e:web # e2e web testsAn alternative dev flow runs the stack the way it is deployed: Helm charts, CloudNativePG, Rook-Ceph object storage and in-cluster service discovery. All required tools (k3d, kubectl, helm, tilt) are installed via mise.
mise k3d:up # create local k3d cluster + registry
mise tilt:up # start Tilt; builds images, renders charts, port-forwards
# edit code — live_update syncs into running pods
mise tilt:down # stop Tilt
mise k3d:down # delete clusterPorts forwarded to localhost:
5173web,3020yucca-api,3030yucca-admin-api,3010michael8092mock-oidc,8025mailpit,9000ceph rgw (S3)8428victoria-metrics,9428victoria-logs
Tilt reads the HelmReleases under kubernetes/apps/dev/local
to discover what to deploy, then builds and live-updates the images. Charts live
in charts/ (a yucca-common library plus per-service charts).
Every merge to main publishes images and charts tagged 0.0.<build>; every
release publishes v<version> images and <version> charts.
| Image | Chart | Package |
|---|---|---|
ghcr.io/futo-org/backups/api |
oci://ghcr.io/futo-org/backups/charts/api |
packages/yucca-api |
ghcr.io/futo-org/backups/admin-api |
oci://ghcr.io/futo-org/backups/charts/admin-api |
packages/yucca-admin-api |
ghcr.io/futo-org/backups/metrics-worker |
oci://ghcr.io/futo-org/backups/charts/metrics-worker |
packages/yucca-metrics-worker |
ghcr.io/futo-org/backups/web |
oci://ghcr.io/futo-org/backups/charts/web |
packages/web |
ghcr.io/futo-org/backups/michael |
oci://ghcr.io/futo-org/backups/charts/michael |
packages/michael |
ghcr.io/futo-org/backups/bot |
oci://ghcr.io/futo-org/backups/charts/bot |
packages/futo-backups-bot |
ghcr.io/futo-org/backups/columbo |
oci://ghcr.io/futo-org/backups/charts/columbo |
packages/columbo |
ghcr.io/futo-org/backups/standalone |
packages/standalone-app |
|
ghcr.io/futo-org/backups/restic-proxy |
packages/restic-proxy |
The meta (.well-known discovery) and cnpg-cluster (CloudNativePG database)
charts are published alongside under the same prefix.
A chart's appVersion is the image tag it was published with, so a chart
version alone pins the images it deploys.
packages/futo-backups-bot handles Discord tickets and optional Freshdesk syncing.
Ticket setup grants Attach Files in the support channel without changing channel
visibility or posting permissions. If a member-specific deny remains, the ticket
shows a visible explanation. Image and log attachments retain filenames and
captions; failed downloads are retried. The attachment limits are 18 MiB per
Freshdesk conversation and 8 MiB per file sent to Discord, where oversized files
are linked instead.
Staff can use /investigate prompt:<instructions> in a staff-notes thread, or
click Ask Columbo again to enter an optional prompt. Retries include the
original issue and recent ticket messages, including mirrored email replies,
and bypass automatic triage. Set COLUMBO_URL and the shared INTERNAL_SECRET
to connect the bot to Columbo.
Automatic investigations wait for one minute of ticket-owner inactivity before
starting. Set COLUMBO_TICKET_QUIET_SECONDS to change the delay, or 0 to disable
it. Follow-up messages and supported attachments are fetched before triage;
text files and scripts are read as untrusted data, images are supplied to models
that support them, and no attachment is executed. Once an investigation starts,
new messages leave it running; only a manual request triggers another run.
The Columbo image embeds a bare Git repository, including packfiles, build HEAD,
release tags, and reachable commit history. CI fetches complete history for this
image. go-git loads a verified, bounded copy into an isolated memory store;
no runtime Git executable, checkout, or shell tool is needed. The LLM can list,
search, read, compare, and inspect history through read-only tools. Internal Git
mutations remain in memory and cannot change the embedded files.
The release index lists up to 128 tags; other embedded commits remain accessible by full hash. Source coverage is fixed at build time, and unavailable versions are never replaced with HEAD. Generated, binary, secret, and test files are excluded from tools, files are capped at 512 KiB, and history is capped at 100 entries per query. See Columbo for details.
To build a local Columbo binary with embedded Git data:
mise columbo:buildNew tickets include observed client and service versions from the last 30 days in their original staff message, independently of whether the LLM investigates. Missing versions and unavailable telemetry are reported explicitly. Columbo uses the same evidence when checking the affected release and later fixes.
Invite-drop claims reserve a spot for ten minutes. Claiming and redeeming run atomically so an in-progress signup keeps its spot. Personal staff invites are not recycled.
Expired-reservation recycling is disabled by default. First deploy the atomic
reservation and redemption implementation to every yucca-api replica, keeping
DISCORD_INVITE_RECYCLING_ENABLED=false. Wait until the rollout completes and
all requests handled by old replicas have drained. Then set
DISCORD_INVITE_RECYCLING_ENABLED=true on the API to revoke expired unused
reservations and return their spots to the pool. Enabling recycling while old
API replicas still serve requests can reclaim a signup that is in progress.
Disable recycling and finish that configuration rollout before rolling back to
an API version without atomic invite redemption.
Invite-drop messages show the remaining spots and refresh after claims and once a minute. When an unused reservation expires, a sold-out drop reopens; cancelled drops remain closed. Count updates do not repeat role mentions.