ci: add Gitea gates and repair boundary drill
This commit is contained in:
parent
378768b8d5
commit
a080c3e183
|
|
@ -72,6 +72,9 @@ core.*
|
|||
/.tools/
|
||||
/.playwright-cli/
|
||||
/output/
|
||||
/.runner
|
||||
/act_runner
|
||||
/act_runner-data/
|
||||
|
||||
# Local configuration can contain deployment credentials and must not be sent
|
||||
# to Docker when Compose builds from the repository root.
|
||||
|
|
|
|||
29
.gitea/workflows/quality.yml
Normal file
29
.gitea/workflows/quality.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: Quality
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
full-local-gates:
|
||||
runs-on: who-need-help-ci
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run isolated quality and security gates
|
||||
run: ./scripts/quality.sh
|
||||
|
||||
- name: Run isolated browser end-to-end tests
|
||||
run: ./scripts/e2e-run.sh
|
||||
|
||||
- name: Run isolated OAuth, SMTP, and push boundary drill
|
||||
run: ./scripts/external-boundaries-run.sh ci-boundaries
|
||||
|
||||
- name: Build, lint, and unit-test the debug Android app
|
||||
env:
|
||||
WNH_ENV_FILE: .env.example
|
||||
run: ./scripts/android-build.sh
|
||||
3
.github/actionlint.yaml
vendored
Normal file
3
.github/actionlint.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
self-hosted-runner:
|
||||
labels:
|
||||
- who-need-help-ci
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -41,6 +41,11 @@ npm-debug.log
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# Gitea act_runner registration state and local binaries must stay outside Git.
|
||||
/.runner
|
||||
/act_runner
|
||||
/act_runner-data/
|
||||
|
||||
# Local environment files can contain deployment credentials. Keep only the
|
||||
# documented template in Git.
|
||||
/.env
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -148,14 +148,14 @@ boundary entirely inside an isolated Docker network:
|
|||
```
|
||||
|
||||
The command publishes no host ports, generates independent one-run credentials
|
||||
in an ignored mode-`0600` file, verifies success, rejection, retry, replay, and
|
||||
timeout paths, retains only non-secret evidence, and removes its exact Compose
|
||||
project, database volume, and images. It also runs two Oban worker replicas and
|
||||
verifies that request acceptance and new-chat events are delivered from their
|
||||
real domain transactions, including an Oban retry and pre-transport replay
|
||||
deduplication. The adapter is not presented as an FCM or APNs implementation;
|
||||
an external provider must resolve the stable user recipient to registered
|
||||
devices.
|
||||
and a separate metrics bearer token in an ignored mode-`0600` file, verifies
|
||||
success, rejection, retry, replay, and timeout paths, retains only non-secret
|
||||
evidence, and removes its exact Compose project, database volume, and images.
|
||||
It also runs two Oban worker replicas and verifies that request acceptance and
|
||||
new-chat events are delivered from their real domain transactions, including
|
||||
an Oban retry and pre-transport replay deduplication. The adapter is not
|
||||
presented as an FCM or APNs implementation; an external provider must resolve
|
||||
the stable user recipient to registered devices.
|
||||
|
||||
The commands, boundaries, and unclaimed production properties are documented
|
||||
in [the operations runbook](docs/operations.md).
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ x-boundary-app-environment: &boundary-app-environment
|
|||
DATABASE_URL: ${EXTERNAL_DATABASE_URL:?Set EXTERNAL_DATABASE_URL}
|
||||
SECRET_KEY_BASE: ${EXTERNAL_SECRET_KEY_BASE:?Set EXTERNAL_SECRET_KEY_BASE}
|
||||
HANDOVER_SECRET: ${EXTERNAL_HANDOVER_SECRET:?Set EXTERNAL_HANDOVER_SECRET}
|
||||
METRICS_TOKEN: ${EXTERNAL_METRICS_TOKEN:?Set EXTERNAL_METRICS_TOKEN}
|
||||
PHX_HOST: boundary.local
|
||||
PHX_SCHEME: http
|
||||
PHX_URL_PORT: "80"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,34 @@ time objective, retention period, storage capacity, or high-availability model;
|
|||
those values require product policy and measurements from the eventual
|
||||
production environment.
|
||||
|
||||
## Private Gitea continuous integration
|
||||
|
||||
The observed private Git remote is hosted by Gitea 1.22.0. Gitea 1.22 reads
|
||||
repository workflows from `.gitea/workflows/`, so the repository keeps a
|
||||
Gitea-specific quality workflow in addition to the GitHub-compatible workflow.
|
||||
Both files are checked by the pinned local actionlint gate.
|
||||
|
||||
The Gitea workflow deliberately targets only the custom
|
||||
`who-need-help-ci` runner label. Its jobs build and run many Docker images and
|
||||
must not be scheduled onto an unrelated shared runner or onto the production
|
||||
application host. Its gates run sequentially in one job so a single push cannot
|
||||
start four heavy Docker suites concurrently. Before enabling it:
|
||||
|
||||
1. enable Actions in the private repository settings;
|
||||
2. register a trusted runner controlled by this project;
|
||||
3. assign `who-need-help-ci:host` only to a Linux host where the exact Docker
|
||||
Engine and Compose commands used by the repository have been verified;
|
||||
4. install the runtime required by the pinned checkout action on that host;
|
||||
5. keep the runner registration state and any Docker socket access outside the
|
||||
repository and away from untrusted projects.
|
||||
|
||||
The runner's availability, labels, software versions, free resources and
|
||||
repository Actions setting cannot be established from the unauthenticated
|
||||
repository clone. Do not treat the presence of the workflow file as evidence
|
||||
that remote CI has run. After provisioning the runner, trigger the workflow
|
||||
manually, require the full job and all four gate steps to pass, and record the
|
||||
run URL and commit before protecting `main`.
|
||||
|
||||
## First production Compose environment
|
||||
|
||||
Run the initializer on the target Docker host after its final public hostname
|
||||
|
|
@ -140,8 +168,9 @@ credentials, a server, or host-published ports:
|
|||
|
||||
The script creates a uniquely named Compose project on an internal-only Docker
|
||||
network. It generates independent one-run OAuth and push credentials in an
|
||||
ignored mode-`0600` environment file, builds the production release plus a
|
||||
non-root standard-library Python protocol mock, and then verifies:
|
||||
ignored mode-`0600` environment file, including a separate bearer token for the
|
||||
protected metrics endpoint, builds the production release plus a non-root
|
||||
standard-library Python protocol mock, and then verifies:
|
||||
|
||||
`compose.external-boundaries.yaml` is the only deployment file that sets
|
||||
`ALLOW_INSECURE_EXTERNAL_HTTP=true`. Ordinary runtime configuration requires
|
||||
|
|
|
|||
|
|
@ -982,6 +982,43 @@ messages. One PostgreSQL error in the audit window came from an incorrect
|
|||
column name in a read-only operator query; PostgreSQL aborted that transaction.
|
||||
The subsequent log window contained no matched error/warning entry.
|
||||
|
||||
## Private-Git and final boundary replay on 2026-07-20
|
||||
|
||||
The newly configured private `origin` and its `main` branch both resolved to
|
||||
commit `378768b` before this replay. The observed server API identified Gitea
|
||||
1.22.0. Because that release reads repository workflows from
|
||||
`.gitea/workflows/`, a separate Gitea workflow now runs the same four local
|
||||
gates sequentially on the dedicated `who-need-help-ci` label. Both the GitHub
|
||||
and Gitea workflows passed pinned actionlint locally. Remote Actions enablement,
|
||||
runner registration and an actual Gitea run remain unverified until a trusted
|
||||
runner is provisioned.
|
||||
|
||||
This replay found and corrected two integration regressions that the ordinary
|
||||
Phoenix suite did not cover: the external-boundary workers were missing the
|
||||
new required metrics credential, and the drill's product request was missing
|
||||
the new required safety acknowledgement. The repaired drill then passed OAuth,
|
||||
SMTP, provider-neutral push, two Oban worker replicas, retry, replay
|
||||
deduplication and product-event delivery. Its non-secret mode-`0600` evidence
|
||||
is `output/external-boundaries/preprod-gitea-pass`.
|
||||
|
||||
- The final isolated quality/security gate passed with 244/244 ExUnit tests and
|
||||
zero configured-threshold source/image findings. The Gitea runner state,
|
||||
binary and local data directory are now excluded from Git and Docker build
|
||||
contexts and covered by the quality script.
|
||||
- The isolated Playwright suite passed all 30 scenarios across Chromium,
|
||||
Firefox and WebKit, including medicine matching, chat, tracking, handover,
|
||||
blind reviews, privacy, moderation, account changes and replica restart
|
||||
recovery. Evidence is `output/e2e/20260720190130-1182`.
|
||||
- The debug Android build passed unit tests, lint, debug APK and test APK
|
||||
assembly using `.env.example`; no generated APK is tracked.
|
||||
- The temporary HTTPS origin returned HTTP 200 from both `/healthz/live` and
|
||||
`/healthz/ready`. The observed local Compose project had two healthy web and
|
||||
two healthy worker replicas with zero restarts.
|
||||
- One idle `docker stats --no-stream` snapshot measured approximately
|
||||
1,040 MiB combined across the database, Docker API proxy, Mailpit, edge
|
||||
proxy, two web replicas and two worker replicas. This workstation snapshot is
|
||||
not a production minimum, limit or capacity claim.
|
||||
|
||||
## Known work before a public production launch
|
||||
|
||||
- Replace the temporary staging origin with the production-owned domain and
|
||||
|
|
|
|||
|
|
@ -390,7 +390,8 @@ defmodule WhoNeedHelp.ExternalBoundaryDrill do
|
|||
"location_visibility" => "approximate_public",
|
||||
"structured_data" => %{"pickup_status" => "reserved"},
|
||||
"expires_at" => DateTime.utc_now(:second) |> DateTime.add(3, :hour),
|
||||
"category_id" => category.id
|
||||
"category_id" => category.id,
|
||||
"safety_confirmed" => true
|
||||
})
|
||||
|
||||
{:ok, assignment} = Help.accept_request(helper_scope, request.id)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ chmod 700 "$ROOT/tmp" "$ROOT/tmp/external-boundaries" "$runtime_dir" \
|
|||
oauth_client_id="local-$(openssl rand -hex 12)"
|
||||
oauth_client_secret=$(openssl rand -hex 32)
|
||||
push_bearer_token=$(openssl rand -hex 32)
|
||||
metrics_token=$(openssl rand -hex 32)
|
||||
database_password=$(openssl rand -hex 24)
|
||||
secret_key_base=$(openssl rand -hex 64)
|
||||
handover_secret=$(openssl rand -hex 32)
|
||||
|
|
@ -51,6 +52,7 @@ external_boundary_mock_image="who-need-help:boundary-mock-$run_id"
|
|||
printf 'EXTERNAL_OAUTH_CLIENT_ID=%s\n' "$oauth_client_id"
|
||||
printf 'EXTERNAL_OAUTH_CLIENT_SECRET=%s\n' "$oauth_client_secret"
|
||||
printf 'EXTERNAL_PUSH_BEARER_TOKEN=%s\n' "$push_bearer_token"
|
||||
printf 'EXTERNAL_METRICS_TOKEN=%s\n' "$metrics_token"
|
||||
printf 'EXTERNAL_POSTGRES_PASSWORD=%s\n' "$database_password"
|
||||
printf 'EXTERNAL_DATABASE_URL=ecto://boundary:%s@boundary-db/boundary\n' "$database_password"
|
||||
printf 'EXTERNAL_SECRET_KEY_BASE=%s\n' "$secret_key_base"
|
||||
|
|
@ -167,8 +169,8 @@ jq -e '
|
|||
.push_product_integration.database_scope == "isolated_ephemeral_volume"
|
||||
' "$output_dir/summary.json" >/dev/null
|
||||
|
||||
for secret in "$oauth_client_secret" "$push_bearer_token" "$database_password" \
|
||||
"$secret_key_base" "$handover_secret"; do
|
||||
for secret in "$oauth_client_secret" "$push_bearer_token" "$metrics_token" \
|
||||
"$database_password" "$secret_key_base" "$handover_secret"; do
|
||||
if grep -R -F -q -- "$secret" "$output_dir"; then
|
||||
echo "Generated secret appeared in retained boundary evidence." >&2
|
||||
exit 1
|
||||
|
|
@ -177,7 +179,7 @@ done
|
|||
|
||||
test "$(stat -c '%a' "$output_dir/summary.json")" = "600"
|
||||
|
||||
unset oauth_client_id oauth_client_secret push_bearer_token database_password \
|
||||
secret_key_base handover_secret
|
||||
unset oauth_client_id oauth_client_secret push_bearer_token metrics_token \
|
||||
database_password secret_key_base handover_secret
|
||||
|
||||
printf 'External boundary evidence: %s\n' "$output_dir"
|
||||
|
|
|
|||
|
|
@ -68,16 +68,27 @@ for dockerfile in Dockerfile Dockerfile.backup Dockerfile.minio \
|
|||
hadolint --failure-threshold warning - <"$dockerfile"
|
||||
done
|
||||
|
||||
echo "Checking the GitHub Actions workflow with actionlint 1.7.12"
|
||||
echo "Checking the GitHub and Gitea Actions workflows with actionlint 1.7.12"
|
||||
docker run --rm \
|
||||
--volume "$ROOT:/repo:ro" \
|
||||
--workdir /repo \
|
||||
"$ACTIONLINT_IMAGE"
|
||||
"$ACTIONLINT_IMAGE" \
|
||||
-config-file .github/actionlint.yaml \
|
||||
.github/workflows/quality.yml \
|
||||
.gitea/workflows/quality.yml
|
||||
|
||||
echo "Checking crash dumps are excluded from the Docker build context"
|
||||
grep -Fx 'core' .dockerignore >/dev/null
|
||||
grep -Fx 'core.*' .dockerignore >/dev/null
|
||||
|
||||
echo "Checking local Gitea runner state is excluded from Git and Docker contexts"
|
||||
grep -Fx '/.runner' .gitignore >/dev/null
|
||||
grep -Fx '/act_runner' .gitignore >/dev/null
|
||||
grep -Fx '/act_runner-data/' .gitignore >/dev/null
|
||||
grep -Fx '/.runner' .dockerignore >/dev/null
|
||||
grep -Fx '/act_runner' .dockerignore >/dev/null
|
||||
grep -Fx '/act_runner-data/' .dockerignore >/dev/null
|
||||
|
||||
echo "Checking production environment initialization and validation"
|
||||
production_env="$scan_dir/.env.production"
|
||||
PRODUCTION_TRAEFIK_TRUSTED_IPS=172.20.0.1/32 \
|
||||
|
|
@ -202,6 +213,7 @@ EXTERNAL_BOUNDARY_HOST_GID="$(id -g)" \
|
|||
EXTERNAL_OAUTH_CLIENT_ID=render-client \
|
||||
EXTERNAL_OAUTH_CLIENT_SECRET=render-secret \
|
||||
EXTERNAL_PUSH_BEARER_TOKEN=render-push-token \
|
||||
EXTERNAL_METRICS_TOKEN=render-metrics-token \
|
||||
EXTERNAL_POSTGRES_PASSWORD=render-database-secret \
|
||||
EXTERNAL_DATABASE_URL=ecto://boundary:render-database-secret@boundary-db/boundary \
|
||||
EXTERNAL_SECRET_KEY_BASE=render-secret-key-base \
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user