chore: add local quality and security gates
This commit is contained in:
parent
d80fb231fb
commit
477517527f
9
.dialyzer_ignore.exs
Normal file
9
.dialyzer_ignore.exs
Normal 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
49
.github/workflows/quality.yml
vendored
Normal 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
|
||||||
31
Dockerfile
31
Dockerfile
|
|
@ -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
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
22
compose.quality.yaml
Normal 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:
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }}"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
18
mix.exs
|
|
@ -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
|
||||||
|
|
|
||||||
5
mix.lock
5
mix.lock
|
|
@ -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"},
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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=$(
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:-}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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" \
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
137
scripts/quality.sh
Executable 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."
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user