chore: upgrade project stack and deployment baseline
This commit is contained in:
parent
68faa4795c
commit
51da6d8881
21
Dockerfile
21
Dockerfile
|
|
@ -1,9 +1,9 @@
|
|||
# This file is based on these images:
|
||||
#
|
||||
# - https://hub.docker.com/r/hexpm/elixir/tags - for the builder image
|
||||
# E.g.: docker.io/hexpm/elixir:1.18.4-erlang-27.3.4.13-debian-trixie-20260623-slim
|
||||
# - https://hub.docker.com/_/debian/tags?name=trixie-20260623-slim - for the runner image
|
||||
# E.g.: docker.io/debian:trixie-20260623-slim
|
||||
# E.g.: docker.io/hexpm/elixir:1.20.2-erlang-29.0.3-debian-trixie-20260713-slim
|
||||
# - https://hub.docker.com/_/debian/tags?name=trixie-20260713-slim - for the runner image
|
||||
# E.g.: docker.io/debian:trixie-20260713-slim
|
||||
#
|
||||
# Find builder and runner images on Docker Hub or on Hex's Build Server (Bob).
|
||||
# We recommend using Bob's Web UI to find recent tags:
|
||||
|
|
@ -17,18 +17,19 @@
|
|||
#
|
||||
# For finding packages in Debian, search on https://packages.debian.org/.
|
||||
|
||||
ARG ELIXIR_VERSION=1.18.4
|
||||
ARG OTP_VERSION=27.3.4.13
|
||||
ARG DEBIAN_VERSION=trixie-20260623-slim
|
||||
ARG ELIXIR_VERSION=1.20.2
|
||||
ARG OTP_VERSION=29.0.3
|
||||
ARG DEBIAN_VERSION=trixie-20260713-slim
|
||||
|
||||
ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
|
||||
ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}"
|
||||
ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}@sha256:6fcd8ea864221b960c1ec418e3b10fa488298ff9e70c9e0f3db18070e610fb8a"
|
||||
ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}@sha256:020c0d20b9880058cbe785a9db107156c3c75c2ac944a6aa7ab59f2add76a7bd"
|
||||
|
||||
FROM docker.io/node:22.21.1-bookworm-slim AS node_deps
|
||||
FROM docker.io/node:24.18.0-bookworm-slim@sha256:6f7b03f7c2c8e2e784dcf9295400527b9b1270fd37b7e9a7285cf83b6951452d AS node_deps
|
||||
|
||||
WORKDIR /assets
|
||||
COPY assets/package.json assets/package-lock.json ./
|
||||
RUN npm ci
|
||||
RUN npm install --global npm@12.0.1 \
|
||||
&& npm ci
|
||||
|
||||
FROM ${BUILDER_IMAGE} AS builder
|
||||
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -151,12 +151,22 @@ Open:
|
|||
- Mailpit: <http://localhost:8028>
|
||||
|
||||
The script refuses to modify a pre-existing cluster named `who-need-help`
|
||||
unless the project ownership marker exists.
|
||||
unless the project ownership marker exists. On first install it creates
|
||||
independent random application and PostgreSQL credentials in the
|
||||
`who-need-help-local` Kubernetes Secret without writing them to Git. The local
|
||||
PostGIS data directory persists inside the kind node. When migrating an older
|
||||
project-owned cluster from the former `emptyDir` deployment, the script creates
|
||||
and validates a local dump before replacing the database workload, then restores
|
||||
that dump. After a successful rollout it also removes the obsolete chart Secret
|
||||
and only the local Helm history revisions that stored the former inline
|
||||
credential fields.
|
||||
|
||||
For an external cluster, provide a real PostgreSQL/PostGIS service and a
|
||||
pre-created Secret through `existingSecret`. The chart intentionally has no
|
||||
invented CPU/RAM limits or HPA thresholds; measure this application in the
|
||||
target environment before setting them.
|
||||
pre-created Secret through required `existingSecret`; the chart never renders
|
||||
credentials from tracked values. The Secret must contain `DATABASE_URL`,
|
||||
`SECRET_KEY_BASE`, `HANDOVER_SECRET`, and `RELEASE_COOKIE`. The chart
|
||||
intentionally has no invented CPU/RAM limits or HPA thresholds; measure this
|
||||
application in the target environment before setting them.
|
||||
|
||||
## Local Codex category review
|
||||
|
||||
|
|
@ -185,6 +195,7 @@ and no fallback provider. Recommendations require a human moderator action.
|
|||
- [Architecture](docs/architecture.md)
|
||||
- [Trust and safety](docs/trust-safety.md)
|
||||
- [Implementation verification and known limits](docs/verification.md)
|
||||
- [Verified dependency baseline](docs/dependency-baseline.md)
|
||||
- [PostgreSQL/PostGIS ADR](docs/decisions/0001-postgresql-postgis-over-spacetimedb.md)
|
||||
|
||||
Exact dependency versions are locked in `mix.lock`,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ FROM gradle:9.6.1-jdk17@sha256:7364ce528f33bb6038672bcef990d524f1ad8fbc292935819
|
|||
|
||||
USER root
|
||||
|
||||
ARG ANDROID_COMMAND_LINE_TOOLS_VERSION=14742923
|
||||
ARG ANDROID_COMMAND_LINE_TOOLS_SHA1=48833c34b761c10cb20bcd16582129395d121b27
|
||||
ARG ANDROID_COMMAND_LINE_TOOLS_VERSION=15859902
|
||||
ARG ANDROID_COMMAND_LINE_TOOLS_SHA1=040d3996a65543d22ec4bf73e4c37aa37a8d4af4
|
||||
|
||||
ENV ANDROID_HOME=/opt/android-sdk
|
||||
ENV ANDROID_SDK_ROOT=/opt/android-sdk
|
||||
|
|
@ -21,12 +21,11 @@ RUN mkdir -p "${ANDROID_HOME}/cmdline-tools" \
|
|||
| sha1sum -c - \
|
||||
&& unzip -q /tmp/android-command-line-tools.zip -d /tmp/android-command-line-tools \
|
||||
&& mv /tmp/android-command-line-tools/cmdline-tools "${ANDROID_HOME}/cmdline-tools/latest" \
|
||||
&& rm -rf /tmp/android-command-line-tools /tmp/android-command-line-tools.zip \
|
||||
&& yes | sdkmanager --licenses >/dev/null
|
||||
&& rm -rf /tmp/android-command-line-tools /tmp/android-command-line-tools.zip
|
||||
|
||||
RUN sdkmanager \
|
||||
"build-tools;37.0.0" \
|
||||
"platforms;android-37.0" \
|
||||
RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \
|
||||
"build-tools/37.0.0" \
|
||||
"platforms/android-37.0" \
|
||||
&& chown -R gradle:gradle "${ANDROID_HOME}"
|
||||
|
||||
FROM android-base AS android-sdk
|
||||
|
|
@ -52,6 +51,11 @@ FROM android-base AS emulator
|
|||
|
||||
USER root
|
||||
|
||||
RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \
|
||||
"emulator" \
|
||||
"system-images/android-37.0/google_apis_ps16k/x86_64" \
|
||||
&& chown -R gradle:gradle "${ANDROID_HOME}"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
libdbus-1-3 \
|
||||
|
|
@ -63,20 +67,16 @@ RUN apt-get update \
|
|||
libxcomposite1 \
|
||||
libxcursor1 \
|
||||
libxi6 \
|
||||
libxkbfile1 \
|
||||
libxrandr2 \
|
||||
libxrender1 \
|
||||
libxtst6 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN sdkmanager \
|
||||
"emulator" \
|
||||
"system-images;android-37.0;google_apis_ps16k;x86_64" \
|
||||
&& chown -R gradle:gradle "${ANDROID_HOME}"
|
||||
|
||||
# The container is operated from the host through `docker exec ... adb`.
|
||||
# Keep platform-tools in the emulator target without invalidating the large
|
||||
# system-image layer when application sources change.
|
||||
RUN sdkmanager "platform-tools" \
|
||||
RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install "platform-tools" \
|
||||
&& chown -R gradle:gradle "${ANDROID_HOME}"
|
||||
|
||||
USER gradle
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ background-location permission.
|
|||
|
||||
- Android Gradle Plugin 9.3.0
|
||||
- Gradle 9.6.1
|
||||
- Android SDK Command-line Tools 22.0
|
||||
- Android CLI 1.0.15857036 (embedded in the locked Command-line Tools archive)
|
||||
- compileSdk / targetSdk 37
|
||||
- Build Tools 37.0.0
|
||||
- Java source and bytecode level 17
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ x-app-environment: &app-environment
|
|||
|
||||
services:
|
||||
proxy:
|
||||
image: traefik:v3.7
|
||||
image: traefik:v3.7.8@sha256:4299bbed850421258fc5448c2e0e6ad350981d4d335a68de11b92448aedbefe5
|
||||
command:
|
||||
- --api.dashboard=false
|
||||
- --providers.docker=true
|
||||
|
|
@ -41,7 +41,7 @@ services:
|
|||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgis/postgis:18-3.6-alpine
|
||||
image: postgis/postgis:18-3.6-alpine@sha256:05d68c7f0f19b9aa0bf7c4a2049b2e8b38b44a63116392b95726a4c913766cf6
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:?Set POSTGRES_DB in .env}
|
||||
POSTGRES_USER: ${POSTGRES_USER:?Set POSTGRES_USER in .env}
|
||||
|
|
@ -57,7 +57,7 @@ services:
|
|||
restart: unless-stopped
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit:v1.27
|
||||
image: axllent/mailpit:v1.30.4@sha256:5a49a77c5bdbe7c5474450b4f46348d09949df3695257729c93a30369382d4f6
|
||||
ports:
|
||||
- "${MAILPIT_PORT:-8027}:8025"
|
||||
networks: [edge, internal]
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ config :who_need_help, WhoNeedHelp.Mailer, adapter: Swoosh.Adapters.Local
|
|||
|
||||
# Configure esbuild (the version is required)
|
||||
config :esbuild,
|
||||
version: "0.25.4",
|
||||
version: "0.28.1",
|
||||
who_need_help: [
|
||||
args:
|
||||
~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),
|
||||
|
|
@ -82,7 +82,7 @@ config :esbuild,
|
|||
|
||||
# Configure tailwind (the version is required)
|
||||
config :tailwind,
|
||||
version: "4.3.0",
|
||||
version: "4.3.3",
|
||||
who_need_help: [
|
||||
args: ~w(
|
||||
--input=assets/css/app.css
|
||||
|
|
|
|||
|
|
@ -22,5 +22,5 @@ app.kubernetes.io/instance: {{ .Release.Name }}
|
|||
{{- end }}
|
||||
|
||||
{{- define "who-need-help.secretName" -}}
|
||||
{{- default (include "who-need-help.fullname" .) .Values.existingSecret }}
|
||||
{{- required "existingSecret must name a pre-created Kubernetes Secret" .Values.existingSecret }}
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,8 @@ spec:
|
|||
app.kubernetes.io/component: {{ $component }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") $root | sha256sum }}
|
||||
{{- with $root.Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
{{- if not .Values.existingSecret }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "who-need-help.fullname" . }}
|
||||
labels:
|
||||
{{- include "who-need-help.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
helm.sh/hook: pre-install,pre-upgrade
|
||||
helm.sh/hook-weight: "-10"
|
||||
helm.sh/hook-delete-policy: before-hook-creation
|
||||
type: Opaque
|
||||
stringData:
|
||||
DATABASE_URL: {{ .Values.app.databaseUrl | quote }}
|
||||
SECRET_KEY_BASE: {{ .Values.app.secretKeyBase | quote }}
|
||||
HANDOVER_SECRET: {{ .Values.app.handoverSecret | quote }}
|
||||
RELEASE_COOKIE: {{ .Values.app.releaseCookie | quote }}
|
||||
{{- end }}
|
||||
|
|
@ -8,11 +8,9 @@ service:
|
|||
port: 80
|
||||
nodePort: 30080
|
||||
|
||||
existingSecret: who-need-help-local
|
||||
|
||||
app:
|
||||
host: localhost
|
||||
scheme: http
|
||||
urlPort: "4011"
|
||||
databaseUrl: ecto://postgres:postgres@postgis/who_need_help
|
||||
secretKeyBase: y9XxDIDxcn8uHarbX1B4feQl0xXi413tdYO472d758DtvslbtlVdUs2HdxPEPXCQ
|
||||
handoverSecret: L2a4yz6nLYkWvIBTlREj8uDByngsY39JfLIfdYNDH/7dnIQEVW4T8OCjCkHOGZ7H
|
||||
releaseCookie: OwNDSCX7aQkkNXovILLiESVolaRuYm67xC+JbMC3j3ERTY6sHIM/F4gc0elm1l07
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@ app:
|
|||
urlPort: "443"
|
||||
port: "4000"
|
||||
poolSize: "10"
|
||||
databaseUrl: ecto://postgres:postgres@postgis/who_need_help
|
||||
secretKeyBase: replace-before-public-deployment
|
||||
handoverSecret: replace-before-public-deployment
|
||||
releaseCookie: replace-before-public-deployment
|
||||
codexSessionId: not-configured
|
||||
# Shared limits are opt-in; set only after product policy thresholds are approved.
|
||||
rateLimitPoliciesJson: "{}"
|
||||
|
|
@ -31,6 +27,8 @@ app:
|
|||
smtpRelay: mailpit
|
||||
smtpPort: "1025"
|
||||
|
||||
# Required. The Secret must contain DATABASE_URL, SECRET_KEY_BASE,
|
||||
# HANDOVER_SECRET, and RELEASE_COOKIE.
|
||||
existingSecret: ""
|
||||
|
||||
ingress:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgis
|
||||
spec:
|
||||
serviceName: postgis
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: {app: postgis}
|
||||
|
|
@ -13,10 +14,23 @@ spec:
|
|||
containers:
|
||||
- name: postgis
|
||||
image: postgis/postgis:18-3.6-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- {name: POSTGRES_DB, value: who_need_help}
|
||||
- {name: POSTGRES_USER, value: postgres}
|
||||
- {name: POSTGRES_PASSWORD, value: postgres}
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: who-need-help-local
|
||||
key: POSTGRES_DB
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: who-need-help-local
|
||||
key: POSTGRES_USER
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: who-need-help-local
|
||||
key: POSTGRES_PASSWORD
|
||||
ports:
|
||||
- {name: postgres, containerPort: 5432}
|
||||
readinessProbe:
|
||||
|
|
@ -27,7 +41,9 @@ spec:
|
|||
- {name: data, mountPath: /var/lib/postgresql}
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
hostPath:
|
||||
path: /var/local/who-need-help/postgres
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
|
@ -52,7 +68,8 @@ spec:
|
|||
spec:
|
||||
containers:
|
||||
- name: mailpit
|
||||
image: axllent/mailpit:v1.27
|
||||
image: axllent/mailpit:v1.30.4
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- {name: smtp, containerPort: 1025}
|
||||
- {name: http, containerPort: 8025}
|
||||
|
|
|
|||
95
docs/dependency-baseline.md
Normal file
95
docs/dependency-baseline.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# Dependency baseline
|
||||
|
||||
Verified on 2026-07-18. This is a point-in-time stable baseline, not a claim
|
||||
that future security updates or major-version migrations can be avoided.
|
||||
Application locks and OCI digests remain committed so the same revision can be
|
||||
rebuilt deterministically.
|
||||
|
||||
## Runtime and application
|
||||
|
||||
| Component | Locked version |
|
||||
| --- | --- |
|
||||
| Elixir | 1.20.2 |
|
||||
| Erlang/OTP | 29.0.3 |
|
||||
| Phoenix | 1.8.9 |
|
||||
| Phoenix LiveView | 1.2.7 |
|
||||
| Ecto / Ecto SQL | 3.14.1 / 3.14.0 |
|
||||
| Oban | 2.23.0 |
|
||||
| Node.js LTS | 24.18.0 |
|
||||
| npm build tool | 12.0.1 |
|
||||
| Tailwind CSS binary | 4.3.3 |
|
||||
| esbuild binary | 0.28.1 |
|
||||
| daisyUI | 5.6.18 |
|
||||
| Heroicons | 2.2.0 |
|
||||
| MapLibre GL JS | 5.24.0 |
|
||||
|
||||
`mix hex.outdated` reported every direct Hex dependency as up-to-date.
|
||||
`npm outdated --json` returned an empty object, and the production `npm ci`
|
||||
audit reported zero known vulnerabilities. The full transitive versions and
|
||||
package checksums are in `mix.lock` and `assets/package-lock.json`.
|
||||
|
||||
## Services and container build
|
||||
|
||||
| Component | Locked version |
|
||||
| --- | --- |
|
||||
| PostgreSQL | 18.4 |
|
||||
| PostGIS | 3.6.4 |
|
||||
| Traefik | 3.7.8 |
|
||||
| Mailpit | 1.30.4 |
|
||||
| Debian builder/runner snapshot | trixie-20260713-slim |
|
||||
|
||||
Every external Compose/kind service image and every Dockerfile base image is
|
||||
locked to both an exact tag and an OCI digest. The observed local Docker tooling
|
||||
was Docker Engine/CLI 29.6.2, Compose 5.3.1, and Buildx 0.35.0; their official
|
||||
release feeds reported those same versions as current during verification.
|
||||
|
||||
## Android and local Kubernetes
|
||||
|
||||
| Component | Locked version |
|
||||
| --- | --- |
|
||||
| Android Gradle Plugin | 9.3.0 |
|
||||
| Gradle | 9.6.1 |
|
||||
| Android SDK Command-line Tools | 22.0 |
|
||||
| Android CLI | 1.0.15857036 |
|
||||
| Android Emulator | 36.6.11.0 |
|
||||
| compileSdk / targetSdk | 37 / 37 |
|
||||
| Android Build Tools | 37.0.0 |
|
||||
| AndroidX Activity | 1.13.0 |
|
||||
| kubectl | 1.36.2 |
|
||||
| kind | 0.32.0 |
|
||||
| Helm | 4.2.3 |
|
||||
|
||||
Android 17 API 37 is the stable target used here. API 37.1 was not selected
|
||||
because the official SDK channel identifies it as a QPR beta.
|
||||
|
||||
## Primary version sources
|
||||
|
||||
- [Elixir releases](https://github.com/elixir-lang/elixir/releases)
|
||||
- [Erlang/OTP releases](https://github.com/erlang/otp/releases)
|
||||
- [Hex package registry](https://hex.pm/)
|
||||
- [Node.js release schedule](https://nodejs.org/en/about/previous-releases)
|
||||
- [npm registry](https://www.npmjs.com/package/npm)
|
||||
- [PostgreSQL 18.4 release notes](https://www.postgresql.org/docs/release/18.4/)
|
||||
- [PostGIS release notes](https://postgis.net/docs/en/release_notes.html)
|
||||
- [Traefik releases](https://github.com/traefik/traefik/releases)
|
||||
- [Mailpit releases](https://github.com/axllent/mailpit/releases)
|
||||
- [Android Gradle Plugin 9.3.0 release notes](https://developer.android.com/build/releases/agp-9-3-0-release-notes)
|
||||
- [Android 17 SDK setup](https://developer.android.com/about/versions/17/setup-sdk)
|
||||
- [Gradle release notes](https://docs.gradle.org/current/release-notes.html)
|
||||
- [kubectl releases](https://dl.k8s.io/release/stable.txt)
|
||||
- [kind releases](https://github.com/kubernetes-sigs/kind/releases)
|
||||
- [Helm releases](https://github.com/helm/helm/releases)
|
||||
- [Docker Engine releases](https://github.com/moby/moby/releases)
|
||||
- [Docker Compose releases](https://github.com/docker/compose/releases)
|
||||
- [Docker Buildx releases](https://github.com/docker/buildx/releases)
|
||||
|
||||
## Repeatable checks
|
||||
|
||||
```bash
|
||||
docker run --rm who-need-help:test mix hex.outdated
|
||||
docker build --target node_deps --tag who-need-help:node-deps .
|
||||
docker run --rm who-need-help:node-deps npm --version
|
||||
docker run --rm who-need-help:node-deps npm outdated --json
|
||||
./scripts/test.sh
|
||||
./scripts/android-build.sh
|
||||
```
|
||||
|
|
@ -21,7 +21,8 @@ results from product limits and unknown production properties.
|
|||
|
||||
## Reproducible checks
|
||||
|
||||
- `./scripts/test.sh`: 124 tests, 0 failures in the final pre-upgrade run.
|
||||
- `./scripts/test.sh`: 124 tests, 0 failures in the final post-upgrade run on
|
||||
Elixir 1.20.2 and Erlang/OTP 29.0.3.
|
||||
- `mix format --check-formatted`: passed in the final run.
|
||||
- Android Docker build target: `testDebugUnitTest`, `lintDebug`, and
|
||||
`assembleDebug` passed; the final lint report contains no errors or warnings.
|
||||
|
|
@ -43,7 +44,7 @@ Local generated evidence (ignored by Git):
|
|||
Android artifact:
|
||||
|
||||
- `android/dist/who-need-help-debug.apk`
|
||||
- SHA-256 before the dependency-upgrade pass:
|
||||
- Final post-upgrade SHA-256:
|
||||
`063f3d8d877009ee229a403692e4b16517244dd9e7dddab4c2b3c202c5def4b8`
|
||||
- Observed manifest values: version `0.1.0-debug`, minimum SDK 24, target and
|
||||
compile SDK 37.
|
||||
|
|
@ -65,6 +66,37 @@ excluded from the root Docker build context.
|
|||
The final local Compose observation found 2 users, 1 help request, and 7
|
||||
messages. These are local scenario data; the database is not assumed empty.
|
||||
|
||||
The final kind observation found 0 users, 0 help requests, and 0 messages. That
|
||||
state was queried before changing the database workload. The migration from the
|
||||
old `emptyDir` Deployment created a custom-format dump, validated it with
|
||||
`pg_restore --list`, changed PostGIS to a persistent StatefulSet, and restored
|
||||
the dump before the Helm upgrade. The final kind database reported PostgreSQL
|
||||
18.4 and PostGIS 3.6.4.
|
||||
|
||||
The local kind Secret contains independently generated values. Only their
|
||||
decoded lengths were inspected: the PostgreSQL password is 64 characters and
|
||||
each application secret is 128 characters. Secret values were not printed or
|
||||
written to tracked files.
|
||||
|
||||
## Dependency-upgrade observations
|
||||
|
||||
- The running Compose and kind releases reported Elixir 1.20.2 and Erlang/OTP
|
||||
29.
|
||||
- Compose reported Traefik 3.7.8 and Mailpit 1.30.4; Compose and kind both
|
||||
reported PostgreSQL 18.4 and PostGIS 3.6.4.
|
||||
- The production asset build reported Tailwind CSS 4.3.3 and daisyUI 5.6.18;
|
||||
esbuild is configured at 0.28.1.
|
||||
- The isolated Node build stage reported Node.js 24.18.0, npm 12.0.1,
|
||||
`npm outdated --json` returned `{}`, and `npm ci` reported zero known
|
||||
vulnerabilities.
|
||||
- The Android build used Android Gradle Plugin 9.3.0, Gradle 9.6.1, Command-line
|
||||
Tools 22.0, and its embedded Android CLI 1.0.15857036. Unit tests, lint, and
|
||||
debug assembly passed; the lint report says “No errors or warnings.” The
|
||||
rebuilt emulator image contains the API 37 ps16k AVD and APK, and its emulator
|
||||
36.6.11.0 binary returned its version successfully at runtime.
|
||||
- The final complete `scripts/kind-up.sh` run was idempotent and finished with
|
||||
both 2-replica Deployments Ready and a successful cross-replica PubSub probe.
|
||||
|
||||
The migration `20260718114233` and reverse block lookup index
|
||||
`blocks_blocked_id_blocker_id_index` were present. An actual `EXPLAIN ANALYZE`
|
||||
for that reverse lookup selected the index. The table contained no block rows,
|
||||
|
|
|
|||
4
mix.exs
4
mix.exs
|
|
@ -5,7 +5,7 @@ defmodule WhoNeedHelp.MixProject do
|
|||
[
|
||||
app: :who_need_help,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.17",
|
||||
elixir: "~> 1.20",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
|
|
@ -63,7 +63,7 @@ defmodule WhoNeedHelp.MixProject do
|
|||
depth: 1},
|
||||
{:daisyui,
|
||||
github: "saadeghi/daisyui",
|
||||
tag: "v5.5.20",
|
||||
tag: "v5.6.18",
|
||||
sparse: "packages/bundle",
|
||||
app: false,
|
||||
compile: false,
|
||||
|
|
|
|||
2
mix.lock
2
mix.lock
|
|
@ -3,7 +3,7 @@
|
|||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"},
|
||||
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
|
||||
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
|
||||
"daisyui": {:git, "https://github.com/saadeghi/daisyui.git", "22ecff57f2c391b80a75617325748cf4d13fdf47", [tag: "v5.5.20", sparse: "packages/bundle", depth: 1]},
|
||||
"daisyui": {:git, "https://github.com/saadeghi/daisyui.git", "374d63758f997317e92e93e547569a5f9ad11250", [tag: "v5.6.18", sparse: "packages/bundle", depth: 1]},
|
||||
"db_connection": {:hex, :db_connection, "2.10.2", "ae391e803a5adff104da913c2fc1c0c14a37f8b10001dcef568796e1fb7bf95c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "510b14482330f1af6490a2fa0efd8d4f1435d1529b165647df22ac0f2df0fa93"},
|
||||
"decimal": {:hex, :decimal, "3.1.1", "430d87b04011ce6cbd4fd205be758311a81f87d552d40904abd00f015935b1d0", [:mix], [], "hexpm", "c5f25f2ced74a0587d03e6023f595db8e924c9d3922c8c8ffd9edfc4498cf1f6"},
|
||||
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,61 @@ export PATH
|
|||
|
||||
CLUSTER=who-need-help
|
||||
MARKER="$ROOT/.tools/${CLUSTER}.owned"
|
||||
NAMESPACE=who-need-help
|
||||
SECRET_NAME=who-need-help-local
|
||||
POSTGIS_IMAGE=postgis/postgis:18-3.6-alpine
|
||||
POSTGIS_SOURCE="${POSTGIS_IMAGE}@sha256:05d68c7f0f19b9aa0bf7c4a2049b2e8b38b44a63116392b95726a4c913766cf6"
|
||||
MAILPIT_IMAGE=axllent/mailpit:v1.30.4
|
||||
MAILPIT_SOURCE="${MAILPIT_IMAGE}@sha256:5a49a77c5bdbe7c5474450b4f46348d09949df3695257729c93a30369382d4f6"
|
||||
|
||||
kube() {
|
||||
kubectl --context "kind-${CLUSTER}" "$@"
|
||||
}
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64) IMAGE_ARCH=amd64 ;;
|
||||
aarch64|arm64) IMAGE_ARCH=arm64 ;;
|
||||
*) echo "Unsupported container architecture: $(uname -m)" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
load_pinned_image() {
|
||||
source=$1
|
||||
local_tag=$2
|
||||
archive=$3
|
||||
|
||||
docker pull --platform "linux/${IMAGE_ARCH}" "$source"
|
||||
image_id=$(docker image inspect "$source" --format '{{.Id}}')
|
||||
docker tag "$image_id" "$local_tag"
|
||||
docker image save --platform "linux/${IMAGE_ARCH}" --output "$archive" "$local_tag"
|
||||
kind load image-archive "$archive" --name "$CLUSTER"
|
||||
rm -f "$archive"
|
||||
}
|
||||
|
||||
remove_legacy_inline_secrets() {
|
||||
legacy_secret=who-need-help-who-need-help
|
||||
|
||||
# Older chart revisions rendered credentials from tracked Helm values. Once
|
||||
# the new deployments are healthy, remove the obsolete Secret and only those
|
||||
# release-history records that still contain the former inline secret keys.
|
||||
kube --namespace "$NAMESPACE" delete secret "$legacy_secret" \
|
||||
--ignore-not-found >/dev/null
|
||||
|
||||
for revision in $(
|
||||
helm history who-need-help \
|
||||
--kube-context "kind-${CLUSTER}" \
|
||||
--namespace "$NAMESPACE" |
|
||||
awk 'NR > 1 {print $1}'
|
||||
); do
|
||||
if helm get values who-need-help \
|
||||
--revision "$revision" \
|
||||
--kube-context "kind-${CLUSTER}" \
|
||||
--namespace "$NAMESPACE" |
|
||||
grep -Eq '^[[:space:]]*(databaseUrl|secretKeyBase|handoverSecret|releaseCookie):'; then
|
||||
kube --namespace "$NAMESPACE" delete secret \
|
||||
--selector="owner=helm,name=who-need-help,version=${revision}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if kind get clusters | grep -Fxq "$CLUSTER"; then
|
||||
if [ ! -f "$MARKER" ]; then
|
||||
|
|
@ -20,25 +75,80 @@ else
|
|||
touch "$MARKER"
|
||||
fi
|
||||
|
||||
load_pinned_image "$POSTGIS_SOURCE" "$POSTGIS_IMAGE" "$ROOT/.tools/postgis-kind.tar"
|
||||
load_pinned_image "$MAILPIT_SOURCE" "$MAILPIT_IMAGE" "$ROOT/.tools/mailpit-kind.tar"
|
||||
|
||||
docker build --tag who-need-help:local "$ROOT"
|
||||
kind load docker-image who-need-help:local --name "$CLUSTER"
|
||||
|
||||
kubectl create namespace who-need-help --dry-run=client --output=yaml | kubectl apply -f -
|
||||
kubectl --namespace who-need-help apply -f "$ROOT/deploy/kind/dependencies.yaml"
|
||||
kubectl --namespace who-need-help rollout status deployment/postgis
|
||||
kubectl --namespace who-need-help rollout status deployment/mailpit
|
||||
kube create namespace "$NAMESPACE" --dry-run=client --output=yaml | kube apply -f -
|
||||
|
||||
legacy_backup=
|
||||
if kube --namespace "$NAMESPACE" get deployment postgis >/dev/null 2>&1; then
|
||||
backup_dir="$ROOT/output/backups"
|
||||
mkdir -p "$backup_dir"
|
||||
umask 077
|
||||
legacy_backup="$backup_dir/kind-who-need-help-$(date +%Y%m%d-%H%M%S).dump"
|
||||
kube --namespace "$NAMESPACE" exec deployment/postgis -- \
|
||||
pg_dump --username postgres --dbname who_need_help --format custom >"$legacy_backup"
|
||||
test -s "$legacy_backup"
|
||||
kube --namespace "$NAMESPACE" exec --stdin deployment/postgis -- \
|
||||
pg_restore --list <"$legacy_backup" >/dev/null
|
||||
echo "Validated legacy kind database backup: $legacy_backup"
|
||||
fi
|
||||
|
||||
if ! kube --namespace "$NAMESPACE" get secret "$SECRET_NAME" >/dev/null 2>&1; then
|
||||
if kube --namespace "$NAMESPACE" get statefulset postgis >/dev/null 2>&1; then
|
||||
echo "Secret '$SECRET_NAME' is missing while the persistent PostGIS StatefulSet exists." >&2
|
||||
echo "Refusing to generate credentials that would not match the existing database." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
postgres_password=$(openssl rand -hex 32)
|
||||
secret_key_base=$(openssl rand -hex 64)
|
||||
handover_secret=$(openssl rand -hex 64)
|
||||
release_cookie=$(openssl rand -hex 64)
|
||||
|
||||
kube --namespace "$NAMESPACE" create secret generic "$SECRET_NAME" \
|
||||
--from-literal=POSTGRES_DB=who_need_help \
|
||||
--from-literal=POSTGRES_USER=postgres \
|
||||
--from-literal="POSTGRES_PASSWORD=$postgres_password" \
|
||||
--from-literal="DATABASE_URL=ecto://postgres:${postgres_password}@postgis/who_need_help" \
|
||||
--from-literal="SECRET_KEY_BASE=$secret_key_base" \
|
||||
--from-literal="HANDOVER_SECRET=$handover_secret" \
|
||||
--from-literal="RELEASE_COOKIE=$release_cookie"
|
||||
|
||||
unset postgres_password secret_key_base handover_secret release_cookie
|
||||
fi
|
||||
|
||||
if [ -n "$legacy_backup" ]; then
|
||||
kube --namespace "$NAMESPACE" delete deployment postgis --wait=true
|
||||
fi
|
||||
|
||||
kube --namespace "$NAMESPACE" apply -f "$ROOT/deploy/kind/dependencies.yaml"
|
||||
kube --namespace "$NAMESPACE" rollout status statefulset/postgis
|
||||
kube --namespace "$NAMESPACE" rollout status deployment/mailpit
|
||||
|
||||
if [ -n "$legacy_backup" ]; then
|
||||
kube --namespace "$NAMESPACE" exec --stdin statefulset/postgis -- \
|
||||
pg_restore --username postgres --dbname who_need_help \
|
||||
--clean --if-exists --no-owner <"$legacy_backup"
|
||||
echo "Restored legacy kind database backup."
|
||||
fi
|
||||
|
||||
helm upgrade --install who-need-help "$ROOT/deploy/helm/who-need-help" \
|
||||
--namespace who-need-help \
|
||||
--kube-context "kind-${CLUSTER}" \
|
||||
--namespace "$NAMESPACE" \
|
||||
--values "$ROOT/deploy/helm/who-need-help/values-kind.yaml" \
|
||||
--set-string app.codexSessionId="${CODEX_SESSION_ID:-${CODEX_THREAD_ID:-not-configured}}" \
|
||||
--wait
|
||||
|
||||
kubectl --namespace who-need-help rollout restart \
|
||||
kube --namespace "$NAMESPACE" rollout restart \
|
||||
deployment/who-need-help-who-need-help-web \
|
||||
deployment/who-need-help-who-need-help-worker
|
||||
kubectl --namespace who-need-help rollout status deployment/who-need-help-who-need-help-web
|
||||
kubectl --namespace who-need-help rollout status deployment/who-need-help-who-need-help-worker
|
||||
kube --namespace "$NAMESPACE" rollout status deployment/who-need-help-who-need-help-web
|
||||
kube --namespace "$NAMESPACE" rollout status deployment/who-need-help-who-need-help-worker
|
||||
remove_legacy_inline_secrets
|
||||
"$ROOT/scripts/verify-realtime-cluster.sh" kind
|
||||
|
||||
echo "Who Need Help: http://localhost:4011"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ case "$MODE" in
|
|||
TARGET=$(
|
||||
kubectl --context kind-who-need-help --namespace who-need-help get pods \
|
||||
-l app.kubernetes.io/component=web \
|
||||
-o jsonpath='{.items[0].metadata.name}'
|
||||
--field-selector=status.phase=Running \
|
||||
--no-headers \
|
||||
-o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[0].ready,DELETING:.metadata.deletionTimestamp' |
|
||||
awk '$2 == "true" && $3 == "<none>" {print $1; exit}'
|
||||
)
|
||||
if [ -z "$TARGET" ]; then
|
||||
echo "No running kind web replica was found." >&2
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user