chore: add local quality and security gates

This commit is contained in:
SimpleTest 2026-07-19 02:50:52 +03:00
parent d80fb231fb
commit 477517527f
45 changed files with 477 additions and 59 deletions

9
.dialyzer_ignore.exs Normal file
View File

@ -0,0 +1,9 @@
# Ecto.Multi stores operation names in a MapSet. OTP 28+ can report
# call_without_opaque at the application call site even though only Ecto's
# public constructors are used. Keep these filters path- and warning-specific;
# `list_unused_filters: true` makes Dialyxir fail when either workaround is no
# longer needed.
[
{"lib/who_need_help/help.ex", :call_without_opaque},
{"lib/who_need_help/tracking.ex", :call_without_opaque}
]

49
.github/workflows/quality.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Quality
on:
push:
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: quality-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
backend-quality:
runs-on: ubuntu-24.04
steps:
- name: Check out the repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run isolated quality and security gates
run: ./scripts/quality.sh
browser-e2e:
runs-on: ubuntu-24.04
steps:
- name: Check out the repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run isolated browser end-to-end tests
run: ./scripts/e2e-run.sh
android-build:
runs-on: ubuntu-24.04
steps:
- name: Check out the repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Build, lint, and unit-test the debug Android app
env:
WNH_ENV_FILE: .env.example
run: ./scripts/android-build.sh

View File

@ -35,7 +35,9 @@ FROM ${BUILDER_IMAGE} AS builder
# install build dependencies # install build dependencies
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential git \ && apt-get install -y --no-install-recommends \
build-essential=12.12 \
git=1:2.47.3-0+deb13u1 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# prepare build dir # prepare build dir
@ -88,7 +90,14 @@ RUN mix release
FROM ${RUNNER_IMAGE} AS final FROM ${RUNNER_IMAGE} AS final
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 libsctp1 locales ca-certificates curl \ && apt-get install -y --no-install-recommends \
ca-certificates=20250419 \
curl=8.14.1-2+deb13u4 \
libncurses6=6.5+20250216-2 \
libsctp1=1.0.21+dfsg-1 \
libstdc++6=14.2.0-19 \
locales=2.41-12+deb13u3 \
openssl=3.5.6-1~deb13u2 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Set the locale # Set the locale
@ -120,7 +129,10 @@ CMD ["/app/bin/server"]
FROM ${BUILDER_IMAGE} AS test FROM ${BUILDER_IMAGE} AS test
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential git ca-certificates \ && apt-get install -y --no-install-recommends \
build-essential=12.12 \
ca-certificates=20250419 \
git=1:2.47.3-0+deb13u1 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
@ -135,7 +147,7 @@ RUN mix deps.get --only test
COPY config config COPY config config
RUN mix deps.compile RUN mix deps.compile
COPY .formatter.exs ./ COPY .dialyzer_ignore.exs .formatter.exs ./
COPY priv priv COPY priv priv
COPY lib lib COPY lib lib
COPY test test COPY test test
@ -144,6 +156,15 @@ RUN mix compile
CMD ["mix", "test"] CMD ["mix", "test"]
FROM test AS quality
# PLTs are expensive to create but deterministic for the pinned Erlang,
# Elixir, dependency lock, and test environment. Cache them in this dedicated
# target so every quality command uses the same analyzed dependency set.
RUN mix dialyzer --plt
CMD ["mix", "dialyzer"]
# Keep the production release as the default build result while exposing the # Keep the production release as the default build result while exposing the
# dedicated `test` target to scripts/test.sh. # dedicated `test` and `quality` targets to local verification scripts.
FROM final AS release FROM final AS release

View File

@ -141,6 +141,21 @@ project's PostGIS service:
It creates/updates only the project-scoped `who_need_help_test` database. It creates/updates only the project-scoped `who_need_help_test` database.
The complete local CI-equivalent command uses pinned containerized tools and
an isolated PostgreSQL volume:
```bash
./scripts/quality.sh
```
It checks shell scripts, Dockerfiles, the GitHub Actions workflow, every Compose
profile, the rendered Helm chart, tracked-source secrets and infrastructure
misconfigurations, Elixir formatting/compilation/xref/Credo/Sobelow/Dialyzer,
retired Hex packages, locked npm dependencies, all Phoenix tests, and the
production release image. Its generated database credentials are random and
exist only for that run. The exact database volume, networks, temporary source
snapshot, and one-run images are removed automatically.
The browser E2E command creates a uniquely named, isolated Compose project with The browser E2E command creates a uniquely named, isolated Compose project with
its own PostGIS volume, Mailpit instance, Traefik proxy, two web replicas, and its own PostGIS volume, Mailpit instance, Traefik proxy, two web replicas, and
two worker replicas: two worker replicas:

View File

@ -1,6 +1,7 @@
FROM gradle:9.6.1-jdk17@sha256:7364ce528f33bb6038672bcef990d524f1ad8fbc292935819c235db886d0fae7 AS android-base FROM gradle:9.6.1-jdk17@sha256:7364ce528f33bb6038672bcef990d524f1ad8fbc292935819c235db886d0fae7 AS android-base
USER root USER root
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ARG ANDROID_COMMAND_LINE_TOOLS_VERSION=15859902 ARG ANDROID_COMMAND_LINE_TOOLS_VERSION=15859902
ARG ANDROID_COMMAND_LINE_TOOLS_SHA1=040d3996a65543d22ec4bf73e4c37aa37a8d4af4 ARG ANDROID_COMMAND_LINE_TOOLS_SHA1=040d3996a65543d22ec4bf73e4c37aa37a8d4af4
@ -10,7 +11,9 @@ ENV ANDROID_SDK_ROOT=/opt/android-sdk
ENV PATH="${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools" ENV PATH="${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools"
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends curl unzip \ && apt-get install -y --no-install-recommends \
curl=8.18.0-1ubuntu2.3 \
unzip=6.0-29ubuntu1 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN mkdir -p "${ANDROID_HOME}/cmdline-tools" \ RUN mkdir -p "${ANDROID_HOME}/cmdline-tools" \
@ -28,6 +31,8 @@ RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \
"platforms/android-37.0" \ "platforms/android-37.0" \
&& chown -R gradle:gradle "${ANDROID_HOME}" && chown -R gradle:gradle "${ANDROID_HOME}"
USER gradle
FROM android-base AS android-sdk FROM android-base AS android-sdk
USER gradle USER gradle
@ -50,6 +55,7 @@ RUN --mount=type=cache,target=/home/gradle/.gradle,uid=1000,gid=1000 \
FROM android-base AS emulator FROM android-base AS emulator
USER root USER root
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \ RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \
"emulator" \ "emulator" \
@ -58,19 +64,19 @@ RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
libdbus-1-3 \ libdbus-1-3=1.16.2-2ubuntu4 \
libfontconfig1 \ libfontconfig1=2.17.1-3ubuntu1 \
libgl1 \ libgl1=1.7.0-3 \
libnss3 \ libnss3=2:3.120-1ubuntu2.1 \
libpulse0 \ libpulse0=1:17.0+dfsg1-2ubuntu4 \
libx11-6 \ libx11-6=2:1.8.13-1 \
libxcomposite1 \ libxcomposite1=1:0.4.6-1build1 \
libxcursor1 \ libxcursor1=1:1.2.3-1build1 \
libxi6 \ libxi6=2:1.8.2-2 \
libxkbfile1 \ libxkbfile1=1:1.1.0-1build5 \
libxrandr2 \ libxrandr2=2:1.5.4-1build1 \
libxrender1 \ libxrender1=1:0.9.12-1build1 \
libxtst6 \ libxtst6=2:1.2.5-1build1 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# The container is operated from the host through `docker exec ... adb`. # The container is operated from the host through `docker exec ... adb`.
@ -108,6 +114,8 @@ CMD ["/opt/android-sdk/emulator/emulator", \
FROM scratch AS artifact FROM scratch AS artifact
USER 65532:65532
COPY --from=android-sdk \ COPY --from=android-sdk \
/workspace/android/app/build/outputs/apk/debug/app-debug.apk \ /workspace/android/app/build/outputs/apk/debug/app-debug.apk \
/who-need-help-debug.apk /who-need-help-debug.apk
@ -137,6 +145,8 @@ RUN --mount=type=cache,target=/home/gradle/.gradle,uid=1000,gid=1000 \
FROM scratch AS staging-artifact FROM scratch AS staging-artifact
USER 65532:65532
COPY --from=android-staging-sdk \ COPY --from=android-staging-sdk \
/workspace/android/app/build/outputs/apk/staging/app-staging.apk \ /workspace/android/app/build/outputs/apk/staging/app-staging.apk \
/who-need-help-staging.apk /who-need-help-staging.apk

View File

@ -20,9 +20,11 @@ services:
context: e2e context: e2e
init: true init: true
ipc: host ipc: host
user: "${E2E_UID:-1000}:${E2E_GID:-1000}"
environment: environment:
BASE_URL: ${E2E_BASE_URL:?Set E2E_BASE_URL in the generated E2E environment} BASE_URL: ${E2E_BASE_URL:?Set E2E_BASE_URL in the generated E2E environment}
MAILPIT_URL: ${E2E_MAILPIT_URL:?Set E2E_MAILPIT_URL in the generated E2E environment} MAILPIT_URL: ${E2E_MAILPIT_URL:?Set E2E_MAILPIT_URL in the generated E2E environment}
HOME: /tmp
CI: ${CI:-} CI: ${CI:-}
NO_PROXY: web,mailpit,db,proxy,localhost,127.0.0.1 NO_PROXY: web,mailpit,db,proxy,localhost,127.0.0.1
no_proxy: web,mailpit,db,proxy,localhost,127.0.0.1 no_proxy: web,mailpit,db,proxy,localhost,127.0.0.1

22
compose.quality.yaml Normal file
View File

@ -0,0 +1,22 @@
services:
db:
image: postgis/postgis:18-3.6-alpine@sha256:05d68c7f0f19b9aa0bf7c4a2049b2e8b38b44a63116392b95726a4c913766cf6
environment:
POSTGRES_DB: postgres
POSTGRES_USER: ${QUALITY_POSTGRES_USER:?Set QUALITY_POSTGRES_USER}
POSTGRES_PASSWORD: ${QUALITY_POSTGRES_PASSWORD:?Set QUALITY_POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 3s
timeout: 3s
retries: 20
volumes:
- quality_postgres_data:/var/lib/postgresql
networks: [internal]
networks:
internal:
internal: true
volumes:
quality_postgres_data:

View File

@ -28,7 +28,6 @@ config :who_need_help,
ecto_repos: [WhoNeedHelp.Repo], ecto_repos: [WhoNeedHelp.Repo],
generators: [timestamp_type: :utc_datetime, binary_id: true], generators: [timestamp_type: :utc_datetime, binary_id: true],
app_role: :web, app_role: :web,
handover_secret: "development-only-handover-secret",
codex_session_id: "not-configured", codex_session_id: "not-configured",
rate_limit_policies: %{}, rate_limit_policies: %{},
map_tile_url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png" map_tile_url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png"

View File

@ -1,5 +1,7 @@
import Config import Config
config :who_need_help, :handover_secret, System.fetch_env!("HANDOVER_SECRET")
# Configure your database # Configure your database
config :who_need_help, WhoNeedHelp.Repo, config :who_need_help, WhoNeedHelp.Repo,
username: "postgres", username: "postgres",

View File

@ -1,5 +1,7 @@
import Config import Config
config :who_need_help, :handover_secret, "isolated-test-handover-secret"
# Only in tests, remove the complexity from the password hashing algorithm # Only in tests, remove the complexity from the password hashing algorithm
config :bcrypt_elixir, :log_rounds, 1 config :bcrypt_elixir, :log_rounds, 1

View File

@ -47,6 +47,11 @@ spec:
env: env:
- name: APP_ROLE - name: APP_ROLE
value: migrate value: migrate
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
containers: containers:
- name: {{ $component }} - name: {{ $component }}
image: "{{ $root.Values.image.repository }}:{{ $root.Values.image.tag }}" image: "{{ $root.Values.image.repository }}:{{ $root.Values.image.tag }}"

View File

@ -11,10 +11,23 @@ spec:
metadata: metadata:
labels: {app: postgis} labels: {app: postgis}
spec: spec:
securityContext:
runAsNonRoot: true
runAsUser: 70
runAsGroup: 70
fsGroup: 70
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers: containers:
- name: postgis - name: postgis
image: postgis/postgis:18-3.6-alpine image: postgis/postgis:18-3.6-alpine@sha256:05d68c7f0f19b9aa0bf7c4a2049b2e8b38b44a63116392b95726a4c913766cf6
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env: env:
- name: POSTGRES_DB - name: POSTGRES_DB
valueFrom: valueFrom:
@ -39,11 +52,17 @@ spec:
periodSeconds: 3 periodSeconds: 3
volumeMounts: volumeMounts:
- {name: data, mountPath: /var/lib/postgresql} - {name: data, mountPath: /var/lib/postgresql}
- {name: runtime, mountPath: /var/run/postgresql}
- {name: tmp, mountPath: /tmp}
volumes: volumes:
- name: data - name: data
hostPath: hostPath:
path: /var/local/who-need-help/postgres path: /var/local/who-need-help/postgres
type: DirectoryOrCreate type: DirectoryOrCreate
- name: runtime
emptyDir: {}
- name: tmp
emptyDir: {}
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@ -66,13 +85,29 @@ spec:
metadata: metadata:
labels: {app: mailpit} labels: {app: mailpit}
spec: spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers: containers:
- name: mailpit - name: mailpit
image: axllent/mailpit:v1.30.4 image: axllent/mailpit:v1.30.4@sha256:5a49a77c5bdbe7c5474450b4f46348d09949df3695257729c93a30369382d4f6
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
ports: ports:
- {name: smtp, containerPort: 1025} - {name: smtp, containerPort: 1025}
- {name: http, containerPort: 8025} - {name: http, containerPort: 8025}
volumeMounts:
- {name: tmp, mountPath: /tmp}
volumes:
- name: tmp
emptyDir: {}
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service

View File

@ -68,6 +68,23 @@ release feeds reported those same versions as current during verification.
Android 17 API 37 is the stable target used here. API 37.1 was not selected 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. because the official SDK channel identifies it as a QPR beta.
## Local quality and security tools
| Component | Pinned version |
| --- | --- |
| ShellCheck | 0.11.0 |
| Hadolint | 2.14.0 |
| actionlint | 1.7.12 |
| Trivy | 0.72.0 |
| Credo | 1.7.19 |
| Dialyxir | 1.4.7 |
| Sobelow | 0.14.1 |
| GitHub checkout action | 7.0.0 / commit `9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0` |
The four containerized command-line tools are pinned by both exact tag and OCI
digest in `scripts/quality.sh`. The checkout action is pinned by commit in the
workflow. Credo, Dialyxir, and Sobelow are locked in `mix.lock`.
## Primary version sources ## Primary version sources
- [Elixir releases](https://github.com/elixir-lang/elixir/releases) - [Elixir releases](https://github.com/elixir-lang/elixir/releases)
@ -91,6 +108,11 @@ because the official SDK channel identifies it as a QPR beta.
- [Docker Engine releases](https://github.com/moby/moby/releases) - [Docker Engine releases](https://github.com/moby/moby/releases)
- [Docker Compose releases](https://github.com/docker/compose/releases) - [Docker Compose releases](https://github.com/docker/compose/releases)
- [Docker Buildx releases](https://github.com/docker/buildx/releases) - [Docker Buildx releases](https://github.com/docker/buildx/releases)
- [ShellCheck releases](https://github.com/koalaman/shellcheck/releases)
- [Hadolint releases](https://github.com/hadolint/hadolint/releases)
- [actionlint releases](https://github.com/rhysd/actionlint/releases)
- [Trivy releases](https://github.com/aquasecurity/trivy/releases)
- [checkout releases](https://github.com/actions/checkout/releases)
## Repeatable checks ## Repeatable checks
@ -100,6 +122,8 @@ 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 --version
docker run --rm who-need-help:node-deps npm outdated --json docker run --rm who-need-help:node-deps npm outdated --json
./scripts/test.sh ./scripts/test.sh
./scripts/quality.sh
./scripts/e2e-run.sh
./scripts/android-build.sh ./scripts/android-build.sh
./scripts/android-instrumentation-test.sh ./scripts/android-instrumentation-test.sh
``` ```

View File

@ -44,6 +44,20 @@ The goal remains open while any row lacks reproducible local evidence.
cleanup, and a disconnected Stop request with visible retry state. It uses an cleanup, and a disconnected Stop request with visible retry state. It uses an
in-process loopback fixture, runs its emulator without external networking, in-process loopback fixture, runs its emulator without external networking,
retains diagnostics on failure, and removes its exact container and image. retains diagnostics on failure, and removes its exact container and image.
- The containerized `scripts/quality.sh` gate passes ShellCheck, Hadolint,
actionlint, all Compose renders, Helm lint, a Trivy scan of tracked source and
the rendered Kubernetes manifest, compiler/xref/Credo/Sobelow/Dialyzer/Hex
checks, 149 Phoenix tests, both npm audits, and a Trivy scan of the production
release image. It creates random one-run database credentials and removes its
exact volume, networks, images, and source snapshot.
- The checked-in GitHub Actions workflow runs the same isolated backend/security
gate, browser E2E suite, and Android unit/lint/APK build. The workflow itself
passes actionlint and uses a commit-pinned checkout action without persisted
credentials.
- The hardened local kind manifests also passed a runtime rollout: PostGIS,
Mailpit, both web replicas, and both worker replicas became Ready as non-root
containers with read-only root filesystems, dropped capabilities, and
RuntimeDefault seccomp where configured. The cross-node PubSub probe passed.
- The remaining rows above are still pending. Browser accessibility, - The remaining rows above are still pending. Browser accessibility,
responsive, offline, and reconnect checks remain part of their respective responsive, offline, and reconnect checks remain part of their respective
later rows; this document is not a completion claim. later rows; this document is not a completion claim.

View File

@ -28,6 +28,18 @@ results from product limits and unknown production properties.
on Elixir 1.20.2 and Erlang/OTP 29.0.3. on Elixir 1.20.2 and Erlang/OTP 29.0.3.
- `mix compile --force --warnings-as-errors` and - `mix compile --force --warnings-as-errors` and
`mix format --check-formatted`: passed against the same final source. `mix format --check-formatted`: passed against the same final source.
- `./scripts/quality.sh` passed ShellCheck 0.11.0, Hadolint 2.14.0 at warning
threshold, actionlint 1.7.12, all four Compose renders, Helm lint, Trivy
source/rendered-manifest scanning, xref, Credo high-priority checks, Sobelow
strict/private checks, Hex audit, 149 Phoenix tests, both npm audits, and the
production-image vulnerability scan. The rendered Helm manifest and Debian
13.6 release image each reported zero HIGH/CRITICAL findings under the
configured gates.
- Dialyzer passed with two exact, documented filters and zero unused filters.
Both findings are opaque-type warnings at Ecto `Multi`/`MapSet` call sites;
the filters are visible in `.dialyzer_ignore.exs` rather than hidden by a
broad pattern. This is not a claim that third-party dependency compilation is
warning-free.
- The generated Activity migration was rolled back by exactly one step and - The generated Activity migration was rolled back by exactly one step and
migrated forward again against `who_need_help_test`; both directions passed. migrated forward again against `who_need_help_test`; both directions passed.
- The Activity-report migration was also rolled back and migrated forward. The - The Activity-report migration was also rolled back and migrated forward. The
@ -45,6 +57,16 @@ results from product limits and unknown production properties.
intended; release configuration accepts only an explicit HTTPS origin. intended; release configuration accepts only an explicit HTTPS origin.
- Helm lint, template rendering, server-side dry-run, rollout waits, readiness - Helm lint, template rendering, server-side dry-run, rollout waits, readiness
checks, and cross-node PubSub verification passed in the local kind cluster. checks, and cross-node PubSub verification passed in the local kind cluster.
- The hardened kind rollout then ran PostGIS as UID/GID 70 and Mailpit as
UID/GID 10001. Both used read-only root filesystems, dropped all Linux
capabilities, disabled privilege escalation, and used RuntimeDefault seccomp.
Both web and both worker replicas were Ready; their migration-wait init
containers also used read-only root filesystems and dropped capabilities.
The cross-node probe observed all four BEAM nodes, and live/readiness returned
`ok`/`ready`. Database counts remained `0 users / 0 help requests`.
- The committed browser suite was repeated after CSP and non-root E2E-runner
changes: its bootstrap test passed 1/1 and its three Chromium scenarios
passed 3/3. The uniquely named Compose volume and networks were removed.
- Browser verification used headed Chrome. The authenticated matched-request - Browser verification used headed Chrome. The authenticated matched-request
page rendered its chat, MapLibre marker, and live-location controls with no page rendered its chat, MapLibre marker, and live-location controls with no
console errors or warnings after the foreground-service rollout. console errors or warnings after the foreground-service rollout.

View File

@ -2,11 +2,13 @@ FROM mcr.microsoft.com/playwright:v1.61.1-noble@sha256:5b8f294aff9041b7191c34a4b
WORKDIR /work WORKDIR /work
COPY package.json package-lock.json ./ COPY --chown=pwuser:pwuser package.json package-lock.json ./
RUN npm ci --ignore-scripts RUN npm ci --ignore-scripts
COPY playwright.config.ts playwright.bootstrap.config.ts ./ COPY --chown=pwuser:pwuser playwright.config.ts playwright.bootstrap.config.ts ./
COPY tests tests COPY --chown=pwuser:pwuser tests tests
COPY setup setup COPY --chown=pwuser:pwuser setup setup
USER pwuser
CMD ["npx", "playwright", "test"] CMD ["npx", "playwright", "test"]

View File

@ -21,9 +21,8 @@ defmodule WhoNeedHelp.SocialOAuth.AssentAdapter do
{:ok, %{user: user}} <- {:ok, %{user: user}} <-
config config
|> Keyword.put(:session_params, session_params) |> Keyword.put(:session_params, session_params)
|> GithubStrategy.callback(params), |> GithubStrategy.callback(params) do
{:ok, identity} <- normalize_github_identity(user) do normalize_github_identity(user)
{:ok, identity}
end end
end end

View File

@ -3,6 +3,9 @@ defmodule WhoNeedHelpWeb.MetricsController do
@content_type "text/plain; version=0.0.4" @content_type "text/plain; version=0.0.4"
# Sobelow's HTML-oriented SendResp check cannot infer this fixed Prometheus
# text content type. The response is not rendered in an HTML context.
# sobelow_skip ["XSS.SendResp"]
def show(conn, _params) do def show(conn, _params) do
token = Application.get_env(:who_need_help, :metrics_token) token = Application.get_env(:who_need_help, :metrics_token)

View File

@ -3,13 +3,17 @@ defmodule WhoNeedHelpWeb.Router do
import WhoNeedHelpWeb.UserAuth import WhoNeedHelpWeb.UserAuth
@secure_browser_headers %{
"content-security-policy" => "base-uri 'self'; frame-ancestors 'self';"
}
pipeline :browser do pipeline :browser do
plug :accepts, ["html"] plug :accepts, ["html"]
plug :fetch_session plug :fetch_session
plug :fetch_live_flash plug :fetch_live_flash
plug :put_root_layout, html: {WhoNeedHelpWeb.Layouts, :root} plug :put_root_layout, html: {WhoNeedHelpWeb.Layouts, :root}
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers, @secure_browser_headers
plug :fetch_current_scope_for_user plug :fetch_current_scope_for_user
plug WhoNeedHelpWeb.Locale plug WhoNeedHelpWeb.Locale
end end
@ -22,7 +26,7 @@ defmodule WhoNeedHelpWeb.Router do
plug :accepts, ["json"] plug :accepts, ["json"]
plug :fetch_session plug :fetch_session
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers, @secure_browser_headers
plug :fetch_current_scope_for_user plug :fetch_current_scope_for_user
end end

18
mix.exs
View File

@ -11,7 +11,12 @@ defmodule WhoNeedHelp.MixProject do
aliases: aliases(), aliases: aliases(),
deps: deps(), deps: deps(),
compilers: [:phoenix_live_view] ++ Mix.compilers(), compilers: [:phoenix_live_view] ++ Mix.compilers(),
listeners: [Phoenix.CodeReloader] listeners: [Phoenix.CodeReloader],
dialyzer: [
plt_add_apps: [:ex_unit, :mix],
ignore_warnings: ".dialyzer_ignore.exs",
list_unused_filters: true
]
] ]
end end
@ -51,6 +56,9 @@ defmodule WhoNeedHelp.MixProject do
{:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 1.2.0"}, {:phoenix_live_view, "~> 1.2.0"},
{:lazy_html, ">= 0.1.0", only: :test}, {:lazy_html, ">= 0.1.0", only: :test},
{:credo, "~> 1.7.19", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4.7", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.14.1", only: [:dev, :test], runtime: false},
{:phoenix_live_dashboard, "~> 0.8.3"}, {:phoenix_live_dashboard, "~> 0.8.3"},
{:esbuild, "~> 0.10", runtime: Mix.env() == :dev}, {:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.5", runtime: Mix.env() == :dev}, {:tailwind, "~> 0.5", runtime: Mix.env() == :dev},
@ -101,7 +109,13 @@ defmodule WhoNeedHelp.MixProject do
"esbuild who_need_help --minify", "esbuild who_need_help --minify",
"phx.digest" "phx.digest"
], ],
precommit: ["compile --warnings-as-errors", "deps.unlock --unused", "format", "test"] precommit: [
"compile --warnings-as-errors",
"format --check-formatted",
"credo --strict --min-priority high",
"sobelow --exit --strict --private --skip",
"test"
]
] ]
end end
end end

View File

@ -2,15 +2,19 @@
"assent": {:hex, :assent, "0.3.1", "7e7b04f4ab5d07b497b80c04f009a0f1efc409787e68b8e086512b9098f3095b", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "3597b31f9eb556d97e64cf60c00d3451f7353d7b465a71d33530b870ebed1ff1"}, "assent": {:hex, :assent, "0.3.1", "7e7b04f4ab5d07b497b80c04f009a0f1efc409787e68b8e086512b9098f3095b", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "3597b31f9eb556d97e64cf60c00d3451f7353d7b465a71d33530b870ebed1ff1"},
"bandit": {:hex, :bandit, "1.12.0", "6c5214daa2469644ac4ab0113b98abc24f75e348378e6a974c6343b3e5da22ef", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.5", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "45dac82dc86f45cf4a196dee9cc5a8b791d9c9469d996055f055e6ee36c66e20"}, "bandit": {:hex, :bandit, "1.12.0", "6c5214daa2469644ac4ab0113b98abc24f75e348378e6a974c6343b3e5da22ef", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.5", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "45dac82dc86f45cf4a196dee9cc5a8b791d9c9469d996055f055e6ee36c66e20"},
"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"}, "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"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "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"}, "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"},
"daisyui": {:git, "https://github.com/saadeghi/daisyui.git", "374d63758f997317e92e93e547569a5f9ad11250", [tag: "v5.6.18", 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"}, "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"}, "decimal": {:hex, :decimal, "3.1.1", "430d87b04011ce6cbd4fd205be758311a81f87d552d40904abd00f015935b1d0", [:mix], [], "hexpm", "c5f25f2ced74a0587d03e6023f595db8e924c9d3922c8c8ffd9edfc4498cf1f6"},
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
"ecto": {:hex, :ecto, "3.14.1", "7b740d87bdf45996aa0c2c2e081640906f10caa7ce5ba328fd294c7d49d0cc6f", [:mix], [{:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "24b991956796700f467d0a3ef3d303138a3ef9ddddf8b98f43758ee067b20a30"}, "ecto": {:hex, :ecto, "3.14.1", "7b740d87bdf45996aa0c2c2e081640906f10caa7ce5ba328fd294c7d49d0cc6f", [:mix], [{:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "24b991956796700f467d0a3ef3d303138a3ef9ddddf8b98f43758ee067b20a30"},
"ecto_sql": {:hex, :ecto_sql, "3.14.0", "06446ab8410d2f85bfbb80857ee224ab3b693700cbb38f6535d507449a627b2e", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.14.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.8", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4d8d36faf294c9417b5a37ec7ac8217ee2abdef5fcf197ba690f361548d3949"}, "ecto_sql": {:hex, :ecto_sql, "3.14.0", "06446ab8410d2f85bfbb80857ee224ab3b693700cbb38f6535d507449a627b2e", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.14.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.8", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4d8d36faf294c9417b5a37ec7ac8217ee2abdef5fcf197ba690f361548d3949"},
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"erlex": {:hex, :erlex, "0.2.9", "7debbbaa9f4f368b8cd648983e0f1d7963028508e9c59e9d4ed504e94ef52a55", [:mix], [], "hexpm", "8cfffc0ec7159e6d73de2ab28a588064de80f88b2798d5cbe4482cbbc200178b"},
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
@ -43,6 +47,7 @@
"postgrex": {:hex, :postgrex, "0.22.3", "bf65941737ee7a9adbe4a64c91080310d11703da343e8ac9188aacb9eb9f6f02", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "f018c13752b2b46e8d35d7e2d84c3276557cbfd880769109021a1d0ee36c1cfe"}, "postgrex": {:hex, :postgrex, "0.22.3", "bf65941737ee7a9adbe4a64c91080310d11703da343e8ac9188aacb9eb9f6f02", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "f018c13752b2b46e8d35d7e2d84c3276557cbfd880769109021a1d0ee36c1cfe"},
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"req": {:hex, :req, "0.6.3", "7fe5e68792ff0546e45d5919104fa1764a13694cfe3e48c8a0f32ad051ae77e4", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "e85b5c6c990e6c3f52bbba68e6f099118f2b8252825f96c7c3636b97a3de307d"}, "req": {:hex, :req, "0.6.3", "7fe5e68792ff0546e45d5919104fa1764a13694cfe3e48c8a0f32ad051ae77e4", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "e85b5c6c990e6c3f52bbba68e6f099118f2b8252825f96c7c3636b97a3de307d"},
"sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"},
"swoosh": {:hex, :swoosh, "1.26.3", "9d8b60077305ce259298d9a1102e5be67cd3c41d1ea930c29e9288af195ca017", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, ">= 1.9.0 and < 5.0.0", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, ">= 6.0.0 and < 8.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c7683d070fe8f8aa9d174e61b01f2d527be73cd8ac40037b7109184941eb569f"}, "swoosh": {:hex, :swoosh, "1.26.3", "9d8b60077305ce259298d9a1102e5be67cd3c41d1ea930c29e9288af195ca017", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, ">= 1.9.0 and < 5.0.0", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, ">= 6.0.0 and < 8.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c7683d070fe8f8aa9d174e61b01f2d527be73cd8ac40037b7109184941eb569f"},
"tailwind": {:hex, :tailwind, "0.5.1", "35435b13158c90d37da11e1cfc808755fca1d7b6c5ab87b1b19c5de87e2f0a10", [:mix], [], "hexpm", "c4e26302a59fec72abc5610ecb6ad2116d9aa31f31aab2d4b8eb6e95d25a689c"}, "tailwind": {:hex, :tailwind, "0.5.1", "35435b13158c90d37da11e1cfc808755fca1d7b6c5ab87b1b19c5de87e2f0a10", [:mix], [], "hexpm", "c4e26302a59fec72abc5610ecb6ad2116d9aa31f31aab2d4b8eb6e95d25a689c"},
"telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},

View File

@ -1,8 +1,13 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env" ENV_FILE=${WNH_ENV_FILE:-"$ROOT/.env"}
case "$ENV_FILE" in
/*) ;;
*) ENV_FILE="$ROOT/$ENV_FILE" ;;
esac
if [ ! -f "$ENV_FILE" ]; then if [ ! -f "$ENV_FILE" ]; then
echo "Missing $ENV_FILE. Copy .env.example to .env and configure WNH_DEBUG_BASE_URL." >&2 echo "Missing $ENV_FILE. Copy .env.example to .env and configure WNH_DEBUG_BASE_URL." >&2
@ -10,6 +15,7 @@ if [ ! -f "$ENV_FILE" ]; then
fi fi
set -a set -a
# shellcheck source=/dev/null
. "$ENV_FILE" . "$ENV_FILE"
set +a set +a

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ANDROID_ENV="$ROOT/.env" ANDROID_ENV="$ROOT/.env"
TEST_ENV="$ROOT/.env.android-test" TEST_ENV="$ROOT/.env.android-test"
run_id=$(date -u +%Y%m%d%H%M%S)-$$ run_id=$(date -u +%Y%m%d%H%M%S)-$$
@ -23,7 +23,9 @@ fi
"$ROOT/scripts/ensure-local-android-test-env.sh" "$ROOT/scripts/ensure-local-android-test-env.sh"
set -a set -a
# shellcheck source=/dev/null
. "$ANDROID_ENV" . "$ANDROID_ENV"
# shellcheck source=/dev/null
. "$TEST_ENV" . "$TEST_ENV"
set +a set +a

View File

@ -1,12 +1,13 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env" ENV_FILE="$ROOT/.env"
"$ROOT/scripts/ensure-local-public-origin.sh" "$ROOT/scripts/ensure-local-public-origin.sh"
set -a set -a
# shellcheck source=/dev/null
. "$ENV_FILE" . "$ENV_FILE"
set +a set +a

View File

@ -2,7 +2,7 @@
set -eu set -eu
umask 077 umask 077
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT" cd "$ROOT"
if [ ! -f "$ROOT/.env" ]; then if [ ! -f "$ROOT/.env" ]; then
@ -11,6 +11,7 @@ if [ ! -f "$ROOT/.env" ]; then
fi fi
set -a set -a
# shellcheck source=/dev/null
. "$ROOT/.env" . "$ROOT/.env"
set +a set +a

View File

@ -24,7 +24,7 @@ case "$MODE" in
docker exec "$TARGET" /app/bin/who_need_help rpc "$EXPRESSION" docker exec "$TARGET" /app/bin/who_need_help rpc "$EXPRESSION"
;; ;;
kind) kind)
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
PATH="$ROOT/.tools/bin:$PATH" PATH="$ROOT/.tools/bin:$PATH"
export PATH export PATH
TARGET=$( TARGET=$(

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
TOOLS="$ROOT/.tools/bin" TOOLS="$ROOT/.tools/bin"
mkdir -p "$TOOLS" mkdir -p "$TOOLS"

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
INPUT=${1:-} INPUT=${1:-}
OUTPUT=${2:-} OUTPUT=${2:-}

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT" cd "$ROOT"
CODEX_SESSION_ID=${CODEX_SESSION_ID:-${CODEX_THREAD_ID:-not-configured}} CODEX_SESSION_ID=${CODEX_SESSION_ID:-${CODEX_THREAD_ID:-not-configured}}
export CODEX_SESSION_ID export CODEX_SESSION_ID

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT" cd "$ROOT"
"$ROOT/scripts/ensure-local-e2e-env.sh" "$ROOT/scripts/ensure-local-e2e-env.sh"
@ -24,7 +24,11 @@ export TRAEFIK_PROJECT_CONSTRAINT="$project"
export TRAEFIK_APP_NAME="who-need-help-e2e-$run_id" export TRAEFIK_APP_NAME="who-need-help-e2e-$run_id"
export TRAEFIK_DOCKER_NETWORK="${project}_internal" export TRAEFIK_DOCKER_NETWORK="${project}_internal"
export E2E_OUTPUT_DIR="$output_dir" export E2E_OUTPUT_DIR="$output_dir"
export CODEX_SESSION_ID=${CODEX_SESSION_ID:-${CODEX_THREAD_ID:-local-e2e}} export E2E_UID
E2E_UID=$(id -u)
export E2E_GID
E2E_GID=$(id -g)
export CODEX_SESSION_ID="${CODEX_SESSION_ID:-${CODEX_THREAD_ID:-local-e2e}}"
compose() { compose() {
docker compose \ docker compose \

View File

@ -16,7 +16,7 @@ case "$project" in
;; ;;
esac esac
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
docker compose \ docker compose \
--project-name "$project" \ --project-name "$project" \

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
target="$ROOT/.env.android-test" target="$ROOT/.env.android-test"
if [ -f "$target" ]; then if [ -f "$target" ]; then

View File

@ -2,7 +2,7 @@
set -eu set -eu
umask 077 umask 077
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
target="$ROOT/.env.e2e" target="$ROOT/.env.e2e"
if [ -f "$target" ]; then if [ -f "$target" ]; then

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
TEMPLATE="$ROOT/.env.load.example" TEMPLATE="$ROOT/.env.load.example"
ENV_FILE="$ROOT/.env.load" ENV_FILE="$ROOT/.env.load"

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env" ENV_FILE="$ROOT/.env"
if [ ! -f "$ENV_FILE" ]; then if [ ! -f "$ENV_FILE" ]; then

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env" ENV_FILE="$ROOT/.env"
if [ ! -f "$ENV_FILE" ]; then if [ ! -f "$ENV_FILE" ]; then
@ -15,6 +15,7 @@ if ! command -v perl >/dev/null 2>&1; then
fi fi
set -a set -a
# shellcheck source=/dev/null
. "$ENV_FILE" . "$ENV_FILE"
set +a set +a

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
"$ROOT/scripts/bootstrap-kubernetes-tools.sh" "$ROOT/scripts/bootstrap-kubernetes-tools.sh"
PATH="$ROOT/.tools/bin:$PATH" PATH="$ROOT/.tools/bin:$PATH"
export PATH export PATH

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env.load" ENV_FILE="$ROOT/.env.load"
K6_IMAGE="grafana/k6@sha256:65c920dc067d5e2e00befbf982af6ad6ad0117034e8b1c65817c7975c52d4669" K6_IMAGE="grafana/k6@sha256:65c920dc067d5e2e00befbf982af6ad6ad0117034e8b1c65817c7975c52d4669"
LABEL=${1:-"run-$(date -u +%Y%m%dT%H%M%SZ)"} LABEL=${1:-"run-$(date -u +%Y%m%dT%H%M%SZ)"}
@ -12,6 +12,7 @@ if [[ ! -f "$ENV_FILE" ]]; then
fi fi
set -a set -a
# shellcheck source=/dev/null
. "$ENV_FILE" . "$ENV_FILE"
set +a set +a
@ -81,6 +82,8 @@ touch "$running_marker"
snapshot_database() { snapshot_database() {
local destination=$1 local destination=$1
# The variables below are intentionally expanded inside the database container.
# shellcheck disable=SC2016
"${compose[@]}" exec -T db sh -c \ "${compose[@]}" exec -T db sh -c \
'psql --no-psqlrc --set ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB"' \ 'psql --no-psqlrc --set ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB"' \
>"$destination" <<'SQL' >"$destination" <<'SQL'

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env.load" ENV_FILE="$ROOT/.env.load"
if [ ! -f "$ENV_FILE" ]; then if [ ! -f "$ENV_FILE" ]; then
@ -10,6 +10,7 @@ if [ ! -f "$ENV_FILE" ]; then
fi fi
set -a set -a
# shellcheck source=/dev/null
. "$ENV_FILE" . "$ENV_FILE"
set +a set +a

View File

@ -1,13 +1,14 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env.load" ENV_FILE="$ROOT/.env.load"
REPLICAS=${1:-} REPLICAS=${1:-}
"$ROOT/scripts/ensure-local-load-env.sh" "$ROOT/scripts/ensure-local-load-env.sh"
set -a set -a
# shellcheck source=/dev/null
. "$ENV_FILE" . "$ENV_FILE"
set +a set +a

137
scripts/quality.sh Executable file
View File

@ -0,0 +1,137 @@
#!/bin/sh
set -eu
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT"
SHELLCHECK_IMAGE="koalaman/shellcheck-alpine:v0.11.0@sha256:9955be09ea7f0dbf7ae942ac1f2094355bb30d96fffba0ec09f5432207544002"
HADOLINT_IMAGE="hadolint/hadolint:v2.14.0-debian@sha256:158cd0184dcaa18bd8ec20b61f4c1cabdf8b32a592d062f57bdcb8e4c1d312e2"
ACTIONLINT_IMAGE="rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667"
TRIVY_IMAGE="aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f"
run_id="$(date -u +%Y%m%d%H%M%S)-$$"
project="wnh_quality_$(printf '%s' "$run_id" | tr -d '-')"
quality_image="who-need-help:quality-$run_id"
assets_image="who-need-help:assets-audit-$run_id"
e2e_image="who-need-help:e2e-audit-$run_id"
release_image="who-need-help:security-$run_id"
scan_dir=$(mktemp -d "${TMPDIR:-/tmp}/wnh-quality-scan.XXXXXX")
scan_list="${scan_dir}.files"
scan_tar="${scan_dir}.tar"
umask 077
QUALITY_POSTGRES_USER="wnh_quality_$(openssl rand -hex 6)"
QUALITY_POSTGRES_PASSWORD=$(openssl rand -base64 48 | tr -d '\n')
export QUALITY_POSTGRES_USER QUALITY_POSTGRES_PASSWORD
compose="docker compose -p $project -f $ROOT/compose.quality.yaml"
cleanup() {
$compose down --volumes --remove-orphans >/dev/null 2>&1 || true
docker image rm "$quality_image" "$assets_image" "$e2e_image" "$release_image" \
>/dev/null 2>&1 || true
rm -rf "$scan_dir" "$scan_list" "$scan_tar"
}
trap cleanup EXIT HUP INT TERM
echo "Checking shell scripts with ShellCheck 0.11.0"
# Word splitting is intentional: find emits repository-controlled paths and
# ShellCheck expects each file as a separate argument.
# shellcheck disable=SC2046
docker run --rm \
--volume "$ROOT:/mnt:ro" \
--workdir /mnt \
--entrypoint shellcheck \
"$SHELLCHECK_IMAGE" \
$(find scripts -type f -name '*.sh' -print | sort)
echo "Checking Dockerfiles with Hadolint 2.14.0"
for dockerfile in Dockerfile android/Dockerfile e2e/Dockerfile; do
docker run --rm --interactive "$HADOLINT_IMAGE" \
hadolint --failure-threshold warning - <"$dockerfile"
done
echo "Checking the GitHub Actions workflow with actionlint 1.7.12"
docker run --rm \
--volume "$ROOT:/repo:ro" \
--workdir /repo \
"$ACTIONLINT_IMAGE"
echo "Rendering every Docker Compose profile"
docker compose --env-file .env.example -f compose.yaml config --quiet
mkdir -p "$scan_dir/e2e-output"
E2E_OUTPUT_DIR="$scan_dir/e2e-output" docker compose --env-file .env.e2e.example \
-f compose.yaml -f compose.e2e.yaml config --quiet
docker compose --env-file .env.load.example \
-f compose.yaml -f compose.load.yaml config --quiet
docker compose -p "$project" -f compose.quality.yaml config --quiet
echo "Linting the Helm chart"
"$ROOT/scripts/bootstrap-kubernetes-tools.sh" >/dev/null
"$ROOT/.tools/bin/helm" lint \
--values "$ROOT/deploy/helm/who-need-help/values-kind.yaml" \
"$ROOT/deploy/helm/who-need-help"
echo "Scanning only tracked and non-ignored source files"
git ls-files --cached --others --exclude-standard -z >"$scan_list"
tar --null --no-recursion --files-from="$scan_list" --create --file="$scan_tar"
tar --extract --file="$scan_tar" --directory "$scan_dir"
"$ROOT/.tools/bin/helm" template who-need-help \
--values "$ROOT/deploy/helm/who-need-help/values-kind.yaml" \
"$ROOT/deploy/helm/who-need-help" \
>"$scan_dir/rendered-helm.yaml"
mkdir -p "$ROOT/.tools/trivy-cache"
docker run --rm \
--volume "$scan_dir:/scan:ro" \
--volume "$ROOT/.tools/trivy-cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" fs \
--scanners misconfig,secret \
--severity HIGH,CRITICAL \
--exit-code 1 \
/scan
echo "Building the pinned quality image and cached Dialyzer PLTs"
docker build --target quality --tag "$quality_image" .
echo "Running Elixir format, compiler, xref, Credo, Sobelow, Dialyzer, and Hex audit"
docker run --rm "$quality_image" sh -euc '
mix format --check-formatted
mix compile --force --warnings-as-errors
mix xref graph --label compile-connected --fail-above 0
mix credo --strict --min-priority high
mix sobelow --exit --strict --private --skip
mix dialyzer
mix hex.audit
'
echo "Starting an isolated PostgreSQL/PostGIS volume for the Phoenix suite"
$compose up --detach --wait db
docker run --rm \
--network "${project}_internal" \
--env MIX_ENV=test \
--env DB_HOST=db \
--env "DB_USER=$QUALITY_POSTGRES_USER" \
--env "DB_PASSWORD=$QUALITY_POSTGRES_PASSWORD" \
--env TEST_POOL_SIZE=10 \
"$quality_image" \
mix test
echo "Auditing locked browser dependencies"
docker build --target node_deps --tag "$assets_image" .
docker run --rm "$assets_image" npm audit --audit-level=high
docker build --tag "$e2e_image" e2e
docker run --rm "$e2e_image" npm audit --audit-level=high
echo "Building and scanning the production release image"
docker build --target release --tag "$release_image" .
docker run --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$ROOT/.tools/trivy-cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" image \
--scanners vuln \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--exit-code 1 \
"$release_image"
echo "All isolated quality and security gates passed."

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT" cd "$ROOT"
if [ "$#" -ne 1 ]; then if [ "$#" -ne 1 ]; then
@ -28,6 +28,7 @@ if [ ! -f "$ROOT/.env" ]; then
fi fi
set -a set -a
# shellcheck source=/dev/null
. "$ROOT/.env" . "$ROOT/.env"
set +a set +a
@ -35,7 +36,7 @@ set +a
: "${POSTGRES_USER:?Set POSTGRES_USER in .env}" : "${POSTGRES_USER:?Set POSTGRES_USER in .env}"
: "${DATABASE_URL:?Set DATABASE_URL in .env}" : "${DATABASE_URL:?Set DATABASE_URL in .env}"
dump_dir=$(CDPATH= cd -- "$(dirname -- "$dump")" && pwd) dump_dir=$(CDPATH='' cd -- "$(dirname -- "$dump")" && pwd)
dump_name=$(basename -- "$dump") dump_name=$(basename -- "$dump")
( (

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env" ENV_FILE="$ROOT/.env"
TEST_IMAGE="${TEST_IMAGE:-who-need-help:test}" TEST_IMAGE="${TEST_IMAGE:-who-need-help:test}"
@ -18,6 +18,7 @@ for command in docker openssl perl; do
done done
set -a set -a
# shellcheck source=/dev/null
. "$ENV_FILE" . "$ENV_FILE"
set +a set +a

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT" cd "$ROOT"
if [ ! -f "$ROOT/.env" ]; then if [ ! -f "$ROOT/.env" ]; then
@ -10,6 +10,7 @@ if [ ! -f "$ROOT/.env" ]; then
fi fi
set -a set -a
# shellcheck source=/dev/null
. "$ROOT/.env" . "$ROOT/.env"
set +a set +a

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
MODE=${1:-compose} MODE=${1:-compose}
COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-who_need_help} COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-who_need_help}