# workshop configuration template. # # Copy to /opt/smart-home/workshop/workshop.env on the container host, fill in, and # chmod 600 it. Same never-commit handling as every other *.env here. # --------------------------------------------------------------------------- # Auth — required. Fails closed (rejects every request) while empty. # openssl rand -hex 32 # --------------------------------------------------------------------------- WORKSHOP_TOKEN= # --------------------------------------------------------------------------- # Storage. THE SPLIT THAT MATTERS: the database is the index, the workspace is the # filing cabinet. Queryable state (projects, parts, decisions) goes in SQLite; # artefacts (datasheets, generated diagrams, photos, notes you want to open from a # laptop) are ordinary files under the workspace, which is exported read-write over # SMB. See docs/workshop-assistant.md. # # The workspace is the ONE writable share in this stack — the photo gallery share is # deliberately read-only — so it gets its own volume and its own SMB account. Nothing # on the host ever executes anything found in it. # --------------------------------------------------------------------------- WORKSHOP_DB_PATH=/data/workshop.db WORKSHOP_WORKSPACE_DIR=/workspace # --------------------------------------------------------------------------- # Run behaviour # --------------------------------------------------------------------------- # 8098 is identity_web — see CoreSystemConfig.json's ports block, which the validator # checks for duplicates. WORKSHOP_PORT=8102 LOG_LEVEL=INFO # --------------------------------------------------------------------------- # Knowledge store — the second database, and the one that is NEVER pruned. # # workshop.db is a record of WORK (projects, decisions, the log). This is a record of # WHAT IS TRUE: standing instructions about how you work, facts and specs by keyword, # what was learned about a project, and the hardware inventory. Every other store in # this stack has a retention window because stale observations are worse than none; # this one is the opposite — the whole point is that you tell it once. There is no # cutoff and no cleanup pass, by design. # --------------------------------------------------------------------------- WORKSHOP_KNOWLEDGE_DB_PATH=/data/workshop-knowledge.db # --------------------------------------------------------------------------- # Gitea (optional; repo creation is refused while unset). # # Lets a project be given a git repository. The service CREATES repos and reports # them — it never pushes, commits or clones, and it has no delete path at all, even # though the token technically permits one. # # The token: Gitea → Settings → Applications → Generate New Token, scope # `write:repository` (plus `write:user` if GITEA_OWNER is left empty so repos are made # under the token's own account). # # STRONGLY PREFERRED: a dedicated Gitea user ("workshop-bot") with access to ONE # organisation, named in GITEA_OWNER — rather than a token on your own account, which # can reach everything you own. The blast radius of a leaked env file is then one org # of generated repos. # --------------------------------------------------------------------------- GITEA_URL= GITEA_TOKEN= GITEA_OWNER= # Generated repos are private unless this is explicitly "false". GITEA_PRIVATE_REPOS=true GITEA_TIMEOUT=15 # --------------------------------------------------------------------------- # Git. The assistant commits and pushes unattended; it NEVER rewrites history. # # Allowed: commit, push, branch, tag, merge — everything that ADDS to history, because # the worst case is a bad commit you revert. # Never, by any path: force-push, rebase, amend, reset --hard, filter-branch, # filter-repo, branch/tag deletion, reflog expiry, gc --prune. Rewriting has no undo. # # That refusal lives in git_ops.py, but the real enforcement is Gitea BRANCH # PROTECTION, applied automatically when a repo is created (enable_force_push: false, # enable_delete: false). Client-side refusal is a policy; branch protection is a # control. Check the create-repo response's `repo.protection.applied` — do not assume # it is on because the repo exists. # # When history genuinely has to be scrubbed (a committed API token), POST # /projects//scrub-request returns the commands to run BY HAND from the repo on # the SMB share. The manual step is the safety mechanism; automating it would remove # the only thing making it safe. # # The working tree is code/ inside the project workspace — never the workspace root — # so no git operation can reach the datasheets and photos beside it. # --------------------------------------------------------------------------- WORKSHOP_GIT_AUTHOR_NAME=workshop assistant WORKSHOP_GIT_AUTHOR_EMAIL=workshop@localhost WORKSHOP_GIT_BRANCH=main WORKSHOP_GIT_TIMEOUT=120 # --------------------------------------------------------------------------- # Infrastructure health — CheckMK + every OPNsense firewall, polled here. # # WHY HERE AND NOT IN digest-engine: the digest runs four times a day, and "is the NAS # disk failing right now" is not a question with a six-hour answer. This service is # always on, so it polls and keeps ~30 days of samples; digest-engine reads THIS over # HTTP (its ENABLE_INFRA_HEALTH_INGEST). One poller, two consumers — and the digest # gets "critical since Tuesday" instead of a snapshot it can't compare to anything. # # READ-ONLY, and not by promise: the CheckMK user should have the Guest role, which # cannot acknowledge, downtime or reschedule. From OPNsense this reads exactly one # endpoint (GET /api/ids/service/status — is Suricata running); the alert query stays # in digest-engine, which already does the paging properly. # # OPNSENSE_JSON is written by tools/config-export.py from the `opnsense` LIST in # CoreSystemConfig.json — every firewall carries its own name, and that name reaches # the digest and the display, so "the IDS is running" can never stand in for two # firewalls of which one is down. # --------------------------------------------------------------------------- CHECKMK_BASE_URL= CHECKMK_SITE=cmk CHECKMK_USERNAME= CHECKMK_SECRET= CHECKMK_ONLY_PROBLEMS=true CHECKMK_MAX_ROWS=200 OPNSENSE_JSON= WORKSHOP_HEALTH_INTERVAL_SECONDS=300 WORKSHOP_HEALTH_TIMEOUT=20 WORKSHOP_HEALTH_RETENTION_DAYS=30 # --------------------------------------------------------------------------- # Network cameras the workshop display may show. # # The USB webcams in this project are fixed-purpose (a kitchen item held to a lens, a # doorway) and their angles are useless for anything else — so what the workshop shows # is the NETWORK cameras, via the go2rtc/Frigate this stack already runs. # # Format: name:stream_id[,name:stream_id...] where stream_id is the camera's name in # go2rtc. The frontend builds a WebRTC/MSE URL from it; nothing here proxies video, # because putting a Python HTTP server in the path of an H.264 stream is how you turn # a working camera into a stuttering one. # --------------------------------------------------------------------------- WORKSHOP_CAMERAS= # Base URL of go2rtc's own web API, reachable from the BROWSER (not from this # container) — the kiosk connects to it directly. GO2RTC_URL=