Compare commits
49 Commits
8117fb8175
...
6e77c1ada5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e77c1ada5 | ||
|
|
5c6f8f3a2b | ||
|
|
dc54a10b51 | ||
|
|
c223e2b5e0 | ||
|
|
63dcd1b82b | ||
|
|
f445f50265 | ||
|
|
0673442451 | ||
|
|
4d4b48cf72 | ||
|
|
cb5f3d66ff | ||
|
|
be035c7a85 | ||
|
|
0554916818 | ||
|
|
2b1db70090 | ||
|
|
da8dfe9196 | ||
|
|
0449272102 | ||
|
|
a2852dab0a | ||
|
|
1d57e26dae | ||
|
|
274d7a8fb9 | ||
|
|
cd37c07a33 | ||
|
|
bd1116048d | ||
|
|
c2efed5937 | ||
|
|
424e57baec | ||
|
|
b1295223b1 | ||
|
|
a3dafa925b | ||
|
|
8f8d3f7680 | ||
|
|
110eca06ce | ||
|
|
5e98b3fce6 | ||
|
|
855fb03e17 | ||
|
|
a2e3031b84 | ||
|
|
2ebbc65dd3 | ||
|
|
2d0292a69b | ||
|
|
d7a11b380c | ||
|
|
3e3b048f5b | ||
|
|
347c4e0c90 | ||
|
|
413800cbcb | ||
|
|
f9ef89f00d | ||
|
|
761712ba71 | ||
|
|
777bab792c | ||
|
|
b42f048f5c | ||
|
|
8a2539e527 | ||
|
|
1d63970113 | ||
|
|
a25366daf7 | ||
|
|
aee2e93803 | ||
|
|
294cf59dd0 | ||
|
|
7b41ed5148 | ||
|
|
78ac8d0cbb | ||
|
|
c2493dc58a | ||
|
|
b95586e74b | ||
|
|
319ec01f0c | ||
|
|
4eb722f393 |
254
ADDING-APPS-NOTES.md
Normal file
254
ADDING-APPS-NOTES.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# Adding Apps - Notes & Documentation Improvements
|
||||||
|
|
||||||
|
## Documentation Improvement Candidates
|
||||||
|
|
||||||
|
Issues and gaps discovered while adding apps. These should be folded back into ADDING-APPS.md.
|
||||||
|
|
||||||
|
### 1. Node.js app memory requirements
|
||||||
|
Node.js apps (NocoDB, Outline, etc.) need more than 512Mi memory. NocoDB crashed with OOM at 512Mi.
|
||||||
|
- Recommended: 1Gi limit, 512Mi request for Node.js apps
|
||||||
|
- Set `NODE_OPTIONS=--max-old-space-size=768` to keep heap within the limit
|
||||||
|
- **Doc suggestion**: Add a section in ADDING-APPS.md about runtime-specific resource defaults:
|
||||||
|
- Node.js apps: 1Gi limit, 512Mi request; add NODE_OPTIONS env var
|
||||||
|
- JVM apps (Java/Kotlin): 1Gi+ limit
|
||||||
|
- Python/Ruby apps: 512Mi typically sufficient
|
||||||
|
- Go/Rust apps: 256-512Mi typically sufficient
|
||||||
|
|
||||||
|
### 2. db-init-job is repeated boilerplate
|
||||||
|
The db-init-job.yaml for PostgreSQL apps is nearly identical across all apps. Only the app name, namespace, and secret name change.
|
||||||
|
- **Doc suggestion**: Provide a copy-paste template for the standard PostgreSQL db-init-job with placeholders marked clearly.
|
||||||
|
|
||||||
|
### 4. Apps requiring hex-encoded secrets
|
||||||
|
Some apps (Outline) require secrets in specific formats (e.g., exactly 64 hex chars). The default Wild Cloud `GenerateSecret` produces alphanumeric (not hex) strings.
|
||||||
|
- **Solution**: Use `crypto.SHA256` gomplate function in the manifest `default` field to generate a valid 64-char hex string from another secret:
|
||||||
|
```yaml
|
||||||
|
defaultSecrets:
|
||||||
|
- key: utilsSecret
|
||||||
|
- key: secretKey
|
||||||
|
default: '{{ crypto.SHA256 .secrets.utilsSecret }}'
|
||||||
|
```
|
||||||
|
- **Doc suggestion**: Add this pattern to ADDING-APPS.md with examples of apps that need it. Note that the order of `defaultSecrets` matters — referenced secrets must come first.
|
||||||
|
|
||||||
|
### 6. Always use `?sslmode=disable` in PostgreSQL connection strings
|
||||||
|
The Wild Cloud internal PostgreSQL does not have SSL enabled. Apps that construct PostgreSQL connection strings must include `?sslmode=disable`, and deployments should set `PGSSLMODE=disable`. Without it, apps crash with "The server does not support SSL connections."
|
||||||
|
- Already done correctly in: listmonk, gitea, discourse
|
||||||
|
- Missing from: outline (fixed), any new app
|
||||||
|
- **Doc suggestion**: Add to the ADDING-APPS.md dbUrl template examples: always append `?sslmode=disable` to postgres connection strings.
|
||||||
|
|
||||||
|
### 5. Re-running `wild app add` is required after template changes
|
||||||
|
When you modify files in wild-directory (e.g., fix a template), you must re-run `wild app add <app>` before deploying. The compiled templates in the instance `apps/` directory are NOT automatically updated when wild-directory changes. Only then will `wild app deploy` use the updated templates.
|
||||||
|
- **Doc suggestion**: Add to ADDING-APPS.md: "After modifying templates in wild-directory, always re-run `wild app add <app>` to recompile before deploying."
|
||||||
|
|
||||||
|
### 7. WriteFreely requires root user (jrasanen/writefreely image)
|
||||||
|
The `jrasanen/writefreely` image startup script creates `config.ini` in `/writefreely/` which is a root-owned directory. The image's default user is non-root, causing "Permission denied" errors at startup.
|
||||||
|
- **Fix**: Set `runAsUser: 0` and `runAsNonRoot: false` in the pod securityContext
|
||||||
|
- Still safe with `allowPrivilegeEscalation: false` and `capabilities.drop: ALL`
|
||||||
|
- **Doc suggestion**: Note that some community images require root. When an image fails with permission denied on startup, check if it needs root and add the security context override with capabilities dropped.
|
||||||
|
|
||||||
|
### 8. Redis URL must include password for authenticated Redis
|
||||||
|
Wild Cloud's Redis requires authentication. Apps that take a Redis URL must include the password in the URL: `redis://:password@host:6379`.
|
||||||
|
- **Pattern**: Add `redis.password` to `requiredSecrets`, then use K8s env var expansion:
|
||||||
|
```yaml
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: myapp-secrets
|
||||||
|
key: redis.password
|
||||||
|
- name: REDIS_URL
|
||||||
|
value: redis://:$(REDIS_PASSWORD)@{{ .redis.host }}:6379
|
||||||
|
```
|
||||||
|
- The `$(VAR_NAME)` syntax is supported by Kubernetes in container env values (evaluated at pod start)
|
||||||
|
- **Doc suggestion**: Add this pattern to ADDING-APPS.md under "Redis Authentication" section.
|
||||||
|
|
||||||
|
### 3. Consistent health check paths across apps
|
||||||
|
Different apps use different health check paths (`/health`, `/healthz`, `/_health`, `/api/v1/health`). This should be documented per app type or noted that you must verify the actual path from the app's Docker docs.
|
||||||
|
- For apps where no valid HTTP health path exists (e.g., Typebot viewer which only serves chatbot URLs), use `tcpSocket` probe instead of `httpGet`.
|
||||||
|
|
||||||
|
### 9. Linuxserver.io and s6-overlay images require root + capabilities
|
||||||
|
Images using s6-overlay init system (most linuxserver.io images) must run as root and need full capabilities to switch to their internal "abc" user.
|
||||||
|
- Set `runAsUser: 0, runAsNonRoot: false` in pod securityContext
|
||||||
|
- Do NOT set `allowPrivilegeEscalation: false` or `capabilities.drop: ALL` in container securityContext
|
||||||
|
- Only keep `readOnlyRootFilesystem: false`
|
||||||
|
- Same applies to Nextcloud (uses rsync/chown during setup)
|
||||||
|
- **Affects**: BookStack (linuxserver.io), any linuxserver.io image
|
||||||
|
|
||||||
|
### 10. CryptPad needs emptyDir + initContainer for config directory
|
||||||
|
The CryptPad image's `/cryptpad/config/` directory is in the image overlay filesystem and not writable by the container process even when running as root.
|
||||||
|
- **Fix**: Mount an emptyDir at `/cryptpad/config`, and use an initContainer to pre-seed `config.example.js` from the image into the emptyDir.
|
||||||
|
- The initContainer mounts the emptyDir at a different path (e.g., `/config-dest`) and copies the file.
|
||||||
|
- **CPAD_CONF** env var must be set to `/cryptpad/config/config.js` (startup script copies config.example.js to config.js if config.js doesn't exist).
|
||||||
|
|
||||||
|
### 11. Pixelfed image requires authentication (ghcr.io access restriction)
|
||||||
|
~~`ghcr.io/pixelfed/pixelfed` returns 403 Forbidden for anonymous pulls.~~
|
||||||
|
- **Resolved**: Use `ghcr.io/mattlqx/docker-pixelfed:v0.12.7-nginx` (community image, publicly accessible)
|
||||||
|
- Note: the mattlqx image uses port 80 (nginx), not 8080. Update `containerPort` and service `targetPort` accordingly.
|
||||||
|
|
||||||
|
### 12. Bitnami images no longer on Docker Hub
|
||||||
|
Bitnami moved their images from Docker Hub (`bitnami/appname`) to their own OCI registry. Apps that were packaged using Bitnami images (e.g., Ghost) need to be updated to use the official upstream image or Bitnami's new registry.
|
||||||
|
- **Ghost fix**: Switched from `bitnami/ghost:5.x` to official `ghost:5.130.6-alpine`
|
||||||
|
- The official ghost image uses different env vars (`database__connection__host` instead of `GHOST_DATABASE_HOST`) and a different mount path (`/var/lib/ghost/content` instead of `/bitnami/ghost`)
|
||||||
|
- Check image availability before packaging: `docker manifest inspect docker.io/bitnami/appname:tag`
|
||||||
|
|
||||||
|
### 13. Required secrets belong in app-secrets, not dep-secrets
|
||||||
|
Apps that use `requiredSecrets` (e.g., `mysql.rootPassword`) receive those secrets copied into their own `<app>-secrets` K8s Secret, under the key `<dep>.<key>` (e.g., `mysql.rootPassword`). db-init jobs and deployments must reference `<app>-secrets`, NOT `mysql-secrets`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# WRONG - mysql-secrets doesn't exist in the app namespace
|
||||||
|
secretKeyRef:
|
||||||
|
name: mysql-secrets
|
||||||
|
key: rootPassword
|
||||||
|
|
||||||
|
# CORRECT - the required secret is copied into ghost-secrets
|
||||||
|
secretKeyRef:
|
||||||
|
name: ghost-secrets
|
||||||
|
key: mysql.rootPassword
|
||||||
|
```
|
||||||
|
|
||||||
|
### 15. mattlqx/docker-pixelfed requires APP_PORT env var
|
||||||
|
The `ghcr.io/mattlqx/docker-pixelfed` community image generates nginx config from env vars using `sed`. The `listen` directive uses `APP_PORT`, which must be set explicitly or nginx crashes with "invalid number of arguments in 'listen' directive".
|
||||||
|
- Add `APP_PORT: "80"` to the deployment env vars
|
||||||
|
- The image listens on port 80 (nginx), not 8080 as the original pixelfed image did
|
||||||
|
- Both the web and worker deployments must mount the shared storage PVC — use ReadWriteMany access mode, not ReadWriteOnce, since both pods need it simultaneously
|
||||||
|
|
||||||
|
### 14. Your Priorities and Polis have no public Docker images
|
||||||
|
- `ghcr.io/citizensfoundation/your-priorities-app` — 403 Forbidden, images require auth
|
||||||
|
- `compdemocracy/polis-server` — private, no public Docker Hub or ghcr.io images
|
||||||
|
- Both apps require building custom images from source before they can be packaged
|
||||||
|
- Attempting to add these to wild-directory results in ImagePullBackOff
|
||||||
|
|
||||||
|
### 16. PHP/Laravel and other heavy-framework apps need extended probe delays
|
||||||
|
Laravel's startup process runs package discovery, autoload optimization, and encryption key generation before the HTTP server is ready. This commonly takes 2–3 minutes on cluster restart or first boot.
|
||||||
|
- The default `initialDelaySeconds: 60` is too short — the liveness probe fires before the app is ready, kills the container, and a restart loop begins
|
||||||
|
- Set `initialDelaySeconds: 120` and `failureThreshold: 6` for liveness probes on Laravel/Symfony apps
|
||||||
|
- Set `initialDelaySeconds: 60` for readiness probes
|
||||||
|
- The symptom is: pod shows `Running` for ~2 minutes then gets `Killing` due to liveness probe failure, followed by a new pod starting the same cycle
|
||||||
|
- The same applies to other frameworks with heavy startup initialization: Rails (`bundle exec`, asset precompilation), Spring Boot (JVM + Spring context loading), Django with migrations
|
||||||
|
- **Affects**: Ushahidi API (Laravel) — fixed with 120s initial delay + failureThreshold: 6
|
||||||
|
|
||||||
|
### 17. MySQL db-init `CREATE USER IF NOT EXISTS` doesn't update passwords
|
||||||
|
The common db-init pattern `CREATE USER IF NOT EXISTS 'user'@'%' IDENTIFIED BY '${PASSWORD}'` only sets the password at creation time. If the MySQL user already exists from a previous deployment (e.g., after `wild app delete` + `wild app add` without dropping the database), the password is silently left unchanged and the new deployment fails with "Access denied for user".
|
||||||
|
- **Symptom**: App starts, connects to MySQL, gets "Access denied" even though the db-init job completed successfully
|
||||||
|
- **Quick fix**: Exec into the MySQL pod and run: `ALTER USER 'user'@'%' IDENTIFIED BY 'current_password'; FLUSH PRIVILEGES;`
|
||||||
|
- **Better pattern** — make db-init jobs truly idempotent for both user creation and password:
|
||||||
|
```sql
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
ALTER USER '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
GRANT ALL PRIVILEGES ON ${DB_DATABASE_NAME}.* TO '${DB_USERNAME}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
```
|
||||||
|
- This `CREATE ... IF NOT EXISTS` + `ALTER USER` pattern is safe to run repeatedly regardless of whether the user existed before
|
||||||
|
- **Affects**: Ghost (MySQL backend) — hit this when redeploying after a previous partial test run
|
||||||
|
|
||||||
|
### 18. Gancio "Non empty db" crash on re-deploy
|
||||||
|
Gancio stores its setup state in `config.json` on the data PVC. If the PVC is lost or the app is deleted and redeployed with an existing database, gancio finds a non-empty DB but no `config.json` and refuses to start with "Non empty db! Please move your current db elsewhere than retry."
|
||||||
|
- **Fix**: Drop the database schema and let gancio recreate it:
|
||||||
|
```bash
|
||||||
|
kubectl exec -n postgres <postgres-pod> -- psql -U postgres gancio \
|
||||||
|
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO gancio; GRANT ALL ON SCHEMA public TO public;"
|
||||||
|
kubectl scale deployment -n gancio gancio --replicas=0
|
||||||
|
# (wait for pod to terminate)
|
||||||
|
kubectl scale deployment -n gancio gancio --replicas=1
|
||||||
|
```
|
||||||
|
- Scale down first to release the DB connection before dropping the schema
|
||||||
|
- This only affects re-deployments; fresh installs work fine
|
||||||
|
|
||||||
|
### 19. Odoo requires explicit database name and `-i base` on first run
|
||||||
|
The official Odoo Docker image does not auto-initialize the database. Without specifying the database name and installing the base module, Odoo starts in multi-database manager mode and health checks fail with "Database not initialized".
|
||||||
|
- Add `args: ["-d", "DATABASE_NAME", "-i", "base"]` to the container spec
|
||||||
|
- The `-i base` flag installs Odoo's base module and creates all database tables on first run
|
||||||
|
- Subsequent runs with `-i base` are safe (idempotent) — it updates rather than reinstalls
|
||||||
|
- Use a `startupProbe` with `failureThreshold: 40, periodSeconds: 30` (20 minutes) since first-run initialization takes 10–15 minutes
|
||||||
|
- Once startup probe passes, the regular liveness/readiness probes with `initialDelaySeconds: 0` take over
|
||||||
|
|
||||||
|
### 20. Headscale v0.29+ CLI: inconsistent user flag types
|
||||||
|
In headscale v0.29, different commands accept different user identifiers:
|
||||||
|
- `preauthkeys create --user <id>` — requires numeric ID
|
||||||
|
- `auth register --user <username>` — accepts username string
|
||||||
|
- `users destroy --identifier <id>` — requires numeric ID, not a positional arg
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Always list users first to get the ID
|
||||||
|
headscale users list
|
||||||
|
|
||||||
|
headscale preauthkeys create --user 1 # numeric ID
|
||||||
|
headscale auth register --auth-id <id> --user payne # username string OK
|
||||||
|
headscale users destroy --identifier 1 # numeric ID
|
||||||
|
```
|
||||||
|
|
||||||
|
Node registration flow: when a Tailscale client connects via browser (not pre-auth key), headscale
|
||||||
|
shows a command like `headscale auth register --auth-id hskey-authreq-XXXX --user USERNAME`.
|
||||||
|
Run it via `kubectl exec -n headscale deploy/headscale -- headscale auth register --auth-id <id> --user <username>`.
|
||||||
|
|
||||||
|
## App Status Tracking
|
||||||
|
|
||||||
|
| App | Status | Notes |
|
||||||
|
|-----|--------|-------|
|
||||||
|
| Firefly III | ✅ working | |
|
||||||
|
| Wiki.js | ✅ working | |
|
||||||
|
| Outline | ✅ working | Redis URL needs password via K8s env var expansion |
|
||||||
|
| WriteFreely | ✅ working | Needs runAsUser: 0 (community image quirk) |
|
||||||
|
| NocoDB | ✅ working | Node.js needs 2Gi memory |
|
||||||
|
| Mattermost | ✅ working | |
|
||||||
|
| Etherpad | ✅ working | |
|
||||||
|
| CryptPad | ✅ working | Needs emptyDir + initContainer for config seeding |
|
||||||
|
| MediaWiki | ✅ working | Post-deploy web installer required for LocalSettings.php |
|
||||||
|
| Nextcloud | ✅ working | |
|
||||||
|
| PeerTube | ✅ working | |
|
||||||
|
| BookStack | ✅ working | linuxserver.io needs root + no capabilities.drop |
|
||||||
|
| Typebot | ✅ working | Builder: /signin health path; Viewer: tcpSocket probe |
|
||||||
|
| Pixelfed | ✅ working | Use ghcr.io/mattlqx/docker-pixelfed:v0.12.7-nginx (community), port 80 not 8080 |
|
||||||
|
| Wekan | pending | needs MongoDB |
|
||||||
|
| Mautic | ✅ working | |
|
||||||
|
| Taiga | ✅ working | |
|
||||||
|
| Pol.is | ❌ blocked | Private AWS ECR images, no public alternative |
|
||||||
|
| Zulip | ✅ working | Bundles own postgres/rabbitmq/memcached; first-run migrations take ~10min |
|
||||||
|
| Rocket.Chat | pending | needs MongoDB |
|
||||||
|
| LimeSurvey | ✅ working | |
|
||||||
|
| Gancio | ✅ working | Re-deploy: must drop DB schema first if prior data exists (see note 18) |
|
||||||
|
| Mobilizon | ✅ working | |
|
||||||
|
| Formbricks | ✅ working | |
|
||||||
|
| Jitsi | ✅ working | |
|
||||||
|
| Mastodon | already done | |
|
||||||
|
| Chamilo | ✅ working | |
|
||||||
|
| Moodle | ✅ working | |
|
||||||
|
| Open edX | pending | complex |
|
||||||
|
| Pretix | ✅ working | |
|
||||||
|
| Indico | ✅ working | |
|
||||||
|
| Eventyay | ✅ working | No versioned Docker tags upstream — main tag is intentional |
|
||||||
|
| Leihs | pending | |
|
||||||
|
| LibreBooking | ✅ working | |
|
||||||
|
| uMap | ✅ working | Host header needed in health probes (Django ALLOWED_HOSTS) |
|
||||||
|
| MapComplete | pending | |
|
||||||
|
| Terrastories | pending | |
|
||||||
|
| Karrot | already done | |
|
||||||
|
| LiquidFeedback | pending | |
|
||||||
|
| Your Priorities | ❌ blocked | No public Docker images (ghcr.io requires auth) |
|
||||||
|
| Helios Voting | pending | |
|
||||||
|
| Belenios | pending | |
|
||||||
|
| Decidim | already done | |
|
||||||
|
| Alaveteli | pending | |
|
||||||
|
| FixMyStreet | pending | |
|
||||||
|
| Ghost | ✅ working | Bitnami image gone; use official ghost:alpine; different env vars + mount path (see note 12) |
|
||||||
|
| Ushahidi | ✅ working | Client: runAsUser:0 + CHOWN/SETUID/SETGID; API: liveness initialDelay 120s (Laravel slow start) |
|
||||||
|
| CiviCRM | pending | |
|
||||||
|
| Open Collective | pending | |
|
||||||
|
| Open Food Network | pending | |
|
||||||
|
| Resonate | pending | |
|
||||||
|
| BookWyrm | ✅ working | |
|
||||||
|
| Lemmy | already done | |
|
||||||
|
| Akaunting | ✅ working | |
|
||||||
|
| GnuCash | skip | desktop app, not containerizable |
|
||||||
|
| OhMyForm | ✅ working | |
|
||||||
|
| Odoo | ✅ working | Needs `-d DATABASE -i base` args; uses startupProbe (20min window) for first-run DB init |
|
||||||
|
| Headscale | ✅ working | SQLite, ConfigMap-based config, no secrets; v0.29 CLI uses numeric user IDs (see note 20) |
|
||||||
|
| Baserow | ✅ working | |
|
||||||
|
| Keila | already done | |
|
||||||
|
| Listmonk | already done | |
|
||||||
|
| Discourse | already done | |
|
||||||
|
| Gitea | already done | |
|
||||||
|
| Immich | already done | |
|
||||||
|
| Loomio | already done | |
|
||||||
|
| Synapse | already done | App renamed from matrix to synapse |
|
||||||
|
| Open WebUI | already done | |
|
||||||
|
| OpenProject | already done | |
|
||||||
|
| vLLM | already done | |
|
||||||
289
ADDING-APPS.md
289
ADDING-APPS.md
@@ -645,19 +645,47 @@ This means individual resources can use simple, component-specific selectors lik
|
|||||||
|
|
||||||
Resource files in this repository are **templates** that get compiled when users add apps via the web app, CLI, or API. Only variables defined in the manifest file's 'defaultConfig' section are available to the resource templates. Use gomplate syntax to reference configuration:
|
Resource files in this repository are **templates** that get compiled when users add apps via the web app, CLI, or API. Only variables defined in the manifest file's 'defaultConfig' section are available to the resource templates. Use gomplate syntax to reference configuration:
|
||||||
|
|
||||||
### External DNS
|
### Ingress
|
||||||
|
|
||||||
Ingress resources should include external-dns annotations for automatic DNS management:
|
Use this as the standard ingress template for all apps:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
annotations:
|
apiVersion: networking.k8s.io/v1
|
||||||
external-dns.alpha.kubernetes.io/target: {{ .domain }}
|
kind: Ingress
|
||||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: myapp
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: 'domain' must be defined in the app manifest's 'defaultConfig' section.
|
**Rules:**
|
||||||
|
- Always use `spec.ingressClassName: traefik` — do NOT use the `kubernetes.io/ingress.class` annotation (deprecated)
|
||||||
|
- Do NOT add `cert-manager.io/cluster-issuer` — the wildcard TLS cert is pre-distributed to app namespaces and managed centrally; adding this annotation causes cert-manager to issue a wrong per-app cert that overwrites the wildcard secret
|
||||||
|
- Do NOT add Traefik redirect annotations — HTTPS redirect is handled globally by Traefik
|
||||||
|
- `externalDnsDomain`, `domain`, and `tlsSecretName` must be defined in `defaultConfig`
|
||||||
|
|
||||||
This creates a CNAME from the app subdomain to the cluster domain (e.g., `myapp.cloud.example.com` → `cloud.example.com`).
|
### External DNS
|
||||||
|
|
||||||
|
The `external-dns.alpha.kubernetes.io/target` annotation creates a CNAME from the app subdomain to the cluster domain (e.g., `myapp.cloud.example.com` → `cloud.example.com`). Always set `cloudflare-proxied: "false"` since Wild Cloud manages its own TLS.
|
||||||
|
|
||||||
## App Dependencies and Reference Mapping
|
## App Dependencies and Reference Mapping
|
||||||
|
|
||||||
@@ -708,6 +736,7 @@ Apps requiring PostgreSQL or MySQL should include a database initialization job
|
|||||||
- Use `restartPolicy: OnFailure`
|
- Use `restartPolicy: OnFailure`
|
||||||
- Include in `kustomization.yaml` resources
|
- Include in `kustomization.yaml` resources
|
||||||
- Use appropriate security context (e.g., `runAsUser: 999` for PostgreSQL)
|
- Use appropriate security context (e.g., `runAsUser: 999` for PostgreSQL)
|
||||||
|
- **Write idempotent SQL**: For MySQL, use `CREATE USER IF NOT EXISTS ... ; ALTER USER ...` so re-running the job after a redeploy doesn't leave stale passwords. See "MySQL db-init User Password Idempotency" in Common Gotchas.
|
||||||
|
|
||||||
**Example apps:** `immich`, `gitea`, `openproject`, `discourse`
|
**Example apps:** `immich`, `gitea`, `openproject`, `discourse`
|
||||||
|
|
||||||
@@ -907,6 +936,247 @@ labels:
|
|||||||
component: server # Simple component label
|
component: server # Simple component label
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Common Gotchas
|
||||||
|
|
||||||
|
Lessons learned from deploying apps to Wild Cloud. These are the non-obvious issues that cause failures.
|
||||||
|
|
||||||
|
### Always Verify Docker Image Availability
|
||||||
|
|
||||||
|
Before packaging an app, confirm the image is **publicly accessible without authentication**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull registry.example.com/org/image:tag
|
||||||
|
# or check via curl for ghcr.io:
|
||||||
|
curl -s "https://ghcr.io/token?scope=repository:org/image:pull&service=ghcr.io" | python3 -c "import json,sys; print(json.load(sys.stdin).get('token','no token')[:20])"
|
||||||
|
```
|
||||||
|
|
||||||
|
Common pitfalls:
|
||||||
|
- **Bitnami images** moved away from Docker Hub — their `bitnami/appname` images may no longer be available; check `oci.bitnami.com` or use the official upstream image instead
|
||||||
|
- **ghcr.io images** frequently require authentication even when the repo is "public" on GitHub — test anonymous pull before committing to them
|
||||||
|
- **compdemocracy/polis** and some other community apps use private AWS ECR or require GitHub tokens — these cannot be packaged for general use without finding a public mirror
|
||||||
|
|
||||||
|
If no public image exists, note it clearly in the wild-directory `app.yaml` description rather than packaging a broken app.
|
||||||
|
|
||||||
|
### Re-run `wild app add` After Template Changes
|
||||||
|
|
||||||
|
`wild app deploy <app>` applies whatever is already compiled in the instance's `apps/` directory. It does **not** recompile templates from wild-directory. After modifying any template file in wild-directory, always re-run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wild app add <app> # recompiles templates into the instance dir
|
||||||
|
wild app deploy <app> # applies the recompiled files
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL Connection Strings: Always Include `?sslmode=disable`
|
||||||
|
|
||||||
|
Wild Cloud's internal PostgreSQL does not have SSL enabled. Any app that constructs a PostgreSQL connection string must include `?sslmode=disable`, or the connection will fail with "The server does not support SSL connections."
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
defaultSecrets:
|
||||||
|
- key: dbUrl
|
||||||
|
default: "postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable"
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add `PGSSLMODE=disable` as an env var for apps that use libpq directly.
|
||||||
|
|
||||||
|
### Redis URLs Must Include the Password
|
||||||
|
|
||||||
|
Wild Cloud's Redis requires authentication. Apps that accept a Redis URL must include the password:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# In manifest.yaml requiredSecrets:
|
||||||
|
requiredSecrets:
|
||||||
|
- redis.password
|
||||||
|
|
||||||
|
# In deployment.yaml env — use K8s env var expansion for the URL:
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: myapp-secrets
|
||||||
|
key: redis.password
|
||||||
|
- name: REDIS_URL
|
||||||
|
value: "redis://:$(REDIS_PASSWORD)@{{ .redis.host }}:6379"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `$(VAR_NAME)` syntax is evaluated by Kubernetes at pod start from other env vars in the same container spec.
|
||||||
|
|
||||||
|
### Runtime-Specific Resource Defaults
|
||||||
|
|
||||||
|
Default resource requests vary by runtime. The standard 256Mi/50m is too low for heavier runtimes:
|
||||||
|
|
||||||
|
| Runtime | Suggested request | Notes |
|
||||||
|
|---------|------------------|-------|
|
||||||
|
| Go/Rust | 64–128Mi | Very efficient |
|
||||||
|
| Python/Ruby/PHP | 128–256Mi | Typical range |
|
||||||
|
| Node.js | 256–512Mi | Add `NODE_OPTIONS=--max-old-space-size=<N>` to cap heap |
|
||||||
|
| JVM (Java/Kotlin/Scala) | 512Mi–1Gi | Set `-Xmx` to keep within limit |
|
||||||
|
| Celery/Sidekiq workers | 256–512Mi | Full worker process per instance |
|
||||||
|
|
||||||
|
For Node.js apps, cap memory use explicitly:
|
||||||
|
```yaml
|
||||||
|
- name: NODE_OPTIONS
|
||||||
|
value: "--max-old-space-size=768" # ~75% of 1Gi limit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check Probes for Django/FastAPI (ALLOWED_HOSTS)
|
||||||
|
|
||||||
|
Django and similar frameworks reject health probe requests because Kubernetes sends them directly to the pod IP, which is not in `ALLOWED_HOSTS`. Fix by adding a `Host` header to the probe:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 8000
|
||||||
|
httpHeaders:
|
||||||
|
- name: Host
|
||||||
|
value: "{{ .domain }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Images That Need Root + Special Capabilities
|
||||||
|
|
||||||
|
Some images run an init system (nginx, s6-overlay, custom entrypoint scripts) that requires elevated privileges to set up directories and drop to a lower-privileged user. These fail when `runAsNonRoot: true` or `capabilities.drop: [ALL]` is set.
|
||||||
|
|
||||||
|
**Pattern for nginx-based images** (e.g., community app frontends):
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
add: [CHOWN, SETUID, SETGID] # needed for nginx to drop to worker user
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern for linuxserver.io / s6-overlay images** (e.g., BookStack, most linuxserver images):
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: false # Only this — do NOT drop capabilities
|
||||||
|
```
|
||||||
|
|
||||||
|
Signs you need this pattern:
|
||||||
|
- `chown(...) failed (1: Operation not permitted)` — add `CHOWN` capability
|
||||||
|
- `Permission denied` on a directory during startup — try running as root
|
||||||
|
- Container exits immediately with code 1 despite image being "official"
|
||||||
|
|
||||||
|
### Hex-Format Secrets
|
||||||
|
|
||||||
|
Some apps require secrets in specific formats (e.g., exactly 64 hex chars for Outline). The default Wild Cloud secret generator produces random alphanumeric strings. Use `crypto.SHA256` in the manifest `default` field to derive a hex string from another secret:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
defaultSecrets:
|
||||||
|
- key: utilsSecret # generated first (alphanumeric)
|
||||||
|
- key: secretKey
|
||||||
|
default: '{{ crypto.SHA256 .secrets.utilsSecret }}' # hex derivative
|
||||||
|
```
|
||||||
|
|
||||||
|
The order of `defaultSecrets` matters — a referenced secret must appear before the entry that references it.
|
||||||
|
|
||||||
|
### Odoo (and Other Apps) Requiring Explicit Database Initialization
|
||||||
|
|
||||||
|
Some apps don't auto-initialize their database — they need to be told which database to use AND to install their schema. Odoo is the most common case: without explicit `-d DATABASE -i base` args, it starts in multi-database manager mode and all health checks fail.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# In the container spec:
|
||||||
|
args: ["-d", "{{ .db.name }}", "-i", "base"]
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-i base` flag is **idempotent**: it installs on first run and updates on subsequent runs. The downside is slightly slower startup on subsequent restarts. For very large apps, use an initContainer to run initialization separately from the main container.
|
||||||
|
|
||||||
|
Combine with a `startupProbe` to give the initialization time to complete:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /web/health
|
||||||
|
port: 8069
|
||||||
|
failureThreshold: 40 # 40 × 30s = 20 minutes
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /web/health
|
||||||
|
port: 8069
|
||||||
|
initialDelaySeconds: 0 # startupProbe takes over startup detection
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 6
|
||||||
|
```
|
||||||
|
|
||||||
|
The `startupProbe` runs until it succeeds (or exhausts `failureThreshold`), at which point the liveness and readiness probes kick in with `initialDelaySeconds: 0`. This is cleaner than inflating `initialDelaySeconds` on the main probes.
|
||||||
|
|
||||||
|
### Gancio Re-deploy: "Non empty db" Crash
|
||||||
|
|
||||||
|
Gancio stores setup state in `config.json` on its data PVC. If the PVC is deleted or the app is re-added without wiping the database, gancio finds tables in PostgreSQL but no `config.json` and refuses to start.
|
||||||
|
|
||||||
|
**Fix**: Drop the schema before restarting:
|
||||||
|
```bash
|
||||||
|
kubectl exec -n postgres <postgres-pod> -- psql -U postgres gancio \
|
||||||
|
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO gancio; GRANT ALL ON SCHEMA public TO public;"
|
||||||
|
```
|
||||||
|
|
||||||
|
Scale the deployment to 0 before dropping the schema to avoid active connection errors.
|
||||||
|
|
||||||
|
### Heavy-Framework Startup Times and Probe Delays
|
||||||
|
|
||||||
|
Frameworks with heavy startup initialization (Laravel, Rails, Spring Boot, Symfony) run tasks like package discovery, autoload generation, database migrations, and asset compilation before accepting HTTP requests. This takes 2–3 minutes — much longer than the default 60-second initial delay.
|
||||||
|
|
||||||
|
The failure mode is a restart loop: pod runs for ~2 minutes, liveness probe fires and fails, container is killed, new pod starts. The app never gets far enough to become Ready.
|
||||||
|
|
||||||
|
For these runtimes, use extended probe delays:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 120 # Give the framework time to fully initialize
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 6 # Tolerate transient failures during startup
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Runtimes that typically need this:**
|
||||||
|
| Runtime | Suggested liveness initialDelaySeconds |
|
||||||
|
|---------|----------------------------------------|
|
||||||
|
| PHP/Laravel/Symfony | 120s |
|
||||||
|
| Ruby on Rails | 90–120s |
|
||||||
|
| Spring Boot (JVM) | 90–120s |
|
||||||
|
| Django (with migrations) | 60–90s |
|
||||||
|
| Node.js | 60s (usually sufficient) |
|
||||||
|
|
||||||
|
### MySQL db-init User Password Idempotency
|
||||||
|
|
||||||
|
The common pattern `CREATE USER IF NOT EXISTS 'user'@'%' IDENTIFIED BY '${PASSWORD}'` only sets the password when the user is first created. If the MySQL user already exists from a previous deployment (e.g., after deleting and re-adding an app without dropping the database), the password is silently unchanged and the new deployment fails with "Access denied".
|
||||||
|
|
||||||
|
Use the `CREATE ... IF NOT EXISTS` + `ALTER USER` pattern to make db-init jobs idempotent regardless of prior state:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE IF NOT EXISTS ${DB_DATABASE_NAME};
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
ALTER USER '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
GRANT ALL PRIVILEGES ON ${DB_DATABASE_NAME}.* TO '${DB_USERNAME}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
```
|
||||||
|
|
||||||
|
The `ALTER USER` line is safe even when the user was just created — it's a no-op in that case. This applies to MySQL/MariaDB only; PostgreSQL's `CREATE USER IF NOT EXISTS` does not exist but `DO $$ BEGIN IF NOT EXISTS ... END $$` patterns serve the same purpose.
|
||||||
|
|
||||||
## Validation Checklist
|
## Validation Checklist
|
||||||
|
|
||||||
Before submitting a new or modified app, verify:
|
Before submitting a new or modified app, verify:
|
||||||
@@ -939,6 +1209,11 @@ Before submitting a new or modified app, verify:
|
|||||||
- [ ] Ingresses include external-dns annotations
|
- [ ] Ingresses include external-dns annotations
|
||||||
- [ ] Database apps include init jobs (if applicable)
|
- [ ] Database apps include init jobs (if applicable)
|
||||||
|
|
||||||
|
- [ ] **Images**
|
||||||
|
- [ ] Docker images are publicly accessible (test anonymous pull)
|
||||||
|
- [ ] Image tags are specific versions, not `latest` or branch names
|
||||||
|
- [ ] Architecture constraints (amd64-only) noted in manifest if applicable
|
||||||
|
|
||||||
- [ ] **Testing**
|
- [ ] **Testing**
|
||||||
- [ ] Templates compile successfully with sample config
|
- [ ] Templates compile successfully with sample config
|
||||||
- [ ] App deploys without errors in test cluster
|
- [ ] App deploys without errors in test cluster
|
||||||
|
|||||||
5
akaunting/app.yaml
Normal file
5
akaunting/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: akaunting
|
||||||
|
is: akaunting
|
||||||
|
description: Akaunting is a free, open-source, and online accounting software for small businesses and freelancers.
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/akaunting.svg
|
||||||
|
latest: "3"
|
||||||
40
akaunting/versions/3/README.md
Normal file
40
akaunting/versions/3/README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Akaunting
|
||||||
|
|
||||||
|
Akaunting is an open-source accounting software for small businesses and freelancers.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **MySQL** - Database for storing accounting data
|
||||||
|
- **SMTP** - For sending invoices and notifications
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Where Akaunting will be accessible
|
||||||
|
- **storage** - Persistent volume size (default: `2Gi`)
|
||||||
|
- **companyName** - Your company name (default: `My Company`)
|
||||||
|
- **companyEmail** - Company contact email (defaults to your operator email)
|
||||||
|
- **adminEmail** - Admin account email (defaults to your operator email)
|
||||||
|
- **locale** - Language/locale setting (default: `en-US`)
|
||||||
|
- **smtp** - Email settings inherited from your Wild Cloud SMTP service
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
1. Add and deploy the app:
|
||||||
|
```bash
|
||||||
|
wild app add akaunting
|
||||||
|
wild app deploy akaunting
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Log in with:
|
||||||
|
- **Email**: value of `adminEmail` in your config
|
||||||
|
- **Password**: value of `adminPassword` in your `secrets.yaml`
|
||||||
|
|
||||||
|
3. Set up your company details, chart of accounts, and start recording income and expenses.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Akaunting supports multiple companies in a single installation — additional companies can be added after login
|
||||||
|
- The `dbPrefix` config (`aka_`) prefixes all database table names to avoid conflicts
|
||||||
|
- Invoices, payments, and reports can be exported as PDF
|
||||||
64
akaunting/versions/3/db-init-job.yaml
Normal file
64
akaunting/versions/3/db-init-job.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: akaunting-db-init
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: mysql-init
|
||||||
|
image: mysql:9.1.0
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
env:
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: akaunting-secrets
|
||||||
|
key: mysql.rootPassword
|
||||||
|
- name: DB_HOSTNAME
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: DB_DATABASE_NAME
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: akaunting-secrets
|
||||||
|
key: dbPassword
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
until mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1" 2>/dev/null; do
|
||||||
|
echo "Waiting for MySQL..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} <<EOF
|
||||||
|
CREATE DATABASE IF NOT EXISTS ${DB_DATABASE_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
ALTER USER '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
GRANT ALL PRIVILEGES ON ${DB_DATABASE_NAME}.* TO '${DB_USERNAME}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
|
echo "Done"
|
||||||
114
akaunting/versions/3/deployment.yaml
Normal file
114
akaunting/versions/3/deployment.yaml
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: akaunting
|
||||||
|
namespace: akaunting
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
runAsNonRoot: false
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: akaunting
|
||||||
|
image: akaunting/akaunting:3.1.21
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: AKAUNTING_SETUP
|
||||||
|
value: "true"
|
||||||
|
- name: APP_URL
|
||||||
|
value: https://{{ .domain }}
|
||||||
|
- name: LOCALE
|
||||||
|
value: {{ .locale }}
|
||||||
|
- name: DB_HOST
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: DB_NAME
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: akaunting-secrets
|
||||||
|
key: dbPassword
|
||||||
|
- name: DB_PREFIX
|
||||||
|
value: {{ .dbPrefix }}
|
||||||
|
- name: COMPANY_NAME
|
||||||
|
value: "{{ .companyName }}"
|
||||||
|
- name: COMPANY_EMAIL
|
||||||
|
value: {{ .companyEmail }}
|
||||||
|
- name: ADMIN_EMAIL
|
||||||
|
value: {{ .adminEmail }}
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: akaunting-secrets
|
||||||
|
key: adminPassword
|
||||||
|
- name: MAIL_MAILER
|
||||||
|
value: smtp
|
||||||
|
- name: MAIL_HOST
|
||||||
|
value: {{ .smtp.host }}
|
||||||
|
- name: MAIL_PORT
|
||||||
|
value: "{{ .smtp.port }}"
|
||||||
|
- name: MAIL_USERNAME
|
||||||
|
value: {{ .smtp.user }}
|
||||||
|
- name: MAIL_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: akaunting-secrets
|
||||||
|
key: smtpPassword
|
||||||
|
- name: MAIL_FROM_ADDRESS
|
||||||
|
value: {{ .smtp.from }}
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 256Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: akaunting-data
|
||||||
|
mountPath: /var/www/html/storage
|
||||||
|
subPath: storage
|
||||||
|
- name: akaunting-data
|
||||||
|
mountPath: /var/www/html/public/uploads
|
||||||
|
subPath: uploads
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 120
|
||||||
|
timeoutSeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
volumes:
|
||||||
|
- name: akaunting-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: akaunting-data
|
||||||
|
restartPolicy: Always
|
||||||
26
akaunting/versions/3/ingress.yaml
Normal file
26
akaunting/versions/3/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: akaunting
|
||||||
|
namespace: akaunting
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: akaunting
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
16
akaunting/versions/3/kustomization.yaml
Normal file
16
akaunting/versions/3/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: akaunting
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: akaunting
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- db-init-job.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
31
akaunting/versions/3/manifest.yaml
Normal file
31
akaunting/versions/3/manifest.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
version: 3.1.21-1
|
||||||
|
requires:
|
||||||
|
- name: mysql
|
||||||
|
- name: smtp
|
||||||
|
defaultConfig:
|
||||||
|
namespace: akaunting
|
||||||
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
|
domain: akaunting.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
locale: en-US
|
||||||
|
dbPrefix: aka_
|
||||||
|
companyName: My Company
|
||||||
|
companyEmail: '{{ .operator.email }}'
|
||||||
|
adminEmail: '{{ .operator.email }}'
|
||||||
|
db:
|
||||||
|
host: '{{ .apps.mysql.host }}'
|
||||||
|
port: "3306"
|
||||||
|
name: akaunting
|
||||||
|
user: akaunting
|
||||||
|
smtp:
|
||||||
|
host: '{{ .apps.smtp.host }}'
|
||||||
|
port: '{{ .apps.smtp.port }}'
|
||||||
|
from: '{{ .apps.smtp.from }}'
|
||||||
|
user: '{{ .apps.smtp.user }}'
|
||||||
|
defaultSecrets:
|
||||||
|
- key: dbPassword
|
||||||
|
- key: adminPassword
|
||||||
|
- key: smtpPassword
|
||||||
|
requiredSecrets:
|
||||||
|
- mysql.rootPassword
|
||||||
11
akaunting/versions/3/pvc.yaml
Normal file
11
akaunting/versions/3/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: akaunting-data
|
||||||
|
namespace: akaunting
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
13
akaunting/versions/3/service.yaml
Normal file
13
akaunting/versions/3/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: akaunting
|
||||||
|
namespace: akaunting
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
component: web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
5
aptly/app.yaml
Normal file
5
aptly/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: aptly
|
||||||
|
is: aptly
|
||||||
|
description: Aptly is a Debian/Ubuntu APT repository management tool for mirroring, snapshotting, and publishing package repositories.
|
||||||
|
icon: https://github.com/aptly-dev.png
|
||||||
|
latest: "1"
|
||||||
84
aptly/versions/1/README.md
Normal file
84
aptly/versions/1/README.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Aptly
|
||||||
|
|
||||||
|
Aptly is a Debian/Ubuntu APT repository management tool. Use it to create and publish private APT repositories that your machines can install from.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in your instance's `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Where aptly will be accessible (default: `aptly.{your-cloud-domain}`)
|
||||||
|
- **storage** - Persistent volume size for packages (default: `5Gi`)
|
||||||
|
|
||||||
|
## Access
|
||||||
|
|
||||||
|
After deployment:
|
||||||
|
- `https://aptly.{your-cloud-domain}/` — Published repository root (for APT clients, no auth required)
|
||||||
|
- `https://aptly.{your-cloud-domain}/api` — REST API for managing repos and snapshots (requires credentials)
|
||||||
|
- `https://aptly.{your-cloud-domain}/public.key` — GPG public key for APT clients to import
|
||||||
|
|
||||||
|
## Credentials
|
||||||
|
|
||||||
|
The API is protected by HTTP basic authentication. The username defaults to `aptly` (configurable via `apiUser` in `config.yaml`). The password is auto-generated and stored in your instance's `secrets.yaml` under `apps.aptly.apiPassword`.
|
||||||
|
|
||||||
|
To retrieve it:
|
||||||
|
```bash
|
||||||
|
wild secret get aptly apiPassword
|
||||||
|
```
|
||||||
|
|
||||||
|
To use the API with curl:
|
||||||
|
```bash
|
||||||
|
curl -u aptly:{your-api-password} https://aptly.{your-cloud-domain}/api/version
|
||||||
|
```
|
||||||
|
|
||||||
|
## GPG Signing
|
||||||
|
|
||||||
|
A GPG signing key is generated automatically on first deployment and stored on the persistent volume. The public key is exported to `/public.key` and is accessible without authentication so APT clients can import it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://aptly.{your-cloud-domain}/public.key | \
|
||||||
|
sudo tee /usr/share/keyrings/my-repo.gpg > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
Aptly is managed through its REST API or the `aptly` CLI (run inside the pod). The REST API docs are at https://www.aptly.info/doc/api/.
|
||||||
|
|
||||||
|
### Running aptly commands inside the pod
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n aptly exec -it deploy/aptly -- bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publish a local repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a local repo
|
||||||
|
aptly repo create my-packages
|
||||||
|
|
||||||
|
# Add packages
|
||||||
|
aptly repo add my-packages /path/to/package.deb
|
||||||
|
|
||||||
|
# Publish
|
||||||
|
aptly publish repo my-packages
|
||||||
|
```
|
||||||
|
|
||||||
|
The published repo will be available at `https://aptly.{your-cloud-domain}/`.
|
||||||
|
|
||||||
|
### Configure a machine to use the repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Import the signing key
|
||||||
|
curl -fsSL https://aptly.{your-cloud-domain}/public.key | \
|
||||||
|
sudo tee /usr/share/keyrings/my-repo.gpg > /dev/null
|
||||||
|
|
||||||
|
# Add the repo
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/my-repo.gpg] https://aptly.{your-cloud-domain}/ stable main" | \
|
||||||
|
sudo tee /etc/apt/sources.list.d/my-repo.list
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All aptly data (GPG keys, database, package pool, published repos) is stored on the persistent volume at `/opt/aptly`.
|
||||||
|
- The REST API (port 8080) and the nginx file server (port 80) both run inside the pod via supervisord. The ingress routes to nginx (port 80).
|
||||||
78
aptly/versions/1/deployment.yaml
Normal file
78
aptly/versions/1/deployment.yaml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: aptly
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: server
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: server
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
initContainers:
|
||||||
|
- name: init-gpg
|
||||||
|
image: urpylka/aptly:1.6.2
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
export GNUPGHOME=/opt/aptly/gpg
|
||||||
|
mkdir -p "$GNUPGHOME" /opt/aptly/public
|
||||||
|
chmod 700 "$GNUPGHOME"
|
||||||
|
if gpg --list-secret-keys 2>/dev/null | grep -q '^sec'; then
|
||||||
|
echo "GPG key already exists, skipping"
|
||||||
|
else
|
||||||
|
echo "Generating GPG signing key..."
|
||||||
|
printf '%%no-protection\nKey-Type: RSA\nKey-Length: 4096\nName-Real: Wild Cloud APT Repository\nName-Email: {{ .operatorEmail }}\nExpire-Date: 0\n%%commit\n' | gpg --batch --gen-key
|
||||||
|
echo "GPG key generated"
|
||||||
|
fi
|
||||||
|
gpg --export --armor > /opt/aptly/public/public.key
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /opt/aptly
|
||||||
|
- name: init-htpasswd
|
||||||
|
image: httpd:alpine
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- htpasswd -bc /opt/aptly/api.htpasswd "$API_USER" "$API_PASSWORD"
|
||||||
|
env:
|
||||||
|
- name: API_USER
|
||||||
|
value: "{{ .apiUser }}"
|
||||||
|
- name: API_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: aptly-secrets
|
||||||
|
key: apiPassword
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /opt/aptly
|
||||||
|
containers:
|
||||||
|
- name: aptly
|
||||||
|
image: urpylka/aptly:1.6.2
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
- containerPort: 8080
|
||||||
|
name: api
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /opt/aptly
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: aptly-pvc
|
||||||
25
aptly/versions/1/ingress.yaml
Normal file
25
aptly/versions/1/ingress.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: aptly
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: aptly
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
15
aptly/versions/1/kustomization.yaml
Normal file
15
aptly/versions/1/kustomization.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: "{{ .namespace }}"
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: aptly
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
11
aptly/versions/1/manifest.yaml
Normal file
11
aptly/versions/1/manifest.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: 1.6.2-3
|
||||||
|
defaultConfig:
|
||||||
|
namespace: aptly
|
||||||
|
externalDnsDomain: "{{ .cloud.domain }}"
|
||||||
|
domain: "aptly.{{ .cloud.domain }}"
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 5Gi
|
||||||
|
apiUser: aptly
|
||||||
|
operatorEmail: "{{ .operator.email }}"
|
||||||
|
defaultSecrets:
|
||||||
|
- key: apiPassword
|
||||||
4
aptly/versions/1/namespace.yaml
Normal file
4
aptly/versions/1/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: "{{ .namespace }}"
|
||||||
12
aptly/versions/1/pvc.yaml
Normal file
12
aptly/versions/1/pvc.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: aptly-pvc
|
||||||
|
spec:
|
||||||
|
storageClassName: longhorn
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
volumeMode: Filesystem
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
14
aptly/versions/1/service.yaml
Normal file
14
aptly/versions/1/service.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: aptly
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
name: api
|
||||||
|
selector:
|
||||||
|
component: server
|
||||||
5
baserow/app.yaml
Normal file
5
baserow/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: baserow
|
||||||
|
is: baserow
|
||||||
|
description: Baserow is an open-source no-code database platform and Airtable alternative. Create and manage databases through a spreadsheet-like interface without writing code.
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/baserow.svg
|
||||||
|
latest: "2"
|
||||||
38
baserow/versions/2/README.md
Normal file
38
baserow/versions/2/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Baserow
|
||||||
|
|
||||||
|
Baserow is an open-source no-code database platform — a self-hosted alternative to Airtable.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **PostgreSQL** - Database for storing tables and workspace data
|
||||||
|
- **Redis** - Used for caching and background job queuing
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Where Baserow will be accessible
|
||||||
|
- **storage** - Persistent volume size (default: `2Gi`)
|
||||||
|
- **db.name** - Database name (default: `baserow`)
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
1. Add and deploy the app:
|
||||||
|
```bash
|
||||||
|
wild app add baserow
|
||||||
|
wild app deploy baserow
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Visit the app URL and sign up to create your account.
|
||||||
|
|
||||||
|
3. The first user to sign up can be promoted to staff/admin via the Django admin panel at `/api/admin/`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- To make a user a staff member (admin):
|
||||||
|
```bash
|
||||||
|
kubectl exec -n baserow deploy/baserow -- python /baserow/backend/manage.py shell \
|
||||||
|
-c "from django.contrib.auth import get_user_model; u = get_user_model().objects.get(email='your@email.com'); u.is_staff = True; u.save()"
|
||||||
|
```
|
||||||
|
- The `secretKey` is auto-generated in `secrets.yaml`
|
||||||
|
- Baserow supports importing data from CSV, JSON, and XML files
|
||||||
63
baserow/versions/2/db-init-job.yaml
Normal file
63
baserow/versions/2/db-init-job.yaml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: baserow-db-init
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: db-init
|
||||||
|
image: postgres:17
|
||||||
|
command: ["/bin/bash", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOSTNAME} -U postgres <<EOF
|
||||||
|
DO \$\$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${DB_USERNAME}') THEN
|
||||||
|
CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
|
||||||
|
ELSE
|
||||||
|
ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
\$\$;
|
||||||
|
|
||||||
|
SELECT 'CREATE DATABASE ${DB_DATABASE_NAME}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_DATABASE_NAME}')\gexec
|
||||||
|
ALTER DATABASE ${DB_DATABASE_NAME} OWNER TO ${DB_USERNAME};
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE ${DB_DATABASE_NAME} TO ${DB_USERNAME};
|
||||||
|
EOF
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgres-secrets
|
||||||
|
key: password
|
||||||
|
- name: DB_HOSTNAME
|
||||||
|
value: "{{ .db.host }}"
|
||||||
|
- name: DB_DATABASE_NAME
|
||||||
|
value: "{{ .db.name }}"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "{{ .db.user }}"
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: baserow-secrets
|
||||||
|
key: dbPassword
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
restartPolicy: OnFailure
|
||||||
98
baserow/versions/2/deployment.yaml
Normal file
98
baserow/versions/2/deployment.yaml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: baserow
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: baserow
|
||||||
|
image: baserow/baserow:2.2.2
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: BASEROW_PUBLIC_URL
|
||||||
|
value: https://{{ .domain }}
|
||||||
|
- name: DATABASE_HOST
|
||||||
|
value: "{{ .db.host }}"
|
||||||
|
- name: DATABASE_PORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: DATABASE_NAME
|
||||||
|
value: "{{ .db.name }}"
|
||||||
|
- name: DATABASE_USER
|
||||||
|
value: "{{ .db.user }}"
|
||||||
|
- name: DATABASE_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: baserow-secrets
|
||||||
|
key: dbPassword
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: "{{ .redis.host }}"
|
||||||
|
- name: REDIS_PORT
|
||||||
|
value: "6379"
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: baserow-secrets
|
||||||
|
key: redis.password
|
||||||
|
- name: SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: baserow-secrets
|
||||||
|
key: secretKey
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
memory: 2Gi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 256Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: baserow-data
|
||||||
|
mountPath: /baserow/data
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/_health/
|
||||||
|
port: 80
|
||||||
|
httpHeaders:
|
||||||
|
- name: Host
|
||||||
|
value: "{{ .domain }}"
|
||||||
|
initialDelaySeconds: 300
|
||||||
|
timeoutSeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/_health/
|
||||||
|
port: 80
|
||||||
|
httpHeaders:
|
||||||
|
- name: Host
|
||||||
|
value: "{{ .domain }}"
|
||||||
|
initialDelaySeconds: 120
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 6
|
||||||
|
volumes:
|
||||||
|
- name: baserow-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: baserow-data
|
||||||
|
restartPolicy: Always
|
||||||
26
baserow/versions/2/ingress.yaml
Normal file
26
baserow/versions/2/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: baserow
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: baserow
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
16
baserow/versions/2/kustomization.yaml
Normal file
16
baserow/versions/2/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: baserow
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
|
- db-init-job.yaml
|
||||||
23
baserow/versions/2/manifest.yaml
Normal file
23
baserow/versions/2/manifest.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: 2.2.2-1
|
||||||
|
requires:
|
||||||
|
- name: postgres
|
||||||
|
- name: redis
|
||||||
|
defaultConfig:
|
||||||
|
namespace: baserow
|
||||||
|
externalDnsDomain: "{{ .cloud.domain }}"
|
||||||
|
domain: baserow.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
db:
|
||||||
|
host: "{{ .apps.postgres.host }}"
|
||||||
|
port: "5432"
|
||||||
|
name: baserow
|
||||||
|
user: baserow
|
||||||
|
redis:
|
||||||
|
host: "{{ .apps.redis.host }}"
|
||||||
|
defaultSecrets:
|
||||||
|
- key: dbPassword
|
||||||
|
- key: secretKey
|
||||||
|
requiredSecrets:
|
||||||
|
- postgres.password
|
||||||
|
- redis.password
|
||||||
4
baserow/versions/2/namespace.yaml
Normal file
4
baserow/versions/2/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
11
baserow/versions/2/pvc.yaml
Normal file
11
baserow/versions/2/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: baserow-data
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
13
baserow/versions/2/service.yaml
Normal file
13
baserow/versions/2/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: baserow
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
component: web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
5
bookstack/app.yaml
Normal file
5
bookstack/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: bookstack
|
||||||
|
is: bookstack
|
||||||
|
description: BookStack is a simple, self-hosted, easy-to-use platform for organising and storing information.
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/bookstack.svg
|
||||||
|
latest: "26"
|
||||||
39
bookstack/versions/26/README.md
Normal file
39
bookstack/versions/26/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# BookStack
|
||||||
|
|
||||||
|
BookStack is a simple, self-hosted platform for organising and storing information in a hierarchical structure of Books, Chapters, and Pages.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **MySQL** - Database for storing content and users
|
||||||
|
- **SMTP** - For email notifications and password resets
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Where BookStack will be accessible
|
||||||
|
- **storage** - Persistent volume size (default: `2Gi`)
|
||||||
|
- **db.name** - Database name (default: `bookstack`)
|
||||||
|
- **smtp** - Email settings inherited from your Wild Cloud SMTP service
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
1. Add and deploy the app:
|
||||||
|
```bash
|
||||||
|
wild app add bookstack
|
||||||
|
wild app deploy bookstack
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Log in with the default admin credentials:
|
||||||
|
- **Email**: `admin@admin.com`
|
||||||
|
- **Password**: `password`
|
||||||
|
|
||||||
|
3. **Immediately change the admin email and password** via **Settings → Your Profile**.
|
||||||
|
|
||||||
|
4. Start creating Books, Chapters, and Pages from the dashboard.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- BookStack uses the linuxserver.io image which runs as root internally — this is expected and required for the image to function
|
||||||
|
- SMTP settings are configured via the `smtp` section in your config
|
||||||
|
- The `appKey` secret is auto-generated and used for encrypting data — do not change it after content has been created
|
||||||
64
bookstack/versions/26/db-init-job.yaml
Normal file
64
bookstack/versions/26/db-init-job.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: bookstack-db-init
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: mysql-init
|
||||||
|
image: mysql:9.1.0
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
env:
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookstack-secrets
|
||||||
|
key: mysql.rootPassword
|
||||||
|
- name: DB_HOSTNAME
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: DB_DATABASE_NAME
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookstack-secrets
|
||||||
|
key: dbPassword
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
until mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1" 2>/dev/null; do
|
||||||
|
echo "Waiting for MySQL..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} <<EOF
|
||||||
|
CREATE DATABASE IF NOT EXISTS ${DB_DATABASE_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
ALTER USER '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
GRANT ALL PRIVILEGES ON ${DB_DATABASE_NAME}.* TO '${DB_USERNAME}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
|
echo "Done"
|
||||||
106
bookstack/versions/26/deployment.yaml
Normal file
106
bookstack/versions/26/deployment.yaml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bookstack
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
runAsNonRoot: false
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: bookstack
|
||||||
|
image: lscr.io/linuxserver/bookstack:26.05.1
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: PUID
|
||||||
|
value: "1000"
|
||||||
|
- name: PGID
|
||||||
|
value: "1000"
|
||||||
|
- name: APP_URL
|
||||||
|
value: https://{{ .domain }}
|
||||||
|
- name: DB_HOST
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: DB_DATABASE
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookstack-secrets
|
||||||
|
key: dbPassword
|
||||||
|
- name: APP_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookstack-secrets
|
||||||
|
key: appKey
|
||||||
|
- name: MAIL_DRIVER
|
||||||
|
value: smtp
|
||||||
|
- name: MAIL_HOST
|
||||||
|
value: {{ .smtp.host }}
|
||||||
|
- name: MAIL_PORT
|
||||||
|
value: "{{ .smtp.port }}"
|
||||||
|
- name: MAIL_FROM
|
||||||
|
value: {{ .smtp.from }}
|
||||||
|
- name: MAIL_USERNAME
|
||||||
|
value: {{ .smtp.user }}
|
||||||
|
- name: MAIL_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookstack-secrets
|
||||||
|
key: smtpPassword
|
||||||
|
- name: MAIL_ENCRYPTION
|
||||||
|
value: tls
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 128Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: bookstack-data
|
||||||
|
mountPath: /config
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 90
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
timeoutSeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
volumes:
|
||||||
|
- name: bookstack-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: bookstack-data
|
||||||
|
restartPolicy: Always
|
||||||
26
bookstack/versions/26/ingress.yaml
Normal file
26
bookstack/versions/26/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: bookstack
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: bookstack
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
16
bookstack/versions/26/kustomization.yaml
Normal file
16
bookstack/versions/26/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: bookstack
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- db-init-job.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
26
bookstack/versions/26/manifest.yaml
Normal file
26
bookstack/versions/26/manifest.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
version: 26.05.1-2
|
||||||
|
requires:
|
||||||
|
- name: mysql
|
||||||
|
- name: smtp
|
||||||
|
defaultConfig:
|
||||||
|
namespace: bookstack
|
||||||
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
|
domain: bookstack.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
db:
|
||||||
|
host: '{{ .apps.mysql.host }}'
|
||||||
|
port: '3306'
|
||||||
|
name: bookstack
|
||||||
|
user: bookstack
|
||||||
|
smtp:
|
||||||
|
host: '{{ .apps.smtp.host }}'
|
||||||
|
port: '{{ .apps.smtp.port }}'
|
||||||
|
from: '{{ .apps.smtp.from }}'
|
||||||
|
user: '{{ .apps.smtp.user }}'
|
||||||
|
defaultSecrets:
|
||||||
|
- key: appKey
|
||||||
|
- key: dbPassword
|
||||||
|
- key: smtpPassword
|
||||||
|
requiredSecrets:
|
||||||
|
- mysql.rootPassword
|
||||||
4
bookstack/versions/26/namespace.yaml
Normal file
4
bookstack/versions/26/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
11
bookstack/versions/26/pvc.yaml
Normal file
11
bookstack/versions/26/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: bookstack-data
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
13
bookstack/versions/26/service.yaml
Normal file
13
bookstack/versions/26/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: bookstack
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
component: web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
5
bookwyrm/app.yaml
Normal file
5
bookwyrm/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: bookwyrm
|
||||||
|
is: bookwyrm
|
||||||
|
description: BookWyrm is a federated social reading platform using ActivityPub. Track your reading, write reviews, and connect with readers across the fediverse.
|
||||||
|
icon: https://raw.githubusercontent.com/bookwyrm-social/bookwyrm/main/bookwyrm/static/images/logo.png
|
||||||
|
latest: "0"
|
||||||
35
bookwyrm/versions/0/README.md
Normal file
35
bookwyrm/versions/0/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# BookWyrm
|
||||||
|
|
||||||
|
BookWyrm is a federated social reading platform — a self-hosted alternative to Goodreads, connected to the ActivityPub network.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **PostgreSQL** - Database for storing books, reviews, and user data
|
||||||
|
- **Redis** - Used for caching and background job queuing
|
||||||
|
- **SMTP** - For account verification and notifications
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Where BookWyrm will be accessible
|
||||||
|
- **storage** - Persistent volume size (default: `2Gi`)
|
||||||
|
- **smtp** - Email settings inherited from your Wild Cloud SMTP service
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
1. Add and deploy the app:
|
||||||
|
```bash
|
||||||
|
wild app add bookwyrm
|
||||||
|
wild app deploy bookwyrm
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Visit the app URL — on first load, a setup wizard will guide you through creating the admin account and configuring the instance.
|
||||||
|
|
||||||
|
3. After setup, log in and configure the instance settings (name, description, registration policy) from the admin panel at `/settings/`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- BookWyrm federates with other BookWyrm instances and Mastodon via ActivityPub — users on other instances can follow and interact with your users
|
||||||
|
- Book metadata is imported from OpenLibrary and Inventaire — internet access is required for book search to work
|
||||||
|
- The `secretKey` is auto-generated in `secrets.yaml`
|
||||||
83
bookwyrm/versions/0/db-init-job.yaml
Normal file
83
bookwyrm/versions/0/db-init-job.yaml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: bookwyrm-db-init
|
||||||
|
namespace: bookwyrm
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: db-init
|
||||||
|
image: postgres:16-alpine
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
env:
|
||||||
|
- name: PGHOST
|
||||||
|
value: "{{ .db.host }}"
|
||||||
|
- name: PGPORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: PGUSER
|
||||||
|
value: postgres
|
||||||
|
- name: PGPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: postgres.password
|
||||||
|
- name: BOOKWYRM_DB_USER
|
||||||
|
value: "{{ .db.user }}"
|
||||||
|
- name: BOOKWYRM_DB_NAME
|
||||||
|
value: "{{ .db.name }}"
|
||||||
|
- name: BOOKWYRM_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: dbPassword
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
echo "Initializing BookWyrm database..."
|
||||||
|
|
||||||
|
# Create user if it doesn't exist
|
||||||
|
psql -c "
|
||||||
|
DO \$\$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$BOOKWYRM_DB_USER') THEN
|
||||||
|
CREATE USER \"$BOOKWYRM_DB_USER\" WITH PASSWORD '$BOOKWYRM_DB_PASSWORD';
|
||||||
|
ELSE
|
||||||
|
ALTER USER \"$BOOKWYRM_DB_USER\" WITH PASSWORD '$BOOKWYRM_DB_PASSWORD';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
\$\$;
|
||||||
|
"
|
||||||
|
|
||||||
|
# Create database if it doesn't exist
|
||||||
|
if ! psql -lqt | cut -d \| -f 1 | grep -qw "$BOOKWYRM_DB_NAME"; then
|
||||||
|
echo "Creating database $BOOKWYRM_DB_NAME..."
|
||||||
|
createdb -O "$BOOKWYRM_DB_USER" "$BOOKWYRM_DB_NAME"
|
||||||
|
else
|
||||||
|
echo "Database $BOOKWYRM_DB_NAME already exists."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Grant privileges
|
||||||
|
psql -d "$BOOKWYRM_DB_NAME" -c "
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE \"$BOOKWYRM_DB_NAME\" TO \"$BOOKWYRM_DB_USER\";
|
||||||
|
GRANT ALL ON SCHEMA public TO \"$BOOKWYRM_DB_USER\";
|
||||||
|
GRANT USAGE ON SCHEMA public TO \"$BOOKWYRM_DB_USER\";
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "Database initialization completed."
|
||||||
120
bookwyrm/versions/0/deployment-worker.yaml
Normal file
120
bookwyrm/versions/0/deployment-worker.yaml
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bookwyrm-worker
|
||||||
|
namespace: bookwyrm
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: worker
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: worker
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: celery-worker
|
||||||
|
image: ghcr.io/bookwyrm-social/bookwyrm:v0.8.6
|
||||||
|
command:
|
||||||
|
- celery
|
||||||
|
- -A
|
||||||
|
- celerywyrm
|
||||||
|
- worker
|
||||||
|
- --pool=threads
|
||||||
|
- --concurrency=10
|
||||||
|
- -l
|
||||||
|
- info
|
||||||
|
- -Q
|
||||||
|
- high_priority,medium_priority,low_priority,streams,images,suggested_users,email,connectors,lists,inbox,imports,import_triggered,broadcast,misc
|
||||||
|
env:
|
||||||
|
- name: SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: secretKey
|
||||||
|
- name: DEBUG
|
||||||
|
value: "false"
|
||||||
|
- name: DOMAIN
|
||||||
|
value: {{ .domain }}
|
||||||
|
- name: ALLOWED_HOSTS
|
||||||
|
value: {{ .domain }}
|
||||||
|
- name: EMAIL
|
||||||
|
value: {{ .smtp.from }}
|
||||||
|
- name: POSTGRES_HOST
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: PGPORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: dbPassword
|
||||||
|
- name: REDIS_ACTIVITY_HOST
|
||||||
|
value: {{ .redis.host }}
|
||||||
|
- name: REDIS_ACTIVITY_PORT
|
||||||
|
value: "6379"
|
||||||
|
- name: REDIS_ACTIVITY_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: redis.password
|
||||||
|
- name: REDIS_ACTIVITY_URL
|
||||||
|
value: redis://:$(REDIS_ACTIVITY_PASSWORD)@{{ .redis.host }}:6379/0
|
||||||
|
- name: REDIS_BROKER_HOST
|
||||||
|
value: {{ .redis.host }}
|
||||||
|
- name: REDIS_BROKER_PORT
|
||||||
|
value: "6379"
|
||||||
|
- name: REDIS_BROKER_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: redis.password
|
||||||
|
- name: REDIS_BROKER_URL
|
||||||
|
value: redis://:$(REDIS_BROKER_PASSWORD)@{{ .redis.host }}:6379/1
|
||||||
|
- name: EMAIL_HOST
|
||||||
|
value: {{ .smtp.host }}
|
||||||
|
- name: EMAIL_PORT
|
||||||
|
value: "{{ .smtp.port }}"
|
||||||
|
- name: EMAIL_HOST_USER
|
||||||
|
value: {{ .smtp.user }}
|
||||||
|
- name: EMAIL_HOST_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: smtpPassword
|
||||||
|
- name: EMAIL_USE_TLS
|
||||||
|
value: "true"
|
||||||
|
- name: EMAIL_USE_SSL
|
||||||
|
value: "false"
|
||||||
|
- name: MEDIA_ROOT
|
||||||
|
value: /app/images
|
||||||
|
- name: STATIC_ROOT
|
||||||
|
value: /app/static
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
memory: 1Gi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 256Mi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
restartPolicy: Always
|
||||||
149
bookwyrm/versions/0/deployment.yaml
Normal file
149
bookwyrm/versions/0/deployment.yaml
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bookwyrm
|
||||||
|
namespace: bookwyrm
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: bookwyrm
|
||||||
|
image: ghcr.io/bookwyrm-social/bookwyrm:v0.8.6
|
||||||
|
command: ["gunicorn", "bookwyrm.wsgi:application"]
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8000
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: secretKey
|
||||||
|
- name: DEBUG
|
||||||
|
value: "false"
|
||||||
|
- name: DOMAIN
|
||||||
|
value: {{ .domain }}
|
||||||
|
- name: ALLOWED_HOSTS
|
||||||
|
value: {{ .domain }}
|
||||||
|
- name: EMAIL
|
||||||
|
value: {{ .smtp.from }}
|
||||||
|
- name: POSTGRES_HOST
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: PGPORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: dbPassword
|
||||||
|
- name: REDIS_ACTIVITY_HOST
|
||||||
|
value: {{ .redis.host }}
|
||||||
|
- name: REDIS_ACTIVITY_PORT
|
||||||
|
value: "6379"
|
||||||
|
- name: REDIS_ACTIVITY_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: redis.password
|
||||||
|
- name: REDIS_ACTIVITY_URL
|
||||||
|
value: redis://:$(REDIS_ACTIVITY_PASSWORD)@{{ .redis.host }}:6379/0
|
||||||
|
- name: REDIS_BROKER_HOST
|
||||||
|
value: {{ .redis.host }}
|
||||||
|
- name: REDIS_BROKER_PORT
|
||||||
|
value: "6379"
|
||||||
|
- name: REDIS_BROKER_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: redis.password
|
||||||
|
- name: REDIS_BROKER_URL
|
||||||
|
value: redis://:$(REDIS_BROKER_PASSWORD)@{{ .redis.host }}:6379/1
|
||||||
|
- name: EMAIL_HOST
|
||||||
|
value: {{ .smtp.host }}
|
||||||
|
- name: EMAIL_PORT
|
||||||
|
value: "{{ .smtp.port }}"
|
||||||
|
- name: EMAIL_HOST_USER
|
||||||
|
value: {{ .smtp.user }}
|
||||||
|
- name: EMAIL_HOST_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bookwyrm-secrets
|
||||||
|
key: smtpPassword
|
||||||
|
- name: EMAIL_USE_TLS
|
||||||
|
value: "true"
|
||||||
|
- name: EMAIL_USE_SSL
|
||||||
|
value: "false"
|
||||||
|
- name: EMAIL_SENDER_NAME
|
||||||
|
value: BookWyrm
|
||||||
|
- name: EMAIL_SENDER_DOMAIN
|
||||||
|
value: {{ .domain }}
|
||||||
|
- name: MEDIA_ROOT
|
||||||
|
value: /app/images
|
||||||
|
- name: STATIC_ROOT
|
||||||
|
value: /app/static
|
||||||
|
- name: ENABLE_THUMBNAIL_GENERATION
|
||||||
|
value: "true"
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
ephemeral-storage: 2Gi
|
||||||
|
memory: 1Gi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 256Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: bookwyrm-images
|
||||||
|
mountPath: /app/images
|
||||||
|
- name: bookwyrm-static
|
||||||
|
mountPath: /app/static
|
||||||
|
- name: bookwyrm-exports
|
||||||
|
mountPath: /app/exports
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 120
|
||||||
|
timeoutSeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
volumes:
|
||||||
|
- name: bookwyrm-images
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: bookwyrm-images
|
||||||
|
- name: bookwyrm-static
|
||||||
|
emptyDir: {}
|
||||||
|
- name: bookwyrm-exports
|
||||||
|
emptyDir: {}
|
||||||
|
restartPolicy: Always
|
||||||
26
bookwyrm/versions/0/ingress.yaml
Normal file
26
bookwyrm/versions/0/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: bookwyrm
|
||||||
|
namespace: bookwyrm
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: bookwyrm
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
17
bookwyrm/versions/0/kustomization.yaml
Normal file
17
bookwyrm/versions/0/kustomization.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: bookwyrm
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- deployment-worker.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
|
- db-init-job.yaml
|
||||||
30
bookwyrm/versions/0/manifest.yaml
Normal file
30
bookwyrm/versions/0/manifest.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
version: 0.8.7-1
|
||||||
|
requires:
|
||||||
|
- name: postgres
|
||||||
|
- name: redis
|
||||||
|
- name: smtp
|
||||||
|
defaultConfig:
|
||||||
|
namespace: bookwyrm
|
||||||
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
|
domain: bookwyrm.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
db:
|
||||||
|
host: '{{ .apps.postgres.host }}'
|
||||||
|
port: '{{ .apps.postgres.port }}'
|
||||||
|
name: bookwyrm
|
||||||
|
user: bookwyrm
|
||||||
|
redis:
|
||||||
|
host: '{{ .apps.redis.host }}'
|
||||||
|
smtp:
|
||||||
|
host: '{{ .apps.smtp.host }}'
|
||||||
|
port: '{{ .apps.smtp.port }}'
|
||||||
|
from: '{{ .apps.smtp.from }}'
|
||||||
|
user: '{{ .apps.smtp.user }}'
|
||||||
|
defaultSecrets:
|
||||||
|
- key: secretKey
|
||||||
|
- key: dbPassword
|
||||||
|
- key: smtpPassword
|
||||||
|
requiredSecrets:
|
||||||
|
- postgres.password
|
||||||
|
- redis.password
|
||||||
4
bookwyrm/versions/0/namespace.yaml
Normal file
4
bookwyrm/versions/0/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
11
bookwyrm/versions/0/pvc.yaml
Normal file
11
bookwyrm/versions/0/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: bookwyrm-images
|
||||||
|
namespace: bookwyrm
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
13
bookwyrm/versions/0/service.yaml
Normal file
13
bookwyrm/versions/0/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: bookwyrm
|
||||||
|
namespace: bookwyrm
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
component: web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8000
|
||||||
|
protocol: TCP
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
version: v1.17.2
|
version: v1.17.2-1
|
||||||
requires:
|
requires:
|
||||||
- name: traefik
|
- name: traefik
|
||||||
defaultConfig:
|
defaultConfig:
|
||||||
|
|||||||
5
chamilo/app.yaml
Normal file
5
chamilo/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: chamilo
|
||||||
|
is: chamilo
|
||||||
|
description: Chamilo is a free and open-source e-learning platform (LMS) for course management, assessments, and online education.
|
||||||
|
icon: https://raw.githubusercontent.com/chamilo/chamilo-lms/master/public/img/logo.png
|
||||||
|
latest: "1"
|
||||||
39
chamilo/versions/1/README.md
Normal file
39
chamilo/versions/1/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Chamilo
|
||||||
|
|
||||||
|
Chamilo is an open-source Learning Management System (LMS) for creating and delivering online courses.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **MySQL** - Database for storing courses, users, and activity
|
||||||
|
- **SMTP** - For account verification and course notifications
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Where Chamilo will be accessible
|
||||||
|
- **storage** - Persistent volume size (default: `2Gi`)
|
||||||
|
- **siteName** - Display name for the LMS (default: `Chamilo LMS`)
|
||||||
|
- **adminUser** - Admin account username (default: `admin`)
|
||||||
|
- **adminEmail** - Admin account email (defaults to your operator email)
|
||||||
|
- **smtp** - Email settings inherited from your Wild Cloud SMTP service
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
1. Add and deploy the app:
|
||||||
|
```bash
|
||||||
|
wild app add chamilo
|
||||||
|
wild app deploy chamilo
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Log in with:
|
||||||
|
- **Username**: value of `adminUser` in your config (default: `admin`)
|
||||||
|
- **Password**: value of `adminPassword` in your `secrets.yaml`
|
||||||
|
|
||||||
|
3. From the admin panel, create courses, enroll learners, and configure the LMS settings.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The admin panel is accessible via **Administration** in the top navigation after logging in
|
||||||
|
- Courses can be assigned to categories, and users can self-enroll or be enrolled by teachers
|
||||||
|
- SMTP is needed for sending course completion certificates and user notifications
|
||||||
64
chamilo/versions/1/db-init-job.yaml
Normal file
64
chamilo/versions/1/db-init-job.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: chamilo-db-init
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: mysql-init
|
||||||
|
image: mysql:9.1.0
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
env:
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chamilo-secrets
|
||||||
|
key: mysql.rootPassword
|
||||||
|
- name: DB_HOSTNAME
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: DB_DATABASE_NAME
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chamilo-secrets
|
||||||
|
key: dbPassword
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
until mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1" 2>/dev/null; do
|
||||||
|
echo "Waiting for MySQL..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} <<EOF
|
||||||
|
CREATE DATABASE IF NOT EXISTS ${DB_DATABASE_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
ALTER USER '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
GRANT ALL PRIVILEGES ON ${DB_DATABASE_NAME}.* TO '${DB_USERNAME}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
|
echo "Done"
|
||||||
76
chamilo/versions/1/deployment.yaml
Normal file
76
chamilo/versions/1/deployment.yaml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: chamilo
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: chamilo
|
||||||
|
image: ipeos/chamilo:1.11.28
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: PHP_TIMEZONE
|
||||||
|
value: UTC
|
||||||
|
- name: PHP_MEMORY_LIMIT
|
||||||
|
value: 256M
|
||||||
|
- name: PHP_MAX_EXECUTION_TIME
|
||||||
|
value: "300"
|
||||||
|
- name: PHP_UPLOAD_MAX_FILESIZE
|
||||||
|
value: 100M
|
||||||
|
- name: PHP_POST_MAX_SIZE
|
||||||
|
value: 100M
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 256Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: chamilo-data
|
||||||
|
mountPath: /var/www/html
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 120
|
||||||
|
timeoutSeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: true
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
volumes:
|
||||||
|
- name: chamilo-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: chamilo-data
|
||||||
|
restartPolicy: Always
|
||||||
26
chamilo/versions/1/ingress.yaml
Normal file
26
chamilo/versions/1/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: chamilo
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: chamilo
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
16
chamilo/versions/1/kustomization.yaml
Normal file
16
chamilo/versions/1/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: chamilo
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- db-init-job.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
29
chamilo/versions/1/manifest.yaml
Normal file
29
chamilo/versions/1/manifest.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
version: 1.11.28-1
|
||||||
|
requires:
|
||||||
|
- name: mysql
|
||||||
|
- name: smtp
|
||||||
|
defaultConfig:
|
||||||
|
namespace: chamilo
|
||||||
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
|
domain: chamilo.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
siteName: Chamilo LMS
|
||||||
|
adminUser: admin
|
||||||
|
adminEmail: '{{ .operator.email }}'
|
||||||
|
db:
|
||||||
|
host: '{{ .apps.mysql.host }}'
|
||||||
|
port: "3306"
|
||||||
|
name: chamilo
|
||||||
|
user: chamilo
|
||||||
|
smtp:
|
||||||
|
host: '{{ .apps.smtp.host }}'
|
||||||
|
port: '{{ .apps.smtp.port }}'
|
||||||
|
from: '{{ .apps.smtp.from }}'
|
||||||
|
user: '{{ .apps.smtp.user }}'
|
||||||
|
defaultSecrets:
|
||||||
|
- key: adminPassword
|
||||||
|
- key: dbPassword
|
||||||
|
- key: smtpPassword
|
||||||
|
requiredSecrets:
|
||||||
|
- mysql.rootPassword
|
||||||
4
chamilo/versions/1/namespace.yaml
Normal file
4
chamilo/versions/1/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
11
chamilo/versions/1/pvc.yaml
Normal file
11
chamilo/versions/1/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: chamilo-data
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
13
chamilo/versions/1/service.yaml
Normal file
13
chamilo/versions/1/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: chamilo
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
component: web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
4
community-search/app.yaml
Normal file
4
community-search/app.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
name: community-search
|
||||||
|
is: community-search
|
||||||
|
description: Community Search is a federated, self-hosted search engine built on community-curated indexes rather than global web crawling.
|
||||||
|
latest: "0"
|
||||||
84
community-search/versions/0/deployment.yaml
Normal file
84
community-search/versions/0/deployment.yaml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: community-search
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
fsGroup: 999
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: community-search
|
||||||
|
image: payneio/community-search:0.1.2
|
||||||
|
imagePullPolicy: Always
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
env:
|
||||||
|
- name: COMMUNITY_SEARCH_BIND_ADDR
|
||||||
|
value: "0.0.0.0"
|
||||||
|
- name: COMMUNITY_SEARCH_PORT
|
||||||
|
value: "8080"
|
||||||
|
- name: COMMUNITY_SEARCH_DATA_DIR
|
||||||
|
value: "/data"
|
||||||
|
- name: COMMUNITY_SEARCH_INDEX_PATH
|
||||||
|
value: "/data/index"
|
||||||
|
- name: COMMUNITY_SEARCH_ADMIN_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: community-search-secrets
|
||||||
|
key: adminToken
|
||||||
|
- name: SELF_URL
|
||||||
|
value: "{{ .selfUrl }}"
|
||||||
|
- name: SELF_NAME
|
||||||
|
value: "{{ .selfName }}"
|
||||||
|
- name: RUST_LOG
|
||||||
|
value: "community_search=info,tower_http=warn"
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1
|
||||||
|
memory: 1Gi
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/collections
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/collections
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: community-search-data
|
||||||
25
community-search/versions/0/ingress.yaml
Normal file
25
community-search/versions/0/ingress.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: community-search
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: community-search
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
15
community-search/versions/0/kustomization.yaml
Normal file
15
community-search/versions/0/kustomization.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: community-search
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: community-search
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
12
community-search/versions/0/manifest.yaml
Normal file
12
community-search/versions/0/manifest.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
version: 0.1.2_1
|
||||||
|
requires: []
|
||||||
|
defaultConfig:
|
||||||
|
namespace: community-search
|
||||||
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
|
domain: search.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
selfUrl: 'https://search.{{ .cloud.domain }}'
|
||||||
|
selfName: 'Community Search'
|
||||||
|
defaultSecrets:
|
||||||
|
- key: adminToken
|
||||||
4
community-search/versions/0/namespace.yaml
Normal file
4
community-search/versions/0/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: "{{ .namespace }}"
|
||||||
10
community-search/versions/0/pvc.yaml
Normal file
10
community-search/versions/0/pvc.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: community-search-data
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
13
community-search/versions/0/service.yaml
Normal file
13
community-search/versions/0/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: community-search
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 8080
|
||||||
|
selector:
|
||||||
|
component: web
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
version: v1.12.0
|
version: v1.12.0-1
|
||||||
requires:
|
requires:
|
||||||
- name: metallb
|
- name: metallb
|
||||||
defaultConfig:
|
defaultConfig:
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ spec:
|
|||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 50m
|
||||||
memory: 200Mi
|
memory: 200Mi
|
||||||
limits:
|
limits:
|
||||||
cpu: 500m
|
cpu: 500m
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: v1.7.8
|
version: v1.7.8-1
|
||||||
requires:
|
requires:
|
||||||
- name: longhorn
|
- name: longhorn
|
||||||
- name: traefik
|
- name: traefik
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ spec:
|
|||||||
- OPTIONS
|
- OPTIONS
|
||||||
accessControlAllowOriginList:
|
accessControlAllowOriginList:
|
||||||
- "*"
|
- "*"
|
||||||
|
accessControlAllowHeaders:
|
||||||
|
- X-Requested-With
|
||||||
|
- Content-Type
|
||||||
|
- Authorization
|
||||||
|
- Date
|
||||||
accessControlMaxAge: 100
|
accessControlMaxAge: 100
|
||||||
customRequestHeaders:
|
customRequestHeaders:
|
||||||
X-Forwarded-Proto: https
|
X-Forwarded-Proto: https
|
||||||
|
|||||||
5
cryptpad/app.yaml
Normal file
5
cryptpad/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: cryptpad
|
||||||
|
is: cryptpad
|
||||||
|
description: CryptPad is an end-to-end encrypted collaboration suite with documents, spreadsheets, kanban boards, and more.
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/cryptpad.svg
|
||||||
|
latest: "2024"
|
||||||
30
cryptpad/versions/2024/README.md
Normal file
30
cryptpad/versions/2024/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# CryptPad
|
||||||
|
|
||||||
|
CryptPad is a privacy-first, end-to-end encrypted collaboration suite. Documents are encrypted in the browser before being stored — the server never sees plaintext content.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Main domain for CryptPad
|
||||||
|
- **sandboxDomain** - Sandbox subdomain for secure iframe isolation (default: `cryptpad-sandbox.{your-cloud-domain}`)
|
||||||
|
- **storage** - Persistent volume size (default: `2Gi`)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
No account is required to create documents. Just visit the app URL and start creating spreadsheets, code pads, presentations, or rich-text documents. Share the document URL with collaborators.
|
||||||
|
|
||||||
|
## Admin Setup
|
||||||
|
|
||||||
|
To access the admin panel, you need to register an account and then link it to the `adminKey` secret:
|
||||||
|
|
||||||
|
1. Register an account at the app URL
|
||||||
|
2. Go to your user settings and copy your **Public Signing Key**
|
||||||
|
3. The `adminKey` in `secrets.yaml` should match this key — it is pre-populated with a generated value, but you must replace it with your actual account's public signing key after registering
|
||||||
|
4. Once set, the **Admin** link will appear in the user menu
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Both the main domain and the sandbox domain must be accessible — CryptPad uses the sandbox domain for secure iframe isolation, and the app will not function correctly if one is missing
|
||||||
|
- All encryption keys are generated client-side — if you lose your account passphrase, documents cannot be recovered
|
||||||
|
- The instance can be configured to require registration before creating documents (via the admin panel)
|
||||||
90
cryptpad/versions/2024/deployment.yaml
Normal file
90
cryptpad/versions/2024/deployment.yaml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: cryptpad
|
||||||
|
namespace: cryptpad
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
runAsNonRoot: false
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
initContainers:
|
||||||
|
- name: seed-config
|
||||||
|
image: cryptpad/cryptpad:latest
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
if [ ! -f /config-dest/config.example.js ]; then
|
||||||
|
cp /cryptpad/config/config.example.js /config-dest/config.example.js
|
||||||
|
fi
|
||||||
|
volumeMounts:
|
||||||
|
- name: cryptpad-config
|
||||||
|
mountPath: /config-dest
|
||||||
|
containers:
|
||||||
|
- name: cryptpad
|
||||||
|
image: cryptpad/cryptpad:latest
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 3000
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: CPAD_CONF
|
||||||
|
value: /cryptpad/config/config.js
|
||||||
|
- name: CPAD_MAIN_DOMAIN
|
||||||
|
value: https://{{ .domain }}
|
||||||
|
- name: CPAD_SANDBOX_DOMAIN
|
||||||
|
value: https://{{ .sandboxDomain }}
|
||||||
|
- name: CPAD_TRUSTED_PROXY
|
||||||
|
value: "true"
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
memory: 1Gi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 256Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: cryptpad-data
|
||||||
|
mountPath: /cryptpad/data
|
||||||
|
- name: cryptpad-config
|
||||||
|
mountPath: /cryptpad/config
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 90
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
timeoutSeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
volumes:
|
||||||
|
- name: cryptpad-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: cryptpad-data
|
||||||
|
- name: cryptpad-config
|
||||||
|
emptyDir: {}
|
||||||
|
restartPolicy: Always
|
||||||
37
cryptpad/versions/2024/ingress.yaml
Normal file
37
cryptpad/versions/2024/ingress.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: cryptpad
|
||||||
|
namespace: cryptpad
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: cryptpad
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- host: {{ .sandboxDomain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: cryptpad
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
- {{ .sandboxDomain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
15
cryptpad/versions/2024/kustomization.yaml
Normal file
15
cryptpad/versions/2024/kustomization.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: cryptpad
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: cryptpad
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
10
cryptpad/versions/2024/manifest.yaml
Normal file
10
cryptpad/versions/2024/manifest.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version: 2024.x-1
|
||||||
|
defaultConfig:
|
||||||
|
namespace: cryptpad
|
||||||
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
|
domain: cryptpad.{{ .cloud.domain }}
|
||||||
|
sandboxDomain: cryptpad-sandbox.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
defaultSecrets:
|
||||||
|
- key: adminKey
|
||||||
4
cryptpad/versions/2024/namespace.yaml
Normal file
4
cryptpad/versions/2024/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
11
cryptpad/versions/2024/pvc.yaml
Normal file
11
cryptpad/versions/2024/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: cryptpad-data
|
||||||
|
namespace: cryptpad
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .storage }}
|
||||||
13
cryptpad/versions/2024/service.yaml
Normal file
13
cryptpad/versions/2024/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: cryptpad
|
||||||
|
namespace: cryptpad
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
component: web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 3000
|
||||||
|
protocol: TCP
|
||||||
@@ -95,7 +95,7 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: decidim-secrets
|
name: decidim-secrets
|
||||||
key: smtpPassword
|
key: smtp.password
|
||||||
- name: SMTP_DOMAIN
|
- name: SMTP_DOMAIN
|
||||||
value: {{ .domain }}
|
value: {{ .domain }}
|
||||||
- name: SMTP_FROM
|
- name: SMTP_FROM
|
||||||
@@ -136,9 +136,9 @@ spec:
|
|||||||
ephemeral-storage: 10Gi
|
ephemeral-storage: 10Gi
|
||||||
memory: 4Gi
|
memory: 4Gi
|
||||||
requests:
|
requests:
|
||||||
cpu: 500m
|
cpu: 50m
|
||||||
ephemeral-storage: 50Mi
|
ephemeral-storage: 50Mi
|
||||||
memory: 1Gi
|
memory: 256Mi
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: decidim-data
|
- name: decidim-data
|
||||||
mountPath: /code/public/uploads
|
mountPath: /code/public/uploads
|
||||||
@@ -203,8 +203,8 @@ spec:
|
|||||||
cpu: 1000m
|
cpu: 1000m
|
||||||
memory: 2Gi
|
memory: 2Gi
|
||||||
requests:
|
requests:
|
||||||
cpu: 250m
|
cpu: 50m
|
||||||
memory: 512Mi
|
memory: 256Mi
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: decidim-data
|
- name: decidim-data
|
||||||
mountPath: /code/public/uploads
|
mountPath: /code/public/uploads
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
version: 0.31.0-1
|
version: 0.31.0-2
|
||||||
requires:
|
requires:
|
||||||
- name: postgres
|
- name: postgres
|
||||||
installed_as: postgres
|
|
||||||
- name: redis
|
- name: redis
|
||||||
installed_as: redis
|
|
||||||
- name: smtp
|
- name: smtp
|
||||||
defaultConfig:
|
defaultConfig:
|
||||||
namespace: decidim
|
namespace: decidim
|
||||||
externalDnsDomain: '{{ .cloud.domain }}'
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
storage: 20Gi
|
storage: 2Gi
|
||||||
systemAdminEmail: '{{ .operator.email }}'
|
systemAdminEmail: '{{ .operator.email }}'
|
||||||
siteName: 'Decidim'
|
siteName: 'Decidim'
|
||||||
domain: decidim.{{ .cloud.domain }}
|
domain: decidim.{{ .cloud.domain }}
|
||||||
@@ -32,10 +30,10 @@ defaultSecrets:
|
|||||||
- key: systemAdminPassword
|
- key: systemAdminPassword
|
||||||
- key: secretKeyBase
|
- key: secretKeyBase
|
||||||
default: "{{ random.AlphaNum 128 }}"
|
default: "{{ random.AlphaNum 128 }}"
|
||||||
- key: smtpPassword
|
|
||||||
- key: dbPassword
|
- key: dbPassword
|
||||||
- key: dbUrl
|
- key: dbUrl
|
||||||
default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}"
|
default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}"
|
||||||
requiredSecrets:
|
requiredSecrets:
|
||||||
- postgres.password
|
- postgres.password
|
||||||
- redis.password
|
- redis.password
|
||||||
|
- smtp.password
|
||||||
|
|||||||
@@ -185,9 +185,9 @@ spec:
|
|||||||
ephemeral-storage: 10Gi
|
ephemeral-storage: 10Gi
|
||||||
memory: 8Gi
|
memory: 8Gi
|
||||||
requests:
|
requests:
|
||||||
cpu: 750m
|
cpu: 50m
|
||||||
ephemeral-storage: 50Mi
|
ephemeral-storage: 50Mi
|
||||||
memory: 1Gi
|
memory: 512Mi
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: discourse-data
|
- name: discourse-data
|
||||||
mountPath: /shared
|
mountPath: /shared
|
||||||
@@ -292,7 +292,7 @@ spec:
|
|||||||
ephemeral-storage: 2Gi
|
ephemeral-storage: 2Gi
|
||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
requests:
|
requests:
|
||||||
cpu: 375m
|
cpu: 50m
|
||||||
ephemeral-storage: 50Mi
|
ephemeral-storage: 50Mi
|
||||||
memory: 512Mi
|
memory: 512Mi
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: 3.5.3-1
|
version: 3.5.3-3
|
||||||
requires:
|
requires:
|
||||||
- name: postgres
|
- name: postgres
|
||||||
- name: redis
|
- name: redis
|
||||||
@@ -6,7 +6,7 @@ requires:
|
|||||||
defaultConfig:
|
defaultConfig:
|
||||||
namespace: discourse
|
namespace: discourse
|
||||||
externalDnsDomain: '{{ .cloud.domain }}'
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
storage: 10Gi
|
storage: 2Gi
|
||||||
adminEmail: '{{ .operator.email }}'
|
adminEmail: '{{ .operator.email }}'
|
||||||
adminUsername: admin
|
adminUsername: admin
|
||||||
siteName: 'Community'
|
siteName: 'Community'
|
||||||
@@ -30,7 +30,7 @@ defaultConfig:
|
|||||||
defaultSecrets:
|
defaultSecrets:
|
||||||
- key: adminPassword
|
- key: adminPassword
|
||||||
- key: secretKeyBase
|
- key: secretKeyBase
|
||||||
default: "{{ random.AlphaNum 64 }}"
|
default: "{{ random.Hex 32 }}"
|
||||||
- key: smtpPassword
|
- key: smtpPassword
|
||||||
- key: dbPassword
|
- key: dbPassword
|
||||||
- key: dbUrl
|
- key: dbUrl
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ requires:
|
|||||||
defaultConfig:
|
defaultConfig:
|
||||||
namespace: docker-registry
|
namespace: docker-registry
|
||||||
host: "registry.{{ .cloud.internalDomain }}"
|
host: "registry.{{ .cloud.internalDomain }}"
|
||||||
storage: "100Gi"
|
storage: 5Gi
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ resources:
|
|||||||
- service.yaml
|
- service.yaml
|
||||||
- pvc.yaml
|
- pvc.yaml
|
||||||
- db-init-job.yaml
|
- db-init-job.yaml
|
||||||
|
- mysql-db-init-job.yaml
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
version: 2.0.0
|
version: 2.0.0-1
|
||||||
requires:
|
requires:
|
||||||
- name: postgres
|
- name: postgres
|
||||||
|
- name: mysql
|
||||||
defaultConfig:
|
defaultConfig:
|
||||||
namespace: e2e-test-app
|
namespace: e2e-test-app
|
||||||
domain: e2e-test-app.{{ .cloud.domain }}
|
domain: e2e-test-app.{{ .cloud.domain }}
|
||||||
@@ -12,9 +13,16 @@ defaultConfig:
|
|||||||
port: '{{ .apps.postgres.port }}'
|
port: '{{ .apps.postgres.port }}'
|
||||||
name: e2e_test_app
|
name: e2e_test_app
|
||||||
user: e2e_test_app
|
user: e2e_test_app
|
||||||
|
mysql:
|
||||||
|
host: '{{ .apps.mysql.host }}'
|
||||||
|
port: '{{ .apps.mysql.port }}'
|
||||||
|
name: e2e_test_app
|
||||||
|
user: e2e_test_app
|
||||||
defaultSecrets:
|
defaultSecrets:
|
||||||
- key: dbPassword
|
- key: dbPassword
|
||||||
- key: dbUrl
|
- key: dbUrl
|
||||||
default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable"
|
default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable"
|
||||||
|
- key: mysqlPassword
|
||||||
requiredSecrets:
|
requiredSecrets:
|
||||||
- postgres.password
|
- postgres.password
|
||||||
|
- mysql.rootPassword
|
||||||
|
|||||||
64
e2e-test-app/versions/2/mysql-db-init-job.yaml
Normal file
64
e2e-test-app/versions/2/mysql-db-init-job.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: e2e-test-app-mysql-db-init
|
||||||
|
labels:
|
||||||
|
component: mysql-db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: mysql-db-init
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: mysql-init
|
||||||
|
image: mysql:9.1.0
|
||||||
|
env:
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: e2e-test-app-secrets
|
||||||
|
key: mysql.rootPassword
|
||||||
|
- name: DB_HOST
|
||||||
|
value: {{ .mysql.host }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "{{ .mysql.port }}"
|
||||||
|
- name: DB_NAME
|
||||||
|
value: {{ .mysql.name }}
|
||||||
|
- name: DB_USER
|
||||||
|
value: {{ .mysql.user }}
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: e2e-test-app-secrets
|
||||||
|
key: mysqlPassword
|
||||||
|
command: ["/bin/bash", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Waiting for MySQL to be ready..."
|
||||||
|
until mysql -h "${DB_HOST}" -P "${DB_PORT}" -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "SELECT 1" &>/dev/null; do
|
||||||
|
echo "MySQL not ready, retrying..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
echo "MySQL ready"
|
||||||
|
|
||||||
|
mysql -h "${DB_HOST}" -P "${DB_PORT}" -uroot -p"${MYSQL_ROOT_PASSWORD}" <<EOF
|
||||||
|
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`;
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USER}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
ALTER USER '${DB_USER}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
|
||||||
|
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mysql -h "${DB_HOST}" -P "${DB_PORT}" -uroot -p"${MYSQL_ROOT_PASSWORD}" "${DB_NAME}" <<EOF
|
||||||
|
CREATE TABLE IF NOT EXISTS e2e_test_data (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
test_key VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
test_value TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "MySQL initialization complete"
|
||||||
5
etherpad/app.yaml
Normal file
5
etherpad/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name: etherpad
|
||||||
|
is: etherpad
|
||||||
|
description: Etherpad is a highly customizable open source online editor providing collaborative editing in real-time.
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/etherpad.svg
|
||||||
|
latest: "2"
|
||||||
36
etherpad/versions/2/README.md
Normal file
36
etherpad/versions/2/README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Etherpad
|
||||||
|
|
||||||
|
Etherpad is a real-time collaborative document editor. Multiple users can write and edit documents simultaneously.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **PostgreSQL** - Database for storing pads and history
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key settings in `config.yaml`:
|
||||||
|
|
||||||
|
- **domain** - Where Etherpad will be accessible
|
||||||
|
- **storage** - Persistent volume size (default: `2Gi`)
|
||||||
|
- **title** - Instance name shown in the browser (default: `Etherpad`)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
No account is required to create or edit pads. Just visit the app URL and:
|
||||||
|
|
||||||
|
- Click **New Pad** to create a document
|
||||||
|
- Share the pad URL with collaborators — anyone with the link can edit in real time
|
||||||
|
|
||||||
|
## Admin Panel
|
||||||
|
|
||||||
|
An admin panel is available at `/admin`:
|
||||||
|
- **Username**: `admin`
|
||||||
|
- **Password**: value of `adminPassword` in your `secrets.yaml`
|
||||||
|
|
||||||
|
Use the admin panel to manage plugins, view connected users, and configure the instance.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Pads are identified by URL — anyone with the link can view and edit by default
|
||||||
|
- Password-protected pads can be created via the admin panel settings
|
||||||
|
- The admin panel also provides access to the plugin manager for extending functionality
|
||||||
63
etherpad/versions/2/db-init-job.yaml
Normal file
63
etherpad/versions/2/db-init-job.yaml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: etherpad-db-init
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: db-init
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: db-init
|
||||||
|
image: postgres:17
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
command: ["/bin/bash", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOSTNAME} -U postgres <<EOF
|
||||||
|
DO \$\$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${DB_USERNAME}') THEN
|
||||||
|
CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
|
||||||
|
ELSE
|
||||||
|
ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
\$\$;
|
||||||
|
|
||||||
|
SELECT 'CREATE DATABASE ${DB_DATABASE_NAME}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_DATABASE_NAME}')\gexec
|
||||||
|
ALTER DATABASE ${DB_DATABASE_NAME} OWNER TO ${DB_USERNAME};
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE ${DB_DATABASE_NAME} TO ${DB_USERNAME};
|
||||||
|
EOF
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgres-secrets
|
||||||
|
key: password
|
||||||
|
- name: DB_HOSTNAME
|
||||||
|
value: "{{ .db.host }}"
|
||||||
|
- name: DB_DATABASE_NAME
|
||||||
|
value: "{{ .db.name }}"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "{{ .db.user }}"
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: etherpad-secrets
|
||||||
|
key: dbPassword
|
||||||
97
etherpad/versions/2/deployment.yaml
Normal file
97
etherpad/versions/2/deployment.yaml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: etherpad
|
||||||
|
namespace: etherpad
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 5001
|
||||||
|
runAsGroup: 0
|
||||||
|
fsGroup: 5001
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: etherpad
|
||||||
|
image: etherpad/etherpad:2.2.7
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 9001
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: DB_TYPE
|
||||||
|
value: postgres
|
||||||
|
- name: DB_HOST
|
||||||
|
value: {{ .db.host }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "{{ .db.port }}"
|
||||||
|
- name: DB_NAME
|
||||||
|
value: {{ .db.name }}
|
||||||
|
- name: DB_USER
|
||||||
|
value: {{ .db.user }}
|
||||||
|
- name: DB_PASS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: etherpad-secrets
|
||||||
|
key: dbPassword
|
||||||
|
- name: TITLE
|
||||||
|
value: {{ .title }}
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: etherpad-secrets
|
||||||
|
key: adminPassword
|
||||||
|
- name: TRUST_PROXY
|
||||||
|
value: "true"
|
||||||
|
- name: EDIT_ONLY
|
||||||
|
value: "false"
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 128Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: etherpad-data
|
||||||
|
mountPath: /opt/etherpad-lite/var
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 9001
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 9001
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
volumes:
|
||||||
|
- name: etherpad-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: etherpad-data
|
||||||
|
restartPolicy: Always
|
||||||
26
etherpad/versions/2/ingress.yaml
Normal file
26
etherpad/versions/2/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: etherpad
|
||||||
|
namespace: etherpad
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: {{ .domain }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: etherpad
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .domain }}
|
||||||
|
secretName: {{ .tlsSecretName }}
|
||||||
16
etherpad/versions/2/kustomization.yaml
Normal file
16
etherpad/versions/2/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: etherpad
|
||||||
|
labels:
|
||||||
|
- includeSelectors: true
|
||||||
|
pairs:
|
||||||
|
app: etherpad
|
||||||
|
managedBy: kustomize
|
||||||
|
partOf: wild-cloud
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- db-init-job.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- pvc.yaml
|
||||||
22
etherpad/versions/2/manifest.yaml
Normal file
22
etherpad/versions/2/manifest.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
version: 2.2.7-1
|
||||||
|
requires:
|
||||||
|
- name: postgres
|
||||||
|
defaultConfig:
|
||||||
|
namespace: etherpad
|
||||||
|
externalDnsDomain: '{{ .cloud.domain }}'
|
||||||
|
domain: etherpad.{{ .cloud.domain }}
|
||||||
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
|
storage: 2Gi
|
||||||
|
title: Etherpad
|
||||||
|
db:
|
||||||
|
host: '{{ .apps.postgres.host }}'
|
||||||
|
port: '{{ .apps.postgres.port }}'
|
||||||
|
name: etherpad
|
||||||
|
user: etherpad
|
||||||
|
defaultSecrets:
|
||||||
|
- key: adminPassword
|
||||||
|
- key: dbPassword
|
||||||
|
- key: dbUrl
|
||||||
|
default: 'postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable'
|
||||||
|
requiredSecrets:
|
||||||
|
- postgres.password
|
||||||
4
etherpad/versions/2/namespace.yaml
Normal file
4
etherpad/versions/2/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user