diff --git a/docs/Architecture.md b/docs/Architecture.md index f82a6f1..24e0e80 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -1,5 +1,34 @@ # Architecture +## 設計判断の記録(ADR) + +### ADR-001: 常駐デーモン方式の採用(v1.5.0〜) + +**決定**: systemd oneshot+timer 方式から常駐デーモン方式へ変更する。 + +**背景**: +DDNS クライアントには 2 つの独立した時間軸がある。 + +| 設定 | 役割 | 典型的な間隔 | +|------|------|------------| +| `DDNS_TIME` | IP 変化検知のチェック間隔 | 1〜60 分 | +| `UPDATE_TIME` | keepalive 送信間隔(サービスの失効防止) | 1 日〜30 日 | + +単一の systemd タイマーではこの 2 軸を独立管理できず、 +`DDNS_TIME > UPDATE_TIME` の組み合わせが正しく動作しない仕様バグがあった。 + +**決定理由**: +1. 2 本の goroutine ticker がプロセス内部で 2 つの間隔を完全に独立管理できる +2. 単一プロセス = 単一ログストリーム → `journalctl -u dipper_ai` だけでデバッグ完結 +3. `install.sh` からタイマー動的生成ロジックが不要になりシンプル化できる +4. プロセスクラッシュ時は systemd の `Restart=on-failure` が自動復帰を担う + +**受容したデメリット**: +- 常駐プロセスになるためメモリを常時使用(DDNS クライアントの規模では無視できる) +- oneshot より goroutine / シグナル管理が必要になる + +--- + ## パッケージ構成 ``` @@ -7,14 +36,15 @@ dipper_ai/ ├── cmd/dipper_ai/ # エントリポイント (main.go) ├── internal/ │ ├── config/ # 設定ファイルのパース・バリデーション -│ ├── ddns/ # DDNS プロバイダ実装 +│ ├── ddns/ # DDNS プロバイダ実装(MyDNS / Cloudflare) │ ├── ip/ # グローバル IP アドレス取得 │ ├── lock/ # 多重起動防止ロック -│ ├── mode/ # コマンドロジック (update / check / err_mail) +│ ├── mode/ # コマンドロジック(daemon / update / check / keepalive / err_mail) │ ├── state/ # 状態ファイル読み書き -│ └── timegate/ # タイムゲート(実行間隔制御) +│ └── timegate/ # タイムゲート(IP_CACHE_TIME / ERR_CHK_TIME 制御に使用) +├── docs/ # このドキュメント群 ├── scripts/ # install.sh / uninstall.sh -└── systemd/ # .service / .timer unit ファイル +└── systemd/ # dipper_ai.service ``` --- @@ -27,27 +57,34 @@ dipper_ai/ - shell-style `key=value` 形式、インラインコメント・クォート除去対応 - `boolVal`: `on` / `off` / `1` / `0` / `true` / `false`(大小文字不問) -- `intMin`: 最小値を強制(`UPDATE_TIME`、`DDNS_TIME` に使用) +- `intMin`: 最小値を強制(`UPDATE_TIME` に使用) - `intGate`: `0` = 無効化、それ以外は最小値を強制(`IP_CACHE_TIME`、`ERR_CHK_TIME` に使用) -- MyDNS エントリは `MYDNS_0_ID` が存在する限りインデックスをインクリメントして解析 -- Cloudflare エントリは `CF_0_ENABLED` が存在する限り同様にインクリメント +- 時間指定は `5m`、`2h`、`1d`、`30s`、または整数(分)で記述可能 +- MyDNS エントリは `MYDNS_N_ID` が存在する限りインデックスをインクリメントして解析 +- Cloudflare エントリは `CF_N_ENABLED` が存在する限り同様にインクリメント ```go type Config struct { StateDir string IPv4, IPv6 bool - UpdateTime int // minutes - IPCacheTime int // 0 = disabled + IPv4DDNS bool + IPv6DDNS bool + DDNSTime int // minutes; daemon check interval (0 = default 5min) + UpdateTime int // minutes; keepalive interval (0 = disabled) + IPCacheTime int // minutes; 0 = disabled + ErrChkTime int // minutes; 0 = disabled MyDNS []MyDNSEntry Cloudflare []CloudflareEntry - // ... + // ...email fields } ``` ### `ip` `ip.Fetch(ipv4, ipv6 bool)` でグローバル IP アドレスを取得します。 -外部 HTTP API(`ipv4.icanhazip.com` / `ipv6.icanhazip.com` 等)を使用し、`dig` などの外部コマンドに依存しません。 +`dig` 等の外部コマンドに依存せず、外部 HTTP API を使用します。 + +テスト時は `DIPPER_AI_FAKE_IP_V4` / `DIPPER_AI_FAKE_IP_V6` 環境変数で差し替え可能です。 ### `ddns` @@ -56,37 +93,31 @@ DDNS プロバイダごとの HTTP 更新実装。 **MyDNS** (`mydns.go`): - `UpdateMyDNSIPv4(entry, url) ProviderResult` - `UpdateMyDNSIPv6(entry, url) ProviderResult` -- HTTP GET + Basic Auth ヘッダ +- HTTP GET + Basic Auth(keepalive も同一エンドポイント) **Cloudflare** (`cloudflare.go`): - `UpdateCloudflare(entry, ip, recordType, zonesURL) ProviderResult` -- 3 ステップ: ゾーン名 → ゾーン ID 解決 → レコード検索 → PATCH 更新 +- ゾーン名 → ゾーン ID 解決 → レコード検索 → PATCH 更新 - `Authorization: Bearer ` ヘッダ - -`ProviderResult` は成功・失敗とエラーメッセージを保持します。 +- keepalive 不要(API 登録は失効しない) ### `timegate` `gate_` ファイルに RFC3339 タイムスタンプを書き込み、前回実行からの経過時間を判定します。 - -```go -gate := timegate.New(stateDir, "update", 1440*time.Minute) -if gate.ShouldRun() { - // 実行 - gate.Touch() -} -``` +現在は `IP_CACHE_TIME`(IP キャッシュ有効期間)と `ERR_CHK_TIME`(エラーメール間隔)にのみ使用。 +チェック間隔・keepalive 間隔はデーモンの ticker が担うため、`DDNS_TIME` / `UPDATE_TIME` にはゲートファイルを使用しません。 ### `state` -IP アドレスや DDNS エラーログを `STATE_DIR` のファイルに保存・読み込みします。 +IP アドレスや DDNS 結果・エラーログを `STATE_DIR` のファイルに保存・読み込みします。 | ファイル名 | 内容 | |------------|------| -| `ip_ipv4` | 最後に取得した IPv4 アドレス | -| `ip_ipv6` | 最後に取得した IPv6 アドレス | -| `ddns_errors` | DDNS 更新エラーのログ | -| `gate_` | タイムゲートのタイムスタンプ(RFC3339) | +| `cache__` | ドメインごとの最終送信 IP(例: `cache_mydns_0_ipv4`) | +| `ddns_errors` | DDNS 更新エラーのログ(`err_mail` が読んでクリア) | +| `ddns_result_` | 最終 DDNS 更新結果(`ok` / `fail:...`) | +| `gate_ip_cache` | IP キャッシュのタイムスタンプ | +| `gate_errchk` | エラーメール送信のタイムスタンプ | | `lock_` | 多重起動防止ロックファイル | ### `lock` @@ -95,46 +126,86 @@ IP アドレスや DDNS エラーログを `STATE_DIR` のファイルに保存 ### `mode` -コマンドごとのロジックを実装します。テスト容易性のため、外部依存(IP 取得・DDNS 更新・メール送信)はパッケージ変数として注入可能にしています。 +コマンドごとのロジックを実装します。テスト容易性のため、外部依存はパッケージ変数として注入可能にしています。 ```go var ( - ipFetch = ip.Fetch - mydnsUpdateIPv4 = ddns.UpdateMyDNSIPv4 + ipFetch = ip.Fetch + mydnsUpdateIPv4 = ddns.UpdateMyDNSIPv4 + mydnsUpdateIPv6 = ddns.UpdateMyDNSIPv6 cloudflareUpdate = ddns.UpdateCloudflare + sendMailFn = mail.Send ) ``` --- -## データフロー(update コマンド) +## デーモンの動作フロー ``` -main() - └─ config.Load() 設定読み込み - └─ lock.Acquire() 多重起動防止 - └─ mode.Update(cfg) - └─ timegate "update" UPDATE_TIME ゲート確認 - └─ ip.Fetch() グローバル IP 取得 - └─ state.ReadIP() 前回 IP 読み込み - └─ [IP 変化あり] - └─ timegate "ddns" DDNS_TIME ゲート確認 - └─ ddns.UpdateMyDNSIPv4/IPv6() MyDNS 更新 - └─ ddns.UpdateCloudflare() CF 更新 - └─ state.WriteIP() 新 IP を保存 - └─ state.AppendError() エラーがあれば記録 +systemd が dipper_ai daemon を起動 + │ + ├─ 設定読み込み (config.Load) + ├─ 起動ログ出力: "starting (check=5m, keepalive=1d)" + ├─ 10 秒待機(ネットワーク確立猶予) + ├─ 初回サイクル実行(起動直後に 1 回) + │ + ├─ checkTicker (DDNS_TIME 間隔) + │ └─ runCycle() + │ ├─ mode.Update(cfg) IP 変化時に DDNS 更新 + │ ├─ mode.Check(cfg) DNS 解決と実際の IP の一致確認 + │ └─ mode.ErrMail(cfg) エラー集計・通知 + │ + ├─ keepaliveTicker (UPDATE_TIME 間隔、0 なら無効) + │ └─ mode.Keepalive(cfg) 全 MyDNS エントリを強制更新 + │ + └─ SIGTERM / SIGINT 受信 → クリーンシャットダウン ``` +### keepalive について + +DDNS サービスは定期的な更新がないと登録を失効させる場合がある(サービスによって期間は異なる)。 +`Keepalive` は IP が変化していなくても全 MyDNS エントリへ強制送信することで失効を防ぐ。 +Cloudflare は API 登録が失効しないため keepalive は不要であり、`Keepalive` では対象外となる。 + +--- + +## update / check / keepalive の責務分担 + +| コマンド | トリガー | 動作 | +|----------|----------|------| +| `update` | checkTicker | キャッシュと現在 IP を比較し、差分があるエントリのみ更新 | +| `check` | checkTicker(updateの直後) | DNS 解決結果と現在 IP を比較し、不一致なら update キャッシュをリセット | +| `keepalive` | keepaliveTicker | 全 MyDNS エントリを無条件で送信(Cloudflare はスキップ) | +| `err_mail` | checkTicker(checkの直後) | エラーログを確認し、閾値超過でメール通知 | + +`check` が IP とキャッシュの不一致を検知した場合、対象エントリのキャッシュをリセットすることで次の `update` が強制的に再送信を行う。 + --- ## テスト構造 | パッケージ | テスト手法 | |------------|-----------| -| `config` | ユニットテスト(`ParseFile` に文字列を渡す) | -| `ddns` | `httptest.NewServer` によるHTTPモックサーバ | +| `config` | ユニットテスト(`ParseFile` に一時ファイルを渡す) | +| `ddns` | `httptest.NewServer` による HTTP モックサーバ | | `mode` | パッケージ変数差し替えによる関数インジェクション | | `acceptance` | バイナリをビルドして `exec.Command` で呼び出すブラックボックステスト | -受け入れテストは外部 API・`dig` コマンドに依存しません。 -IP 取得が必要なテストでは `IP_CACHE_TIME > 0` + `gate_ip_cache` ファイルの事前配置でキャッシュヒットパスを使用します。 +受け入れテストは外部 API に依存しません。 +`DIPPER_AI_FAKE_IP_V4` / `DIPPER_AI_FAKE_IP_V6` / `DIPPER_AI_FAKE_DNS` 環境変数で外部依存を差し替えます。 + +--- + +## 状態ファイルの配置例 + +``` +/etc/dipper_ai/state/ +├── cache_mydns_0_ipv4 # 203.0.113.42 +├── cache_mydns_0_ipv6 # 2001:db8::1 +├── cache_cf_0_A # 203.0.113.42 +├── ddns_errors # エラーログ(err_mail が読んでクリア) +├── gate_ip_cache # 2025-01-15T10:00:00+09:00 +├── gate_errchk # 2025-01-15T09:00:00+09:00 +└── lock_daemon # 多重起動防止 +``` diff --git a/docs/Configuration.md b/docs/Configuration.md index bc2ba81..046ee61 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -13,6 +13,18 @@ KEY="value" # 前後のダブルクォートは自動で除去 # コメント行 # 行頭 # は無視 ``` +### 時間指定フォーマット + +時間に関する設定(`DDNS_TIME`、`UPDATE_TIME`、`IP_CACHE_TIME`、`ERR_CHK_TIME`)はサフィックス形式が使用できます。 + +| 書式 | 意味 | 例 | +|------|------|----| +| `30s` | 秒(分に切り上げ) | `30s` → 1 分 | +| `5m` | 分 | `5m` → 5 分 | +| `2h` | 時間 | `2h` → 120 分 | +| `1d` | 日 | `1d` → 1440 分 | +| `30`(整数) | 分(後方互換) | `30` → 30 分 | + --- ## STATE_DIR @@ -50,25 +62,86 @@ IPV6_DDNS=on --- -## タイムゲート(分単位) +## タイム設定 + +### DDNS_TIME — デーモンのチェック間隔 + +| | | +|--|--| +| **キー** | `DDNS_TIME` | +| **デフォルト** | `5m`(5 分) | +| **最小値** | `1m` | + +デーモン(`dipper_ai daemon`)が IP チェック・DDNS 更新サイクルを実行する間隔。 +`0` を指定した場合はデフォルト(5 分)が適用されます。 + +IP 変化への応答速度を上げたい場合は短く、システムリソースを節約したい場合は長く設定します。 + +```conf +DDNS_TIME=5m # 5 分ごとにチェック +DDNS_TIME=1m # 1 分ごと(敏感な運用) +DDNS_TIME=1h # 1 時間ごと(余裕のある運用) +``` + +### UPDATE_TIME — keepalive 送信間隔 + +| | | +|--|--| +| **キー** | `UPDATE_TIME` | +| **デフォルト** | `1d`(1 日) | +| **最小値** | `3m` | +| **無効化** | `0` | + +DDNS サービスへの定期 keepalive 送信間隔。 +IP が変化していなくても指定間隔で強制更新し、サービスの登録失効を防ぐ。 + +適切な値はご利用の DDNS サービスの失効期間に合わせて設定してください。 +`0` を指定すると keepalive は無効化されます(Cloudflare のみ利用の場合など)。 + +> **`DDNS_TIME` との独立性** +> `DDNS_TIME` と `UPDATE_TIME` はデーモン内部の独立したタイマーで管理されます。 +> `DDNS_TIME=5m`(5 分チェック)と `UPDATE_TIME=30d`(30 日 keepalive)のような +> 任意の組み合わせが正しく動作します。 -実行間隔を制御します。ゲートファイルが `STATE_DIR` に保存されます。 +```conf +UPDATE_TIME=1d # 1 日ごとに keepalive(MyDNS の推奨) +UPDATE_TIME=7d # 7 日ごと +UPDATE_TIME=0 # keepalive 無効(Cloudflare のみ利用の場合など) +``` -| キー | デフォルト | 最小値 | 説明 | -|------|-----------|--------|------| -| `UPDATE_TIME` | `1440` | 3 | 定期更新の間隔(分) | -| `DDNS_TIME` | `1` | 1 | IP 変化後に DDNS を更新する間隔(分) | -| `IP_CACHE_TIME` | `0` | 15(有効時) | IP キャッシュの有効期間(0 = 無効化) | -| `ERR_CHK_TIME` | `0` | 1(有効時) | エラーメール送信の間隔(0 = 無効化) | +### IP_CACHE_TIME — IP キャッシュ有効期間 -- 設定値が最小値を下回る場合は自動的に最小値に切り上げられます。 -- `0` を設定すると「無効化」として扱われる(`IP_CACHE_TIME`、`ERR_CHK_TIME` のみ)。 +| | | +|--|--| +| **キー** | `IP_CACHE_TIME` | +| **デフォルト** | `0`(無効) | +| **最小値** | `15m`(有効時) | +| **無効化** | `0` | + +グローバル IP の取得結果をキャッシュする期間。 +`0` のとき(デフォルト)は毎回外部 API に問い合わせます。 +`DDNS_TIME` が短い場合にキャッシュを設定すると外部 API への負荷を軽減できます。 ```conf -UPDATE_TIME=1440 # 1日 -DDNS_TIME=1 # 1分 IP_CACHE_TIME=0 # キャッシュなし(毎回取得) -ERR_CHK_TIME=0 # エラーメール無効 +IP_CACHE_TIME=15m # 15 分キャッシュ +``` + +### ERR_CHK_TIME — エラーメール送信間隔 + +| | | +|--|--| +| **キー** | `ERR_CHK_TIME` | +| **デフォルト** | `0`(無効) | +| **最小値** | `1m`(有効時) | +| **無効化** | `0` | + +エラーログの確認・通知メール送信の最小間隔。 +`0` のとき(デフォルト)はチェックのたびに確認します(推奨: `EMAIL_ADR` が設定されている場合は適切な値を設定する)。 + +```conf +ERR_CHK_TIME=0 # 無効(毎サイクル確認) +ERR_CHK_TIME=1h # 1 時間ごとに確認 ``` --- @@ -83,7 +156,7 @@ ERR_CHK_TIME=0 # エラーメール無効 |------|----|------|-----------|------| | `MYDNS_N_ID` | string | ✓ | — | MyDNS マスター ID(省略するとそのエントリを終端とみなす) | | `MYDNS_N_PASS` | string | ✓ | — | MyDNS パスワード | -| `MYDNS_N_DOMAIN` | string | | `""` | ドメイン名(現在はログ用途のみ) | +| `MYDNS_N_DOMAIN` | string | | `""` | ドメイン名(ログ識別用) | | `MYDNS_N_IPV4` | bool | | `on` | このエントリで IPv4 を更新する | | `MYDNS_N_IPV6` | bool | | `off` | このエントリで IPv6 を更新する | @@ -115,7 +188,8 @@ MYDNS_1_IPV6=on ## Cloudflare Cloudflare DNS への更新設定。エントリは `CF_0_*`、`CF_1_*` ... と増やせます。 -ゾーン名からゾーン ID を自動解決します(ゾーン ID を直接書く必要はありません)。 +ゾーン名からゾーン ID を自動解決します。 +Cloudflare は API 登録が失効しないため、keepalive(`UPDATE_TIME`)の対象外です。 ### エントリキー(`N` = 0, 1, 2, ...) @@ -150,13 +224,12 @@ CF_0_IPV6=off ## メール通知 エラーが蓄積された場合に `sendmail` 経由で通知します。 -`EMAIL_CHK_DDNS` または `EMAIL_UP_DDNS` を `on` にする場合、`EMAIL_ADR` は必須です。 | キー | 型 | デフォルト | 説明 | |------|----|-----------|------| -| `EMAIL_CHK_DDNS` | bool | `off` | IP 変化時に通知 | -| `EMAIL_UP_DDNS` | bool | `off` | 定期更新時に通知 | -| `EMAIL_ADR` | string | `""` | 通知先メールアドレス | +| `EMAIL_CHK_DDNS` | bool | `off` | IP 変化時に通知(`update` コマンドが送信) | +| `EMAIL_UP_DDNS` | bool | `off` | keepalive 送信時に通知(`keepalive` コマンドが送信) | +| `EMAIL_ADR` | string | `""` | 通知先メールアドレス(`EMAIL_CHK_DDNS` / `EMAIL_UP_DDNS` を使う場合は必須) | ```conf EMAIL_CHK_DDNS=on @@ -165,3 +238,46 @@ EMAIL_ADR=admin@example.com ``` > **Note:** メール送信は `sendmail` コマンドに依存します。事前にメール送信環境を構築してください。 + +--- + +## 設定例(最小構成・MyDNS のみ) + +```conf +# --- IP --- +IPV4=on +IPV6=off + +# --- タイム --- +DDNS_TIME=5m # 5 分ごとに IP チェック +UPDATE_TIME=1d # 1 日ごとに MyDNS keepalive + +# --- MyDNS --- +MYDNS_0_ID=mydns123456 +MYDNS_0_PASS=yourpassword +MYDNS_0_DOMAIN=home.example.com +``` + +## 設定例(MyDNS + Cloudflare 併用) + +```conf +# --- IP --- +IPV4=on +IPV6=off + +# --- タイム --- +DDNS_TIME=5m +UPDATE_TIME=1d # MyDNS のみ keepalive、Cloudflare は不要 + +# --- MyDNS --- +MYDNS_0_ID=mydns123456 +MYDNS_0_PASS=yourpassword +MYDNS_0_DOMAIN=home.example.com + +# --- Cloudflare --- +CF_0_ENABLED=on +CF_0_API=your_cf_api_token +CF_0_ZONE=example.com +CF_0_DOMAIN=sub.example.com +CF_0_IPV4=on +``` diff --git a/docs/Installation.md b/docs/Installation.md index 69c1915..95f9587 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -48,7 +48,7 @@ GOOS=linux GOARCH=amd64 go build -o dipper_ai ./cmd/dipper_ai ## インストール `scripts/install.sh` を root で実行します。 -以下のファイルが配置され、systemd タイマーが有効化されます。 +バイナリと systemd サービスを配置し、サービスを有効化します。 ```bash sudo bash scripts/install.sh @@ -61,9 +61,12 @@ sudo bash scripts/install.sh | `dipper_ai` バイナリ | `/usr/bin/dipper_ai` | | 設定サンプル | `/etc/dipper_ai/user.conf.example` | | systemd サービス | `/etc/systemd/system/dipper_ai.service` | -| systemd タイマー | `/etc/systemd/system/dipper_ai.timer` | | 状態ファイル | `/etc/dipper_ai/state/`(実行時に自動生成) | +> **v1.5.0 以前からアップグレードする場合** +> install.sh が旧タイマーユニット(`dipper_ai.timer`、`dipper_ai-keepalive.timer`)を自動的に停止・削除します。 +> 手動での削除は不要です。 + ### インストール後の手順 ```bash @@ -71,31 +74,22 @@ sudo bash scripts/install.sh sudo cp /etc/dipper_ai/user.conf.example /etc/dipper_ai/user.conf sudo vi /etc/dipper_ai/user.conf -# タイマーの状態確認 -systemctl status dipper_ai.timer +# サービス状態確認 +systemctl status dipper_ai + +# ログ確認 +journalctl -u dipper_ai -n 30 -# 手動で update を実行してテスト +# 手動で動作確認 sudo dipper_ai update +sudo dipper_ai check ``` -> **`DDNS_TIME` と systemd タイマー間隔について** -> -> `install.sh` は `user.conf` の `DDNS_TIME`(分)を読み取り、 -> `OnUnitActiveSec` を自動的に設定します。 -> 設定ファイルを編集して `DDNS_TIME` を変更した場合は、 -> タイマーに反映させるために `sudo bash scripts/install.sh` を再実行してください。 -> -> | `DDNS_TIME` | タイマー間隔 | -> |-------------|-------------| -> | `0`(デフォルト・無効) | 5 分(フォールバック) | -> | `1` | 1 分 | -> | `5` | 5 分 | - --- ## アップデート -新しいバージョンが出たときはリポジトリのディレクトリで以下を実行するだけです。 +新しいバージョンが出たときはリポジトリのディレクトリで以下を実行します。 ```bash cd dipper_ai @@ -104,13 +98,11 @@ go build -o dipper_ai ./cmd/dipper_ai sudo bash scripts/install.sh ``` -4 ステップの意味: - | ステップ | 内容 | |----------|------| | `git pull` | 最新のソースコードを取得 | | `go build ...` | バイナリを再ビルド | -| `sudo bash scripts/install.sh` | バイナリと systemd unit を上書きインストール・`DDNS_TIME` に合わせてタイマー間隔を再設定・タイマー再起動 | +| `sudo bash scripts/install.sh` | バイナリと systemd サービスを上書きインストール・サービス再起動 | > **Note:** 設定ファイル (`/etc/dipper_ai/user.conf`) はインストールスクリプトで上書きされません。設定は引き継がれます。 @@ -122,7 +114,7 @@ sudo bash scripts/install.sh sudo bash scripts/uninstall.sh ``` -サービス・タイマーの停止と無効化、バイナリの削除を行います。 +サービスの停止・無効化とバイナリの削除を行います。 設定ファイル (`/etc/dipper_ai/`) は保持されます。手動で削除してください。 --- diff --git a/docs/Usage.md b/docs/Usage.md index 0799429..81f33ff 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -1,119 +1,179 @@ # Usage -## コマンド +## コマンド一覧 ``` dipper_ai ``` -| コマンド | 説明 | -|----------|------| -| `update` | 現在の IP を取得し、変化していれば DDNS を更新する | -| `check` | 現在の IP と DDNS 状態を stdout に出力する | -| `err_mail` | 蓄積されたエラーを確認し、条件を満たす場合にメール送信する | +| コマンド | 用途 | 通常の起動元 | +|----------|------|------------| +| `daemon` | 常駐プロセスとして起動(通常運用) | systemd | +| `update` | IP を取得し、変化があれば DDNS を更新 | daemon 内部 / 手動 | +| `check` | DNS 解決と現在 IP を比較し、不一致なら update を強制 | daemon 内部 / 手動 | +| `keepalive` | 全 MyDNS エントリを無条件で強制更新 | daemon 内部 / 手動 | +| `err_mail` | エラーログを集計し、条件を満たす場合にメール送信 | daemon 内部 / 手動 | コマンドを省略した場合、または未知のコマンドを指定した場合は usage を stderr に出力して exit 1 します。 --- -## update +## daemon + +常駐プロセスとして起動し、2 本の内部タイマーで定期処理を実行します。 +通常は systemd によって起動・管理されます。 + +```bash +dipper_ai daemon +``` -IP アドレスを取得し、前回から変化があれば DDNS プロバイダに更新リクエストを送ります。 +### 内部タイマー + +| タイマー | 間隔設定 | 実行内容 | +|---------|---------|---------| +| check ticker | `DDNS_TIME` | `update` → `check` → `err_mail` の順に実行 | +| keepalive ticker | `UPDATE_TIME` | `keepalive`(`UPDATE_TIME=0` の場合は無効) | + +- 起動直後に 10 秒待機(ネットワーク確立猶予)してから初回サイクルを即時実行します +- `SIGTERM` / `SIGINT` を受け取るとクリーンシャットダウンします + +### ログ確認 + +```bash +# リアルタイムログ +journalctl -u dipper_ai -f -**タイムゲート**: `UPDATE_TIME` 分以内に再実行された場合はスキップします(exit 0)。 -**DDNS ゲート**: 更新後 `DDNS_TIME` 分以内に再更新が抑制されます。 +# 直近 50 行 +journalctl -u dipper_ai -n 50 + +# 今日のログ +journalctl -u dipper_ai --since today +``` + +--- + +## update + +現在のグローバル IP を取得し、前回送信値と異なる場合に DDNS プロバイダへ更新リクエストを送ります。 ### 動作フロー -1. `gate_update` タイムゲートを確認 → 未経過ならスキップ -2. 現在の IPv4 / IPv6 を取得(`IPV4` / `IPV6` 設定に従う) -3. 前回の IP と比較 -4. 変化あり → 各 MyDNS エントリ・各 Cloudflare エントリへ更新リクエスト -5. 結果を状態ファイルに保存 +1. IPv4 / IPv6 を取得(`IPV4` / `IPV6` 設定に従う) +2. ドメインごとのキャッシュ(`cache__`)と比較 +3. 変化したエントリのみ更新リクエストを送信 +4. 成功したエントリのキャッシュを更新 +5. `EMAIL_CHK_DDNS=on` の場合はメール通知 -### 出力例 +### 出力 ``` -$ dipper_ai update -# 変化なし: 出力なし、exit 0 -# IP 変化あり: 各プロバイダへの更新が実行される +dipper_ai update: IPv4=203.0.113.42 +dipper_ai update: mydns[0] home.example.com ipv4: ok ``` +IP が変化していない場合は**何も出力しません**(journal がクリーンに保たれます)。 + --- ## check -現在保持している IP アドレスを stdout に出力します。 -IP キャッシュが有効(`IP_CACHE_TIME > 0`)かつキャッシュが新鮮な場合は、状態ファイルの値をそのまま使用します。 +DNS 解決(`net.LookupHost`)で各ドメインの実際の IP を確認し、現在のグローバル IP と一致しない場合に対象エントリのキャッシュをリセットします。 +キャッシュがリセットされると次の `update` 実行時に強制再送信が行われます。 -**タイムゲート**: `DDNS_TIME` 分以内の再実行はスキップします(exit 0)。 +### 動作フロー -### 出力例 +1. 現在のグローバル IP を取得 +2. 各ドメインを DNS 解決 +3. 不一致を検知 → 対象エントリのキャッシュを削除 +4. 不一致があった場合は `update` を呼び出して即時修正 + +### 出力 ``` -$ dipper_ai check -ipv4: 203.0.113.42 -ipv6: 2001:db8::1 +dipper_ai check: home.example.com A=1.1.1.1 want=203.0.113.42 → mismatch +dipper_ai check: mismatch detected — forcing DDNS update for affected domains ``` -IPv6 が無効(`IPV6=off`)の場合は `ipv6:` 行は出力されません。 +全ドメインが一致している場合は**何も出力しません**。 --- -## err_mail +## keepalive -`STATE_DIR` に蓄積されたエラーログを確認し、設定された条件で通知メールを送ります。 +全 MyDNS エントリへ IP を問わず強制更新リクエストを送ります。 +Cloudflare は API 登録が失効しないため対象外です。 -**無効条件**: `ERR_CHK_TIME=0` または `EMAIL_ADR` が空の場合は何もせず exit 0。 -**タイムゲート**: `ERR_CHK_TIME` 分以内の再実行はスキップします(exit 0)。 +```bash +# デーモン経由(UPDATE_TIME タイマー)で自動実行されるが手動でも可 +dipper_ai keepalive +``` -エラーログが存在しない場合は何もしません。送信後はエラーログをクリアします。 +### 動作フロー + +1. 現在のグローバル IP を取得 +2. 全 MyDNS エントリへ無条件で送信(キャッシュの差分チェックなし) +3. 送信後にドメインキャッシュを更新 +4. `EMAIL_UP_DDNS=on` の場合はメール通知 + +### 出力 + +``` +dipper_ai keepalive: IPv4=203.0.113.42 +dipper_ai keepalive: mydns[0] home.example.com ipv4: ok +``` --- -## systemd による自動実行 +## err_mail -インストールスクリプトで以下の 2 つの unit が配置されます。 +`STATE_DIR` に蓄積されたエラーログを確認し、設定された条件でメール通知します。 -### dipper_ai.timer +- `ERR_CHK_TIME=0` または `EMAIL_ADR` が空の場合は何もせず exit 0 +- エラーログが存在しない場合は何もしません +- 送信後はエラーログをクリアします -```ini -[Timer] -OnBootSec=2min # 起動 2 分後に初回実行 -OnUnitActiveSec=5min # 以降 5 分ごとに実行 -``` +--- + +## systemd による管理 + +インストールスクリプトで以下のサービスが配置されます。 ### dipper_ai.service ```ini [Service] -Type=oneshot -WorkingDirectory=/etc/dipper_ai -ExecStart=/usr/local/bin/dipper_ai update -ExecStartPost=/usr/local/bin/dipper_ai check -ExecStartPost=/usr/local/bin/dipper_ai err_mail +Type=simple +ExecStart=/usr/bin/dipper_ai daemon +Restart=on-failure +RestartSec=30s ``` -1 回の発火で `update` → `check` → `err_mail` が順番に実行されます。 -各コマンドは自身のタイムゲートを持つため、5 分ごとに起動しても実際の更新は `UPDATE_TIME` 間隔でのみ行われます。 +`Type=simple` のため systemd がプロセスの lifetime を管理します。 +クラッシュ時は `RestartSec=30s` 後に自動再起動します。 ### 操作コマンド ```bash -# タイマー状態確認 -systemctl status dipper_ai.timer +# サービス状態確認 +systemctl status dipper_ai + +# ログ確認(リアルタイム) +journalctl -u dipper_ai -f + +# 手動で即時 keepalive 実行 +dipper_ai keepalive -# 直近のログ確認 -journalctl -u dipper_ai.service -n 50 +# 手動で即時チェック実行 +dipper_ai check -# 手動で即時実行 -systemctl start dipper_ai.service +# サービス再起動(設定変更後など) +systemctl restart dipper_ai -# タイマー無効化(停止) -systemctl disable --now dipper_ai.timer +# サービス停止 +systemctl stop dipper_ai -# タイマー再有効化 -systemctl enable --now dipper_ai.timer +# 自動起動無効化 +systemctl disable dipper_ai ``` ---