3 Commits
Author SHA1 Message Date
maro ad353190f3 Fix SMTP proxy certificates and speed up CI
CI / test (push) Canceled after 0s
Release / promote-and-deploy (push) Canceled after 0s
CI / build-image (push) Canceled after 0s
2026-09-18 11:59:33 +02:00
maro 82a89cd3d9 Fix media permissions inside app container
Release / promote-and-deploy (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / build-image (push) Canceled after 0s
2026-09-18 02:22:15 +02:00
maro 46a7cc23bd Fix media directory permissions during deploy
CI / test (push) Canceled after 0s
Release / promote-and-deploy (push) Canceled after 0s
CI / build-image (push) Canceled after 0s
2026-09-18 02:09:16 +02:00
8 changed files with 48 additions and 11 deletions
+3
View File
@@ -58,6 +58,9 @@ SMTP_PORT=587
SMTP_USER= SMTP_USER=
SMTP_PASSWORD= SMTP_PASSWORD=
SMTP_FROM="ANOUMA <support@anouma.org>" SMTP_FROM="ANOUMA <support@anouma.org>"
# Keep true for publicly trusted SMTP certificates. Set false only when the
# configured SMTP proxy intentionally uses a private/self-signed certificate.
SMTP_TLS_REJECT_UNAUTHORIZED=true
# Address used as Reply-To for CMS replies. Configure this mailbox with an # Address used as Reply-To for CMS replies. Configure this mailbox with an
# inbound provider/webhook that POSTs replies to /api/email/inbound. # inbound provider/webhook that POSTs replies to /api/email/inbound.
+11 -8
View File
@@ -35,16 +35,13 @@ jobs:
run: npx next typegen && npx tsc --noEmit run: npx next typegen && npx tsc --noEmit
- name: Test - name: Test
run: npm test run: npm test
- name: Build
env:
NODE_ENV: production
run: npm run build
build-image: build-image:
needs: test needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push' if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
actions: write
contents: read contents: read
packages: write packages: write
env: env:
@@ -58,7 +55,13 @@ jobs:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image - name: Set up Docker Buildx
run: docker build -t "$IMAGE:$GITHUB_SHA" . uses: docker/setup-buildx-action@v3
- name: Push image - name: Build and push image
run: docker push "$IMAGE:$GITHUB_SHA" uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ env.IMAGE }}:${{ github.sha }}
cache-from: type=gha,scope=anouma-image
cache-to: type=gha,mode=max,scope=anouma-image
+1 -1
View File
@@ -193,7 +193,7 @@ See `.env.example` for the full, commented list. `setup.sh` writes the real file
| `TURNSTILE_SITE_KEY` | Public Cloudflare Turnstile site key | | `TURNSTILE_SITE_KEY` | Public Cloudflare Turnstile site key |
| `TURNSTILE_SECRET_KEY` | Server-only Cloudflare Turnstile secret; keep it in `/opt/anouma/.env` and never in GitHub Actions or the repository | | `TURNSTILE_SECRET_KEY` | Server-only Cloudflare Turnstile secret; keep it in `/opt/anouma/.env` and never in GitHub Actions or the repository |
| `TURNSTILE_HOSTNAMES` | Hostnames accepted by Siteverify; production is `anouma.org` only (local development may use `localhost,127.0.0.1`) | | `TURNSTILE_HOSTNAMES` | Hostnames accepted by Siteverify; production is `anouma.org` only (local development may use `localhost,127.0.0.1`) |
| `SMTP_*` | The existing ANOUMA mail system | | `SMTP_*` | The existing ANOUMA mail system. `SMTP_TLS_REJECT_UNAUTHORIZED` stays `true` unless the configured SMTP proxy intentionally uses a private/self-signed certificate; only then set it to `false`. |
| `INBOUND_REPLY_TO`, `INBOUND_EMAIL_SECRET` | Mailbox and shared secret for the inbound email webhook (`/api/email/inbound`) | | `INBOUND_REPLY_TO`, `INBOUND_EMAIL_SECRET` | Mailbox and shared secret for the inbound email webhook (`/api/email/inbound`) |
| `LIVEKIT_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` | eigener LiveKit-SFU und serverseitige Token-Erzeugung | | `LIVEKIT_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` | eigener LiveKit-SFU und serverseitige Token-Erzeugung |
| `DOCKER_NETWORK`, `NPM_NETWORK` | Docker network names | | `DOCKER_NETWORK`, `NPM_NETWORK` | Docker network names |
+6 -2
View File
@@ -5,10 +5,10 @@
# .env.example) - this image builds without a database connection. # .env.example) - this image builds without a database connection.
FROM node:22-alpine AS base FROM node:22-alpine AS base
RUN apk add --no-cache libc6-compat su-exec
# ---- deps: install once, cached as its own layer ------------------------- # ---- deps: install once, cached as its own layer -------------------------
FROM base AS deps FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci RUN npm ci
@@ -41,7 +41,11 @@ COPY --from=builder --chown=nextjs:nodejs /app /app
# Docker volumes mounted over these paths (see docker-compose.yml). # Docker volumes mounted over these paths (see docker-compose.yml).
RUN mkdir -p media backups && chown -R nextjs:nodejs media backups RUN mkdir -p media backups && chown -R nextjs:nodejs media backups
USER nextjs COPY --from=builder --chown=root:root /app/docker/anouma-entrypoint.sh /usr/local/bin/anouma-entrypoint.sh
RUN chmod 755 /usr/local/bin/anouma-entrypoint.sh
USER root
ENTRYPOINT ["/usr/local/bin/anouma-entrypoint.sh"]
EXPOSE 3000 EXPOSE 3000
ENV PORT=3000 ENV PORT=3000
+8
View File
@@ -129,6 +129,14 @@ deploy_version() {
registry_login registry_login
log_info "Pulling ${ANOUMA_IMAGE} …" log_info "Pulling ${ANOUMA_IMAGE} …"
$COMPOSE pull app || return 1 $COMPOSE pull app || return 1
# The media directory is a bind mount. Its host ownership can differ from
# the unprivileged `nextjs` user inside the image, which otherwise turns a
# valid image upload into Payload's generic FileUploadError. The deploy user
# already controls Docker, so use the image's root user only for this narrow
# ownership repair before starting the app.
log_info "Preparing media storage permissions …"
$COMPOSE run --rm --user root --entrypoint /bin/sh app -c \
'mkdir -p /app/media && chown -R 1001:1001 /app/media && chmod 775 /app/media' || return 1
log_info "Running database migrations …" log_info "Running database migrations …"
$COMPOSE run --rm app npm run migrate || return 1 $COMPOSE run --rm app npm run migrate || return 1
log_info "Starting containers …" log_info "Starting containers …"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
set -eu
# /app/media is a persistent host bind mount in production. Its host owner
# can differ from the unprivileged application user inside the image, so fix
# the mount ownership before dropping privileges for the actual app process.
if [ -d /app/media ]; then
chown -R nextjs:nodejs /app/media
chmod 775 /app/media
fi
exec su-exec nextjs "$@"
+6
View File
@@ -15,6 +15,12 @@ export function getMailTransport(): nodemailer.Transporter {
host: SMTP_HOST, host: SMTP_HOST,
port: Number(SMTP_PORT), port: Number(SMTP_PORT),
secure: Number(SMTP_PORT) === 465, secure: Number(SMTP_PORT) === 465,
tls: {
// Keep certificate verification enabled by default. A private SMTP
// proxy may expose a self-signed certificate; that deployment can
// explicitly opt out without weakening unrelated HTTPS requests.
rejectUnauthorized: process.env.SMTP_TLS_REJECT_UNAUTHORIZED !== "false",
},
connectionTimeout: 10000, connectionTimeout: 10000,
greetingTimeout: 10000, greetingTimeout: 10000,
socketTimeout: 20000, socketTimeout: 20000,
+1
View File
@@ -313,6 +313,7 @@ SMTP_PORT=587
SMTP_USER= SMTP_USER=
SMTP_PASSWORD= SMTP_PASSWORD=
SMTP_FROM= SMTP_FROM=
SMTP_TLS_REJECT_UNAUTHORIZED=true
INBOUND_REPLY_TO=reply-to@anouma.org INBOUND_REPLY_TO=reply-to@anouma.org
INBOUND_EMAIL_SECRET= INBOUND_EMAIL_SECRET=
LIVEKIT_URL= LIVEKIT_URL=