This folder contains the template system for Yundera Personal Cloud Server (PCS) provisioning, including first-install bootstrap, periodic self-checks, and continuous template/app updates. Hypervisor-specific behaviour (LVM partition carving, qemu-guest-agent, CPU/RAM hotplug) is gated at runtime by is_proxmox_host in library/common.sh, which inspects the actual disk layout — no .pcs.env-style provider tag is needed.
This template deploys two compose stacks of its own. The mesh, auth, maison and
terminal stacks belong to the stock mesh-router-template-root, which
ensure-mesh-installed.sh installs into /DATA/AppData/mesh (see
doc/mesh-stock-switch.md), and each of those has its own README in that repo. Every
stack's README sits next to its compose file, and the self-check copies it into the
stack's folder on the box.
| Stack | Services | Source | On the box |
|---|---|---|---|
yundera |
admin (AppShield gate), admin-app | root/docker-compose.yml |
/DATA/AppData/yundera — README |
kopia |
kopia (gate), kopia-app, kopia-engine | root/template/stacks/kopia/ |
/DATA/AppData/kopia — README |
This template also adds the Yundera Login Dex connector to the mesh template's
auth stack, as a drop-in at /DATA/AppData/auth/dex/connectors.d/yundera.yaml. See
the yundera stack README.
root/ # Delivered to /DATA/AppData/yundera
├── docker-compose.yml # `yundera` stack (admin gate + admin-app) — rsynced to the root
├── README.md # the yundera stack's README — rsynced beside it
├── icon.svg # → .icon.svg, the Maison tile
└── template/ # → /DATA/AppData/yundera/template (rsync --delete, nothing else)
├── .pcs.env.example .pcs.secret.env.example .ynd.user.env.example
├── dex-theme/ # branded Dex login theme — the mesh template renders it (DEX_THEME_SRC, library/mesh.sh)
├── caddy/ # pre-switch Caddyfile, kept until every box has switched (doc/mesh-stock-switch.md)
├── stacks/kopia/ # the kopia stack (compose, icon, README)
└── scripts/
├── pcs-init.sh # First-install bootstrap (fetched from jsDelivr by the orchestrator)
├── self-check.sh # Core iterator over scripts-config.txt (used by nightly cron + admin app)
├── self-check-reboot.sh # @reboot wrapper: lock + self-check + compose restart
├── self-check/ # Idempotent ensure-*.sh scripts + scripts-config.txt (order matters)
├── migrations/ # Version-bump update scripts (run before each template rsync)
├── tools/ # CLI / one-shot utilities (os-init, run-migrations, env-file-manager, ...)
└── library/ # Sourced helpers (common.sh, log.sh, env.sh, mesh.sh, stacks.sh, kopia.sh)
The provisioning inputs .pcs.env, .pcs.secret.env and .ynd.user.env live in
/DATA/AppData/yundera/, next to the compose file. They are written by the
orchestrator and ensure-yundera-user-data.sh, not shipped.
Orchestrator-side bootstrap, fetched from jsDelivr at create time and run once per fresh Ubuntu host. Assumes .pcs.env and .pcs.secret.env are already staged. Fetches the template tree via tools/template-download.sh, then hands off to tools/os-init.sh. See pcs-orchestrator/src/library/provisioning/runHostBootstrap.ts.
Core iterator that runs every script listed in self-check/scripts-config.txt. Two-pass over the config (handles mid-run replacement by ensure-template-sync.sh). Used by the nightly cron, the admin container (settings-center-app), and self-check-reboot.sh.
Thin @reboot wrapper. Holds flock on /var/run/yundera-self-check.lock across both self-check.sh and the user compose stack restart. Honours PCS_PROVISIONING=1 (set by os-init.sh) for fail-fast first-run behaviour; otherwise masks errors with || true.
Idempotent ensure-scripts. Order is controlled by scripts-config.txt — there is no auto-discovery. See self-check/README.MD.
Filename-ordered scripts run from the newly-downloaded template tree before rsync (so they can preflight state for an unreleased version). A failure aborts template sync. See migrations/README.md.
Utility scripts invoked by other parts of the system or operators: os-init.sh, run-migrations.sh, env-file-manager.sh, template-download.sh, etc.
.pcs.env: PCS configuration variables (system settings,UPDATE_URL, default service host/port).pcs.secret.env: Secret environment variables (JWT tokens, passwords, signatures).ynd.user.env: User-specific data (UID, domain, email, username, public IP)
ensure-env-vars-valid.sh assembles these into the unified /DATA/AppData/yundera/.env, which every stack and mirror script reads on each self-check tick. Always mutate them via tools/env-file-manager.sh — never raw sed/grep.
The environment files support the following configuration options:
| Variable | Description | File | Default |
|---|---|---|---|
DOMAIN |
User's domain name | .ynd.user.env |
Required |
UID |
User ID | .ynd.user.env |
Required |
EMAIL |
User email address | .ynd.user.env |
Required |
USERNAME |
System username | .ynd.user.env |
Required |
PUBLIC_IPV4 |
Public IPv4 address (if available) | .pcs.env |
Optional |
PUBLIC_IPV6 |
Public IPv6 address (if available) | .pcs.env |
Optional |
USER_JWT |
Yundera JWT token | .pcs.secret.env |
Required |
DEFAULT_PWD |
Default password | .pcs.secret.env |
Required |
PROVIDER_STR |
Provider configuration string | .pcs.secret.env |
Required |
UPDATE_URL |
Template zip URL for updates (or literal local for dev) |
.pcs.env |
https://github.com/Yundera/template-root/archive/refs/heads/stable.zip |
DEFAULT_SERVICE_HOST |
Catch-all target for custom domains (see Caddyfile docs) | .pcs.env |
casaos |
DEFAULT_SERVICE_PORT |
Catch-all target port | .pcs.env |
8080 |
The UPDATE_URL variable must be a direct zip file URL for downloading template updates:
- Official template (stable):
https://github.com/Yundera/template-root/archive/refs/heads/stable.zip(default) - Tracking main:
https://github.com/Yundera/template-root/archive/refs/heads/main.zip - Custom fork:
https://github.com/your-username/template-root/archive/refs/heads/main.zip - Any zip URL: Must end with
.zipand contain a valid template structure - Dev escape hatch:
local— skips download/rsync and runs migrations against the in-place tree (used by thedev/test container)
Requirements:
- URL must end with
.zip(thelocalliteral is the only exception) - Zip file must contain a
root/directory with the template structure - If
UPDATE_URLis unset, the defaultstable.zipURL is used
This folder is designed to be:
- Fetched from jsDelivr by the orchestrator on first install (via
pcs-init.sh). - Deployed to
/DATA/AppData/yundera/on each PCS host. - Kept current by ongoing self-check + template-sync runs (no operator action required).
The template tree is intentionally mutable after first install — every self-check tick can pull a newer version of itself and re-render the user stack.
- @reboot cron —
self-check-reboot.sh(installed byensure-self-check-at-reboot.sh) - Nightly cron —
self-check.sh(installed byensure-nightly-self-check.sh) - Admin container —
settings-center-appinvokesself-check-reboot.shon its own schedule
All triggers run the same three-step sequence at the end of scripts-config.txt:
-
self-check/ensure-template-sync.sh(RUNS FIRST) DownloadsUPDATE_URL, runs migrations from the new tree first, then rsyncs thetemplate/subtree over/DATA/AppData/yundera/template/(plus the root compose file and icon). A failed migration aborts the sync and restores the previous tree from backup. -
self-check/ensure-user-compose-pulled.shdocker compose pullfor the user stack — fetches image versions pinned by the new template. -
self-check/ensure-user-compose-stack-up.shdocker compose up -dto apply.
This sequence covers the yundera stack only (the admin app). The platform stacks — mesh, auth, kopia, maison, terminal — are deployed from template/stacks/<name>/ by their own ensure-<name>-stack.sh through tools/deploy-stack.sh; see scripts-config.txt for their order. User apps installed from the AppStore are not touched by the self-check chain — they run under Docker's own restart: policy and are updated from the CasaOS (or Maison) UI.
Earlier entries in scripts-config.txt are host-level prerequisites (user, ssh, partitions, swap, docker install, public IP detection, auth secrets, cron installation, etc.).
The admin container (settings-center-app) runs additional health checks against the running stack after each periodic self-check tick.
All scripts follow a "quiet success, verbose failure" principle: show only essential information during normal operation, but provide comprehensive debugging details when things go wrong.
1. Users care about outcomes, not process
- Show final status:
✓ migration.sh completedor✓ migration.sh (skipped - already applied) - Hide process messages: Don't log "Found file", "Running", "Starting", "Processing N files"
- Exception: Show actual work output for one-shot operations that change the system
2. Failed operations need full context
- Capture all output when commands fail, then display it for debugging
- Include exit codes, file information, and retry suggestions
- Example:
apt-getruns silently but shows full output on failure
3. Progressive disclosure of information
- Start with minimal output during success
- Add layers of detail only when failures occur
- Use output redirection (
>/dev/null 2>&1) for background operations, capture for error display
# Good: Quiet success, verbose failure
if ! command >/dev/null 2>&1; then
echo "Error: Command failed, running with verbose output:"
command # Show full output for debugging
exit 1
fi
# Bad: Always verbose
echo "Running command..."
command
echo "Command completed"Migration execution:
- Success:
✓ migrate-env.sh (skipped - already applied) - Failure: Shows full migration output with error details
Package installation:
- Success:
Installing required tools...(single line) - Failure: Full
apt-getoutput with package conflicts
Template sync:
- Success:
Template sync completed - Failure: Full
rsyncoutput showing which files failed
This philosophy prioritizes user experience: clean logs that focus on what matters, with comprehensive debugging when needed.