markdown · 8623 bytes Raw Blame History

Configuration

shithub uses a layered configuration loader. Sources, in increasing-precedence order:

  1. Built-in defaults. Encoded in internal/infra/config/Defaults().
  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.
  3. Environment variables. SHITHUB_<area>__<key> (double-underscore separates nested keys). Examples below.
  4. CLI flag overrides. Passed in by the caller (mostly --addr from shithubd web).

After all four merge, config.Load applies a small set of named aliases (e.g. SHITHUB_DATABASE_URLdb.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. .env is gitignored; production keys live in a systemd EnvironmentFile= with mode 0600.
  • 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.
  • 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

  1. Add the field to the appropriate config struct in internal/infra/config/config.go with a toml: tag.
  2. Set its default in Defaults().
  3. Add validation in Validate() if it has invariants.
  4. If it's secret-bearing, confirm its name matches the redactor.
  5. Document it in this file.
  6. Update .env.example if 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.