forked from jakub/ansible
Refactor inventory and playbook for Nextcloud maintenance: update host configuration and streamline health check tasks
This commit is contained in:
3
host_vars/portainer.yml
Normal file
3
host_vars/portainer.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ansible_user: howard
|
||||||
|
ansible_password: "{{ portainer_pass }}"
|
||||||
|
ansible_ssh_common_args: "-o ProxyJump=root@192.168.69.2"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[linux_servers]
|
[linux_servers]
|
||||||
proxmox ansible_host=192.168.69.2
|
proxmox ansible_host=192.168.69.2
|
||||||
|
|
||||||
[nextcloud_stack]
|
[portainer]
|
||||||
vm_portainer ansible_host=192.168.69.123 ansible_user=root ansible_python_interpreter=/usr/bin/python3
|
portainer ansible_host=192.168.69.253
|
||||||
@@ -1,183 +1,104 @@
|
|||||||
# Comments in English in code:
|
|
||||||
---
|
---
|
||||||
- name: Upload and run stack health checks
|
# Play: Run Nextcloud housekeeping commands inside the container
|
||||||
hosts: nextcloud_stack # <-- dříve 'proxmox'
|
- name: Nextcloud maintenance (cron, app updates, repair, status, health check)
|
||||||
|
hosts: portainer
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
become: false
|
|
||||||
vars:
|
vars:
|
||||||
health_script_path: /data/compose/nextcloud/stack-health.sh
|
nextcloud_container: nextcloud
|
||||||
tasks:
|
tasks:
|
||||||
- name: Ensure target directory exists
|
- name: Ensure docker CLI is available
|
||||||
ansible.builtin.file:
|
ansible.builtin.command:
|
||||||
path: "{{ health_script_path | dirname }}"
|
argv: ["/usr/bin/env", "bash", "-lc", "command -v docker"]
|
||||||
state: directory
|
changed_when: false
|
||||||
mode: '0755'
|
failed_when: result.rc != 0
|
||||||
|
register: result
|
||||||
|
# English: Hard fail if docker is not present.
|
||||||
|
|
||||||
- name: Install stack-health.sh (inline content, Jinja disabled via raw)
|
- name: Verify Nextcloud container is running
|
||||||
ansible.builtin.copy:
|
ansible.builtin.command:
|
||||||
dest: "{{ health_script_path }}"
|
argv: ["docker", "ps", "--format", "{{.Names}}"]
|
||||||
mode: '0755'
|
changed_when: false
|
||||||
content: |-
|
register: docker_ps
|
||||||
{% raw %}
|
# English: List running containers by name.
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# --- config ---
|
- name: Fail if '{{ nextcloud_container }}' is not running
|
||||||
NC_CONTAINER="${NC_CONTAINER:-nextcloud}"
|
|
||||||
DB_CONTAINER="${DB_CONTAINER:-nextcloud-db}"
|
|
||||||
REDIS_CONTAINER="${REDIS_CONTAINER:-redis}"
|
|
||||||
COLLA_CONTAINER="${COLLA_CONTAINER:-collabora}"
|
|
||||||
NC_HTTP="${NC_HTTP:-http://127.0.0.1:8080/status.php}"
|
|
||||||
COLLA_DISCOVERY="${COLLA_DISCOVERY:-http://127.0.0.1:9980/hosting/discovery}"
|
|
||||||
NC_PUBLIC_URL="${NC_PUBLIC_URL:-}"
|
|
||||||
|
|
||||||
pass(){ echo -e "✅ $*"; }
|
|
||||||
fail(){ echo -e "❌ $*"; EXIT=1; }
|
|
||||||
EXIT=0
|
|
||||||
TMP_DIR="$(mktemp -d)"; trap 'rm -rf "$TMP_DIR"' EXIT
|
|
||||||
|
|
||||||
# 0) Docker reachable?
|
|
||||||
docker ps >/dev/null 2>&1 || { fail "Docker not accessible"; echo "----------------------------------------"; echo "ONE OR MORE CHECKS FAILED ❌"; exit "$EXIT"; }
|
|
||||||
|
|
||||||
# 1) Containers running?
|
|
||||||
for c in "$NC_CONTAINER" "$DB_CONTAINER" "$REDIS_CONTAINER" "$COLLA_CONTAINER"; do
|
|
||||||
if docker ps --format '{{.Names}}' | grep -qx "$c"; then
|
|
||||||
pass "container '$c' is running"
|
|
||||||
else
|
|
||||||
fail "container '$c' NOT running"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 1b) NC & DB share at least one network?
|
|
||||||
get_nets(){ docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{printf "%s " $k}}{{end}}' "$1" 2>/dev/null; }
|
|
||||||
NC_NETS="$(get_nets "$NC_CONTAINER" | xargs || true)"
|
|
||||||
DB_NETS="$(get_nets "$DB_CONTAINER" | xargs || true)"
|
|
||||||
COMMON_NET="$(printf "%s\n" $NC_NETS $DB_NETS | tr ' ' '\n' | grep -v '^$' | sort | uniq -d || true)"
|
|
||||||
if [ -n "$COMMON_NET" ]; then
|
|
||||||
pass "Nextcloud & DB share network: ${COMMON_NET}"
|
|
||||||
else
|
|
||||||
fail "Nextcloud & DB do NOT share a network (NC: [$NC_NETS], DB: [$DB_NETS])"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2) OCC status
|
|
||||||
if docker exec -u www-data "$NC_CONTAINER" php occ status >"$TMP_DIR/occ_status.txt" 2>"$TMP_DIR/occ_status.err"; then
|
|
||||||
cat "$TMP_DIR/occ_status.txt"
|
|
||||||
grep -q "installed: true" "$TMP_DIR/occ_status.txt" && pass "Nextcloud installed: true" || fail "Nextcloud installed: false"
|
|
||||||
grep -q "maintenance: false" "$TMP_DIR/occ_status.txt" && pass "Nextcloud maintenance: false" || fail "Nextcloud maintenance is ON"
|
|
||||||
else
|
|
||||||
fail "occ status failed: $(cat "$TMP_DIR/occ_status.err" || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3) NC -> DB connectivity via PDO from Nextcloud config.php
|
|
||||||
DB_TEST_PHP="
|
|
||||||
require \"/var/www/html/lib/base.php\";
|
|
||||||
\$c=\\OC::\$server->getConfig();
|
|
||||||
\$h=\$c->getSystemValue(\"dbhost\");
|
|
||||||
\$d=\$c->getSystemValue(\"dbname\");
|
|
||||||
\$u=\$c->getSystemValue(\"dbuser\");
|
|
||||||
\$p=\$c->getSystemValue(\"dbpassword\");
|
|
||||||
if(!is_string(\$h) || \$h===''){ fwrite(STDERR, \"Invalid dbhost\\n\"); exit(2); }
|
|
||||||
\$dsn='';
|
|
||||||
if (\$h[0]==='/' || strpos(\$h,'localhost:/')===0 || strpos(\$h,'127.0.0.1:/')===0) {
|
|
||||||
if (strpos(\$h,':/')!==false) { \$parts=explode(':/', \$h, 2); \$sock='/'.\$parts[1]; } else { \$sock=\$h; }
|
|
||||||
\$dsn='mysql:unix_socket='.\$sock.';dbname='.\$d.';charset=utf8mb4';
|
|
||||||
} else {
|
|
||||||
\$host=\$h; \$port=null;
|
|
||||||
if (strpos(\$h,':')!==false) { list(\$host,\$pp)=explode(':',\$h,2); if (ctype_digit(\$pp)) { \$port=(int)\$pp; } }
|
|
||||||
\$dsn='mysql:host='.\$host.';'; if (\$port) { \$dsn.='port='.\$port.';'; }
|
|
||||||
\$dsn.='dbname='.\$d.';charset=utf8mb4';
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
\$pdo=new PDO(\$dsn,\$u,\$p,[PDO::ATTR_TIMEOUT=>2,PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
|
|
||||||
\$pdo->query('SELECT 1');
|
|
||||||
echo \"DB connect OK\\n\";
|
|
||||||
} catch (Throwable \$e) {
|
|
||||||
fwrite(STDERR, 'PDO error: '.\$e->getMessage().\"\\n\"); exit(2);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
if docker exec -u www-data "$NC_CONTAINER" php -r "$DB_TEST_PHP" >"$TMP_DIR/db_ping.out" 2>"$TMP_DIR/db_ping.err"; then
|
|
||||||
pass "Nextcloud → DB connection: OK ($(tr -d "\r" <"$TMP_DIR/db_ping.out"))"
|
|
||||||
else
|
|
||||||
fail "Nextcloud → DB connection FAILED: $(tr -d "\r" <"$TMP_DIR/db_ping.err")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 4) DB readiness inside container
|
|
||||||
if docker exec "$DB_CONTAINER" sh -c 'mariadb-admin ping -h 127.0.0.1 --silent 2>/dev/null || mysqladmin ping -h 127.0.0.1 --silent 2>/dev/null'; then
|
|
||||||
pass "MariaDB is ready for connections"
|
|
||||||
else
|
|
||||||
fail "MariaDB readiness check FAILED"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 5) NC -> Redis
|
|
||||||
REDIS_TEST_PHP='
|
|
||||||
require "/var/www/html/lib/base.php";
|
|
||||||
$c=\OC::$server->getConfig();
|
|
||||||
$rc=$c->getSystemValue("redis", []);
|
|
||||||
$host = is_array($rc) && isset($rc["host"]) ? $rc["host"] : (getenv("REDIS_HOST") ?: "redis");
|
|
||||||
$port = is_array($rc) && isset($rc["port"]) ? (int)$rc["port"] : 6379;
|
|
||||||
if (class_exists("Redis")) {
|
|
||||||
$r=new Redis();
|
|
||||||
try { $r->connect($host,$port,1.0); echo $r->ping()."\n"; exit(0); }
|
|
||||||
catch (Throwable $e) { fwrite(STDERR, "Redis ext error: ".$e->getMessage()."\n"); exit(2); }
|
|
||||||
} else {
|
|
||||||
$e=$s=0; $fp=@fsockopen($host,$port,$e,$s,1.0);
|
|
||||||
if($fp){ echo "PONG (tcp ok)\n"; fclose($fp); exit(0); }
|
|
||||||
fwrite(STDERR,"No php-redis ext and TCP connect failed\n"); exit(2);
|
|
||||||
}
|
|
||||||
'
|
|
||||||
if docker exec -u www-data "$NC_CONTAINER" php -r "$REDIS_TEST_PHP" >"$TMP_DIR/redis_ping.out" 2>"$TMP_DIR/redis_ping.err"; then
|
|
||||||
pass "Nextcloud → Redis: OK ($(tr -d "\r" <"$TMP_DIR/redis_ping.out"))"
|
|
||||||
else
|
|
||||||
fail "Nextcloud → Redis FAILED: $(tr -d "\r" <"$TMP_DIR/redis_ping.err")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 6) Redis local ping
|
|
||||||
if docker exec "$REDIS_CONTAINER" sh -c 'redis-cli -h 127.0.0.1 ping' >"$TMP_DIR/redis_cli.out" 2>"$TMP_DIR/redis_cli.err"; then
|
|
||||||
if grep -q "PONG" "$TMP_DIR/redis_cli.out"; then pass "Redis container responds to PING"; else fail "Redis cli ping did not return PONG"; fi
|
|
||||||
else
|
|
||||||
fail "redis-cli ping failed: $(cat "$TMP_DIR/redis_cli.err" || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 7) HTTP status.php
|
|
||||||
if curl -fsS "$NC_HTTP" -o "$TMP_DIR/nc_http.json"; then
|
|
||||||
pass "HTTP $NC_HTTP reachable"
|
|
||||||
head -c 300 "$TMP_DIR/nc_http.json"; echo
|
|
||||||
else
|
|
||||||
fail "HTTP $NC_HTTP unreachable"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 8) Optional public URL
|
|
||||||
if [ -n "$NC_PUBLIC_URL" ]; then
|
|
||||||
if curl -fsSIL "$NC_PUBLIC_URL" >/dev/null; then pass "Public URL reachable: $NC_PUBLIC_URL"; else fail "Public URL unreachable: $NC_PUBLIC_URL"; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 9) Collabora discovery
|
|
||||||
if curl -fsS "$COLLA_DISCOVERY" -o "$TMP_DIR/colla_disc.xml"; then
|
|
||||||
pass "Collabora discovery reachable ($COLLA_DISCOVERY)"
|
|
||||||
grep -m1 -o "<wopi-discovery[^>]*>" "$TMP_DIR/colla_disc.xml" || true
|
|
||||||
else
|
|
||||||
fail "Collabora discovery unreachable: $COLLA_DISCOVERY"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# summary
|
|
||||||
if [ "$EXIT" -eq 0 ]; then
|
|
||||||
echo "----------------------------------------"
|
|
||||||
echo "ALL CHECKS PASSED ✅"
|
|
||||||
else
|
|
||||||
echo "----------------------------------------"
|
|
||||||
|
|
||||||
|
|
||||||
- name: Run stack-health.sh
|
|
||||||
ansible.builtin.shell: "{{ health_script_path }}"
|
|
||||||
register: health
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Show health output
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: "{{ health.stdout | default('no stdout') }}"
|
|
||||||
|
|
||||||
- name: Fail if checks failed (rc != 0)
|
|
||||||
ansible.builtin.fail:
|
ansible.builtin.fail:
|
||||||
msg: "Health checks failed"
|
msg: "Container '{{ nextcloud_container }}' is not running on target host."
|
||||||
when: health.rc != 0
|
when: nextcloud_container not in docker_ps.stdout_lines
|
||||||
|
# English: Avoid obscure 'docker exec' errors later.
|
||||||
|
|
||||||
|
- name: Run Nextcloud maintenance pipeline
|
||||||
|
block:
|
||||||
|
- name: 1) Run cron.php
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- docker
|
||||||
|
- exec
|
||||||
|
- -u
|
||||||
|
- www-data
|
||||||
|
- "{{ nextcloud_container }}"
|
||||||
|
- php
|
||||||
|
- -f
|
||||||
|
- /var/www/html/cron.php
|
||||||
|
register: cron_run
|
||||||
|
|
||||||
|
- name: 2) Update all apps
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- docker
|
||||||
|
- exec
|
||||||
|
- -u
|
||||||
|
- www-data
|
||||||
|
- "{{ nextcloud_container }}"
|
||||||
|
- php
|
||||||
|
- occ
|
||||||
|
- app:update
|
||||||
|
- --all
|
||||||
|
register: apps_update
|
||||||
|
|
||||||
|
- name: 3) Run maintenance:repair (include expensive)
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- docker
|
||||||
|
- exec
|
||||||
|
- -u
|
||||||
|
- www-data
|
||||||
|
- "{{ nextcloud_container }}"
|
||||||
|
- php
|
||||||
|
- occ
|
||||||
|
- maintenance:repair
|
||||||
|
- --include-expensive
|
||||||
|
register: repair_run
|
||||||
|
|
||||||
|
- name: 4) Show occ status
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- docker
|
||||||
|
- exec
|
||||||
|
- -u
|
||||||
|
- www-data
|
||||||
|
- "{{ nextcloud_container }}"
|
||||||
|
- php
|
||||||
|
- occ
|
||||||
|
- status
|
||||||
|
register: occ_status
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: 5) Run stack health script
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["/data/compose/nextcloud/stack-health.sh"]
|
||||||
|
register: health
|
||||||
|
# English: If your script returns non-zero, the play will fail (desired in CI).
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Print outputs from maintenance steps
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "cron.php stdout: {{ cron_run.stdout | default('') }}"
|
||||||
|
- "cron.php stderr: {{ cron_run.stderr | default('') }}"
|
||||||
|
- "app:update stdout: {{ apps_update.stdout | default('') }}"
|
||||||
|
- "app:update stderr: {{ apps_update.stderr | default('') }}"
|
||||||
|
- "repair stdout: {{ repair_run.stdout | default('') }}"
|
||||||
|
- "repair stderr: {{ repair_run.stderr | default('') }}"
|
||||||
|
- "occ status:\n{{ occ_status.stdout | default('') }}"
|
||||||
|
- "health stdout:\n{{ health.stdout | default('') }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user