Configuration
shithub uses a layered configuration loader. Sources, in increasing-precedence order:
- Built-in defaults. Encoded in
internal/infra/config/Defaults(). - TOML file. Path comes from
SHITHUB_CONFIGenv var, falling back to/etc/shithub/config.toml. Absence is fine; bad syntax is a hard error. - Environment variables.
SHITHUB_<area>__<key>(double-underscore separates nested keys). Examples below. - CLI flag overrides. Passed in by the caller (mostly
--addrfromshithubd web).
After all four merge, config.Load applies a small set of named aliases (e.g. SHITHUB_DATABASE_URL → db.url) for backward compatibility, then runs Validate. Any validation failure causes shithubd to exit non-zero with a one-line error pointing at the offending key.
Inspecting the active configuration
shithubd config print # writes the resolved config as TOML, with secrets redacted
shithubd config validate # exits non-zero if the resolved config is invalid
shithubd version # includes a one-line summary of which sinks are configured
config print redacts any field whose name contains password, pass, secret, key, token, dsn, or url (URL fields are redacted because they often carry credentials in the userinfo component).
Reference
| Key | Type | Default | Notes |
|---|---|---|---|
env |
string | dev |
One of `dev |
web.addr |
string | :8080 |
Listen address. |
web.read_timeout |
duration | 30s |
Per-request read timeout. |
web.write_timeout |
duration | 30s |
Per-request write timeout. |
web.shutdown_timeout |
duration | 10s |
Graceful drain on SIGTERM. |
db.url |
string | "" |
Postgres DSN. Aliased by SHITHUB_DATABASE_URL. |
db.max_conns |
int | 10 |
pgxpool max conns. |
db.min_conns |
int | 0 |
pgxpool min conns. |
db.connect_timeout |
duration | 5s |
|
log.level |
string | info |
One of `debug |
log.format |
string | text |
One of `text |
metrics.enabled |
bool | true |
Mounts /metrics. |
metrics.basic_auth_user |
string | "" |
When set together with pass, gate /metrics behind HTTP Basic. |
metrics.basic_auth_pass |
string | "" |
|
tracing.enabled |
bool | false |
When true, tracing.endpoint is required. |
tracing.endpoint |
string | "" |
OTLP HTTP endpoint, e.g. http://otel-collector:4318. |
tracing.sample_rate |
float | 0.05 |
Parent-based ratio sampler in [0, 1]. |
tracing.service_name |
string | shithubd |
OTel resource attribute. |
error_reporting.dsn |
string | "" |
Sentry-protocol DSN (works against GlitchTip). Empty disables. |
error_reporting.environment |
string | "" |
Tag for filtering events. |
error_reporting.release |
string | "" |
Tag for filtering events. |
session.key_b64 |
string | "" |
Base64 32-byte AEAD key. Aliased by SHITHUB_SESSION_KEY. |
session.max_age |
duration | 720h |
Cookie session lifetime (30 days). |
session.secure |
bool | false |
Set Secure cookie attribute. Enable under TLS (S37 deploy). |
storage.repos_root |
string | /data/repos |
Filesystem root for bare repos. Required. |
storage.s3.endpoint |
string | "" |
S3-compatible endpoint host[:port], no scheme. Empty disables S3. |
storage.s3.region |
string | us-east-1 |
Region for SigV4 signing. |
storage.s3.access_key_id |
string | "" |
|
storage.s3.secret_access_key |
string | "" |
Redacted by config print. |
storage.s3.bucket |
string | "" |
Single bucket per environment. |
storage.s3.use_ssl |
bool | false |
True for Spaces, false for local MinIO. |
storage.s3.force_path_style |
bool | true |
True for MinIO, false for Spaces. |
auth.require_email_verification |
bool | true |
When true, login is rejected until the primary email is verified. |
auth.base_url |
string | http://127.0.0.1:8080 |
Used for absolute links in transactional emails. |
auth.site_name |
string | shithub |
Branding token for email subjects/bodies. |
auth.email_from |
string | shithub <noreply@shithub.local> |
Envelope From for outgoing email. |
auth.email_backend |
string | stdout |
One of `stdout |
auth.smtp.addr |
string | 127.0.0.1:1025 |
Required when email_backend=smtp. |
auth.smtp.username |
string | "" |
Optional SMTP auth username. |
auth.smtp.password |
string | "" |
Optional SMTP auth password. Redacted by config print. |
auth.postmark.server_token |
string | "" |
Required when email_backend=postmark. Redacted. |
auth.resend.api_key |
string | "" |
Required when email_backend=resend. Redacted. |
auth.argon2.memory_kib |
uint32 | 65536 |
argon2id memory cost (KiB). |
auth.argon2.time |
uint32 | 3 |
argon2id iterations. |
auth.argon2.threads |
uint8 | 2 |
argon2id parallelism. |
auth.totp_key_b64 |
string | "" |
Base64 32-byte AEAD key for at-rest TOTP secrets. Aliased by SHITHUB_TOTP_KEY. Empty disables 2FA enrollment routes. |
billing.enabled |
bool | false |
Enables paid-organization Stripe Billing flows. When false, org plan state is local-only. |
billing.grace_period |
duration | 336h |
Lock grace window applied after failed subscription payments. |
billing.stripe.secret_key |
string | "" |
Stripe secret API key. Required when billing.enabled=true. Redacted. |
billing.stripe.webhook_secret |
string | "" |
Stripe webhook signing secret. Required when billing.enabled=true. Redacted. |
billing.stripe.team_price_id |
string | "" |
Stripe recurring Price ID for the Team plan seat. Required when billing.enabled=true. |
billing.stripe.success_url |
string | "" |
Optional absolute Checkout success URL override. Empty derives from auth.base_url. Redacted by config print. |
billing.stripe.cancel_url |
string | "" |
Optional absolute Checkout cancel URL override. Empty derives from auth.base_url. Redacted by config print. |
billing.stripe.portal_return_url |
string | "" |
Optional absolute Billing Portal return URL override. Empty derives from auth.base_url. Redacted by config print. |
billing.stripe.automatic_tax |
bool | false |
Enables Stripe Checkout automatic tax collection when the Stripe account is configured for it. |
Env-var examples
# Listen elsewhere
export SHITHUB_WEB__ADDR=:9090
# Connect to Postgres
export SHITHUB_DATABASE_URL=postgres://shithub:dev@127.0.0.1:5432/shithub?sslmode=disable
# (equivalent: export SHITHUB_DB__URL=...)
# JSON logs for prod
export SHITHUB_LOG__FORMAT=json
export SHITHUB_LOG__LEVEL=info
# Enable tracing
export SHITHUB_TRACING__ENABLED=true
export SHITHUB_TRACING__ENDPOINT=http://otel-collector.bare-metal:4318
export SHITHUB_TRACING__SAMPLE_RATE=0.05
# Error reporting via GlitchTip
export SHITHUB_ERROR_REPORTING__DSN=https://glitchtip.bare-metal/<project-id>
# Session signing key (deterministic across restarts in prod)
export SHITHUB_SESSION_KEY=$(openssl rand -base64 32)
# Gate /metrics behind Basic auth
export SHITHUB_METRICS__BASIC_AUTH_USER=prom
export SHITHUB_METRICS__BASIC_AUTH_PASS=<long-random>
# Enable Stripe Billing in test mode
export SHITHUB_BILLING__ENABLED=true
export SHITHUB_BILLING__STRIPE__SECRET_KEY=sk_test_...
export SHITHUB_BILLING__STRIPE__WEBHOOK_SECRET=whsec_...
export SHITHUB_BILLING__STRIPE__TEAM_PRICE_ID=price_...
Secrets
- Never commit secrets.
.envis gitignored; production keys live in a systemdEnvironmentFile=with mode0600. - The redaction behavior of
config printis documented above and tested ininternal/infra/config/config_test.go. If you add a new secret-bearing field, name it so the redactor matches it (containingpass,secret,key,token,dsn,password, orurl) — or extendsecretFieldNamesininternal/infra/config/redact.go. - Log-line redaction is independent (see
docs/internal/observability.md). Both layers exist on purpose; secrets in env-loaded config and in handler-emitted logs travel different paths.
Adding a new key
- Add the field to the appropriate config struct in
internal/infra/config/config.gowith atoml:tag. - Set its default in
Defaults(). - Add validation in
Validate()if it has invariants. - If it's secret-bearing, confirm its name matches the redactor.
- Document it in this file.
- Update
.env.exampleif the env-var form is the typical usage.
View source
| 1 | # Configuration |
| 2 | |
| 3 | shithub uses a layered configuration loader. Sources, in increasing-precedence order: |
| 4 | |
| 5 | 1. **Built-in defaults.** Encoded in `internal/infra/config/Defaults()`. |
| 6 | 2. **TOML file.** Path comes from `SHITHUB_CONFIG` env var, falling back to `/etc/shithub/config.toml`. Absence is fine; bad syntax is a hard error. |
| 7 | 3. **Environment variables.** `SHITHUB_<area>__<key>` (double-underscore separates nested keys). Examples below. |
| 8 | 4. **CLI flag overrides.** Passed in by the caller (mostly `--addr` from `shithubd web`). |
| 9 | |
| 10 | After all four merge, `config.Load` applies a small set of named **aliases** (e.g. `SHITHUB_DATABASE_URL` → `db.url`) for backward compatibility, then runs `Validate`. Any validation failure causes `shithubd` to exit non-zero with a one-line error pointing at the offending key. |
| 11 | |
| 12 | ## Inspecting the active configuration |
| 13 | |
| 14 | ```sh |
| 15 | shithubd config print # writes the resolved config as TOML, with secrets redacted |
| 16 | shithubd config validate # exits non-zero if the resolved config is invalid |
| 17 | shithubd version # includes a one-line summary of which sinks are configured |
| 18 | ``` |
| 19 | |
| 20 | `config print` redacts any field whose name contains `password`, `pass`, `secret`, `key`, `token`, `dsn`, or `url` (URL fields are redacted because they often carry credentials in the userinfo component). |
| 21 | |
| 22 | ## Reference |
| 23 | |
| 24 | | Key | Type | Default | Notes | |
| 25 | |---|---|---|---| |
| 26 | | `env` | string | `dev` | One of `dev | staging | prod`. Drives default log format and Sentry environment. | |
| 27 | | `web.addr` | string | `:8080` | Listen address. | |
| 28 | | `web.read_timeout` | duration | `30s` | Per-request read timeout. | |
| 29 | | `web.write_timeout` | duration | `30s` | Per-request write timeout. | |
| 30 | | `web.shutdown_timeout` | duration | `10s` | Graceful drain on SIGTERM. | |
| 31 | | `db.url` | string | `""` | Postgres DSN. Aliased by `SHITHUB_DATABASE_URL`. | |
| 32 | | `db.max_conns` | int | `10` | pgxpool max conns. | |
| 33 | | `db.min_conns` | int | `0` | pgxpool min conns. | |
| 34 | | `db.connect_timeout` | duration | `5s` | | |
| 35 | | `log.level` | string | `info` | One of `debug | info | warn | error`. | |
| 36 | | `log.format` | string | `text` | One of `text | json`. | |
| 37 | | `metrics.enabled` | bool | `true` | Mounts `/metrics`. | |
| 38 | | `metrics.basic_auth_user` | string | `""` | When set together with `pass`, gate `/metrics` behind HTTP Basic. | |
| 39 | | `metrics.basic_auth_pass` | string | `""` | | |
| 40 | | `tracing.enabled` | bool | `false` | When true, `tracing.endpoint` is required. | |
| 41 | | `tracing.endpoint` | string | `""` | OTLP HTTP endpoint, e.g. `http://otel-collector:4318`. | |
| 42 | | `tracing.sample_rate` | float | `0.05` | Parent-based ratio sampler in [0, 1]. | |
| 43 | | `tracing.service_name` | string | `shithubd` | OTel resource attribute. | |
| 44 | | `error_reporting.dsn` | string | `""` | Sentry-protocol DSN (works against GlitchTip). Empty disables. | |
| 45 | | `error_reporting.environment` | string | `""` | Tag for filtering events. | |
| 46 | | `error_reporting.release` | string | `""` | Tag for filtering events. | |
| 47 | | `session.key_b64` | string | `""` | Base64 32-byte AEAD key. Aliased by `SHITHUB_SESSION_KEY`. | |
| 48 | | `session.max_age` | duration | `720h` | Cookie session lifetime (30 days). | |
| 49 | | `session.secure` | bool | `false` | Set `Secure` cookie attribute. Enable under TLS (S37 deploy). | |
| 50 | | `storage.repos_root` | string | `/data/repos` | Filesystem root for bare repos. Required. | |
| 51 | | `storage.s3.endpoint` | string | `""` | S3-compatible endpoint host[:port], no scheme. Empty disables S3. | |
| 52 | | `storage.s3.region` | string | `us-east-1` | Region for SigV4 signing. | |
| 53 | | `storage.s3.access_key_id` | string | `""` | | |
| 54 | | `storage.s3.secret_access_key` | string | `""` | Redacted by `config print`. | |
| 55 | | `storage.s3.bucket` | string | `""` | Single bucket per environment. | |
| 56 | | `storage.s3.use_ssl` | bool | `false` | True for Spaces, false for local MinIO. | |
| 57 | | `storage.s3.force_path_style` | bool | `true` | True for MinIO, false for Spaces. | |
| 58 | | `auth.require_email_verification` | bool | `true` | When true, login is rejected until the primary email is verified. | |
| 59 | | `auth.base_url` | string | `http://127.0.0.1:8080` | Used for absolute links in transactional emails. | |
| 60 | | `auth.site_name` | string | `shithub` | Branding token for email subjects/bodies. | |
| 61 | | `auth.email_from` | string | `shithub <noreply@shithub.local>` | Envelope From for outgoing email. | |
| 62 | | `auth.email_backend` | string | `stdout` | One of `stdout | smtp | postmark | resend`. | |
| 63 | | `auth.smtp.addr` | string | `127.0.0.1:1025` | Required when `email_backend=smtp`. | |
| 64 | | `auth.smtp.username` | string | `""` | Optional SMTP auth username. | |
| 65 | | `auth.smtp.password` | string | `""` | Optional SMTP auth password. Redacted by `config print`. | |
| 66 | | `auth.postmark.server_token` | string | `""` | Required when `email_backend=postmark`. Redacted. | |
| 67 | | `auth.resend.api_key` | string | `""` | Required when `email_backend=resend`. Redacted. | |
| 68 | | `auth.argon2.memory_kib` | uint32 | `65536` | argon2id memory cost (KiB). | |
| 69 | | `auth.argon2.time` | uint32 | `3` | argon2id iterations. | |
| 70 | | `auth.argon2.threads` | uint8 | `2` | argon2id parallelism. | |
| 71 | | `auth.totp_key_b64` | string | `""` | Base64 32-byte AEAD key for at-rest TOTP secrets. Aliased by `SHITHUB_TOTP_KEY`. Empty disables 2FA enrollment routes. | |
| 72 | | `billing.enabled` | bool | `false` | Enables paid-organization Stripe Billing flows. When false, org plan state is local-only. | |
| 73 | | `billing.grace_period` | duration | `336h` | Lock grace window applied after failed subscription payments. | |
| 74 | | `billing.stripe.secret_key` | string | `""` | Stripe secret API key. Required when `billing.enabled=true`. Redacted. | |
| 75 | | `billing.stripe.webhook_secret` | string | `""` | Stripe webhook signing secret. Required when `billing.enabled=true`. Redacted. | |
| 76 | | `billing.stripe.team_price_id` | string | `""` | Stripe recurring Price ID for the Team plan seat. Required when `billing.enabled=true`. | |
| 77 | | `billing.stripe.success_url` | string | `""` | Optional absolute Checkout success URL override. Empty derives from `auth.base_url`. Redacted by `config print`. | |
| 78 | | `billing.stripe.cancel_url` | string | `""` | Optional absolute Checkout cancel URL override. Empty derives from `auth.base_url`. Redacted by `config print`. | |
| 79 | | `billing.stripe.portal_return_url` | string | `""` | Optional absolute Billing Portal return URL override. Empty derives from `auth.base_url`. Redacted by `config print`. | |
| 80 | | `billing.stripe.automatic_tax` | bool | `false` | Enables Stripe Checkout automatic tax collection when the Stripe account is configured for it. | |
| 81 | |
| 82 | ## Env-var examples |
| 83 | |
| 84 | ```sh |
| 85 | # Listen elsewhere |
| 86 | export SHITHUB_WEB__ADDR=:9090 |
| 87 | |
| 88 | # Connect to Postgres |
| 89 | export SHITHUB_DATABASE_URL=postgres://shithub:dev@127.0.0.1:5432/shithub?sslmode=disable |
| 90 | # (equivalent: export SHITHUB_DB__URL=...) |
| 91 | |
| 92 | # JSON logs for prod |
| 93 | export SHITHUB_LOG__FORMAT=json |
| 94 | export SHITHUB_LOG__LEVEL=info |
| 95 | |
| 96 | # Enable tracing |
| 97 | export SHITHUB_TRACING__ENABLED=true |
| 98 | export SHITHUB_TRACING__ENDPOINT=http://otel-collector.bare-metal:4318 |
| 99 | export SHITHUB_TRACING__SAMPLE_RATE=0.05 |
| 100 | |
| 101 | # Error reporting via GlitchTip |
| 102 | export SHITHUB_ERROR_REPORTING__DSN=https://glitchtip.bare-metal/<project-id> |
| 103 | |
| 104 | # Session signing key (deterministic across restarts in prod) |
| 105 | export SHITHUB_SESSION_KEY=$(openssl rand -base64 32) |
| 106 | |
| 107 | # Gate /metrics behind Basic auth |
| 108 | export SHITHUB_METRICS__BASIC_AUTH_USER=prom |
| 109 | export SHITHUB_METRICS__BASIC_AUTH_PASS=<long-random> |
| 110 | |
| 111 | # Enable Stripe Billing in test mode |
| 112 | export SHITHUB_BILLING__ENABLED=true |
| 113 | export SHITHUB_BILLING__STRIPE__SECRET_KEY=sk_test_... |
| 114 | export SHITHUB_BILLING__STRIPE__WEBHOOK_SECRET=whsec_... |
| 115 | export SHITHUB_BILLING__STRIPE__TEAM_PRICE_ID=price_... |
| 116 | ``` |
| 117 | |
| 118 | ## Secrets |
| 119 | |
| 120 | - **Never** commit secrets. `.env` is gitignored; production keys live in a systemd `EnvironmentFile=` with mode `0600`. |
| 121 | - The redaction behavior of `config print` is documented above and tested in `internal/infra/config/config_test.go`. If you add a new secret-bearing field, name it so the redactor matches it (containing `pass`, `secret`, `key`, `token`, `dsn`, `password`, or `url`) — or extend `secretFieldNames` in `internal/infra/config/redact.go`. |
| 122 | - Log-line redaction is independent (see `docs/internal/observability.md`). Both layers exist on purpose; secrets in env-loaded config and in handler-emitted logs travel different paths. |
| 123 | |
| 124 | ## Adding a new key |
| 125 | |
| 126 | 1. Add the field to the appropriate config struct in `internal/infra/config/config.go` with a `toml:` tag. |
| 127 | 2. Set its default in `Defaults()`. |
| 128 | 3. Add validation in `Validate()` if it has invariants. |
| 129 | 4. If it's secret-bearing, confirm its name matches the redactor. |
| 130 | 5. Document it in this file. |
| 131 | 6. Update `.env.example` if the env-var form is the typical usage. |