diff options
| author | yyamashita <yyamashita@hetzner.yyamashita.com> | 2026-10-31 19:28:45 +0900 |
|---|---|---|
| committer | yyamashita <yyamashita@hetzner.yyamashita.com> | 2026-10-31 19:28:45 +0900 |
| commit | ff39e864a0b7e9a98340bf564a7a4bf566958a77 (patch) | |
| tree | 1f3f6db573956f4debb43bbbf605b6fde8bc8cf3 | |
| parent | 01e7d14bf9f1cf3b9a5879ffbe4a8f0b5f553323 (diff) | |
Consolidate status pages into status.yyamashita.com
Drop Uptime Kuma (uptime.yyamashita.com) and the hetzner-admin /status
page; status-app + ntfy alerts is the single status site. hetzner-admin
now just links to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | CLAUDE.md | 30 | ||||
| -rw-r--r-- | Deploy.md | 2 | ||||
| -rw-r--r-- | caddy/Caddyfile | 5 | ||||
| -rwxr-xr-x | git/hooks/hetzner-infra/post-receive | 1 | ||||
| -rw-r--r-- | hetzner-admin/app.py | 67 | ||||
| -rwxr-xr-x | hetzner-admin/run-command.sh | 3 | ||||
| -rwxr-xr-x | mail/dns/setup-route53.py | 1 | ||||
| -rw-r--r-- | status/targets.txt | 2 | ||||
| -rwxr-xr-x | tests/check.sh | 4 | ||||
| -rwxr-xr-x | uptime-kuma/deploy.sh | 5 | ||||
| -rw-r--r-- | uptime-kuma/docker-compose.yml | 17 |
11 files changed, 13 insertions, 124 deletions
@@ -14,13 +14,12 @@ mail/ MTA(Postfix + OpenDKIM, docker-compose.yml, install.sh, dns/se vpn/ strongSwan IKEv2 VPN(Dockerfile, docker-compose.yml, install.sh, renew-cert.sh) status/ 軽量ステータスページ(app.py, targets.txt, Dockerfile, docker-compose.yml, deploy.sh) ntfy/ ntfy 通知サーバー(server.yml, docker-compose.yml, deploy.sh, install.sh) -uptime-kuma/ Uptime Kuma 監視(docker-compose.yml, deploy.sh) server/ サーバー共通設定(authorized_keys, requirements.md) ``` ## デプロイの仕組み -- `git push origin master` → post-receive フック → `caddy/deploy.sh` / `cgit/deploy.sh` / `mail/deploy.sh` / `hetzner-admin/deploy.sh` / `vpn/deploy.sh` / `ntfy/deploy.sh` / `status/deploy.sh` / `uptime-kuma/deploy.sh` を実行 +- `git push origin master` → post-receive フック → `caddy/deploy.sh` / `cgit/deploy.sh` / `mail/deploy.sh` / `hetzner-admin/deploy.sh` / `vpn/deploy.sh` / `ntfy/deploy.sh` / `status/deploy.sh` を実行 - cgit の `lighttpd.conf` / `Dockerfile` 変更はイメージ再ビルドが必要(`deploy.sh` で `--build` するため push だけで反映される) - cgit の `cgitrc` はボリュームマウントのため push だけで反映される - フック・リポジトリの変更は push 後に `bash /app/infra/git/install.sh` を手動実行 @@ -128,32 +127,21 @@ docker compose -f /app/infra/vpn/docker-compose.yml restart vi caddy/docker-compose.yml # PROXY_NO_AUTH=1 を削除(VPN と同じ users.conf の Basic 認証に戻る) git push origin master -# ステータスページ(3 種類) -# - status.yyamashita.com : 公開ステータスページ(status/)。監視対象は status/targets.txt -# JSON は /api/status。targets.txt は push だけで反映(次回チェック時に再読込) -# - uptime.yyamashita.com : Uptime Kuma(uptime-kuma/)。監視設定・通知は Web UI で行う(データは volume に永続化) -# - hetzner-admin /status : 認証付き。status-app の結果 + docker ps -a のコンテナ状態 +# ステータスページ(status.yyamashita.com, status/) +# 監視対象は status/targets.txt(push だけで反映、次回チェック時に再読込)。JSON は /api/status +# ntfy(ntfy.yyamashita.com, 認証必須)のトピック "status" に通知する: +# - 連続 2 回失敗で DOWN、復旧で recovered +# - 毎朝 8:00 JST に稼働率サマリー(status/.env に SUMMARY_HOUR=-1 で無効) vi status/targets.txt && git push origin master -# ステータスページ初回セットアップ +# 初回セットアップ(済み。再構築時の手順) git push origin master ssh root@localhost 'bash /app/infra/git/install.sh' # post-receive フック更新 -ssh root@localhost 'bash /app/infra/status/deploy.sh && bash /app/infra/uptime-kuma/deploy.sh' -python3 /app/infra/mail/dns/setup-route53.py # status / uptime の CNAME 追加 -# → 直後に https://uptime.yyamashita.com を開いて管理者アカウントを作成する -# (初回アクセスした人が管理者になるため、放置しないこと) - -# ntfy 通知(ntfy.yyamashita.com)初回セットアップ -# 認証必須(deny-all)。status-app が連続 2 回失敗で DOWN、復旧で recovered をトピック "status" に通知 -# 毎朝 8:00 JST に前日分の稼働率サマリーも送る(status/docker-compose.yml で SUMMARY_HOUR=-1 にすると無効) -git push origin master -ssh root@localhost 'bash /app/infra/git/install.sh' # post-receive フック更新 -python3 /app/infra/mail/dns/setup-route53.py # ntfy の CNAME 追加 +ssh root@localhost 'bash /app/infra/ntfy/deploy.sh && bash /app/infra/status/deploy.sh' +# DNS(status / ntfy の CNAME): 下の「DNS 登録」参照 ssh -t root@localhost 'bash /app/infra/ntfy/install.sh' # 管理ユーザー作成(パスワード入力) # → トークンを発行して status/.env に NTFY_URL / NTFY_TOKEN を書き込み、status-app を再起動 # スマホ: ntfy アプリでサーバー https://ntfy.yyamashita.com を追加し、トピック status を購読(admin でログイン) -# Uptime Kuma: Settings → Notifications → ntfy -# Server URL: http://ntfy / Topic: status / Access Token: ntfy token add admin で発行したもの # ntfy テスト送信 ssh root@localhost 'docker exec ntfy ntfy publish --token "$(grep NTFY_TOKEN /app/infra/status/.env | cut -d= -f2)" http://localhost/status "test"' @@ -17,7 +17,7 @@ ssh root@<server-ip> 'bash /app/infra/git/install.sh' ## 通常デプロイ ```bash -git push origin master # caddy / cgit / mail / hetzner-admin / vpn / ntfy / status / uptime-kuma を再デプロイ(自動再ビルド含む) +git push origin master # caddy / cgit / mail / hetzner-admin / vpn / ntfy / status を再デプロイ(自動再ビルド含む) ``` push 前に `bash tests/check.sh` で整合性チェックを実行できる。 diff --git a/caddy/Caddyfile b/caddy/Caddyfile index d90d804..682df2d 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -54,11 +54,6 @@ status.yyamashita.com { reverse_proxy status-app:8080 } -uptime.yyamashita.com { - import noindex - reverse_proxy uptime-kuma:3001 -} - ntfy.yyamashita.com { import noindex reverse_proxy ntfy:80 diff --git a/git/hooks/hetzner-infra/post-receive b/git/hooks/hetzner-infra/post-receive index bbc792a..892008b 100755 --- a/git/hooks/hetzner-infra/post-receive +++ b/git/hooks/hetzner-infra/post-receive @@ -8,5 +8,4 @@ bash /app/infra/hetzner-admin/deploy.sh bash /app/infra/vpn/deploy.sh bash /app/infra/ntfy/deploy.sh bash /app/infra/status/deploy.sh -bash /app/infra/uptime-kuma/deploy.sh echo "Deploy complete: hetzner-infra" diff --git a/hetzner-admin/app.py b/hetzner-admin/app.py index 274364e..abef6a9 100644 --- a/hetzner-admin/app.py +++ b/hetzner-admin/app.py @@ -1,7 +1,5 @@ import functools -import json import os -import urllib.request import paramiko from flask import Flask, Response, render_template_string, request @@ -14,10 +12,6 @@ SSH_HOST = os.environ.get("SSH_HOST", "host.docker.internal") SSH_PORT = int(os.environ.get("SSH_PORT", "22")) SSH_USER = os.environ.get("SSH_USER", "root") SSH_KEY_PATH = os.environ.get("SSH_KEY_PATH", "/app/id_rsa") -STATUS_API = os.environ.get("STATUS_API", "http://status-app:8080/api/status") - -# /status ページでコンテナ一覧を取るコマンド(run-command.sh のホワイトリストに要登録) -STATUS_COMMAND = "docker ps -a --format '{{.Names}}|{{.Status}}'" COMMANDS = [ ("Docker Status", "docker ps"), @@ -36,7 +30,7 @@ HTML = """<!DOCTYPE html> <head><title>Server Admin</title></head> <body> <h3>Server Admin</h3> -<p><a href="/status">Status</a></p> +<p><a href="https://status.yyamashita.com">Status</a></p> {% for label, _ in commands %} <form method="post" action="/run" style="display:block;margin-bottom:8px"> <input type="hidden" name="cmd" value="{{ label }}"> @@ -54,40 +48,6 @@ HTML = """<!DOCTYPE html> </body> </html>""" -STATUS_HTML = """<!DOCTYPE html> -<html> -<head><title>Server Status</title><meta http-equiv="refresh" content="60"></head> -<body> -<h3>Server Status</h3> -<p><a href="/">← Admin</a></p> -<h4>Services</h4> -{% if services is none %} -<pre>{{ services_error }}</pre> -{% else %} -<table border="1" cellpadding="4" style="border-collapse:collapse"> -<tr><th>Service</th><th>State</th><th>Detail</th><th>Latency</th><th>Uptime</th></tr> -{% for s in services %} -<tr><td>{{ s.name }}</td> -<td style="color:{{ 'green' if s.up else 'red' }}">{{ 'UP' if s.up else 'DOWN' }}</td> -<td>{{ s.detail }}</td><td>{{ s.ms }} ms</td><td>{{ s.uptime }}%</td></tr> -{% endfor %} -</table> -{% endif %} -<h4>Containers</h4> -{% if containers is none %} -<pre>{{ containers_error }}</pre> -{% else %} -<table border="1" cellpadding="4" style="border-collapse:collapse"> -<tr><th>Container</th><th>Status</th></tr> -{% for name, st in containers %} -<tr><td>{{ name }}</td> -<td style="color:{{ 'green' if st.startswith('Up') and '(unhealthy)' not in st else 'red' }}">{{ st }}</td></tr> -{% endfor %} -</table> -{% endif %} -</body> -</html>""" - def requires_auth(f): @functools.wraps(f) @@ -135,28 +95,3 @@ def run(): except Exception as e: output = f"Error: {e}" return render_template_string(HTML, commands=COMMANDS, output=output) - - -@app.route("/status") -@requires_auth -def status(): - services = services_error = containers = containers_error = None - try: - with urllib.request.urlopen(STATUS_API, timeout=5) as r: - services = json.load(r)["services"] - except Exception as e: - services_error = f"status-app に接続できない: {e}" - try: - out = run_ssh(STATUS_COMMAND) - containers = sorted( - tuple(line.split("|", 1)) for line in out.splitlines() if "|" in line - ) - except Exception as e: - containers_error = f"Error: {e}" - return render_template_string( - STATUS_HTML, - services=services, - services_error=services_error, - containers=containers, - containers_error=containers_error, - ) diff --git a/hetzner-admin/run-command.sh b/hetzner-admin/run-command.sh index 12f96bf..0ed9fe3 100755 --- a/hetzner-admin/run-command.sh +++ b/hetzner-admin/run-command.sh @@ -6,9 +6,6 @@ case "$SSH_ORIGINAL_COMMAND" in "docker ps") exec docker ps ;; - "docker ps -a --format '{{.Names}}|{{.Status}}'") - exec docker ps -a --format '{{.Names}}|{{.Status}}' - ;; "docker compose -f /app/infra/caddy/docker-compose.yml restart") exec docker compose -f /app/infra/caddy/docker-compose.yml restart ;; diff --git a/mail/dns/setup-route53.py b/mail/dns/setup-route53.py index 0b5d569..54eadb2 100755 --- a/mail/dns/setup-route53.py +++ b/mail/dns/setup-route53.py @@ -39,7 +39,6 @@ WEB_SUBDOMAINS = [ "blog", "todo", "status", - "uptime", "ntfy", "vpn", "proxy", diff --git a/status/targets.txt b/status/targets.txt index 5ee1dcc..b9631c9 100644 --- a/status/targets.txt +++ b/status/targets.txt @@ -9,7 +9,7 @@ todo https://todo.yyamashita.com git https://git.yyamashita.com code https://code.yyamashita.com hetzner-admin https://hetzner-admin.yyamashita.com -uptime https://uptime.yyamashita.com +status https://status.yyamashita.com vpn https://vpn.yyamashita.com ntfy http://ntfy/v1/health mail tcp://mail:25 diff --git a/tests/check.sh b/tests/check.sh index 85d19e5..a19b55e 100755 --- a/tests/check.sh +++ b/tests/check.sh @@ -67,7 +67,7 @@ while IFS=: read -r user repo; do done < claude/sessions.txt echo "" -echo "=== hetzner-admin: app.py の COMMANDS / STATUS_COMMAND が run-command.sh で許可されているか ===" +echo "=== hetzner-admin: app.py の COMMANDS が run-command.sh で許可されているか ===" while IFS= read -r cmd; do if grep -qF "\"$cmd\")" hetzner-admin/run-command.sh; then ok "$cmd" @@ -81,8 +81,6 @@ for node in ast.walk(tree): if isinstance(node, ast.Assign) and getattr(node.targets[0], "id", "") == "COMMANDS": for elt in node.value.elts: print(elt.elts[1].value) - if isinstance(node, ast.Assign) and getattr(node.targets[0], "id", "") == "STATUS_COMMAND": - print(node.value.value) EOF ) diff --git a/uptime-kuma/deploy.sh b/uptime-kuma/deploy.sh deleted file mode 100755 index c07b9e8..0000000 --- a/uptime-kuma/deploy.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cd "$(dirname "$0")" -docker compose pull -q -docker compose up -d diff --git a/uptime-kuma/docker-compose.yml b/uptime-kuma/docker-compose.yml deleted file mode 100644 index d166ce2..0000000 --- a/uptime-kuma/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -services: - uptime-kuma: - image: louislam/uptime-kuma:2 - container_name: uptime-kuma - restart: unless-stopped - volumes: - - uptime_kuma_data:/app/data - networks: - - web - -networks: - web: - external: true - name: web - -volumes: - uptime_kuma_data: |
