Delete directory 'docs/superpowers'
This commit is contained in:
@@ -1,786 +0,0 @@
|
||||
# Frappe Bench Infrastructure Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Convert the current Frappe LMS container setup into a production-style, multisite-capable Frappe Bench backed by host MariaDB, host Redis, and agenix-managed secrets.
|
||||
|
||||
**Architecture:** MariaDB and Redis become host infrastructure modules under `hosts/AZ-CLD-1/services/`. The Frappe runtime becomes one shared Bench module under `hosts/AZ-CLD-1/services/containers/frappe.nix`, with separate backend, frontend, websocket, worker, and scheduler containers. Secrets are provided through agenix and consumed via `config.age.secrets.frappe-env.path`.
|
||||
|
||||
**Tech Stack:** NixOS modules, Podman OCI containers, MariaDB, Redis, Frappe Bench, Traefik, agenix, alejandra, `nixos-rebuild build`.
|
||||
|
||||
**Execution constraints:** Do not run `nixos-rebuild switch`. Do not run `git push`. Build-only validation is allowed.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create: `hosts/AZ-CLD-1/services/mariadb.nix`
|
||||
- Owns host MariaDB configuration, Frappe DB admin bootstrap, firewall rules, and MariaDB dump timer.
|
||||
- Create: `hosts/AZ-CLD-1/services/redis.nix`
|
||||
- Owns host Redis configuration and firewall rules.
|
||||
- Rename/create: `hosts/AZ-CLD-1/services/containers/frappe.nix`
|
||||
- Replaces `frappe-lms.nix` as the shared Bench runtime.
|
||||
- Owns Frappe directories, Bench config, site/app initialization, containers, backup timer, Traefik route, and CLI helper.
|
||||
- Modify: `hosts/AZ-CLD-1/services/default.nix`
|
||||
- Imports `mariadb.nix` and `redis.nix`.
|
||||
- Modify: `hosts/AZ-CLD-1/services/containers/default.nix`
|
||||
- Imports `frappe.nix` instead of the old LMS-specific file.
|
||||
- Modify: `hosts/common/ports.nix`
|
||||
- Renames the internal service port key from `frappe-lms` to `frappe` while preserving port `3052`.
|
||||
- Modify: `hosts/AZ-CLD-1/secrets.nix`
|
||||
- Adds the host-local agenix secret reference `frappe-env`.
|
||||
- Modify: `secrets.nix`
|
||||
- Adds `secrets/frappe-env.age` to the public key registry.
|
||||
- Create: `secrets/frappe-env.age`
|
||||
- Encrypted agenix env file containing `FRAPPE_DB_ADMIN_PASSWORD` and `FRAPPE_ADMIN_PASSWORD`.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Add agenix Secret Plumbing
|
||||
|
||||
**Files:**
|
||||
- Create: `secrets/frappe-env.age`
|
||||
- Modify: `secrets.nix`
|
||||
- Modify: `hosts/AZ-CLD-1/secrets.nix`
|
||||
|
||||
- [ ] **Step 1: Generate and encrypt the Frappe env secret**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
db_secret_value="$(${openssl:-openssl} rand -base64 48 | tr -d '\n')"
|
||||
admin_secret_value="$(${openssl:-openssl} rand -base64 32 | tr -d '\n')"
|
||||
tmp_secret="$(mktemp)"
|
||||
cat > "$tmp_secret" <<EOF_SECRET
|
||||
FRAPPE_DB_ADMIN_PASSWORD=$db_secret_value
|
||||
FRAPPE_ADMIN_PASSWORD=$admin_secret_value
|
||||
EOF_SECRET
|
||||
export tmp_secret
|
||||
EDITOR='sh -c '\''cat "$tmp_secret" > "$1"'\'' sh' agenix -e secrets/frappe-env.age
|
||||
rm -f "$tmp_secret"
|
||||
```
|
||||
|
||||
Expected: `secrets/frappe-env.age` exists and contains encrypted data.
|
||||
|
||||
If `openssl` is not in PATH, run inside the dev shell:
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Register the encrypted secret in the global agenix registry**
|
||||
|
||||
In `secrets.nix`, add this line near the other `secrets/*.age` entries:
|
||||
|
||||
```nix
|
||||
"secrets/frappe-env.age".publicKeys = systems ++ users;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Register the secret for `AZ-CLD-1`**
|
||||
|
||||
In `hosts/AZ-CLD-1/secrets.nix`, add this attribute inside `age.secrets`:
|
||||
|
||||
```nix
|
||||
frappe-env = {
|
||||
file = ../../secrets/frappe-env.age;
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Format the changed Nix files**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
alejandra secrets.nix hosts/AZ-CLD-1/secrets.nix
|
||||
```
|
||||
|
||||
Expected: both files format successfully.
|
||||
|
||||
- [ ] **Step 5: Verify the secret path is visible in the host config**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
nix eval --raw .#nixosConfigurations.AZ-CLD-1.config.age.secrets.frappe-env.file
|
||||
```
|
||||
|
||||
Expected output ends with:
|
||||
|
||||
```text
|
||||
secrets/frappe-env.age
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Commit the secret plumbing**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add secrets/frappe-env.age secrets.nix hosts/AZ-CLD-1/secrets.nix
|
||||
git commit -m "secrets: add frappe env secret"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Add Host MariaDB Infrastructure
|
||||
|
||||
**Files:**
|
||||
- Create: `hosts/AZ-CLD-1/services/mariadb.nix`
|
||||
- Modify: `hosts/AZ-CLD-1/services/default.nix`
|
||||
|
||||
- [ ] **Step 1: Create the MariaDB host module**
|
||||
|
||||
Create `hosts/AZ-CLD-1/services/mariadb.nix` with this content:
|
||||
|
||||
```nix
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
frappeEnvFile = config.age.secrets.frappe-env.path;
|
||||
frappeDbAdminUser = "frappe_admin";
|
||||
|
||||
waitForMariaDb = ''
|
||||
mariadb_ready=0
|
||||
for attempt in $(${pkgs.coreutils}/bin/seq 1 60); do
|
||||
if ${pkgs.mariadb_118}/bin/mysql -u root -N -e 'SELECT 1' >/dev/null 2>&1; then
|
||||
mariadb_ready=1
|
||||
break
|
||||
fi
|
||||
${pkgs.coreutils}/bin/sleep 2
|
||||
done
|
||||
|
||||
if [ "$mariadb_ready" != "1" ]; then
|
||||
echo "MariaDB did not become ready in time" >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
in {
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb_118;
|
||||
settings.mysqld = {
|
||||
bind-address = "0.0.0.0";
|
||||
character-set-server = "utf8mb4";
|
||||
collation-server = "utf8mb4_unicode_ci";
|
||||
skip-character-set-client-handshake = true;
|
||||
skip-innodb-read-only-compressed = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.extraCommands = ''
|
||||
iptables -A INPUT -p tcp -s 127.0.0.1 --dport 3306 -j ACCEPT
|
||||
iptables -A INPUT -p tcp -s 10.89.0.0/24 --dport 3306 -j ACCEPT
|
||||
'';
|
||||
|
||||
systemd.services.mariadb-frappe-admin-init = {
|
||||
description = "Create shared Frappe MariaDB admin user";
|
||||
wantedBy = ["multi-user.target"];
|
||||
after = ["mysql.service"];
|
||||
requires = ["mysql.service"];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = "root";
|
||||
Group = "root";
|
||||
};
|
||||
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
set -a
|
||||
source ${frappeEnvFile}
|
||||
set +a
|
||||
|
||||
: "''${FRAPPE_DB_ADMIN_PASSWORD:?FRAPPE_DB_ADMIN_PASSWORD is missing in ${frappeEnvFile}}"
|
||||
|
||||
${waitForMariaDb}
|
||||
|
||||
escaped_db_admin_password=$(${pkgs.coreutils}/bin/printf '%s' "$FRAPPE_DB_ADMIN_PASSWORD" | ${pkgs.python3}/bin/python3 -c 'import sys; print(sys.stdin.read().replace(chr(39), chr(39) * 2), end="")')
|
||||
|
||||
${pkgs.mariadb_118}/bin/mysql -u root <<SQL
|
||||
CREATE USER IF NOT EXISTS '${frappeDbAdminUser}'@'10.89.0.%' IDENTIFIED BY '$escaped_db_admin_password';
|
||||
ALTER USER '${frappeDbAdminUser}'@'10.89.0.%' IDENTIFIED BY '$escaped_db_admin_password';
|
||||
GRANT ALL PRIVILEGES ON *.* TO '${frappeDbAdminUser}'@'10.89.0.%' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.mariadb-backup-all = {
|
||||
description = "Create full MariaDB dump backup";
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
Group = "root";
|
||||
};
|
||||
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
backup_dir=/var/backup/mariadb
|
||||
timestamp="$(${pkgs.coreutils}/bin/date +%Y%m%d-%H%M%S)"
|
||||
${pkgs.coreutils}/bin/mkdir -p "$backup_dir"
|
||||
${pkgs.coreutils}/bin/chown root:root "$backup_dir"
|
||||
${pkgs.coreutils}/bin/chmod 0750 "$backup_dir"
|
||||
|
||||
${pkgs.mariadb_118}/bin/mariadb-dump --all-databases --single-transaction --quick --lock-tables=false \
|
||||
| ${pkgs.gzip}/bin/gzip -c > "$backup_dir/all-$timestamp.sql.gz"
|
||||
|
||||
${pkgs.findutils}/bin/find "$backup_dir" -type f -name 'all-*.sql.gz' -mtime +14 -delete
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.mariadb-backup-all = {
|
||||
wantedBy = ["timers.target"];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 03:20:00";
|
||||
RandomizedDelaySec = "30m";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Import the MariaDB module**
|
||||
|
||||
In `hosts/AZ-CLD-1/services/default.nix`, add `./mariadb.nix` near `./postgres.nix`:
|
||||
|
||||
```nix
|
||||
./mariadb.nix
|
||||
./postgres.nix
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Format and evaluate MariaDB config**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
alejandra hosts/AZ-CLD-1/services/mariadb.nix hosts/AZ-CLD-1/services/default.nix
|
||||
nix eval .#nixosConfigurations.AZ-CLD-1.config.services.mysql.enable
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```text
|
||||
true
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify MariaDB is no longer planned to live in Frappe**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "services\.mysql|mariadb-frappe-admin-init" hosts/AZ-CLD-1/services
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `services.mysql` appears in `hosts/AZ-CLD-1/services/mariadb.nix` only.
|
||||
- `mariadb-frappe-admin-init` appears in `mariadb.nix` and future Frappe ordering references only.
|
||||
|
||||
- [ ] **Step 5: Commit MariaDB infrastructure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add hosts/AZ-CLD-1/services/mariadb.nix hosts/AZ-CLD-1/services/default.nix
|
||||
git commit -m "services: add host mariadb for frappe"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Add Host Redis Infrastructure
|
||||
|
||||
**Files:**
|
||||
- Create: `hosts/AZ-CLD-1/services/redis.nix`
|
||||
- Modify: `hosts/AZ-CLD-1/services/default.nix`
|
||||
|
||||
- [ ] **Step 1: Create the Redis host module**
|
||||
|
||||
Create `hosts/AZ-CLD-1/services/redis.nix` with this content:
|
||||
|
||||
```nix
|
||||
{...}: {
|
||||
services.redis.servers."" = {
|
||||
enable = true;
|
||||
port = 6379;
|
||||
bind = null;
|
||||
databases = 16;
|
||||
appendOnly = true;
|
||||
appendFsync = "everysec";
|
||||
settings = {
|
||||
protected-mode = "no";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.extraCommands = ''
|
||||
iptables -A INPUT -p tcp -s 127.0.0.1 --dport 6379 -j ACCEPT
|
||||
iptables -A INPUT -p tcp -s 10.89.0.0/24 --dport 6379 -j ACCEPT
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Import the Redis module**
|
||||
|
||||
In `hosts/AZ-CLD-1/services/default.nix`, add `./redis.nix` near the other infrastructure services:
|
||||
|
||||
```nix
|
||||
./redis.nix
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Format and evaluate Redis config**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
alejandra hosts/AZ-CLD-1/services/redis.nix hosts/AZ-CLD-1/services/default.nix
|
||||
nix eval .#nixosConfigurations.AZ-CLD-1.config.services.redis.servers."".enable
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```text
|
||||
true
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit Redis infrastructure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add hosts/AZ-CLD-1/services/redis.nix hosts/AZ-CLD-1/services/default.nix
|
||||
git commit -m "services: add host redis"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Rename and Reshape the Frappe Bench Module
|
||||
|
||||
**Files:**
|
||||
- Rename/create: `hosts/AZ-CLD-1/services/containers/frappe.nix`
|
||||
- Remove from imports: `hosts/AZ-CLD-1/services/containers/frappe-lms.nix`
|
||||
- Modify: `hosts/common/ports.nix`
|
||||
|
||||
- [ ] **Step 1: Rename the Frappe module file**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
if [ -f hosts/AZ-CLD-1/services/containers/frappe-lms.nix ]; then
|
||||
mv hosts/AZ-CLD-1/services/containers/frappe-lms.nix hosts/AZ-CLD-1/services/containers/frappe.nix
|
||||
fi
|
||||
```
|
||||
|
||||
Expected: `hosts/AZ-CLD-1/services/containers/frappe.nix` exists.
|
||||
|
||||
- [ ] **Step 2: Rename the service port key**
|
||||
|
||||
In `hosts/common/ports.nix`, replace:
|
||||
|
||||
```nix
|
||||
frappe-lms = 3052;
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```nix
|
||||
frappe = 3052;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Replace the top-level Frappe constants**
|
||||
|
||||
At the top of `hosts/AZ-CLD-1/services/containers/frappe.nix`, use this structure in the `let` block:
|
||||
|
||||
```nix
|
||||
serviceName = "frappe";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
|
||||
primaryDomain = "learn.az-gruppe.com";
|
||||
image = "ghcr.io/frappe/lms:stable";
|
||||
|
||||
dataDir = "/var/lib/${serviceName}";
|
||||
sitesDir = "${dataDir}/sites";
|
||||
frappeEnvFile = config.age.secrets.frappe-env.path;
|
||||
|
||||
dbHost = "10.89.0.1";
|
||||
dbPort = "3306";
|
||||
dbAdminUser = "frappe_admin";
|
||||
|
||||
redisCacheUrl = "redis://10.89.0.1:6379/1";
|
||||
redisQueueUrl = "redis://10.89.0.1:6379/2";
|
||||
|
||||
sites = {
|
||||
"learn.az-gruppe.com" = {
|
||||
apps = ["lms"];
|
||||
adminPasswordEnv = "FRAPPE_ADMIN_PASSWORD";
|
||||
};
|
||||
};
|
||||
|
||||
siteNames = builtins.attrNames sites;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Remove LMS-specific MariaDB and Redis definitions**
|
||||
|
||||
Delete these pieces from `hosts/AZ-CLD-1/services/containers/frappe.nix`:
|
||||
|
||||
```nix
|
||||
redisCacheIp = "${ipBase}.71";
|
||||
redisQueueIp = "${ipBase}.72";
|
||||
redisCacheName = "${serviceName}-redis-cache";
|
||||
redisQueueName = "${serviceName}-redis-queue";
|
||||
```
|
||||
|
||||
Delete the entire local `services.mysql = { ... };` block.
|
||||
|
||||
Delete the container definitions for:
|
||||
|
||||
```text
|
||||
frappe-lms-redis-cache
|
||||
frappe-lms-redis-queue
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Update shared app base dependencies**
|
||||
|
||||
In `appBase`, remove container Redis dependencies so it looks like this:
|
||||
|
||||
```nix
|
||||
appBase = {
|
||||
inherit image;
|
||||
autoStart = true;
|
||||
volumes = ["${sitesDir}:/home/frappe/frappe-bench/sites"];
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Change the directory preparation service**
|
||||
|
||||
Update the directory service so it no longer checks a manual env file under `/var/lib`. Its script should be:
|
||||
|
||||
```nix
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${pkgs.coreutils}/bin/mkdir -p ${dataDir} ${sitesDir}
|
||||
${pkgs.coreutils}/bin/chown root:root ${dataDir}
|
||||
${pkgs.coreutils}/bin/chmod 0750 ${dataDir}
|
||||
${pkgs.coreutils}/bin/chown -R 1000:1000 ${sitesDir}
|
||||
${pkgs.coreutils}/bin/chmod 0750 ${sitesDir}
|
||||
'';
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Update configurator ordering and Redis config**
|
||||
|
||||
The configurator service must order after host MariaDB admin init and host Redis:
|
||||
|
||||
```nix
|
||||
after = [
|
||||
"mariadb-frappe-admin-init.service"
|
||||
"redis.service"
|
||||
"${serviceName}-directories.service"
|
||||
];
|
||||
requires = [
|
||||
"mariadb-frappe-admin-init.service"
|
||||
"redis.service"
|
||||
"${serviceName}-directories.service"
|
||||
];
|
||||
```
|
||||
|
||||
Inside the `bench set-config` commands, use host Redis URLs:
|
||||
|
||||
```bash
|
||||
bench set-config -g db_host ${dbHost}
|
||||
bench set-config -gp db_port ${dbPort}
|
||||
bench set-config -g redis_cache ${redisCacheUrl}
|
||||
bench set-config -g redis_queue ${redisQueueUrl}
|
||||
bench set-config -g redis_socketio ${redisQueueUrl}
|
||||
bench set-config -gp socketio_port 9000
|
||||
bench set-config -g chromium_path /usr/bin/chromium-headless-shell
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Replace site init with agenix-based multisite init**
|
||||
|
||||
In `${serviceName}-site-init`, source the agenix env file:
|
||||
|
||||
```bash
|
||||
set -a
|
||||
source ${frappeEnvFile}
|
||||
set +a
|
||||
|
||||
: "''${FRAPPE_DB_ADMIN_PASSWORD:?FRAPPE_DB_ADMIN_PASSWORD is missing in ${frappeEnvFile}}"
|
||||
```
|
||||
|
||||
For each configured site, use this shell pattern generated from the `sites` attribute set:
|
||||
|
||||
```bash
|
||||
: "''${FRAPPE_ADMIN_PASSWORD:?FRAPPE_ADMIN_PASSWORD is missing in ${frappeEnvFile}}"
|
||||
|
||||
if [ ! -f ${sitesDir}/learn.az-gruppe.com/site_config.json ]; then
|
||||
${pkgs.podman}/bin/podman run --rm \
|
||||
--name ${serviceName}-site-init-learn \
|
||||
--network web \
|
||||
--ip ${transientIp} \
|
||||
--env-file ${frappeEnvFile} \
|
||||
--env SITE_NAME=learn.az-gruppe.com \
|
||||
-v ${sitesDir}:/home/frappe/frappe-bench/sites \
|
||||
${image} \
|
||||
bash -c '
|
||||
set -euo pipefail
|
||||
bench new-site \
|
||||
--db-host ${dbHost} \
|
||||
--db-port ${dbPort} \
|
||||
--mariadb-user-host-login-scope=% \
|
||||
--db-root-username ${dbAdminUser} \
|
||||
--db-root-password "$FRAPPE_DB_ADMIN_PASSWORD" \
|
||||
--admin-password "$FRAPPE_ADMIN_PASSWORD" \
|
||||
"$SITE_NAME"
|
||||
'
|
||||
else
|
||||
echo "Frappe site learn.az-gruppe.com already exists"
|
||||
fi
|
||||
|
||||
for app in lms; do
|
||||
${pkgs.podman}/bin/podman run --rm \
|
||||
--name ${serviceName}-install-app-$app \
|
||||
--network web \
|
||||
--ip ${transientIp} \
|
||||
--env SITE_NAME=learn.az-gruppe.com \
|
||||
--env APP_NAME="$app" \
|
||||
-v ${sitesDir}:/home/frappe/frappe-bench/sites \
|
||||
${image} \
|
||||
bash -c '
|
||||
set -euo pipefail
|
||||
if bench --site "$SITE_NAME" list-apps | grep -Fxq "$APP_NAME"; then
|
||||
echo "App $APP_NAME already installed on $SITE_NAME"
|
||||
else
|
||||
bench --site "$SITE_NAME" install-app "$APP_NAME"
|
||||
fi
|
||||
'
|
||||
done
|
||||
```
|
||||
|
||||
After this works for `learn.az-gruppe.com`, convert the repeated site/app shell to generated Nix using `lib.concatMapStringsSep` and `lib.escapeShellArg` so future sites can be added by extending `sites`.
|
||||
|
||||
- [ ] **Step 9: Update container names and service references**
|
||||
|
||||
Ensure the container names are:
|
||||
|
||||
```text
|
||||
frappe-backend
|
||||
frappe-frontend
|
||||
frappe-websocket
|
||||
frappe-queue-short
|
||||
frappe-queue-long
|
||||
frappe-scheduler
|
||||
```
|
||||
|
||||
Ensure no names retain the `frappe-lms-` prefix.
|
||||
|
||||
- [ ] **Step 10: Update systemd ordering for all Frappe containers**
|
||||
|
||||
Every runtime container override must require site init and host Redis:
|
||||
|
||||
```nix
|
||||
systemd.services."podman-${backendName}" = {
|
||||
after = ["${serviceName}-site-init.service" "redis.service"];
|
||||
requires = ["${serviceName}-site-init.service" "redis.service"];
|
||||
};
|
||||
```
|
||||
|
||||
Apply the same pattern to frontend, websocket, queue-short, queue-long, and scheduler.
|
||||
|
||||
- [ ] **Step 11: Update the CLI helper name and status output**
|
||||
|
||||
The package name must be `frappe` via `serviceName = "frappe"`. The `status` command must check:
|
||||
|
||||
```bash
|
||||
systemctl status \
|
||||
mysql.service \
|
||||
redis.service \
|
||||
mariadb-frappe-admin-init.service \
|
||||
${serviceName}-configurator.service \
|
||||
${serviceName}-site-init.service \
|
||||
podman-${backendName}.service \
|
||||
podman-${frontendName}.service \
|
||||
podman-${websocketName}.service \
|
||||
podman-${queueShortName}.service \
|
||||
podman-${queueLongName}.service \
|
||||
podman-${schedulerName}.service
|
||||
```
|
||||
|
||||
- [ ] **Step 12: Format and statically verify Frappe changes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
alejandra hosts/AZ-CLD-1/services/containers/frappe.nix hosts/common/ports.nix
|
||||
rg -n "frappe-lms|redis-cache|redis-queue|services\.mysql|/var/lib/frappe.*\.env" hosts/AZ-CLD-1/services/containers/frappe.nix hosts/common/ports.nix || true
|
||||
```
|
||||
|
||||
Expected: no matches for removed local Redis containers, local MySQL service, or manual `/var/lib/frappe/*.env` secret file.
|
||||
|
||||
- [ ] **Step 13: Commit Frappe reshape**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add hosts/AZ-CLD-1/services/containers/frappe.nix hosts/AZ-CLD-1/services/containers/frappe-lms.nix hosts/common/ports.nix
|
||||
git commit -m "services: reshape frappe as shared bench"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Enable the Frappe Bench Import and Build-Test
|
||||
|
||||
**Files:**
|
||||
- Modify: `hosts/AZ-CLD-1/services/containers/default.nix`
|
||||
|
||||
- [ ] **Step 1: Import the new Frappe module**
|
||||
|
||||
In `hosts/AZ-CLD-1/services/containers/default.nix`, replace:
|
||||
|
||||
```nix
|
||||
# ./frappe-lms.nix
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```nix
|
||||
./frappe.nix
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Format the container import file**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
alejandra hosts/AZ-CLD-1/services/containers/default.nix
|
||||
```
|
||||
|
||||
Expected: formatting succeeds.
|
||||
|
||||
- [ ] **Step 3: Run focused Nix evaluations**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
nix eval --raw .#nixosConfigurations.AZ-CLD-1.config.services.traefik.dynamicConfigOptions.http.routers.frappe.rule
|
||||
nix eval .#nixosConfigurations.AZ-CLD-1.config.virtualisation.oci-containers.containers.frappe-backend.autoStart
|
||||
nix eval .#nixosConfigurations.AZ-CLD-1.config.services.redis.servers."".enable
|
||||
nix eval .#nixosConfigurations.AZ-CLD-1.config.services.mysql.enable
|
||||
```
|
||||
|
||||
Expected outputs:
|
||||
|
||||
```text
|
||||
Host(`learn.az-gruppe.com`)
|
||||
true
|
||||
true
|
||||
true
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run repository quality checks**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
```
|
||||
|
||||
Expected: command exits with status `0`.
|
||||
|
||||
- [ ] **Step 5: Run the allowed build test**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
nixos-rebuild build --flake .#AZ-CLD-1
|
||||
```
|
||||
|
||||
Expected: command exits with status `0` and creates/updates `./result`.
|
||||
|
||||
Do not run `nixos-rebuild switch`.
|
||||
|
||||
- [ ] **Step 6: Commit the activation and build-tested state**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add hosts/AZ-CLD-1/services/containers/default.nix
|
||||
git commit -m "services: enable frappe bench"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Final Static Verification and Handoff
|
||||
|
||||
**Files:**
|
||||
- Read-only verification across the repository.
|
||||
|
||||
- [ ] **Step 1: Verify no old Frappe LMS structural names remain**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "frappe-lms|FRAPPE_LMS|frappe_lms|redis-cache|redis-queue" hosts secrets.nix secrets || true
|
||||
```
|
||||
|
||||
Expected: no matches except historical documentation under `docs/superpowers/specs/` or `docs/superpowers/plans/`.
|
||||
|
||||
- [ ] **Step 2: Verify no prohibited commands were run**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git log --oneline -5
|
||||
```
|
||||
|
||||
Expected: recent commits are local only. Do not run `git push`.
|
||||
|
||||
- [ ] **Step 3: Verify worktree state**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: no unstaged implementation changes. `result` may appear depending on local `.gitignore`; remove it with `rm result` if it is an untracked symlink.
|
||||
|
||||
- [ ] **Step 4: Write handoff note in final response**
|
||||
|
||||
Include:
|
||||
|
||||
```text
|
||||
Implemented host MariaDB, host Redis, agenix Frappe secrets, and shared Frappe Bench module.
|
||||
Validation run: nix flake check; nixos-rebuild build --flake .#AZ-CLD-1.
|
||||
Not run: nixos-rebuild switch; git push.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
Spec coverage:
|
||||
|
||||
- Host Redis: Task 3.
|
||||
- Host MariaDB: Task 2.
|
||||
- Compose MariaDB settings: Task 2 Step 1.
|
||||
- agenix secrets: Task 1 and Task 4 Step 8.
|
||||
- Rename `frappe-lms.nix` to `frappe.nix`: Task 4 Step 1.
|
||||
- Multisite-capable Bench: Task 4 Steps 3 and 8.
|
||||
- Production-style separated containers: Task 4 Steps 9 and 10.
|
||||
- No `switch`, no push, build allowed: header and Task 5 Step 5.
|
||||
|
||||
Placeholder scan:
|
||||
|
||||
- The plan contains no placeholder markers and no intentionally blank implementation steps.
|
||||
|
||||
Type/name consistency:
|
||||
|
||||
- Secret name: `frappe-env` everywhere.
|
||||
- DB admin user: `frappe_admin` everywhere.
|
||||
- Service name: `frappe` everywhere after rename.
|
||||
- Redis service: default NixOS Redis instance, systemd unit `redis.service`.
|
||||
@@ -1,233 +0,0 @@
|
||||
# Frappe Bench Infrastruktur-Design
|
||||
|
||||
Datum: 2026-06-05
|
||||
Host: `AZ-CLD-1`
|
||||
Status: abgestimmtes Design, noch nicht umgesetzt
|
||||
|
||||
## Ziel
|
||||
|
||||
Das bestehende Frappe-LMS-Setup soll strukturell in eine produktionsnahe, erweiterbare Frappe-Bench-Architektur überführt werden.
|
||||
|
||||
Kernentscheidungen:
|
||||
|
||||
- Frappe läuft produktionsnah getrennt nach Prozessen: Backend, Frontend/Nginx, Websocket, Worker und Scheduler.
|
||||
- Die Frappe-Laufzeit wird als gemeinsame Bench für mehrere Sites verstanden.
|
||||
- Redis läuft als Host-Infrastruktur und nicht mehr als LMS-spezifischer Container.
|
||||
- MariaDB läuft als Host-Infrastruktur und orientiert sich strukturell an `hosts/AZ-CLD-1/services/postgres.nix`.
|
||||
- Die Docker-Compose-Vorlage dient als Referenz für MariaDB-/Redis-/Bench-Erwartungen, nicht als 1:1 Container-Topologie.
|
||||
- Es wird kein `nixos-rebuild switch` und kein `git push` im Rahmen dieser Änderung ausgeführt. Ein Build-Test ist erlaubt.
|
||||
|
||||
## Zielstruktur
|
||||
|
||||
Neue bzw. geänderte Dateien:
|
||||
|
||||
```text
|
||||
hosts/AZ-CLD-1/services/mariadb.nix
|
||||
hosts/AZ-CLD-1/services/redis.nix
|
||||
hosts/AZ-CLD-1/services/containers/frappe.nix
|
||||
```
|
||||
|
||||
Die bestehende Datei:
|
||||
|
||||
```text
|
||||
hosts/AZ-CLD-1/services/containers/frappe-lms.nix
|
||||
```
|
||||
|
||||
wird inhaltlich in `frappe.nix` überführt, weil sie künftig die gemeinsame Frappe-Bench repräsentiert und nicht nur die LMS-Site.
|
||||
|
||||
## MariaDB
|
||||
|
||||
MariaDB wird zentral als Host-Service definiert. Das Frappe-Modul darf nicht mehr selbst `services.mysql` aktivieren.
|
||||
|
||||
`mariadb.nix` übernimmt analog zu `postgres.nix`:
|
||||
|
||||
- Aktivierung des Host-Datenbankdienstes
|
||||
- zentrale Datenbank-Settings
|
||||
- Firewall-Freigaben für erlaubte Netze
|
||||
- Frappe-kompatible Admin-/Init-Logik
|
||||
- perspektivisch zentrale Backups
|
||||
|
||||
Die MariaDB-Settings aus der Compose-Vorlage werden übernommen:
|
||||
|
||||
```text
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
skip-character-set-client-handshake = true
|
||||
skip-innodb-read-only-compressed = true
|
||||
```
|
||||
|
||||
Frappe-Container erreichen MariaDB über die Podman-Host-IP:
|
||||
|
||||
```text
|
||||
10.89.0.1:3306
|
||||
```
|
||||
|
||||
Die Firewall erlaubt Zugriff nur aus dem Podman-Netz, insbesondere `10.89.0.0/24`.
|
||||
|
||||
## Redis
|
||||
|
||||
Redis wird zentral als Host-Service definiert. Frappe bringt keine eigenen Redis-Container mehr mit.
|
||||
|
||||
Frappe nutzt einen gemeinsamen Host-Redis mit getrennten logical DBs:
|
||||
|
||||
```text
|
||||
redis_cache redis://10.89.0.1:6379/1
|
||||
redis_queue redis://10.89.0.1:6379/2
|
||||
redis_socketio redis://10.89.0.1:6379/2
|
||||
```
|
||||
|
||||
Damit bleibt das Setup nah an der Compose-Vorlage, die ebenfalls nur einen Redis-Service definiert, vermeidet aber Kollisionen mit anderen Host-Verbrauchern.
|
||||
|
||||
Firewall-Regel:
|
||||
|
||||
- Zugriff lokal erlauben
|
||||
- Zugriff aus `10.89.0.0/24` erlauben
|
||||
- keine allgemeine externe Freigabe
|
||||
|
||||
## Frappe Bench
|
||||
|
||||
`containers/frappe.nix` beschreibt eine gemeinsame Frappe-Bench mit Multisite-Fähigkeit.
|
||||
|
||||
Die erste Site bleibt:
|
||||
|
||||
```nix
|
||||
sites = {
|
||||
"learn.az-gruppe.com" = {
|
||||
apps = [ "lms" ];
|
||||
adminPasswordEnv = "FRAPPE_ADMIN_PASSWORD";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Secrets werden nicht als manuell gepflegte Datei unter `/var/lib/frappe` verwaltet, sondern wie bei den anderen Diensten über agenix bereitgestellt. Das Frappe-Modul referenziert dafür einen age-Secret-Pfad, z.B.:
|
||||
|
||||
```nix
|
||||
frappeEnvFile = config.age.secrets.frappe-env.path;
|
||||
```
|
||||
|
||||
Die zugehörige Secret-Datei enthält die für Init und Site-Erzeugung benötigten Variablen, insbesondere:
|
||||
|
||||
```text
|
||||
FRAPPE_DB_ADMIN_PASSWORD=<database-admin-secret>
|
||||
FRAPPE_ADMIN_PASSWORD=<site-administrator-secret>
|
||||
```
|
||||
|
||||
Der Secret-Eintrag wird in den bestehenden agenix-Strukturen ergänzt:
|
||||
|
||||
```text
|
||||
secrets/frappe-env.age
|
||||
secrets.nix
|
||||
hosts/AZ-CLD-1/secrets.nix
|
||||
```
|
||||
|
||||
Weitere Sites können später ergänzt werden, ohne die Grundstruktur umzubauen:
|
||||
|
||||
```nix
|
||||
sites."portal.az-gruppe.com" = {
|
||||
apps = [ "lms" "custom_app" ];
|
||||
};
|
||||
```
|
||||
|
||||
Die globalen Bench-Settings werden einmalig gesetzt:
|
||||
|
||||
```text
|
||||
db_host 10.89.0.1
|
||||
db_port 3306
|
||||
redis_cache redis://10.89.0.1:6379/1
|
||||
redis_queue redis://10.89.0.1:6379/2
|
||||
redis_socketio redis://10.89.0.1:6379/2
|
||||
socketio_port 9000
|
||||
```
|
||||
|
||||
## Container-Prozesse
|
||||
|
||||
Die produktionsnahe Trennung bleibt erhalten:
|
||||
|
||||
```text
|
||||
frappe-backend
|
||||
frappe-frontend
|
||||
frappe-websocket
|
||||
frappe-queue-short
|
||||
frappe-queue-long
|
||||
frappe-scheduler
|
||||
```
|
||||
|
||||
Die Container teilen sich das gemeinsame Sites-Verzeichnis unter `/var/lib/frappe/sites` oder einer äquivalenten Bench-Datenstruktur.
|
||||
|
||||
Die Frontend-/Nginx-Instanz bleibt über Traefik erreichbar. Für `learn.az-gruppe.com` bleibt die bestehende Traefik-Domain erhalten.
|
||||
|
||||
## Initialisierung
|
||||
|
||||
Die Initialisierung bleibt idempotent:
|
||||
|
||||
- Verzeichnisse werden vorbereitet.
|
||||
- Secrets werden ausschließlich über `config.age.secrets.*.path` eingebunden.
|
||||
- Eine fehlende agenix-Secret-Definition oder fehlende Variablen im Secret führen zu einem klaren Fehler.
|
||||
- Es wird keine dauerhafte, manuell gepflegte Secret-/Env-Datei unter `/var/lib/frappe` erzeugt.
|
||||
- Gemeinsame Bench-Konfiguration wird gesetzt.
|
||||
- Existiert `sites/<domain>/site_config.json`, wird die Site-Erzeugung übersprungen.
|
||||
- Existiert die Site nicht, wird sie mit `bench new-site` angelegt.
|
||||
- Apps werden gemäß Site-Konfiguration installiert.
|
||||
|
||||
Für die erste Site wird `lms` installiert.
|
||||
|
||||
## CLI-Helfer
|
||||
|
||||
Der bisherige Helfer wird allgemeiner benannt:
|
||||
|
||||
```text
|
||||
frappe {init|migrate|backup|status}
|
||||
```
|
||||
|
||||
Nicht mehr:
|
||||
|
||||
```text
|
||||
frappe-lms ...
|
||||
```
|
||||
|
||||
`status` prüft künftig:
|
||||
|
||||
- `mysql.service`
|
||||
- `redis.service`
|
||||
- Frappe-Konfigurations-/Init-Services
|
||||
- alle Frappe-Podman-Container
|
||||
|
||||
## Backups
|
||||
|
||||
Kurzfristig bleibt der Frappe-Backup-Mechanismus:
|
||||
|
||||
```bash
|
||||
bench --site all backup --with-files
|
||||
```
|
||||
|
||||
MariaDB-Backups werden zentral im MariaDB-Modul ergänzt, soweit die NixOS-Optionen bzw. vorhandenen Backup-Patterns dies sauber unterstützen.
|
||||
|
||||
## Validierung
|
||||
|
||||
Erlaubt:
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
nixos-rebuild build --flake .#AZ-CLD-1
|
||||
```
|
||||
|
||||
Nicht erlaubt im Rahmen dieser Änderung:
|
||||
|
||||
```bash
|
||||
nixos-rebuild switch
|
||||
```
|
||||
|
||||
Ebenfalls nicht ausführen:
|
||||
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
|
||||
Zusätzliche statische Prüfungen:
|
||||
|
||||
- `containers/frappe.nix` enthält keine Redis-Container mehr.
|
||||
- `containers/frappe.nix` definiert nicht mehr `services.mysql`.
|
||||
- `containers/frappe.nix` verwendet agenix-Secrets über `config.age.secrets.*.path` statt eine manuelle `/var/lib/frappe/*.env`-Datei zu erwarten.
|
||||
- `services/default.nix` importiert `mariadb.nix` und `redis.nix`.
|
||||
- `containers/default.nix` importiert `frappe.nix` statt `frappe-lms.nix`.
|
||||
- `secrets/frappe-env.age`, `secrets.nix` und `hosts/AZ-CLD-1/secrets.nix` sind konsistent ergänzt.
|
||||
Reference in New Issue
Block a user