mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-21 02:20:12 +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>
228 lines
6.4 KiB
YAML
228 lines
6.4 KiB
YAML
# -- Number of SuperSync replicas.
|
|
# Running more than 1 replica requires a shared store (e.g., Redis) for in-memory caches and WebSocket connection state (not yet supported).
|
|
replicaCount: 1
|
|
|
|
image:
|
|
# -- SuperSync container image repository
|
|
repository: ghcr.io/super-productivity/supersync
|
|
# -- Image pull policy
|
|
pullPolicy: IfNotPresent
|
|
# -- Overrides the image tag. Defaults to the chart appVersion.
|
|
tag: ""
|
|
|
|
# -- Docker registry pull secrets
|
|
imagePullSecrets: []
|
|
# -- Override the release name prefix
|
|
nameOverride: ""
|
|
# -- Override the full release name
|
|
fullnameOverride: ""
|
|
|
|
serviceAccount:
|
|
# -- Whether to create a ServiceAccount
|
|
create: true
|
|
# -- Automount the service account token (not needed by this app)
|
|
automount: false
|
|
# -- Annotations to add to the ServiceAccount
|
|
annotations: {}
|
|
# -- The name of the ServiceAccount to use. If not set and create is true, a name is generated.
|
|
name: ""
|
|
|
|
# -- SuperSync application configuration
|
|
supersync:
|
|
# -- Application listening port
|
|
port: 1900
|
|
# -- Node.js environment
|
|
nodeEnv: production
|
|
# -- REQUIRED: Public URL of the sync server (e.g. https://sync.example.com)
|
|
publicUrl: ""
|
|
cors:
|
|
# -- Comma-separated list of allowed CORS origins
|
|
origins: "https://app.super-productivity.com"
|
|
webauthn:
|
|
# -- WebAuthn relying party display name
|
|
rpName: "Super Productivity Sync"
|
|
# -- WebAuthn relying party ID (e.g. sync.example.com)
|
|
rpId: ""
|
|
# -- WebAuthn origin (e.g. https://sync.example.com)
|
|
origin: ""
|
|
smtp:
|
|
# -- Enable SMTP email sending
|
|
enabled: false
|
|
# -- SMTP server hostname
|
|
host: ""
|
|
# -- SMTP server port
|
|
port: 587
|
|
# -- Use TLS for SMTP
|
|
secure: false
|
|
# -- Sender email address
|
|
from: ""
|
|
# -- SMTP username
|
|
user: ""
|
|
# -- SMTP password
|
|
password: ""
|
|
# -- Use an existing Kubernetes secret for SMTP credentials.
|
|
# The secret must contain SMTP_USER and SMTP_PASS keys.
|
|
existingSecret: ""
|
|
privacy:
|
|
# -- Contact name for privacy policy
|
|
contactName: ""
|
|
# -- Street address for privacy policy
|
|
addressStreet: ""
|
|
# -- City for privacy policy
|
|
addressCity: ""
|
|
# -- Country for privacy policy
|
|
addressCountry: ""
|
|
# -- Contact email for privacy policy
|
|
contactEmail: ""
|
|
persistence:
|
|
# -- Enable persistent storage for /app/data
|
|
enabled: true
|
|
# -- Size of the persistent volume
|
|
size: 1Gi
|
|
# -- Storage class for the PVC. Empty string uses the default storage class.
|
|
storageClass: ""
|
|
# -- Access modes for the PVC
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
secret:
|
|
# -- REQUIRED: JWT signing secret (minimum 32 characters)
|
|
jwtSecret: ""
|
|
# -- Use a pre-existing Kubernetes secret instead of creating one.
|
|
# The secret must contain a JWT_SECRET key.
|
|
existingSecret: ""
|
|
|
|
# -- Built-in PostgreSQL configuration
|
|
postgresql:
|
|
# -- Deploy a PostgreSQL instance alongside SuperSync.
|
|
# Set to false and configure externalDatabase to use an external PostgreSQL.
|
|
enabled: true
|
|
image:
|
|
# -- PostgreSQL image repository
|
|
repository: postgres
|
|
# -- PostgreSQL image tag
|
|
tag: "16-alpine"
|
|
# -- PostgreSQL image pull policy
|
|
pullPolicy: IfNotPresent
|
|
# -- PostgreSQL database name
|
|
database: supersync
|
|
# -- PostgreSQL username
|
|
username: supersync
|
|
# -- PostgreSQL password (REQUIRED when postgresql.enabled is true)
|
|
password: ""
|
|
# -- Use an existing Kubernetes secret for the PostgreSQL password.
|
|
# The secret must contain a POSTGRES_PASSWORD key.
|
|
existingSecret: ""
|
|
persistence:
|
|
# -- Enable persistent storage for PostgreSQL data
|
|
enabled: true
|
|
# -- Size of the PostgreSQL persistent volume
|
|
size: 5Gi
|
|
# -- Storage class for the PostgreSQL PVC. Empty string uses the default.
|
|
storageClass: ""
|
|
# -- Access modes for the PostgreSQL PVC
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
# -- Resource requests and limits for PostgreSQL
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
memory: 512Mi
|
|
|
|
# -- External database configuration (used when postgresql.enabled is false)
|
|
externalDatabase:
|
|
# -- Full PostgreSQL connection URL (e.g. postgresql://user:pass@host:5432/dbname)
|
|
url: ""
|
|
# -- Use an existing Kubernetes secret for the database URL.
|
|
# The secret must contain a DATABASE_URL key.
|
|
existingSecret: ""
|
|
|
|
# -- Ingress configuration
|
|
ingress:
|
|
# -- Enable ingress
|
|
enabled: false
|
|
# -- Ingress class name
|
|
className: ""
|
|
# -- Ingress annotations
|
|
# WebSocket support requires extended timeouts. Example for nginx-ingress:
|
|
# nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
|
# nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
|
# For Traefik:
|
|
# traefik.ingress.kubernetes.io/router.middlewares: ...
|
|
annotations: {}
|
|
# -- Ingress host rules
|
|
hosts:
|
|
- host: sync.example.com
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
# -- Ingress TLS configuration
|
|
tls: []
|
|
# - secretName: supersync-tls
|
|
# hosts:
|
|
# - sync.example.com
|
|
|
|
# -- Service configuration
|
|
service:
|
|
# -- Service type
|
|
type: ClusterIP
|
|
# -- Service port
|
|
port: 80
|
|
|
|
# -- Resource requests and limits for the SuperSync container
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 128Mi
|
|
limits:
|
|
memory: 256Mi
|
|
|
|
# -- Horizontal Pod Autoscaler configuration
|
|
autoscaling:
|
|
# -- Enable HPA
|
|
enabled: false
|
|
# -- Minimum number of replicas
|
|
minReplicas: 1
|
|
# -- Maximum number of replicas (must be 1 until shared WS state is implemented)
|
|
maxReplicas: 1
|
|
# -- Target CPU utilization percentage
|
|
targetCPUUtilizationPercentage: 80
|
|
|
|
# -- Pod Disruption Budget configuration
|
|
podDisruptionBudget:
|
|
# -- Enable PDB
|
|
enabled: false
|
|
# -- Maximum number of unavailable pods (use this instead of minAvailable for single-replica)
|
|
maxUnavailable: 1
|
|
|
|
# -- Network Policy configuration
|
|
networkPolicy:
|
|
# -- Enable NetworkPolicy
|
|
enabled: false
|
|
# -- Namespace of the ingress controller (for ingress from: selector)
|
|
ingressNamespace: "ingress-nginx"
|
|
|
|
# -- Node selector for pod scheduling
|
|
nodeSelector: {}
|
|
# -- Tolerations for pod scheduling
|
|
tolerations: []
|
|
# -- Affinity rules for pod scheduling
|
|
affinity: {}
|
|
# -- Annotations to add to pods
|
|
podAnnotations: {}
|
|
# -- Labels to add to pods
|
|
podLabels: {}
|
|
# -- Pod-level security context
|
|
podSecurityContext:
|
|
fsGroup: 1001
|
|
# -- Container-level security context
|
|
securityContext:
|
|
runAsUser: 1001
|
|
runAsGroup: 1001
|
|
runAsNonRoot: true
|
|
readOnlyRootFilesystem: true
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop:
|
|
- ALL
|