dev-timer
WIP(現在進行中)
Work In Progress
このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。
現在の状況
ステータス: Phase 1(Backend MVP)+ Phase 2(Lightsail デプロイ)完了。本番運用開始。
完了したこと
- Phase 1: Hono + SQLite + ntfy + CLI を実装、ローカル動作確認 PASS
- Phase 2:
https://dev-timer.ikapps.comで本番稼働中- systemd
dev-timer.service(deploy ユーザー / port 3001 / Restart=on-failure / メモリ ~57MB) - DB:
/var/lib/dev-timer/dev-timer.db - Nginx + Let's Encrypt(HTTPS、HTTP→HTTPS 301、自動更新)
- deploy ユーザーの crontab で毎分
/api/check-dueを発火 - DNS: Route 53 で A レコード(
dev-timer.ikapps.com→13.230.63.19) - ntfy トピック:
dev-timer-ichirokisanuki-{random12}形式で確定(実値は両環境の.envのみ) - iPhone の ntfy アプリで購読済 / e2e 通知到達確認済(CLI から
add --in 2m→ 2分後に着信)
- systemd
- 設定ファイルの真実のコピーを
deploy/配下で git 管理(dev-timer.service/nginx/dev-timer.ikapps.com)
次にやること
Phase 3: Web UI(Astro、ROADMAP の「今月」枠)
- Astro セットアップ(dev-tracker のスタックを流用、
apps/webかfrontend/か配置場所は要検討) - 一覧画面(pending / 全件タブ切替)
- 新規作成フォーム(
fire_atは--in <duration>風 + datetime-local の両対応?) - done / 削除ボタン
- デプロイ:
- 静的サイトとして build → 既存の Hono に同居して
app.use('/*', serveStatic({ root: './dist-web' }))で配信、または別 vhost - 認証なし方針なので URL 知っていれば誰でも叩ける(DECISIONS で確認済)
- 静的サイトとして build → 既存の Hono に同居して
運用面の TODO(任意・優先度低)
- GitHub Actions で自動デプロイ(現状は手動 rsync)
- Lightsail 側のリソース監視(memory tight、~150MB 余裕しかない)
詰まっていること
なし
過去のWIPアーカイブ
(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)
2026-04-27 17:50 時点のスナップショット
ステータス: 設計合意済み・プロジェクト初期化のみ完了。実装はこれから。
完了したこと
- 要件と設計の合意(dev-tracker と分離する判断、技術スタック確定)
~/cdev/dev-timer初期化(git, .devnotes/, README, CLAUDE.md)- GitHub private リポ作成 + push
dev-trackedtopic 付与済(dev-tracker の一覧に出る)
次にやること(Phase 1: Backend MVP)
- package.json と TypeScript セットアップ
- データモデル定義(SQLite スキーマ)
- Hono サーバー実装
- ntfy 連携
- CLI
- ローカルでの動作確認
次にやること(Phase 2: サーバーデプロイ)
- Lightsail 側準備(Node.js /
/opt/dev-timer// SQLite / systemd / Nginx / certbot / DNS / cron) - ntfy トピック名を決定
- iPhone に ntfy アプリ install + topic 購読
次にやること(Phase 3: Web UI)
- Astro フロントエンド
- デプロイ(GitHub Actions → rsync)
詰まっていること
なし(実装着手前)
ROADMAP(計画)
ロードマップ
今週
- Phase 1: Backend MVP
- package.json + TypeScript + Hono + better-sqlite3 セットアップ
- SQLite スキーマ定義(timers テーブル)
- REST API 実装(CRUD + check-due)
- ntfy.sh 送信ロジック
- CLI 実装(add / list / done)
- ローカル動作確認
- Phase 2: サーバーデプロイ
- Lightsail に Node.js install
- systemd unit 作成・起動
- Nginx vhost + certbot で SSL
- DNS A レコード追加(Route 53、AWS CLI)
- crontab で
/api/check-dueを毎分叩く - ntfy トピック名確定 + iPhone 購読
- エンドツーエンド疎通確認
今月
- Phase 3: Web UI
- Astro セットアップ(dev-tracker のスタイル流用)
- 一覧 / 作成フォーム / done / delete
- GitHub Actions で自動デプロイ(現状は手動 rsync)
今四半期
- dev-tracker の詳細ページから「このプロジェクトの open フォローアップ」リンク追加(dev-timer の API を叩く)
- スヌーズ機能(通知時に「1時間後にもう一度」)
いつか
- 通知に Action ボタン(done/snooze)を埋め込む(ntfy のクリックアクション)
- 繰り返しタイマー(毎週月曜 9:00 など)
- Slack / メール通知も選択可に
- 通知履歴の振り返りページ(「今月発火したフォローアップは N 件」など)
DECISIONS(意思決定)
意思決定記録
このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。
2026-04-27: ntfy トピック名は dev-timer-ichirokisanuki-<12文字> 形式
背景: ntfy.sh は subscribe にトークンが要らず誰でも購読可能なため、トピック名自体が実質的なシークレットになる。一方で覚えやすさも欲しい。
決定: 接頭辞を dev-timer-ichirokisanuki- に固定し、接尾辞を node -e 'crypto.randomBytes(9).toString("base64url")' で生成(base64url 12 文字、英数 + -_)。実値はサーバーの /opt/dev-timer/.env とローカルの .env(どちらも gitignore 済)にのみ保管し、git には残さない。
理由: 接頭辞でプロジェクト・所有者を識別、接尾辞 12 文字で 70bit 弱のエントロピーが入るので推測攻撃に十分耐える。これに加えて「タイマー本文に詳細コンテキストを書かない」方針(既存 DECISIONS)を併用すれば、漏れても被害は限定的。
2026-04-27: 設定ファイルの真実のコピーは deploy/ 配下で git 管理
背景: systemd unit と Nginx vhost をサーバー側 (/etc/systemd/system/ / /etc/nginx/sites-available/) に直接置く運用は、設定の追跡性が悪く再現性も低い。dev-tracker は GitHub Actions YAML だけリポ管理で、サーバー側設定はリポ外(multi-purpose-lightsail-server1 の DEVLOG / DECISIONS にメモするのみ)。
決定: dev-timer ではプロジェクトリポ直下の deploy/ に systemd unit と Nginx vhost を置き、サーバー側はそれを scp/install で配置する運用にする。サーバー側で certbot 等が書き換えた場合は、ローカルにコピーを取り直して反映させる。
理由: dev-timer はサーバー常駐型サービスなので、systemd の再現は dev-tracker(静的ファイル rsync のみ)より重要。ローカルで diff 取れる・PR レビューできる利点が大きい。deploy/ 配下にあれば「アプリのソースとは別物」という意図も明確(誤って npm 系のパスと混同しない)。
2026-04-27: ローカル CLI のデフォルト DEV_TIMER_API は本番 URL
背景: CLI は本番運用が主、ローカル開発時のサーバー起動はたまにしか発生しない。デフォルトをどちらに振るかで普段の使い勝手が変わる。
決定: .env / .env.example のデフォルトを https://dev-timer.ikapps.com にする。ローカル開発で API を立ち上げて叩きたいときは、DEV_TIMER_API=http://127.0.0.1:3001 npx tsx bin/dev-timer.ts ... のように env で一時上書き する運用。
理由: 普段の dev-timer add --in 3d "..." は本番に書き込むのが意図したい挙動。ローカル開発のために env を毎回設定するより、開発時だけ env を一時的にセットする方が頻度的に合理的。逆向きの設定(ローカルがデフォルト)だと、CLI を本番運用に使うたびに env 必要で煩わしい。
2026-04-27: dev-tracker と分離して新規プロジェクトにする
背景: 開発中の follow-up リマインダ機能を dev-tracker に組み込むか、別プロジェクトにするか議論。
決定: 別プロジェクト dev-timer として分離し、~/cdev/dev-timer に作成する。
理由: dev-tracker は「GitHub から pull した read-only スナップショット」、dev-timer は「ユーザーが書き込む follow-up タスク」。データソースもライフサイクルも違うので、同居させると概念がブレる。dev-tracker は静的サイト(ビルド済rsync)で済むが dev-timer はサーバー常駐が必要、という運用面の差も大きい。
2026-04-27: iPhone 通知は ntfy.sh を使う
背景: 通知方法の候補は ntfy.sh / Pushover / Slack DM / Telegram bot。
決定: ntfy.sh を採用。
理由: OSS、無料、curl 1発で送信可能、iOS app あり。Pushover は安定だが $5 課金で買い切るほど大規模に使う想定ではない。Slack/Telegram はチャンネル設計が必要で大袈裟。トピック名は推測しにくい長い文字列にすれば実質プライベート(誰でも subscribe できる仕様への対処)。
2026-04-27: データはサーバー側(Lightsail)に置く
背景: タイマーの永続化先は Mac か Lightsail か。
決定: Lightsail 側に SQLite で持つ。
理由: push 通知のために常時走る cron が必要。Mac はスリープしたら cron が動かないので不可。Lightsail は常時稼働で既に他サービスを動かしているので、リソース面でも自然。CLI からは API 経由で書き込む。
2026-04-27: アクセス制御は付けない
背景: タイマー本文に SQL や Issue 参照などの開発コンテキストが入る場合のリスクを議論。
決定: Basic 認証 / API キー等の認証は付けない(dev-tracker と同様、URL を知っている人は誰でも見られる)。
理由: ユーザー方針: 「通知はただアラートだけで、実行する内容を詳細にかかないから大丈夫」。タイマー本文は「ALTER TABLE の効果確認」程度の薄いラベルに留めるため、認証コストを払わない。後から必要になったら追加可能。
2026-04-27: バックエンドは Hono + SQLite
背景: ランタイム選定。Express / Fastify / Hono、DB は SQLite / PostgreSQL / file-JSON。
決定: Hono + better-sqlite3。
理由: Hono は軽量・型安全・モダンで、個人ツールに最適。SQLite はファイル1個で済み運用が楽、個人スケール(タイマー数百〜千件)なら全く問題ない。Postgres は overkill、JSON ファイルは同時書き込み制御が面倒。
2026-04-27: フロントは Astro(dev-tracker と統一)
背景: Web UI のフレームワーク選定。
決定: Astro を採用。
理由: dev-tracker と同じスタックで揃えると学習コスト 0、コンポーネント・スタイルを流用しやすい。dev-timer は SSR 寄りだが、Astro は server endpoints を持てるのでバックエンドへの fetch も自然。
2026-04-27: ポート 3001 を使う
背景: 既存サービスとのポート衝突回避。
決定: 3001 で起動。
理由: 既存: 7channel=8001, aix=5001, mydb=php-fpm。3000番台は空き、3001 が見やすい。
DEVLOG(作業ログ)
開発日誌
このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。
2026-04-27
18:25 - Phase 2: Lightsail デプロイ完了・iPhone 通知 e2e 確認
やったこと
- ntfy トピック名生成:
dev-timer-ichirokisanuki-{random12}形式で確定(node の crypto.randomBytes(9) を base64url 化)。実値は両環境の.envのみに保管 - iPhone に ntfy アプリ install + topic 購読 + 通知許可(ユーザー側で実施、curl publish で届くこと確認)
- Route 53 で A レコード追加:
dev-timer.ikapps.com→13.230.63.19(AWS CLI、UPSERT、伝播 INSYNC まで確認) - Lightsail(multi-purpose-lightsail-server1)への SSH 経由でのセットアップ:
- Node.js 22 LTS を NodeSource からインストール(v22.22.2 / npm 10.9.7)+ build-essential
/opt/dev-timer/・/var/lib/dev-timer/を deploy ユーザー所有で作成- ローカルから
/tmp/dev-timer-stageに rsync → サーバー側で sudo rsync で/opt/dev-timer/へ移動 + chown deploy - サーバー上で
npm ci+npm run build(better-sqlite3 のネイティブビルドも完走) /opt/dev-timer/.env配置(PORT=3001/DB_PATH=/var/lib/dev-timer/dev-timer.db/NTFY_TOPIC/NTFY_SERVER、600 / deploy 所有)dev-timer.serviceを/etc/systemd/system/に install + enable + start(active running、メモリ 57M)- Nginx vhost を
/etc/nginx/sites-available/に配置 + sites-enabled にリンク + reload certbot --nginx -d dev-timer.ikapps.comで Let's Encrypt SSL 発行(自動更新スケジュール込み)- deploy ユーザーの crontab に
* * * * * curl -sS -X POST http://127.0.0.1:3001/api/check-due > /dev/null 2>&1を追加
- e2e 確認: ローカル CLI(
DEV_TIMER_API=https://dev-timer.ikapps.com)からadd --in 2mで予約 → 約 2 分後に iPhone に通知到達 - 後処理:
- ローカル
.env/.env.exampleのDEV_TIMER_APIを本番 URL(https://dev-timer.ikapps.com)に変更 - certbot 適用後の Nginx vhost を
deploy/nginx/dev-timer.ikapps.comに反映(真実のコピーを git 管理)
- ローカル
決めたこと(DECISIONS.md にも転記)
- ntfy トピックは
node -e 'randomBytes(9).toString("base64url")'ベースで生成し、dev-timer-ichirokisanuki-<12文字>形式に統一 - サーバー側設定ファイル(systemd unit / Nginx vhost)は ローカル
deploy/配下に真実のコピーを置いて git 管理 - ローカル CLI のデフォルト
DEV_TIMER_APIは本番 URL にする(ローカル開発で API を起動したいときだけ env 上書き)
詰まったこと / 気づき
deployユーザーは ssh 鍵が違うので直接ログインできず、ubuntu経由 →/tmpに rsync → sudo rsync で/optに移動 + chown という二段構えで配置した- 初回
npm ciで deprecated 警告(prebuild-install)出たが better-sqlite3 11.x は prebuilt binary を引いて動作 OK 127.0.0.1直叩きのHost: dev-timer.ikapps.comで 404(default_server に流れた)が出たが、外部から FQDN で叩くと正しくルーティング → 実害なし
次回やること
- Phase 3: Web UI(Astro)。dev-tracker のスタックを流用
- 必要に応じて GitHub Actions で自動デプロイ(現状は手動 rsync)
18:00 - Phase 1: Backend MVP 実装(Hono + SQLite + ntfy + CLI)
やったこと
- TypeScript + ESM、Node.js 20+ 前提のスケルトンを作成
src/config.ts:dotenvで.env読み込み(PORT / DB_PATH / NTFY_TOPIC / NTFY_SERVER)src/db.ts: better-sqlite3 でtimersテーブル + WAL モード、status はpending/fired/done/cancelledの 4 値src/notifier.ts: ntfy.sh への POST(Title ヘッダは ASCII のみ許容なので非ASCII は RFC2047 で base64 エンコード)src/server.ts: Hono の REST API(POST /api/timers/GET /api/timers/PATCH /api/timers/:id/DELETE /api/timers/:id/POST /api/check-due)、zod でバリデーションsrc/index.ts:@hono/node-serverで port listenbin/dev-timer.ts: CLI(add --in 30m|2h|3d|1w/--at ISO8601/--project/list [--status]/done/cancel/rm/check).env.exampleと.gitignore(SQLite 生成物*.db/*.db-shm/*.db-walを追加)整備- ローカル動作確認: add → list → check-due(過去日 timer が
firedに遷移)→ done → rm まで一通り PASS、tsc --noEmitもエラーなし - commit & push(
bf01466)
気づき
noUncheckedIndexedAccess: trueを有効にしているのでparseDurationでm[1]!のような non-null assertion を明示的に書く必要があった- Phase 2 の rsync 戦略を見越して
dist/は git 管理外、deploy/配下に systemd unit / Nginx vhost のひな形を置く方針も同時に固めた
次回やること
- Phase 2: Lightsail デプロイ
17:45 - プロジェクト発足・設計合意・初期化
やったこと
- dev-tracker のセッション中に follow-up リマインダの実装を相談、別プロジェクトとして分離する判断
- 設計合意:
- 通知: ntfy.sh
- データ: Lightsail (SQLite)
- インターフェース: CLI + Web UI(Astro)
- 認証: なし(タイマー本文に詳細コンテキストを書かない方針)
- ポート: 3001
~/cdev/dev-timer初期化(new-project-setup skill 経由)- git init / .gitignore / README / CLAUDE.md / .devnotes/ 4ファイル
- GitHub private リポ作成
dev-trackedtopic 付与
- 引き継ぎ用に CLAUDE.md / WIP.md / DECISIONS.md / ROADMAP.md を充実
次回やること
- Phase 1(Backend MVP)から着手: package.json と TypeScript セットアップ → Hono サーバー → SQLite スキーマ → ntfy 連携 → CLI
詰まったこと / 気づき
- なし(着手前)
最近のコミット
README
dev-timer
概要
(記入予定)
セットアップ
(記入予定)
使い方
(記入予定)