Compare commits

...

11 Commits

Author SHA1 Message Date
Codex
63a730a940 Enhance mail setup and team plan defaults 2025-11-14 08:05:02 -06:00
Codex
17bb921941 Add icon and wire Cloudron email addon 2025-11-13 21:09:52 -06:00
Codex
21a9ac6c86 Revert to python config update 2025-11-12 12:03:46 -06:00
Codex
1f3505f132 Update redis config via jq 2025-11-12 11:51:08 -06:00
Codex
b8d38f52a2 Override redis creds with addon provided vars 2025-11-12 11:38:27 -06:00
Codex
294a84a414 Leverage Cloudron redis addon env vars 2025-11-12 11:23:05 -06:00
Codex
0bda151abe Guard redis config defaults 2025-11-12 11:09:54 -06:00
Codex
0877db4c36 Persist redis config into config.json 2025-11-12 10:56:39 -06:00
Codex
aee56753d5 Record redis vars from local values 2025-11-12 10:44:10 -06:00
Codex
e758749bd6 Record env vars incrementally 2025-11-12 10:30:45 -06:00
Codex
7a253e0f0c Use printenv when writing runtime env 2025-11-12 10:17:13 -06:00
7 changed files with 306 additions and 66 deletions

View File

@@ -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 200MB assets, then rebuild. - **Large uploads rejected**: adjust `client_max_body_size` in `nginx.conf` if you routinely exceed 200MB assets, then rebuild.

View File

@@ -5,8 +5,9 @@
"description": "Next-gen knowledge base that blends docs, whiteboards, and databases for self-hosted teams.", "description": "Next-gen knowledge base that blends docs, whiteboards, and databases for self-hosted teams.",
"website": "https://affine.pro", "website": "https://affine.pro",
"contactEmail": "support@affine.pro", "contactEmail": "support@affine.pro",
"version": "0.1.6", "version": "0.25.3",
"changelog": "Initial Cloudron packaging", "changelog": "Upgrade upstream AFFiNE runtime to v0.25.3 and keep Cloudron OIDC wiring",
"icon": "file://icon.png",
"manifestVersion": 2, "manifestVersion": 2,
"minBoxVersion": "7.0.0", "minBoxVersion": "7.0.0",
"httpPort": 3000, "httpPort": 3000,
@@ -16,10 +17,9 @@
"redis": {}, "redis": {},
"sendmail": {}, "sendmail": {},
"oidc": { "oidc": {
"redirectUris": [ "loginRedirectUri": "/oauth/callback",
"/api/v1/session/callback" "logoutRedirectUri": "/",
], "tokenSignatureAlgorithm": "RS256"
"loginRedirectUri": "/api/v1/session/callback"
} }
}, },
"memoryLimit": 2147483648, "memoryLimit": 2147483648,

View File

@@ -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"]

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -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;

View File

@@ -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

207
start.sh
View File

@@ -15,6 +15,14 @@ log() {
printf '[%s] %s\n' "$(date --iso-8601=seconds)" "$*" printf '[%s] %s\n' "$(date --iso-8601=seconds)" "$*"
} }
record_env_var() {
local name="$1"
local value="$2"
if [ -n "$value" ]; then
printf '%s=%q\n' "$name" "$value" >> "$ENV_EXPORT_FILE"
fi
}
require_env() { require_env() {
local var_name="$1" local var_name="$1"
if [ -z "${!var_name:-}" ]; then if [ -z "${!var_name:-}" ]; then
@@ -26,7 +34,8 @@ require_env() {
prepare_data_dirs() { prepare_data_dirs() {
log "Preparing persistent directories" log "Preparing persistent directories"
mkdir -p "$APP_DATA_DIR/config" "$APP_DATA_DIR/storage" "$APP_DATA_DIR/logs" "$APP_RUNTIME_DIR" "$APP_HOME_DIR" "$AFFINE_HOME" mkdir -p "$APP_DATA_DIR/config" "$APP_DATA_DIR/storage" "$APP_DATA_DIR/logs" "$APP_RUNTIME_DIR" "$APP_HOME_DIR" "$AFFINE_HOME"
mkdir -p /run/nginx/body /run/nginx/proxy /run/nginx/fastcgi mkdir -p /run/nginx/body /run/nginx/proxy /run/nginx/fastcgi /run/nginx/uwsgi /run/nginx/scgi
: > "$ENV_EXPORT_FILE"
if [ ! -f "$APP_DATA_DIR/config/config.json" ]; then if [ ! -f "$APP_DATA_DIR/config/config.json" ]; then
log "Seeding default configuration" log "Seeding default configuration"
@@ -48,6 +57,19 @@ prepare_data_dirs() {
chown -R cloudron:cloudron "$APP_DATA_DIR" "$APP_RUNTIME_DIR" "$APP_HOME_DIR" chown -R cloudron:cloudron "$APP_DATA_DIR" "$APP_RUNTIME_DIR" "$APP_HOME_DIR"
} }
prepare_runtime_build_dir() {
local source_dir="$APP_BUILD_DIR"
local runtime_build_dir="$APP_RUNTIME_DIR/affine-build"
log "Syncing AFFiNE runtime into $runtime_build_dir"
rm -rf "$runtime_build_dir"
mkdir -p "$runtime_build_dir"
cp -a "$source_dir/." "$runtime_build_dir/"
chown -R cloudron:cloudron "$runtime_build_dir"
APP_BUILD_DIR="$runtime_build_dir"
export APP_BUILD_DIR
record_env_var APP_BUILD_DIR "$APP_BUILD_DIR"
}
configure_database() { configure_database() {
require_env CLOUDRON_POSTGRESQL_URL require_env CLOUDRON_POSTGRESQL_URL
local db_url="$CLOUDRON_POSTGRESQL_URL" local db_url="$CLOUDRON_POSTGRESQL_URL"
@@ -55,6 +77,7 @@ configure_database() {
db_url="postgresql://${db_url#postgres://}" db_url="postgresql://${db_url#postgres://}"
fi fi
export DATABASE_URL="$db_url" export DATABASE_URL="$db_url"
record_env_var DATABASE_URL "$db_url"
log "Configured PostgreSQL endpoint" log "Configured PostgreSQL endpoint"
} }
@@ -65,18 +88,44 @@ configure_redis() {
import os import os
from urllib.parse import urlparse from urllib.parse import urlparse
url = os.environ.get('CLOUDRON_REDIS_URL') url = os.environ.get('CLOUDRON_REDIS_URL')
if not url: parsed = urlparse(url) if url else None
raise SystemExit('redis url missing') host = os.environ.get('CLOUDRON_REDIS_HOST')
parsed = urlparse(url) port = os.environ.get('CLOUDRON_REDIS_PORT')
host = parsed.hostname or 'localhost' password = os.environ.get('CLOUDRON_REDIS_PASSWORD')
port = parsed.port or 6379 username = os.environ.get('CLOUDRON_REDIS_USERNAME')
password = parsed.password or '' db = os.environ.get('CLOUDRON_REDIS_DB')
db = (parsed.path or '/0').lstrip('/') or '0' if not host and parsed:
username = parsed.username or '' host = parsed.hostname or 'localhost'
if not port and parsed:
port = parsed.port or 6379
if not password and parsed:
password = parsed.password or ''
if not db and parsed:
db = (parsed.path or '/0').lstrip('/') or '0'
if username is None:
username = parsed.username if parsed and parsed.username else 'default'
host = host or 'localhost'
port = port or 6379
password = password or ''
db = db or '0'
print(f"{host}\n{port}\n{password}\n{db}\n{username}") print(f"{host}\n{port}\n{password}\n{db}\n{username}")
PY PY
) )
IFS=$'\n' read -r host port password db username <<<"$redis_info" IFS=$'\n' read -r host port password db username <<<"$redis_info"
if [ -n "${CLOUDRON_REDIS_HOST:-}" ]; then
host="$CLOUDRON_REDIS_HOST"
fi
if [ -n "${CLOUDRON_REDIS_PORT:-}" ]; then
port="$CLOUDRON_REDIS_PORT"
fi
if [ -n "${CLOUDRON_REDIS_PASSWORD:-}" ]; then
password="$CLOUDRON_REDIS_PASSWORD"
fi
if [ -n "${CLOUDRON_REDIS_USERNAME:-}" ]; then
username="$CLOUDRON_REDIS_USERNAME"
elif [ -z "$username" ]; then
username="default"
fi
export REDIS_SERVER_HOST="$host" export REDIS_SERVER_HOST="$host"
export REDIS_SERVER_PORT="$port" export REDIS_SERVER_PORT="$port"
export REDIS_SERVER_PASSWORD="$password" export REDIS_SERVER_PASSWORD="$password"
@@ -84,20 +133,79 @@ PY
export REDIS_SERVER_USERNAME="$username" export REDIS_SERVER_USERNAME="$username"
export REDIS_URL="$CLOUDRON_REDIS_URL" export REDIS_URL="$CLOUDRON_REDIS_URL"
export REDIS_SERVER_URL="$CLOUDRON_REDIS_URL" export REDIS_SERVER_URL="$CLOUDRON_REDIS_URL"
record_env_var REDIS_SERVER_HOST "$host"
record_env_var REDIS_SERVER_PORT "$port"
record_env_var REDIS_SERVER_PASSWORD "$password"
record_env_var REDIS_SERVER_DATABASE "$db"
record_env_var REDIS_SERVER_USERNAME "$username"
record_env_var REDIS_URL "$CLOUDRON_REDIS_URL"
record_env_var REDIS_SERVER_URL "$CLOUDRON_REDIS_URL"
python3 - <<'PY'
import json
import os
from pathlib import Path
config_path = Path(os.environ['APP_DATA_DIR']) / 'config' / 'config.json'
data = json.loads(config_path.read_text())
redis = data.setdefault('redis', {})
redis['host'] = os.environ.get('REDIS_SERVER_HOST', '')
redis['port'] = int(os.environ.get('REDIS_SERVER_PORT') or 6379)
redis['password'] = os.environ.get('REDIS_SERVER_PASSWORD', '')
redis['username'] = os.environ.get('REDIS_SERVER_USERNAME', '')
redis['db'] = int(os.environ.get('REDIS_SERVER_DATABASE') or 0)
config_path.write_text(json.dumps(data, indent=2))
PY
log "Configured Redis endpoint" log "Configured Redis endpoint"
} }
configure_mail() { configure_mail() {
if [ -z "${CLOUDRON_MAIL_SMTP_SERVER:-}" ]; then local host=""
log "Cloudron mail addon not configured, skipping SMTP setup" local port=""
local user=""
local password=""
local sender=""
local ignore_tls="false"
if [ -n "${CLOUDRON_EMAIL_SMTP_SERVER:-}" ]; then
host="$CLOUDRON_EMAIL_SMTP_SERVER"
port="${CLOUDRON_EMAIL_SMTPS_PORT:-${CLOUDRON_EMAIL_SMTP_PORT:-587}}"
user="${CLOUDRON_EMAIL_SMTP_USERNAME:-}"
password="${CLOUDRON_EMAIL_SMTP_PASSWORD:-}"
sender="${CLOUDRON_EMAIL_FROM:-AFFiNE <no-reply@cloudron.local>}"
ignore_tls="${MAILER_IGNORE_TLS:-true}"
log "Configuring SMTP using Cloudron email addon"
elif [ -n "${CLOUDRON_MAIL_SMTP_SERVER:-}" ]; then
host="$CLOUDRON_MAIL_SMTP_SERVER"
port="${CLOUDRON_MAIL_SMTP_PORT:-587}"
user="${CLOUDRON_MAIL_SMTP_USERNAME:-}"
password="${CLOUDRON_MAIL_SMTP_PASSWORD:-}"
sender="${CLOUDRON_MAIL_FROM:-AFFiNE <no-reply@cloudron.local>}"
ignore_tls="${MAILER_IGNORE_TLS:-false}"
if [ -n "${CLOUDRON_MAIL_SMTP_SECURE:-}" ]; then
case "${CLOUDRON_MAIL_SMTP_SECURE,,}" in
true|1|yes) port="${CLOUDRON_MAIL_SMTP_PORT:-465}" ;;
esac
fi
log "Configuring SMTP using Cloudron sendmail addon"
else
log "Cloudron mail/email addon not configured, skipping SMTP setup"
return return
fi fi
export MAILER_HOST="$CLOUDRON_MAIL_SMTP_SERVER"
export MAILER_PORT="${CLOUDRON_MAIL_SMTP_PORT:-587}" export MAILER_HOST="$host"
export MAILER_USER="${CLOUDRON_MAIL_SMTP_USERNAME:-}" export MAILER_PORT="$port"
export MAILER_PASSWORD="${CLOUDRON_MAIL_SMTP_PASSWORD:-}" export MAILER_USER="$user"
export MAILER_SENDER="${CLOUDRON_MAIL_FROM:-AFFiNE <no-reply@cloudron.local>}" export MAILER_PASSWORD="$password"
export MAILER_SENDER="${sender:-AFFiNE <no-reply@cloudron.local>}"
export MAILER_SERVERNAME="${MAILER_SERVERNAME:-AFFiNE Server}" export MAILER_SERVERNAME="${MAILER_SERVERNAME:-AFFiNE Server}"
export MAILER_IGNORE_TLS="$ignore_tls"
record_env_var MAILER_HOST "$MAILER_HOST"
record_env_var MAILER_PORT "$MAILER_PORT"
record_env_var MAILER_USER "$MAILER_USER"
record_env_var MAILER_PASSWORD "$MAILER_PASSWORD"
record_env_var MAILER_SENDER "$MAILER_SENDER"
record_env_var MAILER_SERVERNAME "$MAILER_SERVERNAME"
record_env_var MAILER_IGNORE_TLS "$MAILER_IGNORE_TLS"
log "Configured SMTP relay" log "Configured SMTP relay"
} }
@@ -121,37 +229,10 @@ PY
fi fi
fi fi
export AFFINE_INDEXER_ENABLED=${AFFINE_INDEXER_ENABLED:-false} export AFFINE_INDEXER_ENABLED=${AFFINE_INDEXER_ENABLED:-false}
} record_env_var AFFINE_SERVER_EXTERNAL_URL "${AFFINE_SERVER_EXTERNAL_URL:-}"
record_env_var AFFINE_SERVER_HOST "${AFFINE_SERVER_HOST:-}"
write_runtime_env() { record_env_var AFFINE_SERVER_HTTPS "${AFFINE_SERVER_HTTPS:-}"
: > "$ENV_EXPORT_FILE" record_env_var AFFINE_INDEXER_ENABLED "$AFFINE_INDEXER_ENABLED"
local vars=(
DATABASE_URL
REDIS_SERVER_HOST
REDIS_SERVER_PORT
REDIS_SERVER_PASSWORD
REDIS_SERVER_DATABASE
REDIS_SERVER_USERNAME
REDIS_URL
REDIS_SERVER_URL
MAILER_HOST
MAILER_PORT
MAILER_USER
MAILER_PASSWORD
MAILER_SENDER
MAILER_SERVERNAME
AFFINE_SERVER_EXTERNAL_URL
AFFINE_SERVER_HOST
AFFINE_SERVER_HTTPS
AFFINE_INDEXER_ENABLED
)
local var value
for var in "${vars[@]}"; do
value="${!var-}"
if [ -n "$value" ]; then
printf '%s=%q\n' "$var" "$value" >> "$ENV_EXPORT_FILE"
fi
done
} }
configure_auth() { configure_auth() {
@@ -160,6 +241,7 @@ configure_auth() {
python3 - <<'PY' python3 - <<'PY'
import json import json
import os import os
import re
from pathlib import Path from pathlib import Path
config_path = Path(os.environ['APP_DATA_DIR']) / 'config' / 'config.json' config_path = Path(os.environ['APP_DATA_DIR']) / 'config' / 'config.json'
data = json.loads(config_path.read_text()) data = json.loads(config_path.read_text())
@@ -168,9 +250,34 @@ providers = auth.setdefault('providers', {})
oidc = providers.setdefault('oidc', {}) oidc = providers.setdefault('oidc', {})
oidc['clientId'] = os.environ.get('CLOUDRON_OIDC_CLIENT_ID', '') oidc['clientId'] = os.environ.get('CLOUDRON_OIDC_CLIENT_ID', '')
oidc['clientSecret'] = os.environ.get('CLOUDRON_OIDC_CLIENT_SECRET', '') oidc['clientSecret'] = os.environ.get('CLOUDRON_OIDC_CLIENT_SECRET', '')
oidc['issuer'] = os.environ.get('CLOUDRON_OIDC_ISSUER') or os.environ.get('CLOUDRON_OIDC_DISCOVERY_URL', '') issuer = os.environ.get('CLOUDRON_OIDC_ISSUER') or ''
discovery = os.environ.get('CLOUDRON_OIDC_DISCOVERY_URL') or ''
resolved_issuer = issuer
if not resolved_issuer and discovery:
resolved_issuer = re.sub(r'/\.well-known.*$', '', discovery)
if not resolved_issuer:
resolved_issuer = discovery
oidc['issuer'] = resolved_issuer
default_scope = os.environ.get('AFFINE_OIDC_SCOPE', 'openid profile email')
default_claims = {
'claim_id': os.environ.get('AFFINE_OIDC_CLAIM_ID', 'preferred_username'),
'claim_email': os.environ.get('AFFINE_OIDC_CLAIM_EMAIL', 'email'),
'claim_name': os.environ.get('AFFINE_OIDC_CLAIM_NAME', 'name'),
}
args = oidc.setdefault('args', {}) args = oidc.setdefault('args', {})
args.setdefault('scope', 'openid profile email') args['scope'] = default_scope
for key, value in default_claims.items():
args.setdefault(key, value)
oauth = data.setdefault('oauth', {})
oauth_providers = oauth.setdefault('providers', {})
oauth_oidc = oauth_providers.setdefault('oidc', {})
oauth_oidc['clientId'] = oidc['clientId']
oauth_oidc['clientSecret'] = oidc['clientSecret']
oauth_oidc['issuer'] = resolved_issuer
oauth_args = oauth_oidc.setdefault('args', {})
oauth_args['scope'] = default_scope
for key, value in default_claims.items():
oauth_args.setdefault(key, value)
config_path.write_text(json.dumps(data, indent=2)) config_path.write_text(json.dumps(data, indent=2))
PY PY
log "Enabled Cloudron OIDC for AFFiNE" log "Enabled Cloudron OIDC for AFFiNE"
@@ -199,13 +306,13 @@ PY
main() { main() {
export HOME="$APP_HOME_DIR" export HOME="$APP_HOME_DIR"
prepare_data_dirs prepare_data_dirs
prepare_runtime_build_dir
configure_database configure_database
configure_redis configure_redis
configure_mail configure_mail
configure_server_metadata configure_server_metadata
update_server_config update_server_config
configure_auth configure_auth
write_runtime_env
chown -R cloudron:cloudron "$APP_DATA_DIR" "$APP_HOME_DIR" chown -R cloudron:cloudron "$APP_DATA_DIR" "$APP_HOME_DIR"
log "Starting supervisor" log "Starting supervisor"
exec /usr/bin/supervisord -c "$APP_CODE_DIR/supervisord.conf" exec /usr/bin/supervisord -c "$APP_CODE_DIR/supervisord.conf"