The "import accounts" browse was shelling out a single unbounded ldapsearch call, which failed outright with "Size limit exceeded (4)" once the directory grew past whatever sizeLimit the LDAP server enforces for this bind account. Added RFC 2696 paged results (-E pr=500/noprompt) so ldapsearch transparently walks the whole base DN in pages instead of one request that trips the limit. Separately: execFile's rejection .message is "Command failed: <full argv>\n<stderr>", and the full argv includes the bind password passed via -w — that was reaching the admin UI verbatim in the error banner shown after a failed search. Only stderr (ldapsearch's own diagnostic text, which never echoes its invocation) is now surfaced. Also fixes an unrelated image-build flake: drizzle-kit's migrate step leaves db.sqlite in SQLite's default (non-WAL) journal mode, and next build's parallel page-data-collection workers then race to perform the first-ever journal_mode=WAL switch — a mode change that, unlike ordinary statements, doesn't reliably honor busy_timeout, so one worker's pragma call throws SQLITE_BUSY regardless of the configured timeout. Switching to WAL once, single-process, right after migration avoids the race entirely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gh2UXUQUVBWroWEnn1FLFG
43 lines
2.1 KiB
Docker
43 lines
2.1 KiB
Docker
FROM node:20-bookworm-slim
|
|
|
|
# python3/make/g++ let npm fall back to compiling better-sqlite3/argon2 from
|
|
# source if no prebuilt binary matches this platform. ldap-utils provides
|
|
# the `ldapsearch` binary used for the LDAP directory browse — ldapjs's own
|
|
# BER decoder proved unreliable against real AD responses for that query.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
python3 make g++ ca-certificates ldap-utils \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /app
|
|
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci
|
|
|
|
COPY . .
|
|
# `next build`'s page-data collection spins up several parallel workers that
|
|
# each import the db client at module-eval time. If data/db.sqlite doesn't
|
|
# exist yet, every worker races to create it from scratch — that race is at
|
|
# the file-creation level, before our code ever gets to run a busy_timeout
|
|
# pragma, so it can throw SQLITE_BUSY (or worse) independent of pragma order.
|
|
# Migrating first — as its own isolated, single-process step — means the
|
|
# workers only ever see an already-existing, already-stable file.
|
|
RUN mkdir -p data && npm run db:migrate
|
|
# A second, separate race: drizzle-kit's migrate command opens its own
|
|
# connection and leaves the file in SQLite's default (non-WAL) journal
|
|
# mode. The *first* pragma("journal_mode = WAL") call on a file is a real
|
|
# mode switch requiring a brief exclusive lock — and unlike ordinary
|
|
# statements, that specific switch doesn't reliably honor busy_timeout, so
|
|
# when all of next build's workers race to be the one performing it, one
|
|
# loses and throws SQLITE_BUSY regardless of the 30s timeout configured in
|
|
# lib/db/client.ts. Switching to WAL here, once, single-process, means
|
|
# every worker's own pragma call below is just a no-op mode check.
|
|
RUN node -e "require('better-sqlite3')('data/db.sqlite').pragma('journal_mode = WAL')"
|
|
RUN npm run build
|
|
|
|
ENV NODE_ENV=production
|
|
EXPOSE 8081
|
|
|
|
# Migrate the (volume-mounted) SQLite DB and bootstrap the admin account on
|
|
# every start — both are no-ops once already applied.
|
|
CMD ["sh", "-c", "npm run db:migrate && npm run bootstrap-admin && npm start"]
|