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
+108
View File
@@ -0,0 +1,108 @@
# Self-hosting ThreadCount (Community edition)
The Community edition is the hosted product with `EDITION=community` in its environment. This
page says what that changes, how to run it, how to keep it, and what is not included.
## What `EDITION=community` changes
| | Hosted (threadcount.tech) | Community |
|---|---|---|
| Features | All | All |
| Plans, staff ceiling, read-only for non-payment | Yes | None. Every facility behaves as a grandfathered one. |
| Public website at `/` | The marketing site | Redirects to sign-in |
| Demo facility | Yes | Off |
| Operations console (`ops.` hostname) | Yes | Off unless `OPS_SESSION_SECRET` and the ops database roles are set up |
| Cloudflare Turnstile on sign-in and sign-up | Required | Optional. Set both keys to enforce it; without it the per-address rate limits stand alone. |
| Error reports | To ThreadCount's own GlitchTip, scrubbed | Nowhere, unless you set `NEXT_PUBLIC_GLITCHTIP_DSN` to your own |
| Usage statistics | To ThreadCount's own Umami, scrubbed | Nowhere. The tracker only mounts on `*.threadcount.tech`. |
| Single sign-on for a facility | Via ThreadCount's Jackson broker | Run your own [BoxyHQ Jackson](https://boxyhq.com/docs/jackson/overview) and set `JACKSON_URL` and `JACKSON_API_KEY`; otherwise no SSO |
| Transactional email | ThreadCount's SMTP | Your SMTP, or none |
Everything the legal pages on threadcount.tech say applies to the hosted service. Your instance
is yours: publish your own privacy statement for your staff. The pages under `/privacy`,
`/terms` and so on still render on a Community instance, but they describe threadcount.tech.
## Running it
`docker-compose.yml` runs three services: `db` (Postgres 16, in a named volume), `migrate` (a
one-shot `prisma migrate deploy` that runs before the app on every `up`), and `app` (Next.js's
standalone server, port 3000, photographs in a second named volume). Put TLS in front of port
3000. A minimal Caddyfile:
```
uniforms.example.health {
reverse_proxy 127.0.0.1:3000
}
```
The image is built on your machine, not pulled, because `NEXT_PUBLIC_SITE_URL` and the optional
Turnstile site key are compiled into the browser bundle. If you change either, rebuild:
`docker compose up -d --build`.
### The first facility
Open the hostname. Create an account; that creates a facility and makes you its administrator.
Under Settings, name your staff groups first (nothing works until a facility has them), then load
the catalogue, departments, staff register, barcodes and opening stock from CSV under Settings
Data. Add a second administrator under Settings Account Users before you sign out — a
forgotten password with one admin and no SMTP locks the facility.
Then set `SIGNUPS_DISABLED=1` in `.env` and `docker compose up -d`. Your instance is now one
facility, and nobody else can create another.
## Upgrading
```sh
git pull
docker compose up -d --build
```
`migrate` applies any new schema migrations before the new app starts; the old app keeps
serving until then. Take a backup first (below). Releases that need more than that say so in
their notes.
## Backups
`docker/backup.sh <directory>` writes one directory per run: the database as a custom-format
`pg_dump` and the photographs as a tarball, keeping the last fourteen. Run it nightly from cron:
```
30 3 * * * cd /srv/threadcount && docker/backup.sh /srv/backups/threadcount >> /var/log/threadcount-backup.log 2>&1
```
Copy that directory somewhere off the machine. A backup on the same disk as the database is not a
backup.
Separately, an administrator can download the whole facility as one JSON file from Settings
Data at any time, and restore it into a fresh instance from the same screen. That file is the
portable form; the `pg_dump` is the fast one.
### Restoring
```sh
docker compose down
docker volume rm threadcount_db threadcount_photos # only if you mean to replace everything
docker compose up -d db
docker compose exec -T db pg_restore -U threadcount -d threadcount --clean --if-exists < 2026-09-13-033001/threadcount.dump
docker compose up -d
docker compose cp 2026-09-13-033001/photos app:/data/ # after extracting photos.tgz
```
## The Android apps
The two Play apps (ThreadCount, the counter; ThreadCount Staff) load their screens from
`https://threadcount.tech` and cannot be pointed at another host. On a self-hosted instance use
the same screens in a phone's browser: `https://your-host/m` for the counter (camera scanning
works in Chrome and Edge) and `https://your-host/my` for staff. Add either to the home screen.
## Environment reference
`.env.example` lists every variable the application reads, with a comment on each. The ones that
matter for a Community instance are `SESSION_SECRET`, `POSTGRES_PASSWORD`, `NEXT_PUBLIC_SITE_URL`,
`EDITION`, the `SMTP_*` group, and `SIGNUPS_DISABLED`. Leave the operations console, Cloudflare
Access, Listmonk, Umami and GlitchTip variables empty unless you run those yourself.
## Getting help
Open an issue on the repository. If you would rather someone else ran it, that is what
[threadcount.tech/pricing](https://threadcount.tech/pricing) is for.