ThreadCount Community edition

Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
ThreadCount
2026-09-13 08:45:19 +10:00
commit 1bc2de655a
505 changed files with 56223 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
# Every variable the application actually reads, so a rebuilt secrets file cannot silently drop one.
# On the server this is /etc/threadcount/secrets.env; locally it is .env.local.
#
# The variables beginning NEXT_PUBLIC_ are compiled into the browser bundle at build time, not read
# at runtime — set them before `next build`, and never put anything secret in one.
# ---- required. instrumentation.ts refuses to start production without these ----
DATABASE_URL=postgresql://user:pass@localhost:5432/threadcount
# Signs every session cookie. Generate one per environment (`openssl rand -base64 48`) and never
# ship the placeholder — production refuses to start while it still says change-me.
SESSION_SECRET=change-me
# Cloudflare Turnstile. Protects sign-in, sign-up, password reset, the contact form and the update
# list. Missing in production means either an unprotected front door or an authentication outage,
# so the server refuses to start without both.
TURNSTILE_SECRET=
NEXT_PUBLIC_TURNSTILE_SITEKEY=
# ---- transactional mail ----
# With these unset nothing is sent: a request is still raised, and the screens say plainly that
# nobody was emailed rather than claiming otherwise.
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM="ThreadCount <no-reply@threadcount.tech>"
# Where the website's contact form is delivered.
CONTACT_TO=
# ---- addresses and storage ----
# Absolute base URL, used for links in emails (approval links, password resets).
NEXT_PUBLIC_SITE_URL=https://threadcount.tech
# Where uploaded photographs are written. Must be on a volume that is backed up with the database.
PHOTO_DIR=/var/lib/threadcount/photos
# ---- Android app links ----
# The SHA-256 signing-certificate fingerprints served from /.well-known/assetlinks.json. Several may
# be listed per app, comma-separated. ANDROID_APP_FINGERPRINTS is the fallback for both.
ANDROID_APP_FINGERPRINTS_COUNTER=
ANDROID_APP_FINGERPRINTS_STAFF=
# ---- operations console (ops.threadcount.tech) ----
# Signs the operator session cookie (tc_ops). Its OWN secret, never SESSION_SECRET: that one also
# derives the key encrypting every customer's TOTP secret, so sharing it would mean a compromised
# operator credential forces a rotation that destroys every facility's second factor. Generate it
# the same way (`openssl rand -base64 48`). Only the console reads it; the product starts without it.
OPS_SESSION_SECRET=
# The console reads customer data through restricted Postgres roles, never the app role:
# ops_ro — SELECT on control-plane columns only (counts, dates, configuration).
# ops_reveal — SELECT on a facility's id and three coordinator contact columns, nothing else.
# Same host and database as DATABASE_URL, different user. Create the roles by hand as postgres;
# the grants are in prisma/migrations (ops_ro_grants, ops_reveal_grants).
OPS_DATABASE_URL=
OPS_REVEAL_DATABASE_URL=
# Single sign-on for FACILITIES (coordinators and, if a facility allows it, staff): the self-hosted
# BoxyHQ Jackson broker at sso.threadcount.tech holds each facility's IdP metadata. With either
# unset there is no SSO anywhere in the product; the key authenticates the management API only.
JACKSON_URL=
JACKSON_API_KEY=
# Single sign-on for the console: ops.threadcount.tech sits behind a Cloudflare Access application
# (Authentik + one-time-PIN). With BOTH set, the app verifies the Access assertion (RS256, fail
# closed) and mints the operator session from the verified email; an email Access admits but the
# console does not know falls back to the password door. With either unset there is no SSO.
CF_ACCESS_TEAM_DOMAIN=
CF_ACCESS_AUD=
# Every contact reveal emails a notice here (who, which facility, why, until when — never the
# contacts). Defaults to the revealing operator's own address.
OPS_ALERT_TO=
# ---- optional switches ----
# 1 hides the create-account form and refuses the signup endpoint.
SIGNUPS_DISABLED=
# 1 takes the public demo facility out of service.
DEMO_DISABLED=
# Bearer token the demo facility's scheduled reset presents.
DEMO_RESET_TOKEN=
# Local production-mode smoke tests ONLY: 1 makes Turnstile advisory so `next start` and the e2e
# runners work on a box with no Cloudflare keys. NEVER set this in /etc/threadcount/secrets.env.
TURNSTILE_OPTIONAL=
# ---- optional: analytics, errors, update list ----
# All three default to ThreadCount's own self-hosted infrastructure; set them only to point
# somewhere else.
NEXT_PUBLIC_UMAMI_SITE_ID=
NEXT_PUBLIC_UMAMI_APP_ID=
NEXT_PUBLIC_GLITCHTIP_DSN=
# Stamped on error reports so a fault can be tied to a deploy.
NEXT_PUBLIC_RELEASE=
LISTMONK_URL=
LISTMONK_LIST_UUID=
# ---- local development only ----
# 1 is required by the embedded `prisma dev` server, which cannot handle concurrent queries.
# Never set it in production: it serialises every database call in the app.
DB_POOL_MAX=
# Plans. 1 forces plans live on this box whatever the console switch says: new sign-ups start on
# Hosted Small (60 staff records) and Settings shows the Plan tab. Leave unset; flip it from the
# console once the notice to existing rooms has run.
# PLANS_LIVE=1
# ---- edition ----
# "community" = the self-hosted edition (Dockerfile / docker-compose.yml): every feature, no plans,
# no staff ceiling, no demo, no operations console, no error reports or usage statistics sent
# anywhere, and Turnstile optional. Leave unset on threadcount.tech.
EDITION=
# Only docker-compose.yml reads this: the password of the bundled Postgres.
POSTGRES_PASSWORD=