# NOTE: this image builds from the **repository root**, not from `web/`. # The landing page's install endpoint signs with zsign, and zsign is built here # from the monorepo's own `manager/Zsign` sources, so the server signs with # exactly the code the Manager signs with on-device. `Dockerfile.worker` still # builds from `web/` — the worker never signs anything. # # Stage 0: zsign # # bookworm-slim on purpose: node:20-slim is bookworm too, so the binary links # against the same libssl3/libminizip1 the runner installs. FROM debian:bookworm-slim AS zsign # zlib1g-dev is not redundant: minizip's own headers open with `#include # "zlib.h"` and libminizip-dev does not pull the zlib headers in behind it. RUN apt-get update && apt-get install -y --no-install-recommends \ g++ make pkg-config libssl-dev libminizip-dev zlib1g-dev \ && rm -rf /var/lib/apt/lists/* WORKDIR /src COPY manager/Zsign ./Zsign # Upstream maintains the Swift package, not this CLI. `ZBundle::SignFolder` # grew a tenth parameter, `src/zsign.mm` was updated for it and `src/zsign.cpp` # was not, so `build/linux` does not compile at the commit the submodule is # pinned to. The argument is supplied here rather than by moving the pin: # ios/Zsign and manager/Zsign sit at the same commit, and moving it would change # what both apps sign with on-device to fix a server build. # # The parameter reads backwards — `true` is what *writes* embedded.mobileprovision # and seals it into the signature, `false` strips it. `true` is therefore the # value to pass: it is what the apps send by default (ZsignHandler passes # `removeProvision: !options.removeProvisioning`), and an over-the-air install # is refused without the profile. # # The grep is a tripwire, not decoration. If a later submodule bump fixes the # call site, this stops the build instead of letting a stale edit through. RUN grep -q 'bWeakInject, bEnableCache);' Zsign/src/zsign.cpp && \ sed -i 's/bWeakInject, bEnableCache);/bWeakInject, bEnableCache, true);/' \ Zsign/src/zsign.cpp # The Makefile writes to ../../bin/zsign, relative to build/linux. RUN make -C Zsign/build/linux -j"$(nproc)" # Stage 1: Dependencies FROM node:20-slim AS deps WORKDIR /app COPY web/package.json web/package-lock.json ./ RUN npm ci --ignore-scripts COPY web/prisma ./prisma/ RUN npx prisma generate # Stage 2: Build FROM node:20-slim AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY web/ . ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production RUN npm run build # Stage 3: Runner FROM node:20-slim AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV ZSIGN_PATH=/usr/local/bin/zsign # libminizip1 is zsign's only runtime dependency beyond libssl3, which the # openssl package already pulls in. RUN apt-get update && apt-get install -y --no-install-recommends \ openssl \ libminizip1 \ && rm -rf /var/lib/apt/lists/* && \ addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs COPY --from=zsign /src/Zsign/bin/zsign /usr/local/bin/zsign COPY --from=builder /app/public ./public COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/prisma ./prisma COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/scripts ./scripts # /app/storage holds the IPA files and tweak icons and is shared with the # worker. NOTE: a bind mount there shadows this directory, so the host path must # already be owned by uid/gid 1001 — the build-time chown does not reach it. # # storage/install is the landing page's signing scratch: signed builds waiting # for an `itms-services://` prompt to collect them, swept on a short expiry. RUN mkdir -p /app/tdlib-data/db /app/tdlib-data/files \ /app/storage/ipa /app/storage/icons /app/storage/tmp \ /app/storage/install \ /tmp/ftstore && \ chown -R nextjs:nodejs /app /tmp/ftstore USER nextjs EXPOSE 3000 # `pre-push.sql` first, every boot: it applies the changes `db push` refuses to # make to a database that already has rows — a dropped column, a dropped table, # a unique index over existing rows — so the push that follows only ever sees # work it considers safe. It is a no-op on a fresh database and on one already # up to date. See `prisma/pre-push.sql` for why this is not `--accept-data-loss`. CMD ["sh", "-c", "node node_modules/prisma/build/index.js db execute --schema prisma/schema.prisma --file prisma/pre-push.sql && node node_modules/prisma/build/index.js db push --skip-generate && node server.js"]