# hetzner-infra Hetzner VPS 上で動くサービス群のインフラ管理リポジトリ。 ## ディレクトリ構成 ``` caddy/ リバースプロキシ + フォワードプロキシ(Caddyfile, Dockerfile, entrypoint.sh, docker-compose.yml, deploy.sh) cgit/ cgit Web UI(Dockerfile, lighttpd.conf, cgitrc, deploy.sh) git/ ベアリポジトリ・フック管理(repos.txt, hooks/, install.sh) claude/ Claude Code セッション管理(sessions.txt, systemd unit, sync.sh) hetzner-admin/ サーバー管理 Web UI(app.py, Dockerfile, docker-compose.yml, deploy.sh) mail/ MTA(Postfix + OpenDKIM, docker-compose.yml, install.sh, dns/setup-route53.py) 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) 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` を実行 - cgit の `lighttpd.conf` / `Dockerfile` 変更はイメージ再ビルドが必要(`deploy.sh` で `--build` するため push だけで反映される) - cgit の `cgitrc` はボリュームマウントのため push だけで反映される - `caddy/Caddyfile` は `caddy/deploy.sh` が `caddy reload` するため push だけで反映される(無効な Caddyfile なら reload が失敗し、旧設定のまま動き続ける) - `status/targets.txt` はチェックごとに再読込されるため push だけで反映される - フック・リポジトリの変更は push 後に `bash /app/infra/git/install.sh` を手動実行 - Claude セッションの変更は push 後に `bash /app/infra/claude/sync.sh` を手動実行 - mail ドメイン変更(`mail/domains.txt`)は push 後に `bash /app/infra/mail/install.sh` を手動実行 ## 管理対象リポジトリ `git/repos.txt` に定義。`git/hooks//post-receive` がデプロイ内容。 | リポジトリ | work tree | |---|---| | hetzner-infra | /app/infra | | tokyo-livehouse-events | /app/tokyo-livehouse-events | | whois-band | /app/whois-band | | mosquitone-admin | /home/yyamashita/workspaces/repos/mosquitone-admin | | microblog | /app/microblog | | todo | /app/todo | | license-slot-checker | /home/yyamashita/workspaces/repos/license-slot-checker | | studio-slots | /app/studio-slots | ## よく使うコマンド ```bash # 整合性チェック(push 前に実行推奨) bash tests/check.sh # Caddy 設定変更 vi caddy/Caddyfile && git push origin master # cgit 設定変更(lighttpd.conf / cgitrc) vi cgit/lighttpd.conf # または cgit/cgitrc git push origin master # --build 付きで自動再デプロイ # フック・リポジトリ定義変更 vi git/hooks/.../post-receive # または git/repos.txt git push origin master ssh root@localhost 'bash /app/infra/git/install.sh' # Claude セッション変更 vi claude/sessions.txt git push origin master ssh root@localhost 'bash /app/infra/claude/sync.sh' # SSH 鍵追加 vi server/authorized_keys git push origin master ssh root@localhost 'bash /app/infra/git/install.sh' # MTA(mail)初回セットアップ(サーバー上で root として実行) ssh root@ 'bash /app/infra/mail/install.sh' # → DKIM 鍵生成 + コンテナ起動。公開鍵が表示される。 # Route 53 DNS 設定 → 下の「DNS 登録」参照(AWS キーの場所と実行コマンドあり。 # HOSTED_ZONE_ID / SERVER_IP は省略時に自動検出) # mail ドメイン追加(golive → whoisband も追加する例) vi mail/domains.txt git push origin master ssh root@localhost 'bash /app/infra/mail/install.sh' # テーブル再生成 + 再起動 # hetzner-admin 初回セットアップ(サーバー上で実行) ssh root@ cat > /app/infra/hetzner-admin/.env < SSH_USER=root EOF bash /app/infra/hetzner-admin/install.sh # → SSH鍵生成 + authorized_keys登録(command= 制限付き)+ コンテナ起動 # → hetzner-admin.yyamashita.com でアクセス可能 # ログイン: パスワード(.env)またはパスキー。初回はパスワードでログイン → # 下部の「Register this device's passkey」で登録 → 以後「Login with Passkey」 # パスキーは hetzner-admin/data/passkeys.json(gitignore)。全削除すれば登録し直し # VPN 初回セットアップ(サーバー上で実行) # 前提: ファイアウォールで UDP 500, 4500 を開放済み git push origin master ssh root@ 'bash /app/infra/git/install.sh' # post-receive フック更新 # → Caddy が vpn.yyamashita.com の証明書を取得するまで数分待つ ssh root@ cat > /app/infra/vpn/users.conf < EOF chmod 600 /app/infra/vpn/users.conf bash /app/infra/vpn/install.sh # → Caddy の証明書を流用 + コンテナ起動 # → vpn.yyamashita.com:500/4500 (IKEv2) で接続可能 # VPN 証明書更新(Let's Encrypt 更新後 90 日ごと) ssh root@ 'bash /app/infra/vpn/renew-cert.sh' # VPN ユーザー追加・変更 ssh root@ vi /app/infra/vpn/users.conf # 1行1ユーザー: "username password" docker compose -f /app/infra/vpn/docker-compose.yml restart # Proxy 初回セットアップ(Caddy に統合済み。追加作業なし) # → push するだけで Caddy が proxy.yyamashita.com の証明書を自動取得 # → proxy.yyamashita.com:80 (HTTP) / :443 (HTTPS) でフォワードプロキシとして動作 # → 認証は無効(PROXY_NO_AUTH=1)。代わりに日本国内 IP のみ許可(ipdeny.com の JP CIDR リスト) # Proxy 認証を有効に戻す場合 vi caddy/docker-compose.yml # PROXY_NO_AUTH=1 を削除(VPN と同じ users.conf の Basic 認証に戻る) git push origin master # ステータスページ(status.yyamashita.com, status/) # 監視対象は status/targets.txt(push だけで反映、次回チェック時に再読込)。JSON は /api/status # ntfy(ntfy.yyamashita.com)のトピック "status" に通知する(購読は誰でも可・送信は status-app のトークンのみ): # - 連続 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/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 トピックを匿名読み取り可にし、トークンを発行して status/.env に NTFY_URL / NTFY_TOKEN を書き込み、status-app を再起動 # 現サーバーの admin パスワードは /root/ntfy-admin-password(root のみ読める) # スマホ: ntfy アプリでサーバー https://ntfy.yyamashita.com / トピック status を購読(ログイン不要) # ディープリンク: ntfy://ntfy.yyamashita.com/status(status ページ下部にもリンクあり) # ntfy テスト送信 ssh root@localhost 'docker exec ntfy ntfy publish --token "$(grep NTFY_TOKEN /app/infra/status/.env | cut -d= -f2)" http://localhost/status "test"' # DNS 登録(サブドメイン追加時。WEB_SUBDOMAINS に CNAME、mail 用 TXT も UPSERT する) # AWS キーは /home/yyamashita/workspaces/repos/mosquitone-admin/.env にある。DKIM 鍵を読むため root で実行 ssh root@localhost 'set -a; eval "$(grep -E "^(export )?AWS_(ACCESS_KEY_ID|SECRET_ACCESS_KEY|DEFAULT_REGION)=" /home/yyamashita/workspaces/repos/mosquitone-admin/.env)"; set +a; python3 /app/infra/mail/dns/setup-route53.py' # UPSERT のみで削除はしない。サブドメインを廃止したら Route 53 から手動で CNAME を消す # 「SKIP TXT golive.yyamashita.com」は既存レコードとの競合で、正常 ``` ## ステータス・通知 - ステータスページ: https://status.yyamashita.com (JSON: `/api/status`。hetzner-admin 上部にもリンク) - 監視対象: `status/targets.txt`(60 秒ごと。HTTP は 5xx 未満で UP、`tcp://` は接続できれば UP) - 通知: ntfy `https://ntfy.yyamashita.com` のトピック `status` - DOWN(2 回連続失敗、優先度 高)/ recovered(優先度 普通)/ 毎朝 8:00 JST の Daily status(優先度 低) - 購読はログイン不要(アプリで購読、またはディープリンク `ntfy://ntfy.yyamashita.com/status`) - 送信は status-app のトークン(`status/.env`)のみ。他トピックは全拒否 - 稼働履歴・日次集計は status-app のメモリ上のみ(コンテナ再起動でリセット。8 時以降に再起動した日のサマリーは翌朝に回る) - 案(未実施): ntfy の Web Push を有効化(`ntfy webpush keys` → server.yml)すると ntfy.yyamashita.com を PWA として通知受信できる ## VPN クライアント設定 **iOS / macOS** (ネイティブ IKEv2): - サーバー: `vpn.yyamashita.com` - タイプ: IKEv2 - 認証: ユーザー名 + パスワード (EAP) - 証明書インストール不要(Let's Encrypt は自動信頼) **Meta Quest 3S / FireTV** (Android): - strongSwan VPN Client アプリをインストール - Quest: sideload `org.strongswan.android` APK - FireTV: 同様に APK をサイドロード - プロファイル設定: - タイプ: IKEv2 EAP (Username/Password) - サーバー: `vpn.yyamashita.com` - ユーザー名・パスワード: `users.conf` の内容 ## Proxy クライアント設定 - HTTP プロキシ: `proxy.yyamashita.com:80` - HTTPS プロキシ: `proxy.yyamashita.com:443` - 認証: なし(`PROXY_NO_AUTH=1`)。日本国内 IP からのみ接続可能 (有効化すると VPN と同じ `users.conf` の Basic 認証になる)