recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)

Sanitized single-commit public mirror of recipe-maintainer.
- Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders.
- Removed plans/ and planned-updates/ (deployment-planning docs) so no client/
  deployment domains appear in the public repo.
- All other secret stores were already gitignored.
- docs.coopcloud.tech retained as a submodule (public upstream).
This commit is contained in:
2026-06-16 20:18:24 +00:00
commit f283a371bb
253 changed files with 15975 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
name = "cryptpad"
[dependencies]
requires = ["authentik"]
[sso]
provider = "authentik"
setup_script = "setup/sso_integration.py"
+52
View File
@@ -0,0 +1,52 @@
# CryptPad — First-Time Setup
## Prerequisites
- DNS: `cryptpad.<domain_suffix>` must resolve to the server
- DNS: `sandbox.cryptpad.<domain_suffix>` must resolve to the server (sandbox iframe domain)
- **Authentik** must be deployed and running (dependency)
## Steps
1. **Create the app:**
```bash
abra app new cryptpad --server <SERVER> --domain cryptpad.<DOMAIN_SUFFIX> --no-input
```
2. **Generate secrets:**
```bash
abra app secret generate cryptpad.<DOMAIN_SUFFIX> --all -m --no-input
```
Save output to `recipe-info/testsecrets/cryptpad.<DOMAIN_SUFFIX>`.
3. **Configure SSO compose file:**
Edit the env file at `~/.abra/servers/<SERVER>/cryptpad.<DOMAIN_SUFFIX>.env` and set:
```
COMPOSE_FILE=compose.yml:compose.sso.yml
```
This enables the SSO overlay that adds OIDC support.
4. **Deploy:**
```bash
abra app deploy cryptpad.<DOMAIN_SUFFIX> --chaos --force --no-input
```
5. **Authentik SSO integration:**
```bash
python3 recipe-info/cryptpad/setup_authentik_integration.py
```
This creates an OAuth2 provider and application in Authentik, creates a test user, inserts the client secret, and updates CryptPad's env file with SSO settings.
6. **Redeploy with SSO settings:**
```bash
abra app deploy cryptpad.<DOMAIN_SUFFIX> --chaos --force --no-input
```
Wait ~2 minutes for the SSO plugin to install and CryptPad to rebuild.
7. **Verify:** curl `https://cryptpad.<DOMAIN_SUFFIX>` returns HTTP 200.
## Notes
- Credentials are saved to `recipe-info/cryptpad/authentik-test-credentials.<DOMAIN_SUFFIX>.toml`.
- OIDC test user: `testuser` / `testpass123`.
- The SSO plugin takes a couple of minutes to install on first deploy.
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Setup Authentik OIDC integration for CryptPad SSO.
Creates an OAuth2 provider, application, and test user in Authentik,
then updates the CryptPad env file with SSO settings and inserts
the client secret as a Docker secret.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from lib.abra import app_secret_insert
from lib.authentik import AuthentikAdmin
from lib.env import apply_env_overrides, get_abra_env_path, read_env_file
from lib.models import load_default_instance
from lib.secrets import load_secrets
# Configuration
PROVIDER_NAME = "cryptpad"
APP_SLUG = "cryptpad"
CLIENT_ID = "cryptpad"
TEST_USER = "testuser"
TEST_PASS = "testpass123"
TEST_EMAIL = f"{TEST_USER}@test.example.com"
def main():
inst = load_default_instance()
cpad_domain = inst.default_domain("cryptpad")
ak_domain = inst.default_domain("authentik")
ak_url = f"https://{ak_domain}"
# Get Authentik admin token from synced secrets
ak_secrets = load_secrets(ak_domain)
ak_token = ak_secrets["admin_token"]
ak = AuthentikAdmin(ak_url, ak_token)
# Resolve Authentik UUIDs
uuids = ak.resolve_uuids()
# Step 1: Create OAuth2 provider
provider_pk, client_secret = ak.ensure_provider(
PROVIDER_NAME, CLIENT_ID,
redirect_uris=[{
"matching_mode": "strict",
"url": f"https://{cpad_domain}/ssoauth",
}],
uuids=uuids,
)
# Step 2: Create application
ak.ensure_application("CryptPad", APP_SLUG, provider_pk,
f"https://{cpad_domain}")
# Step 3: Ensure test user with APP_PASSWORD
user_pk = ak.ensure_user(TEST_USER, TEST_EMAIL, TEST_PASS)
app_password = ak.ensure_app_password(user_pk)
# Step 4: Update CryptPad env with SSO settings
print("=== Update CryptPad SSO settings in env file ===", flush=True)
env_path = get_abra_env_path(inst.server, cpad_domain)
if not env_path.exists():
print(f" WARNING: CryptPad env file not found at {env_path}", flush=True)
print(f" Create the app first: abra app new cryptpad --server {inst.server} --domain {cpad_domain} --no-input", flush=True)
print(" Skipping env update", flush=True)
else:
oidc_url = f"{ak_url}/application/o/{APP_SLUG}"
overrides = {
"SSO_ENABLED": "true",
"SSO_PROVIDER_NAME": "Authentik",
"SSO_OIDC_URL": oidc_url,
"SSO_CLIENT_ID": CLIENT_ID,
}
# Remove old SSO_CLIENT_SECRET env var if present (now a Docker secret)
env_data = read_env_file(env_path)
if "SSO_CLIENT_SECRET" in env_data:
print(" Removing old SSO_CLIENT_SECRET env var (now a Docker secret)", flush=True)
# Read file lines and filter out SSO_CLIENT_SECRET
with open(env_path) as f:
lines = f.readlines()
with open(env_path, "w") as f:
for line in lines:
if not line.strip().startswith("SSO_CLIENT_SECRET="):
f.write(line)
apply_env_overrides(env_path, overrides)
# Ensure SSO_CLIENT_SECRET_VERSION exists
env_data = read_env_file(env_path)
if "SSO_CLIENT_SECRET_VERSION" not in env_data:
with open(env_path, "a") as f:
f.write("SSO_CLIENT_SECRET_VERSION=v1\n")
# Insert client secret as Docker secret if not already present
try:
existing = load_secrets(cpad_domain).get("sso_client_s")
except Exception:
existing = None
if existing:
print(" Secret sso_client_s already exists in local testsecrets, skipping insert", flush=True)
else:
env_data = read_env_file(env_path)
current_version = env_data.get("SSO_CLIENT_SECRET_VERSION", "v1")
print(f" Inserting sso_client_s {current_version} ...", flush=True)
app_secret_insert(cpad_domain, "sso_client_s", current_version,
client_secret)
print(f" Inserted sso_client_s {current_version}", flush=True)
# Step 5: Write credentials file
script_dir = os.path.dirname(os.path.abspath(__file__))
creds_file = os.path.join(script_dir, f"authentik-test-credentials.{inst.domain_suffix}.toml")
print(f"=== Write credentials to {creds_file} ===", flush=True)
with open(creds_file, "w") as f:
f.write(f'# Authentik OIDC credentials for CryptPad SSO test instance\n')
f.write(f'#\n')
f.write(f'# Authentik instance: {ak_domain}\n')
f.write(f'# Application slug: {APP_SLUG}\n')
f.write(f'# Created by: setup_authentik_integration.py\n')
f.write(f'\n')
f.write(f'# Authentik admin\n')
f.write(f'ak_token = "{ak_token}"\n')
f.write(f'\n')
f.write(f'# OIDC provider\n')
f.write(f'ak_app_slug = "{APP_SLUG}"\n')
f.write(f'ak_client_id = "{CLIENT_ID}"\n')
f.write(f'ak_client_secret = "{client_secret}"\n')
f.write(f'\n')
f.write(f'# Authentik OIDC endpoints\n')
f.write(f'ak_token_endpoint = "https://{ak_domain}/application/o/token/"\n')
f.write(f'ak_userinfo_endpoint = "https://{ak_domain}/application/o/userinfo/"\n')
f.write(f'ak_discovery_endpoint = "https://{ak_domain}/application/o/{APP_SLUG}/.well-known/openid-configuration"\n')
f.write(f'\n')
f.write(f'# Test user (password for browser login, app_password for password grant)\n')
f.write(f'ak_test_user = "{TEST_USER}"\n')
f.write(f'ak_test_pass = "{TEST_PASS}"\n')
f.write(f'ak_test_app_password = "{app_password}"\n')
f.write(f'ak_test_email = "{TEST_EMAIL}"\n')
f.write(f'\n')
f.write(f'# CryptPad instance\n')
f.write(f'cpad_domain = "{cpad_domain}"\n')
print(f" Written to {creds_file}", flush=True)
print("", flush=True)
print("=== Authentik OIDC integration setup for CryptPad complete ===", flush=True)
print("", flush=True)
print("Next steps:", flush=True)
print(f" 1. Redeploy CryptPad: abra app deploy {cpad_domain} --chaos --force --no-input", flush=True)
print(f" 2. Wait ~2min for SSO plugin to install and CryptPad to rebuild", flush=True)
print(f" 3. Run OIDC test: python3 recipe-info/cryptpad/tests/oidc_login.py", flush=True)
if __name__ == "__main__":
main()
+91
View File
@@ -0,0 +1,91 @@
# CryptPad Tests
## Target
- **URL:** https://cryptpad.<DOMAIN_SUFFIX>
- **Sandbox URL:** https://sandbox.cryptpad.<DOMAIN_SUFFIX>
## Dependencies
- **Authentik** (`authentik.<DOMAIN_SUFFIX>`) — required for SSO/OIDC testing
## Test Setup
Before running all tests, the following must be in place:
### 1. Deploy authentik
```bash
abra app deploy authentik.<DOMAIN_SUFFIX> --chaos --force --no-input
```
### 2. Deploy CryptPad
```bash
abra app deploy cryptpad.<DOMAIN_SUFFIX> --chaos --force --no-input
```
### 3. Run the Authentik integration setup
```bash
python3 recipe-info/cryptpad/setup_authentik_integration.py
```
This configures authentik as the OIDC provider for CryptPad:
1. Creates an OAuth2 provider (`cryptpad`) via the authentik REST API
2. Creates an Application linked to the provider
3. Creates a test user (`testuser` / `testpass123`) with an APP_PASSWORD token
4. Writes OIDC env vars to the CryptPad instance env file (enables `compose.sso.yml`)
5. Writes credentials to `authentik-test-credentials.<DOMAIN_SUFFIX>.toml`
**Important:** The APP_PASSWORD token becomes invalid if authentik is redeployed. If the `oidc_login.py` test fails with "invalid, expired, revoked" token errors, re-run this setup script and redeploy CryptPad.
### 4. Redeploy CryptPad with SSO config
```bash
abra app deploy cryptpad.<DOMAIN_SUFFIX> --chaos --force --no-input
```
Wait ~2 minutes for the SSO plugin to install and CryptPad to rebuild.
## Test Instance SSO Configuration
The test instance has SSO enabled via `compose.sso.yml`. The instance env file includes:
```
COMPOSE_FILE="compose.yml:compose.sso.yml"
```
Note: SSO is **not** enabled by default in `.env.sample`. The test instance has it enabled explicitly to test the SSO integration. If you need to reset the test instance without SSO, change `COMPOSE_FILE` to just `"compose.yml"` and redeploy.
## Automated Tests
- `tests/health_check.py` — Confirms the instance is reachable and returns HTTP 200.
- `tests/oidc_login.py` — Tests SSO/OIDC integration with Authentik. Checks OIDC discovery, APP_PASSWORD token grant, and `/ssoauth` endpoint.
### Credentials
| Key | Description |
|-----|-------------|
| `ak_client_id` / `ak_client_secret` | OIDC client ID and secret |
| `ak_test_user` / `ak_test_pass` | Test user credentials (password for browser login) |
| `ak_test_app_password` | APP_PASSWORD token for password grant (authentik requires this instead of regular passwords) |
| `ak_test_email` | Test user email |
| `ak_discovery_endpoint` | Authentik OIDC discovery URL |
Stored in `authentik-test-credentials.<DOMAIN_SUFFIX>.toml`.
## Manual Verification
1. Open https://cryptpad.<DOMAIN_SUFFIX> in a browser.
2. Confirm the CryptPad landing page loads without errors (not a white screen).
3. Verify the sandbox domain https://sandbox.cryptpad.<DOMAIN_SUFFIX> is reachable.
4. Register a user account and confirm it succeeds.
5. Create a pad and verify real-time editing works.
### SSO Manual Verification
6. Confirm the CryptPad login page shows an SSO login button (labelled "Authentik").
7. Click the SSO login button — it should redirect to Authentik.
8. Log in with `testuser` / `testpass123` on Authentik.
9. After authentication, you should be redirected back to CryptPad and logged in.
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""Health check for CryptPad."""
import argparse
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from utils.tests.helpers import http_get, resolve_domain
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN'))
args = parser.parse_args()
domain = args.domain or resolve_domain('cryptpad')
url = f"https://{domain}"
print(f"Checking CryptPad at {url} ...")
status, _ = http_get(url)
if status == 200:
print(f"PASS: CryptPad returned HTTP {status}")
else:
print(f"FAIL: CryptPad returned HTTP {status} (expected 200)")
sys.exit(1)
if __name__ == '__main__':
main()
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""CryptPad SSO/OIDC integration test."""
import argparse
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from utils.tests.helpers import (
http_get, http_post, load_toml_credentials, resolve_domain,
)
def main():
if os.environ.get('SKIP_INTEGRATION') == '1':
print("SKIP: OIDC integration test (SKIP_INTEGRATION=1)")
return
parser = argparse.ArgumentParser()
parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN'))
args = parser.parse_args()
domain = args.domain or resolve_domain('cryptpad')
url = f"https://{domain}"
recipe_dir = os.path.join(os.path.dirname(__file__), '..')
creds = load_toml_credentials(recipe_dir, 'authentik')
if creds is None:
print("FAIL: Credentials file not found: authentik-test-credentials.<domain_suffix>.toml")
print("Run setup_authentik_integration.py first.")
sys.exit(1)
print("=== CryptPad SSO/OIDC Integration Test ===")
print()
# Step 1: Check CryptPad is deployed
print("Step 1: Checking CryptPad is deployed ...")
status, _ = http_get(url)
if status == 0:
print(f" FAIL: CryptPad is not reachable at {url}")
sys.exit(1)
elif status >= 500:
print(f" FAIL: CryptPad returned HTTP {status}")
sys.exit(1)
print(f" OK: CryptPad is reachable (HTTP {status})")
# Step 2: Verify Authentik OIDC discovery
print("Step 2: Checking Authentik OIDC discovery ...")
discovery_url = creds["ak_discovery_endpoint"]
status, _ = http_get(discovery_url)
if status != 200:
print(f" FAIL: OIDC discovery returned HTTP {status}")
print(f" URL: {discovery_url}")
sys.exit(1)
print(f" PASS: OIDC discovery endpoint OK (app '{creds['ak_app_slug']}')")
# Step 3: Obtain token from Authentik
print("Step 3: Obtaining token from Authentik for test user ...")
print(" Using APP_PASSWORD for password grant (authentik requirement)")
status, data = http_post(
creds["ak_token_endpoint"],
data={
"grant_type": "password",
"client_id": creds["ak_client_id"],
"client_secret": creds["ak_client_secret"],
"username": creds["ak_test_user"],
"password": creds["ak_test_app_password"],
"scope": "openid email profile",
},
content_type="application/x-www-form-urlencoded",
)
access_token = (data or {}).get("access_token", "")
if not access_token:
error = (data or {}).get("error_description", (data or {}).get("error", "unknown"))
print(f" FAIL: Token request failed: {error}")
sys.exit(1)
print(f" PASS: Got access token ({len(access_token)} chars)")
# Step 4: Verify CryptPad's /ssoauth endpoint (SSO plugin loaded)
print("Step 4: Checking CryptPad /ssoauth endpoint ...")
status, _ = http_get(f"{url}/ssoauth")
if status == 404:
print(" FAIL: /ssoauth returned 404 — SSO plugin may not be loaded")
sys.exit(1)
print(f" PASS: /ssoauth endpoint exists (HTTP {status})")
print()
print("PASS: CryptPad SSO/OIDC integration test passed")
print(" Authentik OIDC discovery OK, token grant OK, /ssoauth endpoint exists.")
if __name__ == '__main__':
main()
+14
View File
@@ -0,0 +1,14 @@
# CryptPad Upstream
## Main Project
- **Repository:** https://github.com/cryptpad/cryptpad
- **Releases:** https://github.com/cryptpad/cryptpad/releases
- **Website:** https://cryptpad.org
## Images
| Service | Image | Release Notes |
|---------|-------|---------------|
| app | `cryptpad/cryptpad` | https://github.com/cryptpad/cryptpad/releases |
| web | `nginx` | https://nginx.org/en/CHANGES |