diff --git a/BUILD.md b/BUILD.md index e40d939..d354611 100644 --- a/BUILD.md +++ b/BUILD.md @@ -12,29 +12,29 @@ cloudron build \ --set-build-service builder.docker.due.ren \ --build-service-token e3265de06b1d0e7bb38400539012a8433a74c2c96a17955e \ - --set-repository andreasdueren/ente-cloudron \ - --tag 0.1.0 + --set-repository andreasdueren/affine-cloudron \ + --tag 0.25.3 ``` ## Deployment Steps 1. Remove any previous dev install of AFFiNE on the Cloudron (always reinstall from scratch). 2. Install the freshly built image: ```bash - cloudron install --location ente.due.ren --image andreasdueren/ente-cloudron:0.1.0 + cloudron install --location affine.due.ren --image andreasdueren/affine-cloudron:0.25.3 ``` 3. When prompted, confirm the app info and wait for Cloudron to report success (abort after ~30 seconds if installation stalls or errors to avoid hanging sessions). -4. Visit `https://ente.due.ren` (or the chosen location) and sign in using Cloudron SSO. +4. Visit `https://affine.due.ren` (or the chosen location) and sign in using Cloudron SSO. ## Testing Checklist - Open the app dashboard and ensure the landing page loads without 502/504 errors. - Create a workspace, add a note, upload an asset, and reload to confirm `/app/data` persistence. -- Trigger OIDC login/logout flows to verify Cloudron SSO works (callback `/api/v1/session/callback`). +- Trigger OIDC login/logout flows to verify Cloudron SSO works (callback `/oauth/callback`). - Send an invitation email to validate SMTP credentials wired from the Cloudron mail addon. -- Inspect logs with `cloudron logs --app ente.due.ren -f` for migration output from `scripts/self-host-predeploy.js`. +- Inspect logs with `cloudron logs --app affine.due.ren -f` for migration output from `scripts/self-host-predeploy.js`. ## Troubleshooting - **Migrations hang**: restart the app; migrations rerun automatically before the server starts. Check PostgreSQL reachability via `cloudron exec --app -- env | grep DATABASE_URL`. -- **Login issues**: confirm the Cloudron OIDC client is enabled for the app and that the callback URL `/api/v1/session/callback` is allowed. +- **Login issues**: confirm the Cloudron OIDC client is enabled for the app and that the callback URL `/oauth/callback` is allowed. - **Email failures**: verify the Cloudron sendmail addon is provisioned and that `MAILER_*` env vars show up inside the container (`cloudron exec --app -- env | grep MAILER`). - **Large uploads rejected**: adjust `client_max_body_size` in `nginx.conf` if you routinely exceed 200 MB assets, then rebuild. diff --git a/Dockerfile b/Dockerfile index a3d4e3e..4fdcb32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM ghcr.io/toeverything/affine:stable AS upstream +ARG AFFINE_VERSION=stable +FROM ghcr.io/toeverything/affine:${AFFINE_VERSION} AS upstream FROM cloudron/base:5.0.0 @@ -30,7 +31,8 @@ COPY config.example.json "$APP_CODE_DIR/config.example.json" COPY tmp_data/ "$APP_TMP_DIR/" RUN chmod +x "$APP_CODE_DIR/start.sh" "$APP_CODE_DIR/run-affine.sh" && \ - chown -R cloudron:cloudron "$APP_CODE_DIR" "$APP_DATA_DIR" "$APP_RUNTIME_DIR" "$APP_TMP_DIR" + chown cloudron:cloudron "$APP_CODE_DIR/start.sh" "$APP_CODE_DIR/run-affine.sh" && \ + chown -R cloudron:cloudron "$APP_DATA_DIR" "$APP_RUNTIME_DIR" "$APP_TMP_DIR" EXPOSE 3000 CMD ["/app/code/start.sh"] diff --git a/light.png b/light.png deleted file mode 100644 index ac49d36..0000000 Binary files a/light.png and /dev/null differ diff --git a/nginx.conf b/nginx.conf index 0de9eac..4585971 100644 --- a/nginx.conf +++ b/nginx.conf @@ -17,6 +17,8 @@ http { client_body_temp_path /run/nginx/body; proxy_temp_path /run/nginx/proxy; fastcgi_temp_path /run/nginx/fastcgi; + uwsgi_temp_path /run/nginx/uwsgi; + scgi_temp_path /run/nginx/scgi; log_format cloudron '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' @@ -36,7 +38,7 @@ http { server { listen 3000; server_name _; - client_max_body_size 200m; + client_max_body_size 600m; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; diff --git a/run-affine.sh b/run-affine.sh index e56e8d0..230b74a 100644 --- a/run-affine.sh +++ b/run-affine.sh @@ -99,9 +99,138 @@ ensure_runtime_envs() { ensure_server_env } +patch_upload_limits() { + local target="$APP_DIR/dist/main.js" + if [ ! -f "$target" ]; then + return + fi + python3 - "$target" <<'PY' +import sys +from pathlib import Path + +target = Path(sys.argv[1]) +data = target.read_text() +updated = data +updated = updated.replace("limit: 100 * OneMB", "limit: 512 * OneMB", 1) +updated = updated.replace("maxFileSize: 100 * OneMB", "maxFileSize: 512 * OneMB", 1) +if updated != data: + target.write_text(updated) +PY +} + +grant_team_plan_features() { + log "Ensuring self-hosted workspaces have team plan features" + node <<'NODE' +const { PrismaClient } = require('@prisma/client'); + +const prisma = new PrismaClient(); + +async function main() { + const feature = await prisma.feature.findFirst({ + where: { name: 'team_plan_v1' }, + orderBy: { deprecatedVersion: 'desc' }, + }); + if (!feature) { + console.warn('[team-plan] Feature record not found, skipping'); + return; + } + + const workspaces = await prisma.workspace.findMany({ + select: { id: true }, + }); + + for (const { id } of workspaces) { + const existing = await prisma.workspaceFeature.findFirst({ + where: { + workspaceId: id, + name: 'team_plan_v1', + activated: true, + }, + }); + if (existing) continue; + + await prisma.workspaceFeature.create({ + data: { + workspaceId: id, + featureId: feature.id, + name: 'team_plan_v1', + type: feature.deprecatedType ?? 1, + configs: feature.configs, + reason: 'selfhost-default', + activated: true, + }, + }); + console.log(`[team-plan] Granted team plan to workspace ${id}`); + } + + await prisma.$executeRawUnsafe(` + CREATE OR REPLACE FUNCTION grant_team_plan_feature() + RETURNS TRIGGER AS $$ + DECLARE + feature_id integer; + feature_type integer; + feature_configs jsonb; + BEGIN + SELECT id, type, configs + INTO feature_id, feature_type, feature_configs + FROM features + WHERE feature = 'team_plan_v1' + ORDER BY version DESC + LIMIT 1; + + IF feature_id IS NULL THEN + RETURN NEW; + END IF; + + INSERT INTO workspace_features + (workspace_id, feature_id, name, type, configs, reason, activated, created_at) + SELECT + NEW.id, + feature_id, + 'team_plan_v1', + feature_type, + feature_configs, + 'selfhost-default', + TRUE, + NOW() + WHERE NOT EXISTS ( + SELECT 1 FROM workspace_features + WHERE workspace_id = NEW.id AND name = 'team_plan_v1' AND activated = TRUE + ); + + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + `); + + await prisma.$executeRawUnsafe(` + DO $$ BEGIN + CREATE TRIGGER grant_team_plan_feature_trigger + AFTER INSERT ON workspaces + FOR EACH ROW + EXECUTE FUNCTION grant_team_plan_feature(); + EXCEPTION + WHEN duplicate_object THEN NULL; + END $$; + `); +} + +main() + .then(() => console.log('[team-plan] Workspace quota ensured')) + .catch(err => { + console.error('[team-plan] Failed to grant features', err); + }) + .finally(async () => { + await prisma.$disconnect(); + }); +NODE +} + log "Running AFFiNE pre-deployment migrations" ensure_runtime_envs node ./scripts/self-host-predeploy.js +patch_upload_limits +grant_team_plan_features log "Starting AFFiNE server" exec node ./dist/main.js