@starci/hfs 4.9.9 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +20 -8
- package/db/backup-receipt.mjs +10 -47
- package/db/drift.mjs +289 -0
- package/db/dump.mjs +12 -11
- package/db/entity-list.mjs +9 -0
- package/db/migrate.mjs +84 -260
- package/db/new.mjs +46 -0
- package/db/post-cutover.mjs +13 -17
- package/db/postgres.mjs +80 -0
- package/emit/contracts.mjs +5 -9
- package/lint/run.mjs +12 -2
- package/package.json +5 -3
- package/release/gate.mjs +82 -31
- package/release/index.mjs +19 -8
- package/release/push.mjs +4 -4
- package/release/records.mjs +54 -0
- package/release/urgent.mjs +129 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +54 -24
- package/runtime/knowledge/hfs/design-truths.yaml +1 -1
- package/runtime/knowledge/hfs/fe-stack.yaml +4 -11
- package/runtime/knowledge/hfs/rules.yaml +250 -157
- package/runtime/knowledge/hfs/slots.yaml +199 -174
- package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
- package/runtime/knowledge/patterns/be/idempotency.yaml +3 -3
- package/runtime/knowledge/patterns/be/jobs.yaml +4 -4
- package/runtime/knowledge/patterns/be/projections.yaml +7 -5
- package/runtime/knowledge/patterns/be/queues.yaml +8 -8
- package/runtime/knowledge/patterns/be/reactors.yaml +2 -2
- package/runtime/knowledge/patterns/be/realtime.yaml +6 -6
- package/runtime/knowledge/patterns/be/saga.yaml +3 -3
- package/runtime/knowledge/patterns/be/webhooks.yaml +1 -1
- package/runtime/modules/kernel/command-policy.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +86 -115
- package/runtime/modules/schemas/application-stacks.schema.yaml +5 -5
- package/runtime/scripts/hfs/app-wiring.mjs +23 -2
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +1 -1
- package/runtime/scripts/hfs/architecture/{supabase-be.mjs → identity.mjs} +45 -15
- package/runtime/scripts/hfs/architecture/index.mjs +2 -3
- package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
- package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
- package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
- package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
- package/runtime/scripts/hfs/check.mjs +8 -8
- package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
- package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
- package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
- package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
- package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
- package/runtime/scripts/hfs/manifest-shape.mjs +6 -5
- package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
- package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
- package/runtime/scripts/hfs/rules/cli.mjs +1 -1
- package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
- package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
- package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
- package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
- package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
- package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
- package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
- package/runtime/scripts/hfs/rules/database.mjs +36 -39
- package/runtime/scripts/hfs/rules/edition.mjs +1 -1
- package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
- package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
- package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
- package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
- package/runtime/scripts/hfs/slots.mjs +8 -2
- package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
- package/runtime/scripts/lib/secret-patterns.mjs +2 -2
- package/runtime/scripts/lib/swarm-stack.mjs +7 -4
- package/scaffold/add-app.mjs +2 -10
- package/scaffold/add-cli-lite.mjs +6 -6
- package/scaffold/add-compose.mjs +1 -1
- package/scaffold/add-inbox-lite.mjs +4 -8
- package/scaffold/add-plan.mjs +1 -1
- package/scaffold/add-queue-lite.mjs +16 -24
- package/scaffold/add-table.mjs +34 -26
- package/scaffold/add.mjs +2 -2
- package/scaffold/app.mjs +19 -86
- package/scaffold/lite-exports.mjs +1 -1
- package/src/main.mjs +24 -22
- package/stack/cli.mjs +10 -2
- package/stack/deploy.mjs +21 -4
- package/stack/target-binding.mjs +0 -60
- package/stack/urgent-floor.mjs +147 -0
- package/sync/ci-commands.mjs +3 -1
- package/sync/index.mjs +32 -8
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +4 -1
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +4 -1
- package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
- package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
- package/templates/app/gitignore-lite +0 -3
- package/templates/app/package-scripts/db.json +9 -0
- package/templates/app/package-scripts/package.json +0 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
- package/templates/app/skeleton/README.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
- package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
- package/templates/app/skeleton-lite/README.md +11 -11
- package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
- package/templates/app/tool-config-lite/prettierignore +0 -1
- package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
- package/templates/be/inbox/database-client.ts.tpl +13 -4
- package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
- package/templates/be/inbox/platform/migration.sql.tpl +22 -0
- package/templates/be/inbox/service.spec.ts.tpl +2 -2
- package/templates/be/inbox/service.ts.tpl +1 -2
- package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
- package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
- package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
- package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
- package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -7
- package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/composition/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +15 -1
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.ts +10 -9
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +34 -18
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +29 -8
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +80 -4
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.ts +39 -11
- package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
- package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
- package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -13
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
- package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
- package/templates/be/table/entities.index.ts.tpl +4 -0
- package/templates/be/table/entity.ts.tpl +21 -0
- package/templates/be/table/index.ts.tpl +1 -0
- package/templates/be/table/module.ts.tpl +1 -1
- package/templates/be/table/service.api.ts.tpl +10 -11
- package/templates/be/table/service.ts.tpl +5 -6
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
- package/db/local-migrate.mjs +0 -194
- package/db/reset.mjs +0 -125
- package/emit/db-types.mjs +0 -271
- package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
- package/runtime/scripts/api/supabase/database-push.mjs +0 -5
- package/runtime/scripts/api/supabase/database-query.mjs +0 -5
- package/runtime/scripts/api/supabase/lib.mjs +0 -55
- package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
- package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
- package/scaffold/supabase-ports.mjs +0 -26
- package/templates/app/package-scripts/supabase.json +0 -7
- package/templates/app/skeleton-lite/supabase/config.toml +0 -48
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
- package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
- package/templates/be/inbox/sql.ts.tpl +0 -16
- package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
- package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
- package/templates/be/table/rows.ts.tpl +0 -4
- package/templates/be/table/sql.api.ts.tpl +0 -7
- package/templates/be/table/sql.ts.tpl +0 -4
- package/templates/fe/auth/supabase-client.ts.tpl +0 -38
- package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
- /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
# The environment input names a protected GitHub environment (required reviewers) that holds:
|
|
9
9
|
# vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
|
|
10
10
|
# secrets SOPS_AGE_KEY (the age identity of .sops.yaml), VPS_SSH_KEY (CI-only deploy key), VPS_KNOWN_HOSTS (pinned host keys)
|
|
11
|
-
# Nothing else is a secret: images are pushed with the workflow token
|
|
11
|
+
# Nothing else is a secret: images are pushed with the workflow token, which also reads the CI verdict of the tag's commit a production deploy
|
|
12
|
+
# waits for (requireCi deploy.prod). Dispatch inputs reach scripts only through env.
|
|
12
13
|
name: deploy stack
|
|
13
14
|
|
|
14
15
|
on:
|
|
@@ -48,6 +49,7 @@ jobs:
|
|
|
48
49
|
permissions:
|
|
49
50
|
contents: read
|
|
50
51
|
packages: write
|
|
52
|
+
actions: read
|
|
51
53
|
env:
|
|
52
54
|
STACK_ENVIRONMENT: ${{ inputs.environment }}
|
|
53
55
|
steps:{{stackGuard}}
|
|
@@ -88,6 +90,7 @@ jobs:
|
|
|
88
90
|
- name: deploy the release
|
|
89
91
|
env:
|
|
90
92
|
XDG_DATA_HOME: ${{ runner.temp }}/starci-data
|
|
93
|
+
GH_TOKEN: ${{ github.token }}
|
|
91
94
|
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}{{stackImageEnv}}{{stackPassEnv}}
|
|
92
95
|
run: |
|
|
93
96
|
npx --no-install starci app stack deploy --env "$STACK_ENVIRONMENT" \{{stackImageArgs}}
|
|
@@ -17,7 +17,7 @@ concurrency:
|
|
|
17
17
|
cancel-in-progress: false
|
|
18
18
|
|
|
19
19
|
# The job shape of the StarCi CI standard (knowledge/hfs/ci-standard.yaml): fast, lint per side, the unit run in shards, one coverage merge, build,
|
|
20
|
-
#
|
|
20
|
+
# and sonar. Timeouts and the shard count come from ci.jobs of .starcistacks/application-stacks.yaml (defaults in the standard).
|
|
21
21
|
jobs:
|
|
22
22
|
fast:
|
|
23
23
|
timeout-minutes: {{timeoutFast}}
|
|
@@ -110,19 +110,3 @@ jobs:
|
|
|
110
110
|
NODE_OPTIONS: {{nodeOptions}}
|
|
111
111
|
|
|
112
112
|
{{sonarJob}}
|
|
113
|
-
db-types:
|
|
114
|
-
timeout-minutes: {{timeoutDbTypes}}
|
|
115
|
-
runs-on: ubuntu-latest
|
|
116
|
-
steps:
|
|
117
|
-
{{> app/ci-workflows/install-steps.yml}}
|
|
118
|
-
- name: start Supabase
|
|
119
|
-
run: npm run db:start
|
|
120
|
-
- name: apply migrations
|
|
121
|
-
run: npm run db:reset
|
|
122
|
-
- name: regenerate database types
|
|
123
|
-
run: npm run db:types
|
|
124
|
-
- name: database types are committed
|
|
125
|
-
run: git diff --exit-code -- supabase/types/database.types.ts
|
|
126
|
-
- name: stop Supabase
|
|
127
|
-
if: always()
|
|
128
|
-
run: npm run db:stop
|
|
@@ -5,7 +5,7 @@ on:
|
|
|
5
5
|
workflow_dispatch:
|
|
6
6
|
inputs:
|
|
7
7
|
environment:
|
|
8
|
-
description: Protected environment
|
|
8
|
+
description: Protected environment whose sealed custody under .starcistacks holds DATABASE_URL
|
|
9
9
|
required: true
|
|
10
10
|
type: environment
|
|
11
11
|
backup_receipt:
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
|
|
46
46
|
with:
|
|
47
47
|
version: v3.13.2
|
|
48
|
-
- name: materialize sealed custody and
|
|
48
|
+
- name: materialize sealed custody and migrate the database
|
|
49
49
|
env:
|
|
50
50
|
XDG_DATA_HOME: ${{ runner.temp }}/starci-data
|
|
51
51
|
shell: bash
|
|
@@ -57,9 +57,7 @@ jobs:
|
|
|
57
57
|
secret_dir="$XDG_DATA_HOME/StarCi/secrets/{{project}}/$STACK_ENVIRONMENT"
|
|
58
58
|
trap 'rm -rf -- "$secret_dir"' EXIT
|
|
59
59
|
npx --no-install starci app secret materialize "$STACK_ENVIRONMENT"
|
|
60
|
-
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
npx --no-install
|
|
64
|
-
npm run db:push -- --dry-run
|
|
65
|
-
npm run db:push
|
|
60
|
+
DATABASE_URL="$(cat "$secret_dir/DATABASE_URL")"
|
|
61
|
+
export DATABASE_URL
|
|
62
|
+
npx --no-install starci app db migrate --url-env DATABASE_URL
|
|
63
|
+
STARCI_DB_ARMED=1 npx --no-install starci app db migrate --url-env DATABASE_URL --execute
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
# The environment input names a protected GitHub environment (required reviewers) that holds:
|
|
9
9
|
# vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
|
|
10
10
|
# secrets SOPS_AGE_KEY (the age identity of .sops.yaml), VPS_SSH_KEY (CI-only deploy key), VPS_KNOWN_HOSTS (pinned host keys)
|
|
11
|
-
# Nothing else is a secret: images are pushed with the workflow token
|
|
11
|
+
# Nothing else is a secret: images are pushed with the workflow token, which also reads the CI verdict of the tag's commit a production deploy
|
|
12
|
+
# waits for (requireCi deploy.prod). Dispatch inputs reach scripts only through env.
|
|
12
13
|
name: deploy stack
|
|
13
14
|
|
|
14
15
|
on:
|
|
@@ -48,6 +49,7 @@ jobs:
|
|
|
48
49
|
permissions:
|
|
49
50
|
contents: read
|
|
50
51
|
packages: write
|
|
52
|
+
actions: read
|
|
51
53
|
env:
|
|
52
54
|
STACK_ENVIRONMENT: ${{ inputs.environment }}
|
|
53
55
|
steps:{{stackGuard}}
|
|
@@ -88,6 +90,7 @@ jobs:
|
|
|
88
90
|
- name: deploy the release
|
|
89
91
|
env:
|
|
90
92
|
XDG_DATA_HOME: ${{ runner.temp }}/starci-data
|
|
93
|
+
GH_TOKEN: ${{ github.token }}
|
|
91
94
|
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}{{stackImageEnv}}{{stackPassEnv}}
|
|
92
95
|
run: |
|
|
93
96
|
npx --no-install starci app stack deploy --env "$STACK_ENVIRONMENT" \{{stackImageArgs}}
|
|
@@ -1,42 +1,5 @@
|
|
|
1
1
|
// Generated by {{appBinary}} sync (app root). Do not edit: run "npx {{appBinary}} sync --write".
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// stale file, so lint, typecheck, dev and build never run against types older than the migrations; it starts nothing and needs no stack.
|
|
5
|
-
// `--graphql` runs only the GraphQL wire types below (`npm run codegen:graphql`).
|
|
6
|
-
import { createHash } from "node:crypto"
|
|
7
|
-
import fs from "node:fs"
|
|
8
|
-
import path from "node:path"
|
|
9
|
-
|
|
10
|
-
const byCodeUnit = (left, right) => {
|
|
11
|
-
if (left === right) return 0
|
|
12
|
-
return left < right ? -1 : 1
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const MIGRATIONS = path.join("supabase", "migrations")
|
|
16
|
-
const TYPES = path.join("supabase", "types", "database.types.ts")
|
|
17
|
-
const STAMP = /^\/\/ migrations-sha256: ([0-9a-f]{64})$/
|
|
18
|
-
|
|
19
|
-
const fail = (message) => {
|
|
20
|
-
console.error(`codegen: ${message}`)
|
|
21
|
-
process.exit(1)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (!process.argv.includes("--graphql")) {
|
|
25
|
-
const hash = createHash("sha256")
|
|
26
|
-
for (const name of (fs.existsSync(MIGRATIONS) ? fs.readdirSync(MIGRATIONS) : [])
|
|
27
|
-
.filter((file) => file.endsWith(".sql"))
|
|
28
|
-
.sort(byCodeUnit)) {
|
|
29
|
-
hash.update(`${name}\0${fs.readFileSync(path.join(MIGRATIONS, name), "utf8").replace(/\r\n/g, "\n")}\0`)
|
|
30
|
-
}
|
|
31
|
-
const digest = hash.digest("hex")
|
|
32
|
-
|
|
33
|
-
if (!fs.existsSync(TYPES))
|
|
34
|
-
fail(`${TYPES} is missing; start the local stack and run \`npm run contract:emit\`, then commit it`)
|
|
35
|
-
const stamped = STAMP.exec(fs.readFileSync(TYPES, "utf8").split(/\r?\n/, 1)[0])?.[1]
|
|
36
|
-
if (stamped !== digest)
|
|
37
|
-
fail(
|
|
38
|
-
`${TYPES} is ${stamped === undefined ? "not stamped" : "older than the migrations"}; run \`npm run contract:emit\` and commit the regenerated types`,
|
|
39
|
-
)
|
|
40
|
-
}
|
|
2
|
+
// The GraphQL wire types of the front end (`npm run codegen`, also `npm run codegen:graphql`); a lite app generates no database types.
|
|
3
|
+
// {{> app/codegen-prelude.mjs.partial}}
|
|
41
4
|
|
|
42
5
|
// {{> app/codegen-graphql.mjs.partial}}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
-- Managed by starci app sync (slot app.db.bootstrap); do not edit: run "npx starci app sync --write".
|
|
2
|
+
-- The pre-migration bootstrap of a lite app's self-hosted PostgreSQL (ruling lite-self-hosted-postgres-passport-minio). Migrations written
|
|
3
|
+
-- for Supabase reference objects GoTrue and Supabase Storage created: the browser roles, the `auth` schema (auth.users, auth.identities,
|
|
4
|
+
-- auth.sessions, auth.uid/jwt/role) and the `storage` schema (storage.buckets, storage.objects). The supabase/postgres image ships only a partial
|
|
5
|
+
-- copy of them, and a lite app runs neither GoTrue nor storage-api: auth is the app's own (integrations/passport) and storage is the S3 endpoint
|
|
6
|
+
-- of integrations/object-storage. This file gives the migrations those objects, EMPTY, and nothing else. It is idempotent; `starci app db migrate`
|
|
7
|
+
-- applies it as the database owner before the first migration of an empty database.
|
|
8
|
+
--
|
|
9
|
+
-- auth.users keeps the GoTrue column set so restored rows and foreign keys from public tables stay valid; encrypted_password holds bcrypt hashes.
|
|
10
|
+
-- auth.uid(), auth.role() and auth.jwt() read the request.jwt.claims setting; the API connects as app_be and sets none, so they return NULL and
|
|
11
|
+
-- every policy that names them denies the browser roles, which have no door in lite.
|
|
12
|
+
|
|
13
|
+
create schema if not exists extensions;
|
|
14
|
+
create schema if not exists auth;
|
|
15
|
+
create schema if not exists storage;
|
|
16
|
+
|
|
17
|
+
do $roles$
|
|
18
|
+
begin
|
|
19
|
+
if not exists (select 1 from pg_roles where rolname = 'anon') then create role anon nologin noinherit; end if;
|
|
20
|
+
if not exists (select 1 from pg_roles where rolname = 'authenticated') then create role authenticated nologin noinherit; end if;
|
|
21
|
+
if not exists (select 1 from pg_roles where rolname = 'service_role') then create role service_role nologin noinherit bypassrls; end if;
|
|
22
|
+
end
|
|
23
|
+
$roles$;
|
|
24
|
+
|
|
25
|
+
grant usage on schema auth, storage to anon, authenticated, service_role;
|
|
26
|
+
grant usage on schema extensions to anon, authenticated, service_role;
|
|
27
|
+
|
|
28
|
+
-- auth.users: the full GoTrue column set (the image's old copy is extended in place).
|
|
29
|
+
create table if not exists auth.users (id uuid primary key);
|
|
30
|
+
alter table auth.users
|
|
31
|
+
add column if not exists instance_id uuid,
|
|
32
|
+
add column if not exists aud varchar(255),
|
|
33
|
+
add column if not exists role varchar(255),
|
|
34
|
+
add column if not exists email varchar(255),
|
|
35
|
+
add column if not exists encrypted_password varchar(255),
|
|
36
|
+
add column if not exists email_confirmed_at timestamptz,
|
|
37
|
+
add column if not exists invited_at timestamptz,
|
|
38
|
+
add column if not exists confirmation_token varchar(255),
|
|
39
|
+
add column if not exists confirmation_sent_at timestamptz,
|
|
40
|
+
add column if not exists recovery_token varchar(255),
|
|
41
|
+
add column if not exists recovery_sent_at timestamptz,
|
|
42
|
+
add column if not exists email_change_token_new varchar(255),
|
|
43
|
+
add column if not exists email_change varchar(255),
|
|
44
|
+
add column if not exists email_change_sent_at timestamptz,
|
|
45
|
+
add column if not exists last_sign_in_at timestamptz,
|
|
46
|
+
add column if not exists raw_app_meta_data jsonb,
|
|
47
|
+
add column if not exists raw_user_meta_data jsonb,
|
|
48
|
+
add column if not exists is_super_admin boolean,
|
|
49
|
+
add column if not exists created_at timestamptz,
|
|
50
|
+
add column if not exists updated_at timestamptz,
|
|
51
|
+
add column if not exists phone text,
|
|
52
|
+
add column if not exists phone_confirmed_at timestamptz,
|
|
53
|
+
add column if not exists confirmed_at timestamptz,
|
|
54
|
+
add column if not exists banned_until timestamptz,
|
|
55
|
+
add column if not exists deleted_at timestamptz,
|
|
56
|
+
add column if not exists is_anonymous boolean not null default false;
|
|
57
|
+
create unique index if not exists users_email_partial_key on auth.users (email) where is_anonymous = false;
|
|
58
|
+
|
|
59
|
+
create table if not exists auth.identities (
|
|
60
|
+
provider_id text not null,
|
|
61
|
+
user_id uuid not null references auth.users (id) on delete cascade,
|
|
62
|
+
identity_data jsonb not null,
|
|
63
|
+
provider text not null,
|
|
64
|
+
last_sign_in_at timestamptz,
|
|
65
|
+
created_at timestamptz,
|
|
66
|
+
updated_at timestamptz,
|
|
67
|
+
email text generated always as (lower(identity_data ->> 'email')) stored,
|
|
68
|
+
id uuid not null default gen_random_uuid() primary key,
|
|
69
|
+
unique (provider_id, provider)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
create table if not exists auth.sessions (
|
|
73
|
+
id uuid primary key,
|
|
74
|
+
user_id uuid not null references auth.users (id) on delete cascade,
|
|
75
|
+
created_at timestamptz,
|
|
76
|
+
updated_at timestamptz,
|
|
77
|
+
factor_id uuid,
|
|
78
|
+
aal text,
|
|
79
|
+
not_after timestamptz,
|
|
80
|
+
refreshed_at timestamp,
|
|
81
|
+
user_agent text,
|
|
82
|
+
ip inet,
|
|
83
|
+
tag text
|
|
84
|
+
);
|
|
85
|
+
create index if not exists sessions_user_id_idx on auth.sessions (user_id);
|
|
86
|
+
|
|
87
|
+
create or replace function auth.uid() returns uuid language sql stable as
|
|
88
|
+
$$ select nullif(coalesce(current_setting('request.jwt.claim.sub', true), (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')), '')::uuid $$;
|
|
89
|
+
|
|
90
|
+
create or replace function auth.role() returns text language sql stable as
|
|
91
|
+
$$ select nullif(coalesce(current_setting('request.jwt.claim.role', true), (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role')), '')::text $$;
|
|
92
|
+
|
|
93
|
+
create or replace function auth.jwt() returns jsonb language sql stable as
|
|
94
|
+
$$ select coalesce(nullif(current_setting('request.jwt.claim', true), ''), nullif(current_setting('request.jwt.claims', true), ''))::jsonb $$;
|
|
95
|
+
|
|
96
|
+
grant execute on function auth.uid(), auth.role(), auth.jwt() to public;
|
|
97
|
+
|
|
98
|
+
-- storage: the two tables the migrations seed and police; the objects live in MinIO, these stay empty bookkeeping.
|
|
99
|
+
create table if not exists storage.buckets (
|
|
100
|
+
id text primary key,
|
|
101
|
+
name text not null,
|
|
102
|
+
owner uuid,
|
|
103
|
+
created_at timestamptz default now(),
|
|
104
|
+
updated_at timestamptz default now(),
|
|
105
|
+
public boolean default false,
|
|
106
|
+
avif_autodetection boolean default false,
|
|
107
|
+
file_size_limit bigint,
|
|
108
|
+
allowed_mime_types text[],
|
|
109
|
+
owner_id text
|
|
110
|
+
);
|
|
111
|
+
create unique index if not exists bname on storage.buckets (name);
|
|
112
|
+
|
|
113
|
+
create table if not exists storage.objects (
|
|
114
|
+
id uuid not null default gen_random_uuid() primary key,
|
|
115
|
+
bucket_id text references storage.buckets (id),
|
|
116
|
+
name text,
|
|
117
|
+
owner uuid,
|
|
118
|
+
created_at timestamptz default now(),
|
|
119
|
+
updated_at timestamptz default now(),
|
|
120
|
+
last_accessed_at timestamptz default now(),
|
|
121
|
+
metadata jsonb,
|
|
122
|
+
path_tokens text[] generated always as (string_to_array(name, '/')) stored,
|
|
123
|
+
version text,
|
|
124
|
+
owner_id text,
|
|
125
|
+
user_metadata jsonb
|
|
126
|
+
);
|
|
127
|
+
create unique index if not exists bucketid_objname on storage.objects (bucket_id, name);
|
|
128
|
+
alter table storage.objects enable row level security;
|
|
129
|
+
alter table storage.buckets enable row level security;
|
|
130
|
+
|
|
131
|
+
grant select, insert, update, delete on storage.objects, storage.buckets to authenticated, service_role;
|
|
132
|
+
grant select on storage.objects, storage.buckets to anon;
|
|
133
|
+
|
|
134
|
+
-- Replication publication the baseline adds tables to (the image ships it; a bare PostgreSQL does not).
|
|
135
|
+
do $pub$
|
|
136
|
+
begin
|
|
137
|
+
if not exists (select 1 from pg_publication where pubname = 'supabase_realtime') then create publication supabase_realtime; end if;
|
|
138
|
+
end
|
|
139
|
+
$pub$;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
{{> common/gitignore.base}}
|
|
2
2
|
# Jest transforms reused by the CI test job
|
|
3
3
|
.jest-cache/
|
|
4
|
-
# Supabase CLI state is local Docker metadata, never application source.
|
|
5
|
-
supabase/.temp/
|
|
6
|
-
supabase/.branches/
|
|
7
4
|
# be: the schema Nest writes when it boots GraphQL
|
|
8
5
|
schema.gql
|
|
9
6
|
# fe: Next build output and its generated type file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"db:create": "docker create --name {{project}}-dev-db -p 127.0.0.1:54322:5432 -e POSTGRES_HOST_AUTH_METHOD=trust -v {{project}}-dev-db-data:/var/lib/postgresql/data {{dbImage}}",
|
|
2
|
+
"db:start": "docker start {{project}}-dev-db",
|
|
3
|
+
"db:stop": "docker stop {{project}}-dev-db",
|
|
4
|
+
"db:reset": "docker rm -f {{project}}-dev-db && docker volume rm {{project}}-dev-db-data && npm run db:create && npm run db:start",
|
|
5
|
+
"db:new": "{{appBinary}} db new",
|
|
6
|
+
"db:push": "{{appBinary}} db migrate --url-env DATABASE_URL --execute",
|
|
7
|
+
"db:plan": "{{appBinary}} db plan --url-env DATABASE_URL",
|
|
8
|
+
"db:dump": "{{appBinary}} db dump --url-env DATABASE_URL --out ../{{project}}-db-backups",
|
|
9
|
+
"db:lint": "{{appBinary}} check --fast",
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"test:e2e": "npm run typecheck:tests && cd be && jest --selectProjects e2e",
|
|
10
10
|
"test:contract": "npm run typecheck:tests && cd be && jest --selectProjects contract",
|
|
11
11
|
"test:stack": "starci app stack",
|
|
12
|
-
{{supabaseScripts}}
|
|
13
12
|
"contract:emit": "starci app emit"
|
|
14
13
|
}
|
|
15
14
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"typecheck:tests": "tsc -p be/src/tests/tsconfig.json",
|
|
7
7
|
"test": "cd be && jest --selectProjects unit --coverage",
|
|
8
8
|
"test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
|
|
9
|
-
{{> app/package-scripts/
|
|
9
|
+
{{> app/package-scripts/db.json}}
|
|
10
10
|
{{backingScripts}}
|
|
11
11
|
"contract:emit": "{{appBinary}} emit"
|
|
12
12
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Development stack
|
|
2
2
|
|
|
3
|
-
Every development dependency runs in Docker Linux containers. This starter's Docker Compose model contains the core API, the product and landing front ends, and a pinned local PostgreSQL database. Compose is an implementation detail; declared dependencies may also use managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts
|
|
3
|
+
Every development dependency runs in Docker Linux containers. This starter's Docker Compose model contains the core API, the product and landing front ends, and a pinned local PostgreSQL database. Compose is an implementation detail; declared dependencies may also use managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts. A declared native profile may run app processes locally while dependencies stay in Docker. Trust authentication is confined to this local, unpublished database.
|
|
4
4
|
Starter release images and external secrets must be prepared before starting the container model. Local source development remains available through the app scripts.
|
|
5
5
|
|
|
6
6
|
| Command | Operation |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Production stack
|
|
2
2
|
|
|
3
|
-
The app and its dependencies run on Docker Swarm
|
|
3
|
+
The app and its dependencies run on Docker Swarm. The tracked model declares the application services, one digest-pinned Caddy ingress and one digest-pinned PostgreSQL service on an internal backing network, with its data on a named persistent volume and credentials supplied through mounted secrets. On the first boot of an empty data volume, PostgreSQL runs the mounted `.starcistacks/production/infra/swarm/postgres-init.sh` config to create the `app_be` role with access to this app's database and schema, without superuser, database-creation, role-creation, replication or RLS-bypass privileges. Existing data volumes skip initialization and retain their roles and credentials.
|
|
4
4
|
The model is a deployment declaration. It contains no owner credentials, real DNS receipt, completed release or restore proof. The public records under `.starcistacks/production/infra/swarm/` mark those owner-run operations as pending. Its all-zero application image digests are illustrative and must be replaced before deployment.
|
|
5
5
|
|
|
6
6
|
The runtime template tracks no encrypted members. Scaffolding generates the declared database connection, application password and PostgreSQL administrator password members with placeholder values encrypted to this app's own recipient in `.sops.yaml`. Replace them with `starci app secret set production` using the matching host age identity in untracked local custody. The sealed connection uses role `app_be`, database `{{project}}` and internal endpoint `postgres:5432`; its URI-encoded password matches the separate `primary-db-password` member. The administrator password is distinct. The API receives only its least-privilege connection; only PostgreSQL receives the two password files for initialization. The non-empty application password contains no line breaks. The service has no TTY: initialization creates the role without a password, then feeds the two `psql` `\password` prompts through stdin using Bash's builtin `printf`, so psql encrypts it before sending SQL. No password travels in an initialization command argument or a plaintext SQL statement.
|
|
@@ -22,7 +22,7 @@ Declare `ci.github.repository` and `ci.github.defaultBranch` in `.starcistacks/a
|
|
|
22
22
|
|
|
23
23
|
## Development
|
|
24
24
|
|
|
25
|
-
Every development dependency runs in Docker Linux containers.
|
|
25
|
+
Every development dependency runs in Docker Linux containers. Every declared dependency runs through its managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts or the declared container model. This starter uses Compose to run its pinned local PostgreSQL and app services; Compose is an implementation detail. A declared native profile may run the app processes locally while dependencies stay in Docker. In production, the app and its dependencies run on Docker Swarm, except Supabase, which is a hosted service.
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
28
|
npm install
|
|
@@ -7,15 +7,15 @@ schema: starci/application-stacks@1
|
|
|
7
7
|
sources:
|
|
8
8
|
- repository: {{project}}
|
|
9
9
|
backing:
|
|
10
|
-
|
|
11
|
-
provider:
|
|
12
|
-
|
|
10
|
+
db:
|
|
11
|
+
provider: postgres
|
|
12
|
+
component: db
|
|
13
13
|
components:
|
|
14
14
|
api:
|
|
15
15
|
role: service
|
|
16
16
|
image: "{{project}}/api:v0.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
17
17
|
compose: infra/swarm/stack.yaml
|
|
18
|
-
purpose: The application's GraphQL API
|
|
18
|
+
purpose: The application's GraphQL API, its own Google and password sign-in and its storage integration.
|
|
19
19
|
required: true
|
|
20
20
|
web:
|
|
21
21
|
role: service
|
|
@@ -23,6 +23,14 @@ components:
|
|
|
23
23
|
compose: infra/swarm/stack.yaml
|
|
24
24
|
purpose: The standalone Next.js application with public configuration supplied at build time.
|
|
25
25
|
required: true
|
|
26
|
+
db:
|
|
27
|
+
role: service
|
|
28
|
+
stateful: true
|
|
29
|
+
volumes: [db-data]
|
|
30
|
+
image: "supabase/postgres:17.11.0.006@sha256:c141fd17ac6a76c4323e90b3aee8ff1a23d2e89e547c2b963cfadc309c079cc2"
|
|
31
|
+
compose: infra/swarm/stack.yaml
|
|
32
|
+
purpose: The app's own PostgreSQL (pg_cron, pg_net, pgmq, vector), migrated by starci app db migrate.
|
|
33
|
+
required: true
|
|
26
34
|
ingress:
|
|
27
35
|
role: ingress
|
|
28
36
|
stateful: true
|
|
@@ -39,6 +47,7 @@ environments:
|
|
|
39
47
|
components:
|
|
40
48
|
api: {replicas: 1, port: 3001}
|
|
41
49
|
web: {replicas: 1, port: 3000}
|
|
50
|
+
db: {replicas: 1, port: 5432}
|
|
42
51
|
ingress: {replicas: 1, port: 80}
|
|
43
52
|
runbook: README.md
|
|
44
53
|
ingress:
|
|
@@ -62,8 +71,8 @@ environments:
|
|
|
62
71
|
retention: Seven daily snapshots
|
|
63
72
|
owner: Development operator
|
|
64
73
|
restoreProof: .starcistacks/dev/infra/swarm/recovery/restore-proof.md
|
|
65
|
-
- component:
|
|
66
|
-
target: Owner-provisioned encrypted off-host storage for
|
|
74
|
+
- component: db
|
|
75
|
+
target: Owner-provisioned encrypted off-host storage for starci app db dump archives
|
|
67
76
|
retention: Seven daily exports
|
|
68
77
|
owner: Development operator
|
|
69
78
|
restoreProof: .starcistacks/dev/infra/swarm/recovery/restore-proof.md
|
|
@@ -75,10 +84,34 @@ environments:
|
|
|
75
84
|
where: secrets/primary-db-url
|
|
76
85
|
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
77
86
|
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
78
|
-
- name:
|
|
79
|
-
runtimeName: "{{project}}-dev-
|
|
87
|
+
- name: db-password
|
|
88
|
+
runtimeName: "{{project}}-dev-db-password-v1"
|
|
89
|
+
version: 1
|
|
90
|
+
where: secrets/db-password
|
|
91
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
92
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
93
|
+
- name: google-client-secret
|
|
94
|
+
runtimeName: "{{project}}-dev-google-client-secret-v1"
|
|
95
|
+
version: 1
|
|
96
|
+
where: secrets/google-client-secret
|
|
97
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
98
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
99
|
+
- name: auth-jwt-private-key
|
|
100
|
+
runtimeName: "{{project}}-dev-auth-jwt-private-key-v1"
|
|
101
|
+
version: 1
|
|
102
|
+
where: secrets/auth-jwt-private-key
|
|
103
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
104
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
105
|
+
- name: storage-access-key-id
|
|
106
|
+
runtimeName: "{{project}}-dev-storage-access-key-id-v1"
|
|
107
|
+
version: 1
|
|
108
|
+
where: secrets/storage-access-key-id
|
|
109
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
110
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
111
|
+
- name: storage-secret-access-key
|
|
112
|
+
runtimeName: "{{project}}-dev-storage-secret-access-key-v1"
|
|
80
113
|
version: 1
|
|
81
|
-
where: secrets/
|
|
114
|
+
where: secrets/storage-secret-access-key
|
|
82
115
|
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
83
116
|
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
84
117
|
production:
|
|
@@ -88,6 +121,7 @@ environments:
|
|
|
88
121
|
components:
|
|
89
122
|
api: {replicas: 1, port: 3001}
|
|
90
123
|
web: {replicas: 1, port: 3000}
|
|
124
|
+
db: {replicas: 1, port: 5432}
|
|
91
125
|
ingress: {replicas: 1, port: 443, httpPort: 80}
|
|
92
126
|
runbook: README.md
|
|
93
127
|
ingress:
|
|
@@ -114,9 +148,9 @@ environments:
|
|
|
114
148
|
retention: Thirty daily snapshots
|
|
115
149
|
owner: Production operator
|
|
116
150
|
restoreProof: .starcistacks/production/infra/swarm/recovery/restore-proof.md
|
|
117
|
-
- component:
|
|
118
|
-
target:
|
|
119
|
-
retention:
|
|
151
|
+
- component: db
|
|
152
|
+
target: Owner-provisioned encrypted off-host storage for starci app db dump archives
|
|
153
|
+
retention: Thirty daily encrypted exports
|
|
120
154
|
owner: Production operator
|
|
121
155
|
restoreProof: .starcistacks/production/infra/swarm/recovery/restore-proof.md
|
|
122
156
|
# Scaffolding seals generated placeholders to this app's own recipient; set real values before use.
|
|
@@ -127,10 +161,34 @@ environments:
|
|
|
127
161
|
where: secrets/primary-db-url
|
|
128
162
|
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
129
163
|
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
130
|
-
- name:
|
|
131
|
-
runtimeName: "{{project}}-production-
|
|
164
|
+
- name: db-password
|
|
165
|
+
runtimeName: "{{project}}-production-db-password-v1"
|
|
166
|
+
version: 1
|
|
167
|
+
where: secrets/db-password
|
|
168
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
169
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
170
|
+
- name: google-client-secret
|
|
171
|
+
runtimeName: "{{project}}-production-google-client-secret-v1"
|
|
132
172
|
version: 1
|
|
133
|
-
where: secrets/
|
|
173
|
+
where: secrets/google-client-secret
|
|
174
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
175
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
176
|
+
- name: auth-jwt-private-key
|
|
177
|
+
runtimeName: "{{project}}-production-auth-jwt-private-key-v1"
|
|
178
|
+
version: 1
|
|
179
|
+
where: secrets/auth-jwt-private-key
|
|
180
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
181
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
182
|
+
- name: storage-access-key-id
|
|
183
|
+
runtimeName: "{{project}}-production-storage-access-key-id-v1"
|
|
184
|
+
version: 1
|
|
185
|
+
where: secrets/storage-access-key-id
|
|
186
|
+
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
187
|
+
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
188
|
+
- name: storage-secret-access-key
|
|
189
|
+
runtimeName: "{{project}}-production-storage-secret-access-key-v1"
|
|
190
|
+
version: 1
|
|
191
|
+
where: secrets/storage-secret-access-key
|
|
134
192
|
recipientPolicy: This app's own age recipient configured in .sops.yaml
|
|
135
193
|
keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
|
|
136
194
|
# What `starci app stack` deploys (verbs: prepare, doctor, deploy, verify, rollback); names and shapes only, never a value.
|
|
@@ -151,9 +209,9 @@ environments:
|
|
|
151
209
|
buildArgs:
|
|
152
210
|
values:
|
|
153
211
|
NEXT_PUBLIC_SITE_URL: "https://{{project}}.example.invalid"
|
|
154
|
-
|
|
155
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY: "REPLACE_WITH_PUBLIC_SUPABASE_ANON_KEY"
|
|
212
|
+
NEXT_PUBLIC_API_URL: "https://api.{{project}}.example.invalid"
|
|
156
213
|
volumes:
|
|
214
|
+
- {{project}}-production-db-data
|
|
157
215
|
- {{project}}-production-ingress-data
|
|
158
216
|
- {{project}}-production-ingress-config-data
|
|
159
217
|
secrets:
|
|
@@ -162,7 +220,14 @@ environments:
|
|
|
162
220
|
filePointers: true
|
|
163
221
|
from:
|
|
164
222
|
- member: primary-db-url
|
|
165
|
-
- member:
|
|
223
|
+
- member: google-client-secret
|
|
224
|
+
- member: auth-jwt-private-key
|
|
225
|
+
- member: storage-access-key-id
|
|
226
|
+
- member: storage-secret-access-key
|
|
227
|
+
- service: db
|
|
228
|
+
filePointers: true
|
|
229
|
+
from:
|
|
230
|
+
- member: db-password
|
|
166
231
|
# Release tasks: an idempotent write the release owes the data (a catalogue sync, a seed upsert) is declared under tasks and listed in postDeploy.
|
|
167
232
|
# A deploy runs postDeploy after the stack converged and before verify, which stays read-only. Uncomment and name the real command:
|
|
168
233
|
# tasks:
|
|
@@ -184,7 +249,7 @@ environments:
|
|
|
184
249
|
retry: true
|
|
185
250
|
k8s:
|
|
186
251
|
status: deferred
|
|
187
|
-
reason: Lite production uses Docker Swarm with
|
|
252
|
+
reason: Lite production uses Docker Swarm with its own PostgreSQL service; Kubernetes belongs to a future full-edition deployment.
|
|
188
253
|
ci:
|
|
189
254
|
secrets:
|
|
190
255
|
- {name: SONAR_TOKEN, scope: repository, purpose: Sonar analysis token provisioned for this app}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Development stack
|
|
2
2
|
|
|
3
|
-
Every development dependency runs in Docker Linux containers.
|
|
4
|
-
The application image references contain illustrative all-zero digests. Replace them with verified published release digests in the manifest, model and `.starcistacks/dev/infra/swarm/releases/images.lock.yaml` before starting the container model.
|
|
3
|
+
Every development dependency runs in Docker Linux containers. The `db` service (image supabase/postgres: PostgreSQL with pg_cron, pg_net, pgmq and pgvector) starts with `npm run db:start` and takes the migrations with `npm run db:push`; storage is the S3-compatible MinIO the environment names. Every other declared dependency, such as Redis, starts through its managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts or the declared container model; Compose is an implementation detail. The app services may run in the local Docker Swarm model or through a declared native development profile while dependencies stay in Docker.
|
|
4
|
+
The application image references contain illustrative all-zero digests. Replace them with verified published release digests in the manifest, model and `.starcistacks/dev/infra/swarm/releases/images.lock.yaml` before starting the container model. Seal the local database connection and the integration credentials; a container cannot use the host's loopback address without owner-configured networking. Local source development remains available through the app scripts.
|
|
5
5
|
|
|
6
|
-
The runtime template tracks no encrypted members. Scaffolding generates the
|
|
6
|
+
The runtime template tracks no encrypted members. Scaffolding generates the six declared members with placeholder values encrypted to this app's own recipient in `.sops.yaml`. Replace those values with `starci app secret set dev` using the matching host age identity in untracked local custody. Existing generated envelopes are decryptable with that app identity and can be updated in place. Each Docker secret contains the corresponding key's real value, and the API receives it through its declared mounted file.
|
|
7
7
|
|
|
8
8
|
| Command | Operation |
|
|
9
9
|
| --- | --- |
|
|
10
|
-
| prepare | Run `npm run db:start` at the app root;
|
|
11
|
-
| doctor | From this directory, run `docker stack config -c infra/swarm/stack.yaml`. Confirm image, secret and local
|
|
10
|
+
| prepare | Run `npm run db:start` at the app root; then `npm run db:push` to apply the migrations; export `DATABASE_URL` for the database verbs. For a declared Redis (its `redis:create`, `redis:start` and `redis:stop` scripts arrive in package.json with the first queue or job), run the create script once, then the start script when using the native app profile. An owner who chooses local Swarm initializes it, labels its node `{{project}}=dev` and provisions the ingress volumes; the model starts its declared Redis with the app. Replace the reserved development host consistently in the manifest, Caddyfile and public configuration; resolve it to the node and record this in `.starcistacks/dev/infra/swarm/owner-actions.md`. Create the external development secrets named in the model from real sealed members. |
|
|
11
|
+
| doctor | From this directory, run `docker stack config -c infra/swarm/stack.yaml`. Confirm image, secret and local database availability. |
|
|
12
12
|
| up | Run `docker stack deploy -c infra/swarm/stack.yaml {{project}}-dev` from this directory after preparation. |
|
|
13
13
|
| status | Run `docker stack services {{project}}-dev`. |
|
|
14
14
|
| logs | Run `docker service logs {{project}}-dev_api`; never print mounted credentials. |
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Status: pending owner execution. No backup or restore has been run by scaffolding.
|
|
4
4
|
|
|
5
|
-
The development operator owns recovery of the
|
|
5
|
+
The development operator owns recovery of the db service's volume `{{project}}-dev-db-data` and both external ingress volumes, `{{project}}-dev-ingress-data` and `{{project}}-dev-ingress-config-data`. Retain seven daily encrypted off-host exports or snapshots for each target.
|
|
6
6
|
|
|
7
7
|
Before relying on the backup policy, restore a database export and both volume snapshots into isolated infrastructure. Record the actual off-host destination, encryption custody, backup artifact digests, restoration time and operator. Verify database connectivity and the API readiness endpoint, then verify the restored ingress configuration and state. Attach the dated result here without credentials.
|