Enhance mail setup and team plan defaults
This commit is contained in:
14
BUILD.md
14
BUILD.md
@@ -12,29 +12,29 @@
|
|||||||
cloudron build \
|
cloudron build \
|
||||||
--set-build-service builder.docker.due.ren \
|
--set-build-service builder.docker.due.ren \
|
||||||
--build-service-token e3265de06b1d0e7bb38400539012a8433a74c2c96a17955e \
|
--build-service-token e3265de06b1d0e7bb38400539012a8433a74c2c96a17955e \
|
||||||
--set-repository andreasdueren/ente-cloudron \
|
--set-repository andreasdueren/affine-cloudron \
|
||||||
--tag 0.1.0
|
--tag 0.25.3
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deployment Steps
|
## Deployment Steps
|
||||||
1. Remove any previous dev install of AFFiNE on the Cloudron (always reinstall from scratch).
|
1. Remove any previous dev install of AFFiNE on the Cloudron (always reinstall from scratch).
|
||||||
2. Install the freshly built image:
|
2. Install the freshly built image:
|
||||||
```bash
|
```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).
|
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
|
## Testing Checklist
|
||||||
- Open the app dashboard and ensure the landing page loads without 502/504 errors.
|
- 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.
|
- 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.
|
- 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
|
## Troubleshooting
|
||||||
- **Migrations hang**: restart the app; migrations rerun automatically before the server starts. Check PostgreSQL reachability via `cloudron exec --app <id> -- env | grep DATABASE_URL`.
|
- **Migrations hang**: restart the app; migrations rerun automatically before the server starts. Check PostgreSQL reachability via `cloudron exec --app <id> -- 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 <id> -- env | grep MAILER`).
|
- **Email failures**: verify the Cloudron sendmail addon is provisioned and that `MAILER_*` env vars show up inside the container (`cloudron exec --app <id> -- env | grep MAILER`).
|
||||||
- **Large uploads rejected**: adjust `client_max_body_size` in `nginx.conf` if you routinely exceed 200 MB assets, then rebuild.
|
- **Large uploads rejected**: adjust `client_max_body_size` in `nginx.conf` if you routinely exceed 200 MB assets, then rebuild.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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/"
|
COPY tmp_data/ "$APP_TMP_DIR/"
|
||||||
|
|
||||||
RUN chmod +x "$APP_CODE_DIR/start.sh" "$APP_CODE_DIR/run-affine.sh" && \
|
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
|
EXPOSE 3000
|
||||||
CMD ["/app/code/start.sh"]
|
CMD ["/app/code/start.sh"]
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ http {
|
|||||||
client_body_temp_path /run/nginx/body;
|
client_body_temp_path /run/nginx/body;
|
||||||
proxy_temp_path /run/nginx/proxy;
|
proxy_temp_path /run/nginx/proxy;
|
||||||
fastcgi_temp_path /run/nginx/fastcgi;
|
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" '
|
log_format cloudron '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
@@ -36,7 +38,7 @@ http {
|
|||||||
server {
|
server {
|
||||||
listen 3000;
|
listen 3000;
|
||||||
server_name _;
|
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-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|||||||
129
run-affine.sh
129
run-affine.sh
@@ -99,9 +99,138 @@ ensure_runtime_envs() {
|
|||||||
ensure_server_env
|
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"
|
log "Running AFFiNE pre-deployment migrations"
|
||||||
ensure_runtime_envs
|
ensure_runtime_envs
|
||||||
node ./scripts/self-host-predeploy.js
|
node ./scripts/self-host-predeploy.js
|
||||||
|
patch_upload_limits
|
||||||
|
grant_team_plan_features
|
||||||
|
|
||||||
log "Starting AFFiNE server"
|
log "Starting AFFiNE server"
|
||||||
exec node ./dist/main.js
|
exec node ./dist/main.js
|
||||||
|
|||||||
Reference in New Issue
Block a user