mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
* feat(sync): add Helm chart for SuperSync Kubernetes deployment Includes Deployment, StatefulSet (PostgreSQL), Service, Ingress, ConfigMap, Secret, HPA, PDB, NetworkPolicy, and test templates. Supports both bundled PostgreSQL and external database configurations. * feat(sync): add WebSocket push notifications for near-realtime sync Server: Fastify WebSocket plugin with connection manager, app-level heartbeat (30s), debounced notifications, and per-user routing. Client: WebSocket service with exponential backoff reconnection, WS-triggered download service, and reduced polling when connected. * fix(sync): improve WebSocket error handling and reactivity Make syncInterval$ reactive to WS connection state, fix Set/Map mutation during heartbeat iteration, add error handling to WS route handler, separate JSON parsing from message handling, detect auth failures in WS-triggered downloads, and add logging to all empty catch blocks. * fix(sync): address PR review findings for WebSocket and Helm Fix race condition in WS-triggered download pipeline by moving isSyncInProgress filter after debounce and adding guard in _downloadOps. Add logging to remaining empty catch blocks, fix missing $NODE_IP in Helm NOTES.txt, and correct inaccurate comments in values.yaml and ws-triggered-download.service.ts. * fix(sync): add rate limiting to WebSocket upgrade endpoint Limit WS connection attempts to 10 per minute per IP to prevent connection flooding, matching the rate-limit pattern used by other sync endpoints. * fix(sync): extract shared constants, fix debounce correctness, replace deprecated toPromise Extract CLIENT_ID_REGEX and MAX_CLIENT_ID_LENGTH into sync.const.ts to prevent drift between sync.routes.ts and websocket.routes.ts. Fix debounce in notifyNewOps to accumulate excluded client IDs across rapid calls from different clients, preventing self-notifications. Replace deprecated toPromise() with firstValueFrom in connectWebSocket and _sync methods. Add .catch() to reconnect path and logging to silent early returns in _downloadOps. * fix(build): restore correct package-lock.json and fix upstream lint errors * revert: restore upstream HTML formatting to match CI prettier config * fix(sync): use DownloadOutcome discriminated union in WsTriggeredDownloadService * fix(sync): narrow TokenVerificationResult before accessing userId * fix(sync): await async getProviderById in connectWebSocket Missing await caused the Promise object to be cast to SuperSyncProvider, making getWebSocketParams undefined. * feat(sync): add SEED_USERS env var to create verified users on startup For self-hosted single-user setups: set SEED_USERS=email1,email2 to create verified users on boot and log their access tokens. Skips existing users. Removes need for SMTP/magic link registration. Also fix Dockerfile to use npm install instead of npm ci for lockfile compatibility. * fix(sync): address PR review feedback for Helm chart and server Security: - Remove seed-users.ts (logged full JWT tokens to stdout) - Add fail assertions for missing jwtSecret and postgresql.password - Add smtp.user/smtp.password values fields - Add from: selector to NetworkPolicy ingress - Add egress rule for external database when postgresql.enabled=false Correctness: - Fix PostgreSQL StatefulSet indentation for non-persistent mode - Fix NOTES.txt panic on empty tls list (use len instead of index) - Restore npm ci in Dockerfile by using node:24-alpine (ships npm 11) - Add Recreate deployment strategy when using RWO PVC Operational: - Add fail guard preventing HPA maxReplicas > 1 (in-memory WS state) - Fix PDB to use maxUnavailable instead of minAvailable - Add WebSocket ingress timeout annotation examples - Add Prisma db push init container for schema migrations - Default serviceAccount.automount to false - Add Chart.yaml maintainers, home, sources metadata * feat(sync): add ALLOWED_EMAILS env var to restrict registration Supports fully qualified emails (user@example.com) and domain wildcards (*@example.com). When unset, all emails are allowed. Applied to all three registration endpoints (passkey options, passkey verify, magic link). * fix(sync): exempt health endpoint from rate limiting Kubernetes liveness/readiness probes hit /health every 5-15s, exhausting the global rate limit (100 req/15min) and causing 429 responses that trigger container restarts. * fix(sync): harden WebSocket, Helm chart, and sync services from multi-agent review - Pin prisma@5.22.0 in init container and use migrate deploy instead of db push - Add per-user WebSocket connection limit (max 10) to prevent resource exhaustion - Add replicaCount > 1 fail guard in deployment template (in-memory WS state) - Set maxPayload: 1024 on WebSocket plugin (only pong messages expected) - Remove premature IN_SYNC status from download-only WS-triggered sync - Fix double removeConnection on error+close events (let close handle cleanup) - DRY debounce logic in notifyNewOps and store latestSeq on pending entry - Remove dead fromClientId from NewOpsNotification and WsMessage interfaces - Add takeUntilDestroyed to _wsProviderCleanup subscription - Simplify email-allowlist.ts to eager init (eliminate mutable state) - Guard connectWebSocket at call site for non-SuperSync providers - Add distinctUntilChanged to syncInterval$ to prevent unnecessary resets - Add container-level securityContext to PostgreSQL StatefulSet - Fix HPA maxReplicas default to 1 (matches single-replica constraint) * fix(sync): restore migration in Dockerfile CMD for non-Helm Docker users Helm users get migration via init container (runs first, CMD becomes no-op). Docker-compose/plain Docker users get automatic migration back on startup. * fix(boards): add missing drag delay for touch and extract shared signal Add cdkDragStartDelay to board-panel drag items, which was missing entirely. Extract the repeated `isTouchActive() ? DRAG_DELAY_FOR_TOUCH : 0` expression into a shared `dragDelayForTouch` computed signal and refactor all 8 components to use it. * test(sync): add comprehensive WebSocket test coverage Add unit tests for the new WebSocket real-time sync notification pipeline: - WebSocketConnectionService (server): connection lifecycle, max-per-user limits, notification debouncing, heartbeat, graceful shutdown (13 tests) - WebSocket routes validation: token/clientId validation, close codes, error handling, validation ordering (18 tests) - SuperSyncWebSocketService (frontend): reconnection with exponential backoff, heartbeat, disconnect cleanup, URL conversion (6 tests) - WsTriggeredDownloadService: auth error handling, pipeline resilience, start() idempotency (3 tests) - SyncWrapperService: connectWebSocket guards for non-SuperSync, null params, and already-connected cases (3 tests) - E2E realtime push: verifies WS-triggered download between two clients Quality fixes: - Replace waitForTimeout with syncAndWait in E2E - Add explanatory comment for microtask flushing in sync-wrapper spec - Use getter for isSyncInProgress mock in ws-triggered-download spec --------- Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
70 lines
No EOL
2.6 KiB
Docker
70 lines
No EOL
2.6 KiB
Docker
# ---- Builder stage ----
|
|
FROM node:24-alpine AS builder
|
|
|
|
WORKDIR /repo
|
|
|
|
RUN apk add --no-cache openssl libc6-compat
|
|
|
|
# Root package + lock (for workspace install)
|
|
COPY package.json package-lock.json ./
|
|
COPY packages/shared-schema/package.json ./packages/shared-schema/
|
|
COPY packages/super-sync-server/package.json ./packages/super-sync-server/
|
|
|
|
# Install ALL dependencies for the monorepo
|
|
# Use npm ci for reproducible builds; requires compatible lockfile
|
|
RUN npm ci --ignore-scripts
|
|
|
|
# Copy source code
|
|
COPY packages/shared-schema/ ./packages/shared-schema/
|
|
COPY packages/super-sync-server/ ./packages/super-sync-server/
|
|
|
|
# Build shared-schema
|
|
WORKDIR /repo/packages/shared-schema
|
|
RUN npm run build
|
|
# Pack shared-schema to tgz for installation in production
|
|
RUN npm pack
|
|
|
|
# Build super-sync-server
|
|
WORKDIR /repo/packages/super-sync-server
|
|
RUN npx prisma generate
|
|
RUN rm -rf dist && npm run build
|
|
|
|
# ---- Runtime stage ----
|
|
FROM node:24-alpine AS production
|
|
|
|
# System deps for Prisma & healthcheck + create user BEFORE copying files
|
|
RUN apk add --no-cache openssl libc6-compat wget && \
|
|
addgroup -g 1001 -S nodejs && \
|
|
adduser -S supersync -u 1001 -G nodejs && \
|
|
mkdir -p /app/data && chown supersync:nodejs /app /app/data
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy built artifacts from builder (note: tsconfig rootDir is "." so structure is dist/src/ and dist/scripts/)
|
|
COPY --from=builder --chown=supersync:nodejs /repo/packages/super-sync-server/dist ./dist
|
|
COPY --from=builder --chown=supersync:nodejs /repo/packages/super-sync-server/public ./public
|
|
COPY --from=builder --chown=supersync:nodejs /repo/packages/super-sync-server/prisma ./prisma
|
|
COPY --from=builder --chown=supersync:nodejs /repo/packages/super-sync-server/scripts ./scripts
|
|
COPY --from=builder --chown=supersync:nodejs /repo/packages/super-sync-server/src ./src
|
|
COPY --from=builder --chown=supersync:nodejs /repo/packages/super-sync-server/package.json ./package.json
|
|
COPY --from=builder --chown=supersync:nodejs /repo/packages/shared-schema/sp-shared-schema-*.tgz ./shared-schema.tgz
|
|
|
|
# Install dependencies and generate Prisma client in single layer
|
|
RUN npm install ./shared-schema.tgz --ignore-scripts && \
|
|
npm install --ignore-scripts --omit=dev && \
|
|
npx prisma@5.22.0 generate && \
|
|
rm -f shared-schema.tgz && \
|
|
npm cache clean --force
|
|
|
|
USER supersync
|
|
|
|
EXPOSE 1900
|
|
|
|
ENV NODE_ENV=production
|
|
ENV PORT=1900
|
|
ENV DATA_DIR=/app/data
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT}/health || exit 1
|
|
|
|
CMD ["sh", "-c", "npx prisma@5.22.0 migrate deploy && node dist/src/index.js"] |