Repository navigation
Expand file tree
/
Copy pathTaskfile.yaml
More file actions
332 lines (285 loc) · 11.5 KB
/
Copy pathTaskfile.yaml
File metadata and controls
332 lines (285 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
version: "3"
# secp256k1 exists in jwx only under this build tag, and its absence is silent:
# without it the curve is simply missing from the JWA registry. Setting it as
# GOFLAGS means every go invocation below — build, test, lint, air — picks it
# up, rather than each command carrying its own -tags.
env:
GOFLAGS: -tags=jwx_es256k
# The compose file is named for the environment it describes, so every
# `docker compose` below — and any run by hand in a shell that exports this —
# finds it without a -f on each command.
COMPOSE_FILE: docker-compose.dev.yaml
# Written by `task auth:bootstrap`, and absent until it runs: it carries the
# client secret and the session key, which is why it is generated rather than
# committed. Task tolerates it not existing.
dotenv: [".env"]
vars:
BIN: alexandria
# Read from the configuration rather than repeated here: the port the process
# actually listens on is `internal_port`, since what the outside world sees is
# the proxy's.
NODE_PORT:
sh: sed -n 's/^[[:space:]]*internal_port:[[:space:]]*"\(.*\)".*/\1/p' config/config.yaml | head -n 1
ISSUER:
sh: sed -n 's/^[[:space:]]*issuer:[[:space:]]*"\(.*\)".*/\1/p' config/config.yaml | head -n 1
MAIN: ./cmd/alexandria
VERSION:
sh: git describe --tags --always --dirty 2>/dev/null || echo dev
tasks:
default:
cmds: [{ task: check }]
fmt:
desc: Format the code with gofumpt.
cmds: ["go tool gofumpt -w ."]
lint:
desc: Run golangci-lint.
cmds: ["go tool golangci-lint run"]
test:
desc: Run tests with the race detector and a coverage profile.
env:
ALEXANDRIA_AUTH_CONFIG_ENABLED: "false"
cmds: ["go test -race -coverprofile=coverage.out ./..."]
test:integration:
desc: Run all integration tests against live wallet services.
env:
ALEXANDRIA_AUTH_CONFIG_ENABLED: "false"
cmds: ['go test -race -v -tags="integration,jwx_es256k" ./...']
test:integration:fafnir:
desc: Run Fafnir integration tests.
cmds: ['go test -race -v -tags="integration,jwx_es256k" ./internal/ssi-auth/fafnir/...']
test:integration:identityhub:
desc: Run IdentityHub integration tests.
cmds: ['go test -race -v -tags="integration,jwx_es256k" ./internal/ssi-auth/identityhub/...']
test:all:
desc: Run both unit tests and integration tests.
env:
ALEXANDRIA_AUTH_CONFIG_ENABLED: "false"
cmds: ['go test -race -v -tags="integration,jwx_es256k" -coverprofile=coverage.out ./...']
cover:
desc: Open the coverage report in the browser.
deps: [test]
cmds: ["go tool cover -html=coverage.out"]
build:
desc: Build the binary into bin/.
cmds:
- go build -ldflags="-s -w -X main.version={{.VERSION}}" -o bin/{{.BIN}} {{.MAIN}}
run:
desc: Run the application.
cmds: ["go run {{.MAIN}}"]
dev:
desc: Hot reload with air.
cmds: ["go tool air"]
dev:auto:
desc: "One command to start working: clear what is stale, bring everything up, run the node."
deps: [tls:ca]
cmds:
# A node left running from yesterday holds the port this one wants, and
# the error that produces names neither the process nor the port.
- |
stale="$(lsof -ti tcp:{{.NODE_PORT}} 2>/dev/null || true)"
if [ -n "$stale" ]; then
echo "stale node on {{.NODE_PORT}}, stopping it"
echo "$stale" | xargs kill
fi
# Everything the node talks to. Idempotent: already-running services are
# left alone, and --wait blocks until they are actually answering rather
# than merely started.
- docker compose up -d --wait postgres caddy zitadel fafnir-wallet
# Idempotent too: it finds the project and application if they are there,
# and re-registers the redirect URI in case the configuration moved.
- bash scripts/zitadel-bootstrap.sh
# Likewise: it gives the wallet a key and a DID only if it has none. The
# node's first call is GET /dids/default, and a wallet that has just been
# created answers 404 to it.
- bash scripts/fafnir-bootstrap.sh
# The one thing no task can do for you, because it needs an administrator.
- |
if ! curl -sf {{.ISSUER}}/.well-known/openid-configuration >/dev/null 2>&1; then
echo
echo " This machine does not trust the project authority yet, so the"
echo " node will not reach {{.ISSUER}}:"
echo
echo " task tls:trust"
echo
fi
- task: dev
db:up:
desc: Start the local Postgres and wait for it to accept connections.
cmds: ["docker compose up -d --wait postgres"]
db:down:
desc: Stop the local Postgres, keeping its data.
cmds: ["docker compose down"]
db:reset:
desc: Destroy the local Postgres and its data, then start it again.
prompt: This deletes the local database volume. Continue?
cmds:
- docker compose down -v
- task: db:up
db:psql:
desc: Open a psql shell on the local Postgres.
cmds: ['docker compose exec postgres sh -c ''psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"']
db:logs:
desc: Follow the local Postgres logs.
cmds: ["docker compose logs -f postgres"]
wallet:up:
desc: Start the local wallet and wait for it to answer.
cmds: ["docker compose up -d --wait fafnir-wallet"]
wallet:bootstrap:
desc: Give the wallet a signing key and a default DID, if it has none.
deps: [wallet:up]
cmds: ["bash scripts/fafnir-bootstrap.sh"]
wallet:down:
desc: Stop the local wallet, keeping its keys and its data.
cmds: ["docker compose stop fafnir-wallet fafnir-db"]
wallet:reset:
desc: Destroy the local wallet, its keys and its data, then start over.
prompt: This deletes the node's private key. Its DID cannot be recovered. Continue?
cmds:
- docker compose rm -sfv fafnir-wallet fafnir-db fafnir-setup
# Compose prefixes a volume with the project, which is the directory name
# unless COMPOSE_PROJECT_NAME says otherwise.
- docker volume rm -f ${COMPOSE_PROJECT_NAME:-alexandria}_fafnir-data ${COMPOSE_PROJECT_NAME:-alexandria}_fafnir-keys
- task: wallet:bootstrap
wallet:logs:
desc: Follow the local wallet logs.
cmds: ["docker compose logs -f fafnir-wallet"]
identityhub:up:
desc: Start IdentityHub container and wait for it to be healthy.
cmds:
- |
if [ ! "$(docker ps -q -f name=identityhub-test)" ]; then
if [ "$(docker ps -aq -f status=exited -f name=identityhub-test)" ]; then
docker rm identityhub-test
fi
docker run -d --rm --name identityhub-test \
-p 8181:8181 -p 8182:8182 \
-e "WEB_HTTP_PORT=8181" \
-e "WEB_HTTP_PATH=/api" \
-e "WEB_HTTP_IDENTITY_PORT=8182" \
-e "WEB_HTTP_IDENTITY_PATH=/api/identity" \
${IDENTITYHUB_IMAGE:-identity-hub:latest}
fi
- |
echo "Waiting for IdentityHub to be healthy..."
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:8181/api/check/health >/dev/null 2>&1; then
echo "IdentityHub is healthy on :8181 / :8182"
exit 0
fi
sleep 1
done
echo "Timed out waiting for IdentityHub" >&2
exit 1
identityhub:down:
desc: Stop the IdentityHub container.
cmds:
- docker stop identityhub-test 2>/dev/null || true
identityhub:logs:
desc: Follow IdentityHub container logs.
cmds:
- docker logs -f identityhub-test
vault:up:
desc: "Start Vault, seed it, and run the wallet against it instead of files."
cmds:
- docker compose --profile vault up -d --wait vault
- docker compose --profile vault up vault-seed
# The wallet reads is_vault_real from its YAML, and there is no
# environment override for it, so switching modes means mounting the
# other file. --force-recreate because only the mount changed.
- FAFNIR_CONFIG=./config/fafnir/wallet-vault.yaml docker compose up -d --force-recreate --wait fafnir-wallet
- |
echo
echo " The wallet now keeps its keys in Vault. Its previous file-backed"
echo " identity is still on the fafnir-keys volume but is not in use, so"
echo " give it one here:"
echo
echo " task wallet:bootstrap"
echo
echo " Vault UI http://127.0.0.1:8200 token alexandria-dev-root"
echo
vault:down:
desc: "Stop Vault and put the wallet back on files."
cmds:
- docker compose up -d --force-recreate --wait fafnir-wallet
- docker compose --profile vault rm -sf vault vault-seed
vault:logs:
desc: Follow the local Vault logs.
cmds: ["docker compose --profile vault logs -f vault"]
tls:ca:
desc: Create the project's certificate authority in .certs/, if it has none.
cmds: ["bash scripts/tls-ca.sh"]
tls:trust:
desc: Trust that authority on this machine. Needs sudo, once.
deps: [tls:ca]
cmds:
- |
case "$(uname -s)" in
Darwin)
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain .certs/root.crt
;;
Linux)
sudo cp .certs/root.crt /usr/local/share/ca-certificates/alexandria-local.crt
sudo update-ca-certificates
;;
*)
echo "Trust .certs/root.crt by hand on this system." >&2
exit 1
;;
esac
tls:up:
desc: Start the local TLS terminator.
deps: [tls:ca]
cmds: ["docker compose up -d --wait caddy"]
tls:logs:
desc: Follow the Caddy logs.
cmds: ["docker compose logs -f caddy"]
auth:up:
desc: Start the local Zitadel and wait for it to answer.
cmds: ["docker compose up -d --wait zitadel"]
auth:bootstrap:
desc: Create the Zitadel project and application, and write .env.
# Not tls:up. TLS is infrastructure of its own: it fronts the node whether
# or not authentication is enabled, and this talks to Zitadel directly.
deps: [auth:up]
cmds: ["bash scripts/zitadel-bootstrap.sh"]
auth:down:
desc: Stop the local Zitadel, keeping its data.
cmds: ["docker compose stop zitadel zitadel-db"]
auth:reset:
desc: Destroy the local Zitadel and its data, then start it again.
prompt: This deletes every user, project and application in the local Zitadel. Continue?
cmds:
- docker compose rm -sfv zitadel zitadel-db zitadel-init
# Compose prefixes a volume with the project, which is the directory name
# unless COMPOSE_PROJECT_NAME says otherwise. Spelling the default here
# rather than hard-coding "alexandria" keeps a renamed checkout working.
- docker volume rm -f ${COMPOSE_PROJECT_NAME:-alexandria}_zitadel-data ${COMPOSE_PROJECT_NAME:-alexandria}_zitadel-machinekey
- rm -f .env
- task: auth:bootstrap
auth:logs:
desc: Follow the local Zitadel logs.
cmds: ["docker compose logs -f zitadel"]
auth:console:
desc: Open the Zitadel console in the browser.
cmds: ["open http://127.0.0.1:1600/ui/console"]
tidy:
desc: Sync go.mod and go.sum.
cmds: ["go mod tidy"]
vuln:
desc: Scan for known vulnerabilities.
cmds: ["go run golang.org/x/vuln/cmd/govulncheck@latest ./..."]
docs:dev:
desc: Start the Fumadocs documentation dev server.
dir: docs
cmds: ["pnpm dev"]
docs:build:
desc: Build static documentation for GitHub Pages.
dir: docs
cmds: ["pnpm build"]
check:
desc: "Full pipeline: format, lint and tests."
cmds:
- task: fmt
- task: lint
- task: test