# shellcheck shell=bash # ════════════════════════════════════════════════════════════════════════════ # app.env — the ONE config for an app installed by the deploy/ framework # # Every script sources this file, so it is the single source of truth. # Copy it and edit the values: # # cp deploy/app.env.example /deploy/app.env # # It is plain bash: `KEY="value"`, and everything after # is a comment. # Paths may reference earlier keys (INSTALL_DIR uses ${APP_NAME}). # # A block below is only needed for the mode you actually use: fill the # "source mode" block OR the "binary mode" block, not both. # ════════════════════════════════════════════════════════════════════════════ # ── Identity ─────────────────────────────────────────────────────────────── APP_NAME="myapp" # short lower-case name: log prefix [myapp], unit name, default paths APP_DESC="My App — a service that does things" # one line for the systemd Description # ── Distribution ─────────────────────────────────────────────────────────── DIST="source" # "source" = git checkout, "binary" = release artifact # ── source mode ──────────────────────────────────────────────────────────── REPO_URL="" # git URL (https://...; file://... works too, e.g. for tests) CHECKOUT_POLICY="stable" # stable | latest | prerelease | (stable = newest tag without '-') # in binary mode a is rejected — a branch has no release asset # ── binary mode ──────────────────────────────────────────────────────────── # RELEASE_API is the LIST endpoint (…/releases?per_page=N), not /releases/latest: # /latest silently excludes prereleases and cannot express CHECKOUT_POLICY. RELEASE_API="" # unauthenticated = 60 req/hour/IP; a 403/429 tells you to pass --version RELEASES_BASE="" # download base; asset URL = ${RELEASES_BASE}/${TAG}/${ASSET} # Single quotes are REQUIRED: every script SOURCES this file, so double quotes # would expand ${APP_NAME}/${OS}/${ARCH}/${TAG} at load time and render_template() # would have nothing left to substitute. ASSET_PATTERN='${APP_NAME}-${TAG}-${OS}-${ARCH}.tar.gz' # may be a plain binary or a .tar.gz payload APP_VERSION_CMD='${BIN} --version' # must print "APP_NAME "; ${BIN} = the binary path # ── binary mode: payload shape ───────────────────────────────────────────── # The four lists describe THIS app's release tarball — its executable name, its # native libraries, its example files. The defaults are SynaptoMind's, so an app # with a different payload names its own here rather than editing framework code. # BINARY_SUPPORTED_PLATFORMS="linux-x86_64" # "os-arch" list, space separated # BINARY_REQUIRED_FILES="synaptomind vec0.so lib/libonnxruntime.so.1" # verified before anything is touched # BINARY_ROLLBACK_FILES="vec0.so lib/libonnxruntime.so.1 synaptomind" # kept as .prev for a no-git rollback # BINARY_SWAP_ORDER="vec0.so lib/libonnxruntime.so.1 config.json.example .env.example synaptomind" # ── Paths ────────────────────────────────────────────────────────────────── INSTALL_DIR="/opt/${APP_NAME}" # code/binary live here; empty -> /opt/${APP_NAME} DATA_DIR="/var/lib/${APP_NAME}" # persistent data; empty disables the ./data symlink RUN_DIR="" # state (scripts, hooks, config); empty -> ${HOME}/.${APP_NAME} # ── First-run seeding (copied only when the destination does NOT exist) ───── # An "example:dest" pair copies ${INSTALL_DIR}/ to . The source is # always resolved against INSTALL_DIR — the payload ships the examples — so only # the DESTINATION differs between the two lists. A seeded config.json has # "port"/"httpPort" aligned to PORT/MCP_PORT, on the seeding path only. SEED_FILES="config.json.example:config.json .env.example:.env" # "example:dest" pairs into INSTALL_DIR, space separated SEED_FILES_DATA="" # the same pairs, seeded into DATA_DIR instead — for an app whose config home is /var/lib/ rather than the payload tree (empty = off) CONFIG_LINK_NAME="" # bare FILE NAME (e.g. "config.json"); empty = no link. Seeds into DATA_DIR? Then this is how the app finds it: # ${INSTALL_DIR}/ -> ${DATA_DIR}/, created/refreshed AFTER seeding. Use it when the app resolves its # config relative to its WorkingDirectory (/opt/) and has no path flag — the link is what lets the config # live in DATA_DIR and survive a payload swap. An app that takes --settings / STATE_DIRECTORY / an # EnvironmentFile needs none. A real file at the link path is reported, never replaced. Name a dest in # ONE list only: in both, the install produces two files and the link step refuses to replace the # payload-tree one (with a warning naming the move that fixes it). GENERATE_SECRET_IN=".env" # add _SECRET= here when seeded (empty = off) # an ABSOLUTE path is used as given (e.g. "/etc/myapp/myapp.env"); a relative one resolves under INSTALL_DIR. Parent directories are created. # ── Runtime / service ────────────────────────────────────────────────────── SERVICE_USER="" # systemd User=; empty -> invoking user (SUDO_USER when run via sudo) SERVICE_GROUP="" # systemd Group=; empty -> TARGET_GROUP, which itself defaults to SERVICE_USER. Group= is ALWAYS rendered, so the line and the chown agree; the group must already exist EXEC_START="" # start command; empty -> mode-specific default (binary / bun run …) PORT="3000" # port written to the seeded config and used by the health check MCP_PORT="" # optional; port written to mcp.httpPort in a seeded config. Empty -> PORT + 1. Must differ from PORT HEALTH_URL="" # empty -> http://127.0.0.1:${PORT}/health HEALTH_TIMEOUT="60" # seconds to wait for a healthy service after (re)start HEALTH_CONFIRM_TIMEOUT="30" # second window, used only when the first expired with NO answer # ── Health contract ──────────────────────────────────────────────────────── # Which JSON keys wait_health() reads, and what counts as healthy. The defaults # are SynaptoMind's own /health shape; an app that answers {"ok":true} — no # status, no version — sets its own three keys instead of being reported as a # contract violation on a perfectly healthy service. # The field names must be plain JSON keys (letters, digits, _). They are put into # a pattern, and a name carrying `.` or `*` would match more than the field it names. HEALTH_STATUS_FIELD="status" # key carrying the state HEALTH_OK_VALUES="ok degraded" # values of that key that mean healthy; space separated HEALTH_VERSION_FIELD="version" # key carrying the version; EMPTY disables the version check (and warns once) # ── Unit extras (each rendered only when non-empty) ──────────────────────── # Needed by an app the fixed template cannot express. All three are guarded by # the same check as every other value that lands in a unit file. UNIT_STATE_DIRECTORY="" # e.g. "myapp" -> StateDirectory= + StateDirectoryMode=0700, exports STATE_DIRECTORY UNIT_ENV_FILE="" # e.g. "/etc/myapp/myapp.env" -> EnvironmentFile= UNIT_EXTRA_ENV="" # "K=V" pairs, SPACE separated, one Environment= line each (e.g. "BUN_RUNTIME_TRANSPILER_CACHE_PATH=0"). A TAB or a newline separates nothing — it is refused, because the guard reads those as a line break in the unit (task #1117) # ── source mode: dependencies & build ───────────────────────────────────── # Flags for `bun install`, run in INSTALL_DIR as the service user. Drop # --production when BUILD_CMD is set: a build needs its devDependencies, and # --production prunes exactly those. INSTALL_FLAGS="--frozen-lockfile --production" BUILD_CMD="" # e.g. "bun run build"; run in INSTALL_DIR as the service user. Empty = no build step BUILD_TIMEOUT="600" # seconds; a build that hangs aborts the install instead of hanging it # a non-zero BUILD_CMD aborts the install (a failed build is a missing artefact) # ── host requirements ────────────────────────────────────────────────────── REQUIRES_BUN="yes" # yes -> install Bun when missing and run `bun install` SYSTEM_DEP_CMDS="git tar" # OS packages the mode needs (source: "git"; binary: "tar" + git for the updater bootstrap) HOOKS_DIR="" # pre-update / post-update hooks; empty -> ${RUN_DIR}/hooks LIB_RAW_URL="" # curl|bash: lib/common.sh source; optional (defaults to the published deploy base) # ── curl|bash only (environment variables, not needed for a local checkout) ─ # In the piped form BOTH LIB_RAW_URL (above) and APP_ENV_URL are optional: # install.sh defaults them to the published deploy base (DEPLOY_RAW_URL). # Set either only to install from a fork or mirror. # APP_ENV_URL="https://example.com/myapp/app.env" # published app.env for the one-liner