---
name: ssmm
description: AWS SSM Parameter Store を .env ファイル同期の source of truth にする OSS ワークフロー。systemd の EnvironmentFile= と組み合わせて平文 .env を脱却する時、team-scoped prefix (/<team>/<app>/<key>) で横断管理したい時、.env を安全に再生成 / 移行したい時に使う。Rust CLI `ssmm` (crates.io) が必要。
---

Base directory for this skill: <cloned repo>/.claude/skills/ssmm

# ssmm: SSM-backed `.env` sync workflow

このプロジェクト付属 skill。`ssmm` CLI の使い方と、typical な
`put → sync → systemd` フローを示す。チーム内部固有の運用ルールは
含まない generic 版。

## いつ使うか

- 新しいアプリを systemd service 化するとき、最初から `.env` を SSM
  ベースにしたい
- 既存の平文 `.env` を SSM に移行したい
- 旧 prefix (`/<app>/` 直下など) を新 `/<team>/<app>/` 規約に揃えたい
- 複数アプリ間の重複キー / 重複値を棚卸ししたい
- SecureString で保存すべき key が誤って String で投入されていないか
  チェックしたい

## ゴール

```
<project>/.env (平文、ディスク常駐、git commit 事故のリスク)
        ↓
SSM Parameter Store /<team>/<app>/*  を source of truth にし、
systemd 起動時に ssmm sync が .env (mode 0600) を再生成する運用
```

`.env` は実行時産物。git/ディスクに永続的な秘密情報を残さない。
トレードオフの詳細は README の **Security model** section 参照。

## 前提

- `ssmm` CLI install 済: `cargo install ssmm` か
  `cargo install --git https://github.com/kok1eee/ssmm`
- **`SSMM_PREFIX_ROOT` 環境変数 or `--prefix /<your-team>` 明示必須** (v0.3.0 以降)。
  未設定で呼ぶとエラーで停止する
- AWS IAM 権限: `ssm:PutParameter` / `GetParametersByPath` /
  `GetParameters` / `DescribeParameters` / `DeleteParameter(s)` /
  `AddTagsToResource` / `RemoveTagsFromResource` /
  `ListTagsForResource`
- SecureString は AWS managed key (`alias/aws/ssm`) デフォルト。
  カスタム CMK 使用時は `kms:Decrypt` 追加

## Prefix と命名規則

```
/<team>/<app>/<key>                → KEY
/<team>/<app>/<segment>/<key>      → SEGMENT_KEY
```

- `<team>`: チーム namespace (IAM policy を
  `arn:...:parameter/<team>/*` で一括付与可能にする)
- `<app>`: **dash-case** で統一 (`billing-api`, `auth-worker` など)
- CWD basename が snake_case (`hikken_schedule`) でも `ssmm` が自動で
  dash-case に変換して `--app` に使う
- `<key>`: フラット dash-case。`.env` 側の `KINTONE_API_TOKEN` は
  SSM 上 `kintone-api-token` として保存され、`sync` 時に
  `KINTONE_API_TOKEN` に戻る

## SecureString 自動判定

`ssmm put` は**保守的デフォルト** (unknown は SecureString):

- 末尾 `_PATH` / `_DIR` / `_CHANNEL` / `_NAME` / `_HOST` / `_PORT` /
  `_REGION` / `_ENDPOINT` → String
- **`_URL` は safe list に入っていない** (URL は credentials を含む
  ことが多い: `postgres://user:pass@host/db`, Slack webhook URL,
  Sentry DSN)
- それ以外 → SecureString

ヒューリスティックが不適切な時は per-key オーバーライド:

```bash
ssmm put --env .env --secure SENTRY_DSN --secure DATABASE_URL
ssmm put --env .env --plain-key PUBLIC_METRICS_URL
ssmm put --env .env --plain-all   # 全 String 強制 (public-only app)
```

`put` 出力に判定根拠が付く:
```
✓ /<team>/<app>/sentry-dsn (SecureString [forced: --secure], len=...)
✓ /<team>/<app>/log-dir    (String [auto: suffix], len=...)
```

## 手順

### 0. prefix 設定

```bash
export SSMM_PREFIX_ROOT=/<your-team>   # 一度 shell rc に入れると楽
# or: 毎回 `ssmm --prefix /<your-team> <subcmd>`
```

### 1. SSM 投入

```bash
cd <app-root>
ssmm put --env .env                    # CWD basename を <app> に使う
```

### 2. 検証

```bash
ssmm list                              # CWD 自動判定
ssmm list --keys-only
```

### 3. systemd に接続

新規 wrapper:

```bash
# scripts/start-pre.sh
#!/usr/bin/env bash
set -euo pipefail
APP_ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$APP_ROOT"

export SSMM_PREFIX_ROOT=/<your-team>
ssmm sync --app <a> --out "$APP_ROOT/.env" --strict
# 後続: uv sync / playwright install 等あれば
```

systemd unit に:
```ini
[Service]
ExecStartPre=/opt/app/scripts/start-pre.sh
EnvironmentFile=/opt/app/.env
ExecStart=/opt/app/run.sh
```

### 4. 動作確認

```bash
systemctl --user start --no-block <service>
journalctl --user -u <service> --no-pager --since "1 minute ago" -n 20
#   ssmm: no change (N variables; ...)    ← 既存 .env と一致
#   ssmm: wrote N variables to ...        ← 初回 or SSM 変更あり
```

`--strict` を付けておくと、shared / tag overlay の衝突が起きた時に
service が failure ステータスで止まる (journalctl で warning が埋も
れる事故を防ぐ)。

## 運用パターン

### 共有値 (複数アプリで同じ token)

2 通り:

```bash
# path-based: /<team>/shared/* に明示配置
ssmm put --env /path/shared.env --app shared

# tag-based: 既存 per-app parameter に shared=true を付ける
ssmm tag add kintone-api-token shared=true
```

`sync` はデフォルトで `/<team>/shared/*` を overlay する
(`--no-shared` で off)。tag-based は `--include-tag shared=true` で
取り込み。衝突時の優先順位: **app > include-tag > shared**。

### 棚卸し

```bash
ssmm dirs                              # 全 app namespace と件数
ssmm check --duplicates                # キー名重複 (同名 key が複数 app に)
ssmm check --values                    # 値一致グループ (SHA-256 マスク表示)
ssmm check --values --show-values      # 値を露出 (秘匿注意)
```

### prefix 移行 (旧→新)

**SSM には soft-delete がないので 3 段階で**:

```bash
# 1. copy のみ (旧は残る、safe)
ssmm migrate /old-prefix/app /<team>/app

# 2. --delete-old (単独では dry-run、backup JSON を /tmp に出力)
ssmm migrate /old-prefix/app /<team>/app --delete-old
# → /tmp/ssmm-migrate-backup-<ts>.json (mode 0600)

# 3. 確認後に実削除
ssmm migrate /old-prefix/app /<team>/app --delete-old --confirm
```

### タグ管理 (既存 parameter に後付け)

```bash
ssmm tag add <key> env=prod owner=backend criticality=high
ssmm tag list <key>
ssmm tag remove <key> criticality
```

`app` タグは `put` 時に自動付与され、`tag add/remove` では操作不可
(予約タグ)。

## Concurrency チューニング

SSM の PutParameter は per-account で TPS ~3/s (standard)。他の
プロセス (CI / 他チーム) と同時に叩くとスロットリングする:

```bash
ssmm --write-concurrency 1 put --env .env   # 他と同居時は 1 まで絞る
ssmm --read-concurrency 20 list --all       # read は余裕あるので上げる
```

adaptive retry (max 10 回) は自動。

## 検証チェックリスト

- [ ] `ssmm list` で想定 key 数が出る
- [ ] SecureString (🔒 アイコン) が secret 系 key に付いている
- [ ] `ssmm sync` 2 回目に `ssmm: no change (N variables)` (冪等)
- [ ] unit ファイルに `ExecStartPre=.../scripts/start-pre.sh` +
      `EnvironmentFile=.../.env` が揃う
- [ ] `systemctl --user start --no-block` 後 `Result=success`
- [ ] `.env` が mode 0600
- [ ] `.gitignore` に `.env*` 含まれる
- [ ] `ssmm check --duplicates` で意図しない横断重複がない

## トラブルシュート

| 症状 | 対処 |
|---|---|
| `Error: no prefix configured` | `export SSMM_PREFIX_ROOT=/<your-team>` or `--prefix /<your-team>` を追加 |
| `cannot determine CWD basename` | `--app <name>` を明示、あるいは `cd <project-dir>` してから呼ぶ |
| `warning: empty value, skipped: KEY` | `.env` 側の `KEY=` が空。SSM は空文字列 reject、意図していれば無視 |
| `ThrottlingException: Rate exceeded` | `--write-concurrency 1` に落とす。adaptive retry はすでに有効 |
| `.env` を手動で変えたのに `sync` で `no change` | ローカル .env は SSM と既に一致。意図的に regenerate したい時は `rm .env && ssmm sync --out .env` |
| systemd の cron 起動で `ssmm sync` が失敗 | `export SSMM_PREFIX_ROOT=...` が `start-pre.sh` に入っているか確認 |
| `sync --strict` で exit 非0 | shared / tag key が app-level で overridden。意図しない上書きなら key rename、意図通りなら `--strict` 外すか override を削除 |

## 将来拡張 (backlog)

- `ssmm verify` — SSM vs 実 `.env` の diff (CI pre-commit 用)
- `ssmm install` — `scripts/start-pre.sh` 雛形生成 (playwright / uv
  sync flag 付き)
- `ssmm tag add/remove` を bulk 化 (prefix 指定で配下一括 tag 付与)

issues: https://github.com/kok1eee/ssmm/issues
