Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e562881
chore(deps): update all non-major dependencies
renovate[bot] Aug 14, 2026
20fa922
docs: add password-strength cross-service contract (#296)
dargmuesli Aug 16, 2026
a02b5a8
chore(deps): update ghcr.io/maevsi/vibetype docker tag to v15.16.0
renovate[bot] Aug 17, 2026
cee805b
chore(deps): update all non-major dependencies
renovate[bot] Aug 18, 2026
c916e4b
chore(deps): update adminer docker tag to v6 (#297)
renovate[bot] Aug 19, 2026
cf109f0
chore(deps): update all non-major dependencies
renovate[bot] Aug 19, 2026
ee03c9a
chore(deps): update all non-major dependencies
renovate[bot] Aug 20, 2026
0d871bd
fix(vibetype): correct notification secret variable name (#298)
dargmuesli Aug 21, 2026
5e447b4
chore(release): 20.3.2 [skip ci]
semantic-release-bot Aug 21, 2026
1c59f89
chore(deps): update redpandadata/redpanda docker tag to v26.2.2
renovate[bot] Aug 22, 2026
a84351d
chore(deps): update dargmuesli/github-actions action to v5.9.4
renovate[bot] Aug 23, 2026
41b7cfd
chore(deps): update ghcr.io/dargstack/dargstack docker tag to v4.13.6
renovate[bot] Aug 23, 2026
e952b4c
fix(traefik): add acme volume
dargmuesli Aug 24, 2026
6cdf058
chore(release): 20.3.3 [skip ci]
semantic-release-bot Aug 24, 2026
30bc832
fix(traefik): append production volumes
dargmuesli Aug 24, 2026
578fec1
chore(release): 20.3.4 [skip ci]
semantic-release-bot Aug 24, 2026
c5afae0
chore(deps): update all non-major dependencies
renovate[bot] Aug 25, 2026
dfae07c
chore(deps): update all non-major dependencies
renovate[bot] Aug 27, 2026
28dae74
chore(deps): update ghcr.io/maevsi/postgraphile docker tag to v2.2.4
renovate[bot] Aug 27, 2026
2cfc76a
chore(deps): update ghcr.io/maevsi/vibetype docker tag to v15.22.0
renovate[bot] Aug 28, 2026
ff23b01
chore(deps): update ghcr.io/maevsi/vibetype docker tag to v15.22.1
renovate[bot] Aug 28, 2026
02bf4d6
chore(deps): update ghcr.io/maevsi/vibetype docker tag to v15.23.0
renovate[bot] Aug 28, 2026
15a4174
feat(debezium)!: consolidate cdc capture on the outbox table
dargmuesli Aug 14, 2026
5eb7330
feat(debezium)!: drop direct upload capture now that it routes throug…
dargmuesli Aug 14, 2026
4d2b2fc
feat(debezium)!: partition outbox topics by aggregate_id
dargmuesli Aug 14, 2026
831fcf6
feat(debezium)!: route outbox topics by aggregate_type instead of type
dargmuesli Aug 14, 2026
4199a49
chore(debezium): remove outbox payload field config matching Debezium…
dargmuesli Aug 18, 2026
3a6ea51
feat(jobber): add outbox purge job
dargmuesli Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
jobs:
ci-optimization:
name: CI optimization
uses: dargmuesli/github-actions/.github/workflows/ci-optimization.yml@e9c9cf391baeb261cf62c0af6c4af2245bae0662 # 5.9.2
uses: dargmuesli/github-actions/.github/workflows/ci-optimization.yml@ab0335fad9df7941df2880f0645d8f685278da1c # 5.9.4
permissions:
pull-requests: read
build:
Expand Down Expand Up @@ -52,7 +52,7 @@ jobs:
release-semantic:
needs: build
name: Semantic Release
uses: dargmuesli/github-actions/.github/workflows/release-semantic.yml@e9c9cf391baeb261cf62c0af6c4af2245bae0662 # 5.9.2
uses: dargmuesli/github-actions/.github/workflows/release-semantic.yml@ab0335fad9df7941df2880f0645d8f685278da1c # 5.9.4
permissions:
contents: write
id-token: write
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-schedule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
jobs:
release-schedule:
name: "Release: Scheduled"
uses: dargmuesli/github-actions/.github/workflows/release-schedule.yml@e9c9cf391baeb261cf62c0af6c4af2245bae0662 # 5.9.2
uses: dargmuesli/github-actions/.github/workflows/release-schedule.yml@ab0335fad9df7941df2880f0645d8f685278da1c # 5.9.4
permissions:
contents: read
secrets:
Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ applyTo: '**'
**For contributing:**
- [CONTRIBUTING.md](CONTRIBUTING.md): Development setup, dargstack guidelines, code style, git workflow

**Cross-service contracts** (policies two or more services must implement identically, with no code shared between them):
- [docs/password-strength.md](docs/password-strength.md): Password strength requirements enforced by `vibetype` and `postgraphile`

## Code Style

- Do not use abbreviations in naming, except where omitting them would look unnatural
- Use natural language in any non-code text instead of referring to code directly, e.g. "the database's password" instead of "the `postgres_password`", except when a code reference is needed
- Use backticks in any non-code text to refer to code, e.g. "`postgres`" instead of "postgres"
- In markdown prose, start each sentence on its own line (semantic line breaks); renders the same, but keeps diffs scoped to the sentence that changed
- Sort YAML keys lexicographically except where order is semantically significant
- Code formatting is done by the editor via `.editorconfig`

Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## [20.3.4](https://github.com/maevsi/stack/compare/20.3.3...20.3.4) (2026-08-24)

### Bug Fixes

* **traefik:** append production volumes ([30bc832](https://github.com/maevsi/stack/commit/30bc832458b7f963098aa003741b314e847aa1f5))

## [20.3.3](https://github.com/maevsi/stack/compare/20.3.2...20.3.3) (2026-08-24)

### Bug Fixes

* **traefik:** add acme volume ([e952b4c](https://github.com/maevsi/stack/commit/e952b4c9f45774c0006d428f129ccc176c357592))

## [20.3.2](https://github.com/maevsi/stack/compare/20.3.1...20.3.2) (2026-08-21)

### Bug Fixes

* **vibetype:** correct notification secret variable name ([#298](https://github.com/maevsi/stack/issues/298)) ([0d871bd](https://github.com/maevsi/stack/commit/0d871bdbd322ca6ecb28821080fbaa52f15104b9))

## [20.3.1](https://github.com/maevsi/stack/compare/20.3.0...20.3.1) (2026-08-14)

## [20.3.0](https://github.com/maevsi/stack/compare/20.2.2...20.3.0) (2026-08-13)
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# <DEPENDENCIES>
FROM ghcr.io/dargstack/dargstack:4.13.0
FROM ghcr.io/dargstack/dargstack:4.14.1
# </DEPENDENCIES>
63 changes: 63 additions & 0 deletions docs/password-strength.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Password strength policy

This document is the single source of truth for the password strength policy that any implementation setting an account password must satisfy.
It lives here, rather than in `vibetype` or `postgraphile`, because both services implement it independently (code sharing between them is not an option), and a policy shared across services belongs in `stack` rather than being duplicated per repo.
When either service's implementation changes, check it against this document rather than against the other service's code.

## Scope

Applies to every operation that sets a password a user will authenticate with:

| Operation | Field carrying the new password | Covered |
| ------------------------ | -------------------------------- | ------- |
| `accountRegistration` | `input.password` | yes |
| `accountPasswordReset` | `input.password` | yes |
| `accountPasswordChange` | `input.passwordNew` | yes |

`accountPasswordChange`'s `input.passwordCurrent` is explicitly **out of scope**: it authenticates an existing password, which may predate this policy, and must never be strength-checked.

## Requirements

Both of the following must hold.

1. **Minimum length**: 8 characters.
This matches NIST SP 800-63B's own floor.
It is a cheap sanity backstop, not the control doing the real work, see [Why keep a length floor](#why-keep-a-length-floor).
2. **Minimum strength**: a [zxcvbn](https://github.com/zxcvbn-ts/zxcvbn) score of at least 3 ("safely unguessable", resists an offline, slow-hash attack; see the library's own scoring guidance).
This is the control that actually determines whether a password is accepted in practice.

### Why keep a length floor

NIST SP 800-63B requires a minimum length (>= 8) plus screening against common or compromised passwords.
It does not separately mandate a guessability-estimator score on top of that.
zxcvbn's score already factors in length as one of its inputs, so once score >= 3 is required, an 8 character floor rarely does independent work: empirically, the shortest fully random password (e.g. `xK9#mL2qP`, drawn from a large character set) needed to reach score 3 is 9 characters, one above this floor.
The floor is kept anyway as a structural backstop that does not depend on zxcvbn's heuristics being correct for a given input, and as a small margin against future improvements in offline hash-cracking speed, which erode a short password's safety margin fastest regardless of how patternless it is.

## Algorithm and configuration

Both implementations must use identical configuration, or they will disagree on borderline passwords (a password accepted by the client but rejected by the server, or vice versa).

- **Library**: `@zxcvbn-ts/core`, via `new ZxcvbnFactory(options).check(password).score`.
- **Dictionaries**: `@zxcvbn-ts/language-common` (common passwords plus adjacency graphs for keyboard-pattern detection) merged with `@zxcvbn-ts/language-de` and `@zxcvbn-ts/language-en` (both dictionaries only; German and English are the platform's supported locales).
- **Translations**: `@zxcvbn-ts/language-en`.
This only affects zxcvbn's internal feedback strings.
Neither implementation surfaces them to the user, so the specific language here is not user-visible, but the `ZxcvbnFactory` constructor requires a non-empty value.
- **Package versions**: pinned independently in each repo's `package.json`.
Keep `@zxcvbn-ts/core`, `@zxcvbn-ts/language-common`, `@zxcvbn-ts/language-de`, and `@zxcvbn-ts/language-en` at the same version in both repos.
A dictionary update can change which side of the score-3 boundary a given password falls on.

## Current implementation status

| Layer | Minimum length (8) | zxcvbn score (>= 3) |
| ------------------------------- | -------------------- | --------------------- |
| `vibetype` (client) | enforced, all 3 operations | enforced, all 3 operations |
| `postgraphile` (server) | not this layer's job, see below | enforced, all 3 operations |
| `sqitch` (database) | enforced, all 3 operations (`char_length(...) < 8` in each function) | not applicable, zxcvbn cannot run in SQL |

`postgraphile` intentionally does not re-check length: since every underlying sqitch function already rejects anything shorter than 8 characters, and that is exactly this policy's floor, duplicating the check in `postgraphile` would add no protection.

## Where each side implements this

- `vibetype`: `src/app/utils/passwordStrength.ts` (scoring), `src/app/utils/validation.ts` (`SCHEMA_PASSWORD_V2`, length), `src/app/composables/useAuthPasswordValidation.ts` and `usePasswordPairValidation.ts` (live field validation wiring).
- `postgraphile`: `src/presets/passwordStrength.ts` (`PasswordStrengthPlugin`, a Grafserv middleware that inspects `accountRegistration`, `accountPasswordReset`, and `accountPasswordChange` mutations before they execute).
- `sqitch`: the `char_length(...) < 8` check in `function_account_registration.sql`, `function_account_password_reset.sql`, and `function_account_password_change.sql`.
2 changes: 1 addition & 1 deletion src/development/adminer/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ services:
timeout: 5s
retries: 3
start_period: 10s
image: adminer:5.5.1-standalone
image: adminer:6.0.1-standalone
volumes:
- ./configurations/adminer.css:/var/www/html/adminer.css:ro
13 changes: 11 additions & 2 deletions src/development/debezium/configurations/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,17 @@ curl --fail --output /dev/null --silent --show-error \
"database.password": "'"$POSTGRES_PASSWORD"'",
"database.user": "'"$POSTGRES_USER"'",
"plugin.name": "pgoutput",
"table.include.list": "vibetype.event,vibetype.upload,vibetype_private.notification",
"topic.prefix" : "vibetype"
"table.include.list": "vibetype_private.outbox",
"topic.prefix" : "vibetype",
"transforms": "outbox",
"transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter",
"transforms.outbox.route.by.field": "aggregate_type",
"transforms.outbox.route.topic.replacement": "vibetype.outbox.${routedByValue}",
"transforms.outbox.table.field.event.key": "aggregate_id",
"transforms.outbox.predicate": "isOutbox",
"predicates": "isOutbox",
"predicates.isOutbox.type": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches",
"predicates.isOutbox.pattern": "vibetype\\.vibetype_private\\.outbox"
}'

echo "PostgreSQL connector '$CONNECTOR_NAME' is up to date."
2 changes: 1 addition & 1 deletion src/development/elasticsearch/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ services:
# timeout: 10s
# retries: 3
# start_period: 60s
image: elasticsearch:9.5.1
image: elasticsearch:9.5.2
secrets:
- source: elasticsearch-keystore-password
uid: "1000"
Expand Down
2 changes: 1 addition & 1 deletion src/development/grafana/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ services:
timeout: 5s
retries: 3
start_period: 30s
image: grafana/grafana:13.1.3
image: grafana/grafana:13.2.0
secrets:
- grafana-admin-email
- grafana-admin-password
Expand Down
5 changes: 5 additions & 0 deletions src/development/jobber/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ services:
# You cannot access the jobber via a web interface.
environment:
AWS_SHARED_CREDENTIALS_FILE: /run/secrets/jobber-aws-credentials
POSTGRES_DB_FILE: /run/secrets/postgres-db
POSTGRES_HOST: postgres
image: ghcr.io/dargmuesli/jobber-aws-msmtp:1.3.0
secrets:
- source: jobber-aliases
Expand All @@ -29,6 +31,9 @@ services:
target: /home/jobberuser/.aws/config
- source: jobber-msmtprc
target: /etc/msmtprc
- postgres-db
- postgres-role-service-jobber-password
- postgres-role-service-jobber-username
volumes:
- ./configurations/.jobber:/home/jobberuser/.jobber:ro
- ./configurations/sinks:/srv/sinks:ro
Expand Down
9 changes: 9 additions & 0 deletions src/development/jobber/configurations/.jobber
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ jobs:
- type: system-email
notifyOnFailure:
- type: system-email
OutboxPurge:
cmd: PGPASSWORD="$(cat /run/secrets/postgres-role-service-jobber-password)" psql -v ON_ERROR_STOP=1 -h "$POSTGRES_HOST" -U "$(cat /run/secrets/postgres-role-service-jobber-username)" -d "$(cat "$POSTGRES_DB_FILE")" -c "DELETE FROM vibetype_private.outbox WHERE created_at < now() - interval '24 hours'"
time: 0 0 */2 *
notifyOnSuccess:
- type: system-email
notifyOnError:
- type: system-email
notifyOnFailure:
- type: system-email
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

curl "${SENTRY_CRONS_OUTBOX_PURGE}?status=error"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

curl "${SENTRY_CRONS_OUTBOX_PURGE}?status=in_progress"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

curl "${SENTRY_CRONS_OUTBOX_PURGE}?status=ok"
4 changes: 2 additions & 2 deletions src/development/portainer/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ services:
timeout: 5s
retries: 3
start_period: 30s
image: portainer/portainer-ce:2.44.0-alpine
image: portainer/portainer-ce:2.45.0-alpine
secrets:
- portainer-admin-password
volumes:
Expand All @@ -41,7 +41,7 @@ services:
placement:
constraints:
- node.platform.os == linux
image: portainer/agent:2.44.0
image: portainer/agent:2.45.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
Expand Down
11 changes: 11 additions & 0 deletions src/development/postgres/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ secrets:
postgres-role-service-grafana-username:
# The username of the observation dashboard's database role.
file: ~~/artifacts/secrets/postgres-role-service-grafana-username.secret
postgres-role-service-jobber-password:
# The password of the job scheduler's database role.
file: ~~/artifacts/secrets/postgres-role-service-jobber-password.secret
postgres-role-service-jobber-username:
# The username of the job scheduler's database role.
file: ~~/artifacts/secrets/postgres-role-service-jobber-username.secret
postgres-role-service-postgraphile-password:
# The password of the GraphQL API database wrapper's database role.
file: ~~/artifacts/secrets/postgres-role-service-postgraphile-password.secret
Expand Down Expand Up @@ -90,6 +96,11 @@ x-dargstack:
type: random_string
postgres-role-service-grafana-username:
type: wordlist_word
postgres-role-service-jobber-password:
special_characters: false
type: random_string
postgres-role-service-jobber-username:
type: wordlist_word
postgres-role-service-postgraphile-password:
special_characters: false
type: random_string
Expand Down
2 changes: 1 addition & 1 deletion src/development/prometheus/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
timeout: 5s
retries: 3
start_period: 15s
image: prom/prometheus:v3.13.2
image: prom/prometheus:v3.14.0
volumes:
- ./configurations/prometheus.yaml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
Expand Down
2 changes: 1 addition & 1 deletion src/development/redis/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
timeout: 5s
retries: 5
start_period: 10s
image: redis:8.10.0-alpine
image: redis:8.10.1-alpine
volumes:
- redis-data:/data
volumes:
Expand Down
4 changes: 2 additions & 2 deletions src/development/redpanda/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
timeout: 10s
retries: 3
start_period: 30s
image: redpandadata/redpanda:v26.2.1
image: redpandadata/redpanda:v26.2.2
volumes:
- redpanda-data:/var/lib/redpanda/data
redpanda-console:
Expand All @@ -42,7 +42,7 @@ services:
timeout: 5s
retries: 3
start_period: 15s
image: redpandadata/console:v3.10.0
image: redpandadata/console:v3.11.0
volumes:
- ./configurations/config.yaml:/srv/app/redpanda-config.yaml:ro
volumes:
Expand Down
2 changes: 2 additions & 0 deletions src/development/sqitch/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ services:
secrets:
- postgres-role-service-grafana-password
- postgres-role-service-grafana-username
- postgres-role-service-jobber-password
- postgres-role-service-jobber-username
- postgres-role-service-postgraphile-password
- postgres-role-service-postgraphile-username
- postgres-role-service-reccoom-password
Expand Down
2 changes: 1 addition & 1 deletion src/development/traefik/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ services:
timeout: 5s
retries: 3
start_period: 15s
image: traefik:v3.7.10
image: traefik:v3.7.12
ports: # dargstack:dev-only
- mode: host # dargstack:dev-only
protocol: tcp # dargstack:dev-only
Expand Down
2 changes: 1 addition & 1 deletion src/development/vibetype/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ services:
image: vibetype/vibetype:development
secrets:
- source: vibetype-api-notification-secret
target: /run/environment-variables/NUXT_API_NOTIFICATION_SECRET
target: /run/environment-variables/NUXT_VIBETYPE_API_NOTIFICATION_SECRET
- source: vibetype-aws-credentials
target: /home/node/.aws/credentials
- source: vibetype-firebase-service-account-credentials
Expand Down
1 change: 1 addition & 0 deletions src/production/.env.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CLOUDFLARED_TUNNEL_TOKEN=
SENTRY_CRONS=
SENTRY_CRONS_OUTBOX_PURGE=
STACK_DOMAIN=
TRAEFIK_ACME_EMAIL=
TRAEFIK_ACME_PROVIDER=
2 changes: 1 addition & 1 deletion src/production/cloudflared/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ services:
order: start-first
environment:
TUNNEL_TOKEN: ${CLOUDFLARED_TUNNEL_TOKEN}
image: cloudflare/cloudflared:2026.7.3
image: cloudflare/cloudflared:2026.8.2
1 change: 1 addition & 0 deletions src/production/jobber/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ services:
order: stop-first
environment:
SENTRY_CRONS: ${SENTRY_CRONS}
SENTRY_CRONS_OUTBOX_PURGE: ${SENTRY_CRONS_OUTBOX_PURGE}
volumes:
- (( append ))
- postgres-backup-data:/backups/
14 changes: 14 additions & 0 deletions src/production/jobber/configurations/.jobber
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ jobs:
notifyOnFailure:
- type: program
path: /srv/sinks/sentry/error.sh
OutboxPurge:
cmd: |
/srv/sinks/sentry-outbox-purge/in-progress.sh
PGPASSWORD="$(cat /run/secrets/postgres-role-service-jobber-password)" psql -v ON_ERROR_STOP=1 -h "$POSTGRES_HOST" -U "$(cat /run/secrets/postgres-role-service-jobber-username)" -d "$(cat "$POSTGRES_DB_FILE")" -c "DELETE FROM vibetype_private.outbox WHERE created_at < now() - interval '24 hours'"
time: 0 0 */2 *
notifyOnSuccess:
- type: program
path: /srv/sinks/sentry-outbox-purge/ok.sh
notifyOnError:
- type: program
path: /srv/sinks/sentry-outbox-purge/error.sh
notifyOnFailure:
- type: program
path: /srv/sinks/sentry-outbox-purge/error.sh
2 changes: 1 addition & 1 deletion src/production/postgraphile/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ services:
- traefik.http.routers.postgraphile-secure-ruru.tls.certresolver=default
update_config:
order: start-first
image: ghcr.io/maevsi/postgraphile:2.2.2
image: ghcr.io/maevsi/postgraphile:2.2.4
Loading
Loading