diff --git a/nextcloud/check_stack_nextcloud.yml b/nextcloud/check_stack_nextcloud.yml index c1be746..79feec1 100644 --- a/nextcloud/check_stack_nextcloud.yml +++ b/nextcloud/check_stack_nextcloud.yml @@ -8,14 +8,23 @@ health_script_path: /data/compose/nextcloud/stack-health.sh tasks: - - name: Install stack-health.sh (inline content) + - name: Ensure target directory exists + ansible.builtin.file: + path: "{{ health_script_path | dirname }}" + state: directory + mode: '0755' + + - name: Install stack-health.sh (inline content, Jinja disabled via raw) ansible.builtin.copy: dest: "{{ health_script_path }}" mode: '0755' - # Comments in English: keep the script inline so we don't depend on repo paths - content: | + # Comments in English: wrap the whole script with {% raw %} ... {% endraw %} so Jinja doesn't eat {{...}} in bash. + content: |- + {% raw %} #!/usr/bin/env bash set -euo pipefail + + # --- config --- NC_CONTAINER="${NC_CONTAINER:-nextcloud}" DB_CONTAINER="${DB_CONTAINER:-nextcloud-db}" REDIS_CONTAINER="${REDIS_CONTAINER:-redis}" @@ -23,66 +32,146 @@ 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 + + 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 + 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)" - [ -n "$COMMON_NET" ] && pass "Nextcloud & DB share network: ${COMMON_NET}" || fail "Nextcloud & DB do NOT share a network (NC: [$NC_NETS], DB: [$DB_NETS])" + 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 "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 + 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\"); + \$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) { + \$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; } } + \$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); } + 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 + 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 + 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 (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); }' + 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 + 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 - grep -q "PONG" "$TMP_DIR/redis_cli.out" && pass "Redis container responds to PING" || fail "Redis cli ping did not return PONG" - else fail "redis-cli ping failed: $(cat "$TMP_DIR/redis_cli.err" || true)"; fi - 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 - if [ -n "$NC_PUBLIC_URL" ]; then curl -fsSIL "$NC_PUBLIC_URL" >/dev/null && pass "Public URL reachable: $NC_PUBLIC_URL" || fail "Public URL unreachable: $NC_PUBLIC_URL"; fi - if curl -fsS "$COLLA_DISCOVERY" -o "$TMP_DIR/colla_disc.xml"; then pass "Collabora discovery reachable ($COLLA_DISCOVERY)"; grep -m1 -o "]*>" "$TMP_DIR/colla_disc.xml" || true; else fail "Collabora discovery unreachable: $COLLA_DISCOVERY"; fi - if [ "$EXIT" -eq 0 ]; then echo "----------------------------------------"; echo "ALL CHECKS PASSED ✅"; else echo "----------------------------------------"; echo "ONE OR MORE CHECKS FAILED ❌ (see above)"; fi + 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 "]*>" "$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 "----------------------------------------" + echo "ONE OR MORE CHECKS FAILED ❌ (see above)" + fi + exit "$EXIT" + {% endraw %} - name: Run stack-health.sh ansible.builtin.shell: "{{ health_script_path }}" @@ -94,7 +183,7 @@ ansible.builtin.debug: msg: "{{ health.stdout | default('no stdout') }}" - - name: Fail if checks failed + - name: Fail if checks failed (rc != 0) ansible.builtin.fail: msg: "Health checks failed" when: health.rc != 0 \ No newline at end of file