diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10b0d1f7..c252453a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,24 +40,54 @@ jobs: # `next build` (web, and docs, whose MDX compiles nowhere else) and the # Dockerfiles would otherwise first run after merge, in `build`. amd64 only, - # nothing pushed. Reads main's layer cache but never writes one: PR-scoped - # entries would crowd main's out of the repository's cache quota. + # nothing pushed; the app image is loaded locally to boot it. Reads main's + # layer cache but never writes one: PR-scoped entries would crowd main's out + # of the repository's cache quota. image: if: github.event_name == 'pull_request' runs-on: ubuntu-latest + services: + postgres: + image: postgres:17-alpine@sha256:18cfe3ef5e6815560c98237d6216d1e5119702fb0f3894c8785dd58b8bbe5d73 + env: + POSTGRES_USER: millionsend + POSTGRES_PASSWORD: millionsend + ports: ["5432:5432"] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . - outputs: type=cacheonly + load: true + tags: millionsend:ci cache-from: type=gha,scope=app-amd64 - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: scripts/backup outputs: type=cacheonly cache-from: type=gha,scope=backup-amd64 + # Runs the image as the compose files do: read-only rootfs, non-root, + # and Docker's default tmpfs (noexec) at the same paths. A dependency + # that writes or executes anywhere else fails here instead of on deploy. + - name: Image boots read-only + run: | + docker run -d --name app --network host --init --read-only --user millionsend \ + --cap-drop ALL --security-opt no-new-privileges \ + --tmpfs /tmp --tmpfs /app/apps/web/.next/cache \ + --tmpfs /app/apps/docs/.next/cache --tmpfs /app/apps/docs/.source \ + -e DATABASE_URL=postgres://millionsend:millionsend@localhost:5432/millionsend \ + -e MASTER_ENCRYPTION_KEY="$(openssl rand -base64 32)" \ + -e BETTER_AUTH_SECRET="$(openssl rand -base64 32)" \ + -e APP_BASE_URL=http://localhost:3000 \ + millionsend:ci + for _ in $(seq 60); do + curl -fs -o /dev/null localhost:3000/login && curl -fs -o /dev/null localhost:3001/health && exit 0 + [ "$(docker inspect -f '{{.State.Running}}' app)" = true ] || break + sleep 2 + done + docker logs app + exit 1 # Whole-history secret scan with the pinned gitleaks image (the GitHub # action needs a license key for organization repos; the CLI does not). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b5cb9aa..397169e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ settings: overrides: esbuild@<0.25.0: '>=0.25.0' deepmerge-ts@<8.0.0: '>=8.0.0' + '@swc/core': 1.16.2 importers: @@ -3332,86 +3333,86 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@swc/core-darwin-arm64@1.16.18': - resolution: {integrity: sha512-ozgiCHyFDCwfXzTnaLR+i8WxEyt06GTudn9rPh08vj+u3Z8vMvt5Pi8T+BikyNXm7y++h6UMdqsnJuWrV+tk2A==} + '@swc/core-darwin-arm64@1.16.2': + resolution: {integrity: sha512-i/j0HNbnn79qnTVPicvay92Nark8fW8NQqn1e2mGERjUXNpBV0+SwQxlRpk2zBhn6laJ8PDI6Kn1nHZhnz3LCA==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.16.18': - resolution: {integrity: sha512-tuwxXi+sjxxTAi7GP3NeQXQxOf4SXSiFxKAMYLBdoy7JqGYby6uSDXojcFoLOhSp7oiNGstnaFXxfbP0Hpro9w==} + '@swc/core-darwin-x64@1.16.2': + resolution: {integrity: sha512-HrwqHyEyHVXO3qTk8EkNK7/b6sOZSEoNh+pot6RdE5x0LbNqfo8LtJUvi3UTXr+5ja/o5HbJdW80eCXo+NjbiA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.16.18': - resolution: {integrity: sha512-SMdhmY1P9Kveh0MRj6vqQF98bsjnDa0lBBvkd/lcpxmjj5QHJOQAU0N3g6h7GPWpBMs3h2g3lHu/QTDn+uMsXg==} + '@swc/core-linux-arm-gnueabihf@1.16.2': + resolution: {integrity: sha512-MdXi83Z/gGp1LIrg+h7HKxiul/z/Bty/ZJSvYAFqDl9zteC1XLSAZdScquKtXPp50rdyXqritTDCqQBhwVfZKA==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.16.18': - resolution: {integrity: sha512-ep/mQyhblQYZ6tkfq0IJspcVSFl5jdywks3kejanrbB31uuujRD/QWOoGNp2L3lLRdAdQvgl/M3pA7g3hSuQug==} + '@swc/core-linux-arm64-gnu@1.16.2': + resolution: {integrity: sha512-/jcTmK6Ktz3owM3YtiKvjofV6p3VpHnYzTIrOGwDIOsDigRAAVuZ8east33wYO/7UTdKYFlyHNnJNT0WJqOA3Q==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [glibc] - '@swc/core-linux-arm64-musl@1.16.18': - resolution: {integrity: sha512-pkzGVxrAQu5+febW1a/Aiph3YJk0UiJKXoWbxnDVEIOZWkk5aETHTuyVsOQ3kOgn2dzNMvJVeqcoEQdH+ntqWQ==} + '@swc/core-linux-arm64-musl@1.16.2': + resolution: {integrity: sha512-4gFarKaFnlJTSlJYKmMhV4u+3YE4uYfiydpBoYjmgQhCf9lAieOq+WilZaK9vVSHeqLuQpTEiGULZqAdsRX5Dw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [musl] - '@swc/core-linux-ppc64-gnu@1.16.18': - resolution: {integrity: sha512-v9kiu4as1pJyQ8ESFTtMNydZ99m86QXLfAtm+XiKPKS93pXL80BCaXokAs9ebioGkIXgmwfbzUCdUC62H3JWkw==} + '@swc/core-linux-ppc64-gnu@1.16.2': + resolution: {integrity: sha512-syqSLGd6KlZ1PciNzs6bIUlhOuFztZufebOHaERjc4N4SqNZxyqYd4I+jj/EfOYnpe0kNjccn9HJLN1p5dz3+w==} engines: {node: '>=10'} cpu: [ppc64] os: [linux] libc: [glibc] - '@swc/core-linux-s390x-gnu@1.16.18': - resolution: {integrity: sha512-vU7TKRJ26i86YQr4QY/5bdcH72KzkBbSSUfPlErW6d6KOR47BcVGlYZFTcMMYlmd3CVSXFHW18tBSila66YfoA==} + '@swc/core-linux-s390x-gnu@1.16.2': + resolution: {integrity: sha512-ZBBLK+ewGyXLzWeMS7wbKtWBdnif6etn7xvPY/iOfbdsjX/+bgkp1pQt2lWF2wlu2hXYZuhJ/tHZE/QR8/apzg==} engines: {node: '>=10'} cpu: [s390x] os: [linux] libc: [glibc] - '@swc/core-linux-x64-gnu@1.16.18': - resolution: {integrity: sha512-Ml6kbe0BoVJkSgmYPAEUudGcu84jaQeqDN8xeMumbwNpT0vlmm7241Ddp9XKCjQABvjomnXF0OiNOAfjBhNDNw==} + '@swc/core-linux-x64-gnu@1.16.2': + resolution: {integrity: sha512-LyHJgxCA4Tje0ysBMbEb0tt/ie8kgUKoFE3JAKFhpevmTmhYEoC0H9s47WuDsqiFckF1ITUguZIXJG6K5e0dvg==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [glibc] - '@swc/core-linux-x64-musl@1.16.18': - resolution: {integrity: sha512-TGU24+QmmS8y/hg0BLKgOU3W5TCcQC9bgGl8AB0R61gTNQdWdcmCehRLAJwlZfxtAml66xMJ6PWrX8NKrABOsA==} + '@swc/core-linux-x64-musl@1.16.2': + resolution: {integrity: sha512-PghXJlVM1cgtLfNUR1vxFo1z+PDRAe8cWAJlZZ7spmeiN7BospGXg/MHUg7oNSgwSX7Zo//YKv9P5yD9apsFJQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [musl] - '@swc/core-win32-arm64-msvc@1.16.18': - resolution: {integrity: sha512-S53hm7Amc3YV8t2BUT6R7J+zSnwiGarG+GnxMca4W0uMkxBZ3Gl6jBwEgb6YqrYjf2DzOuRfauztqptCO76a/w==} + '@swc/core-win32-arm64-msvc@1.16.2': + resolution: {integrity: sha512-StTOSefYBxemvNYYUI3UmO1a8y+hSPjjfHogC2TEHL+Z1PlEBim/XtLas5rS04jAzT9RrNmbtX911SZ42H9jSQ==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.16.18': - resolution: {integrity: sha512-+6/10+OJmU12zWkrPVG0QeQEr8VsWeF8gyXrOfHZMIfMOfz8/ULXVZPhV9GhGMKSWake1xEJj2IYCiFRS/xMpQ==} + '@swc/core-win32-ia32-msvc@1.16.2': + resolution: {integrity: sha512-fycER209DYIzsibpTMC+chND05OfOjgztWL9U8OE6/uUlsOUZH3eh98isBLEnOymYUhlJLEt5++W1+KL/FOh5Q==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.16.18': - resolution: {integrity: sha512-7LsVrIICAZWSajXT6DwO7PYSTnW9maamOCyQylaOh13Viptrgi/7hl9fP75SiHe+kscaJahGXcorgt4MOOh/1w==} + '@swc/core-win32-x64-msvc@1.16.2': + resolution: {integrity: sha512-cSd1z6ivSrJPVr+moVwOHWjeKy6TpO4/Shwcv5KCrKYXCccxwh4pRy1C3fDioNx2PF1jPZWHKZjtXt+Be9VbaQ==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.16.18': - resolution: {integrity: sha512-0cYQLkpcwUngg55TDwpwrrVOU+B6zh1NFbp+oS7dVFhmYkzaARelwugo/bzPJh1FrcnppmF+KTqioCGwyqbcEw==} + '@swc/core@1.16.2': + resolution: {integrity: sha512-95I4kiSMeveI/Mhi+tE4fiWcWLUMfzfKrk0jtr8LRMqHgOgq+xHS+zExkDqoO4b5OeeuXHMWVdD5MeP3X6sULw==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -8834,59 +8835,59 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@swc/core-darwin-arm64@1.16.18': + '@swc/core-darwin-arm64@1.16.2': optional: true - '@swc/core-darwin-x64@1.16.18': + '@swc/core-darwin-x64@1.16.2': optional: true - '@swc/core-linux-arm-gnueabihf@1.16.18': + '@swc/core-linux-arm-gnueabihf@1.16.2': optional: true - '@swc/core-linux-arm64-gnu@1.16.18': + '@swc/core-linux-arm64-gnu@1.16.2': optional: true - '@swc/core-linux-arm64-musl@1.16.18': + '@swc/core-linux-arm64-musl@1.16.2': optional: true - '@swc/core-linux-ppc64-gnu@1.16.18': + '@swc/core-linux-ppc64-gnu@1.16.2': optional: true - '@swc/core-linux-s390x-gnu@1.16.18': + '@swc/core-linux-s390x-gnu@1.16.2': optional: true - '@swc/core-linux-x64-gnu@1.16.18': + '@swc/core-linux-x64-gnu@1.16.2': optional: true - '@swc/core-linux-x64-musl@1.16.18': + '@swc/core-linux-x64-musl@1.16.2': optional: true - '@swc/core-win32-arm64-msvc@1.16.18': + '@swc/core-win32-arm64-msvc@1.16.2': optional: true - '@swc/core-win32-ia32-msvc@1.16.18': + '@swc/core-win32-ia32-msvc@1.16.2': optional: true - '@swc/core-win32-x64-msvc@1.16.18': + '@swc/core-win32-x64-msvc@1.16.2': optional: true - '@swc/core@1.16.18(@swc/helpers@0.5.23)': + '@swc/core@1.16.2(@swc/helpers@0.5.23)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.28 optionalDependencies: - '@swc/core-darwin-arm64': 1.16.18 - '@swc/core-darwin-x64': 1.16.18 - '@swc/core-linux-arm-gnueabihf': 1.16.18 - '@swc/core-linux-arm64-gnu': 1.16.18 - '@swc/core-linux-arm64-musl': 1.16.18 - '@swc/core-linux-ppc64-gnu': 1.16.18 - '@swc/core-linux-s390x-gnu': 1.16.18 - '@swc/core-linux-x64-gnu': 1.16.18 - '@swc/core-linux-x64-musl': 1.16.18 - '@swc/core-win32-arm64-msvc': 1.16.18 - '@swc/core-win32-ia32-msvc': 1.16.18 - '@swc/core-win32-x64-msvc': 1.16.18 + '@swc/core-darwin-arm64': 1.16.2 + '@swc/core-darwin-x64': 1.16.2 + '@swc/core-linux-arm-gnueabihf': 1.16.2 + '@swc/core-linux-arm64-gnu': 1.16.2 + '@swc/core-linux-arm64-musl': 1.16.2 + '@swc/core-linux-ppc64-gnu': 1.16.2 + '@swc/core-linux-s390x-gnu': 1.16.2 + '@swc/core-linux-x64-gnu': 1.16.2 + '@swc/core-linux-x64-musl': 1.16.2 + '@swc/core-win32-arm64-msvc': 1.16.2 + '@swc/core-win32-ia32-msvc': 1.16.2 + '@swc/core-win32-x64-msvc': 1.16.2 '@swc/helpers': 0.5.23 '@swc/counter@0.1.3': {} @@ -10874,7 +10875,7 @@ snapshots: '@eloqnt/format-po': 0.1.1 '@formatjs/intl-localematcher': 0.8.14 '@parcel/watcher': 2.6.0 - '@swc/core': 1.16.18(@swc/helpers@0.5.23) + '@swc/core': 1.16.2(@swc/helpers@0.5.23) icu-minify: 4.14.9 negotiator: 1.1.0 next: 16.4.1(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 21b70ee8..13d42d8c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,3 +14,8 @@ overrides: "esbuild@<0.25.0": ">=0.25.0" # Stack-exhaustion advisory in <8 (GHSA via mailparser→html-to-text). "deepmerge-ts@<8.0.0": ">=8.0.0" + # From 1.16.12, @swc/core unpacks its native addon into a cache directory + # on first load and refuses to load without a writable, executable one, + # which a read-only container with a noexec /tmp lacks. next.config.ts + # loads it through next-intl's plugin, so `next start` would die at boot. + "@swc/core": "1.16.2"