diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index a756465..36e17a0 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..15b3dd5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,71 @@ +name: release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "再リリースする既存タグ (例 v0.1.0)" + required: true + +# 既定はトークンを read のみに絞り、リリースジョブだけ write に昇格する。 +permissions: + contents: read + +# 同一タグへの連続 push / 手動再実行で公開ジョブが多重起動し、Release 作成や +# アセット添付が競合しないよう直列化する (実行中のジョブはキャンセルしない)。 +concurrency: + group: release-${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }} + cancel-in-progress: false + +jobs: + release: + name: Build and publish release archives + runs-on: ubuntu-latest + permissions: + # GitHub Release の作成・アセット添付に必要 (最小権限)。 + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + # git describe --tags のタグ解決と SOURCE_DATE_EPOCH (git log %ct) の双方に + # 全履歴・タグが必要。浅いチェックアウトだとバージョンがハッシュへフォール + # バックし、アーカイブの mtime も不定になる。 + fetch-depth: 0 + ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', github.event.inputs.tag) || github.ref }} + + - name: Set up Go + uses: actions/setup-go@v6 + with: + # バージョンの単一情報源は go.mod。check-latest で go.mod が指すバージョンを + # 確実に取得する (ランナー同梱の Go に依存しない)。新しい Go の取り込みは go.mod の更新で行う。 + go-version-file: go.mod + check-latest: true + + - name: Run tests + # 公開前にタグのコミットでテストを通し、壊れたバイナリの出荷を防ぐ。 + run: make test + + - name: Build cross-platform archives and checksums + # ビルド・アーカイブ・チェックサムは Makefile が単一の情報源 (CLAUDE.md)。 + # 同一コミットに複数タグがある場合に git describe が別タグを拾わないよう、 + # 公開する tag を VERSION として明示的に渡し tag_name と一致させる。 + run: make package VERSION=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }} + + - name: Publish GitHub Release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }} + # 自動生成ノートはタグ push 時のみ。手動再実行で手書きのリリースノートを上書きしない。 + generate_release_notes: ${{ github.event_name == 'push' }} + files: | + dist/*.tar.gz + dist/*.zip + dist/SHA256SUMS + # GITHUB_TOKEN は自動注入。上の contents:write で fork (yuu61/deadman) へ公開できる。 + # 注意: fork からの pull_request では token が read-only になり Release 作成に失敗するため、 + # この workflow に pull_request trigger を足さないこと。 diff --git a/Makefile b/Makefile index 749e010..9318ec7 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,12 @@ GOLANGCI ?= golangci-lint # golangci-lint を `make tools` で入れる際のバージョン (現行 v2 系)。 GOLANGCI_VERSION ?= v2.12.2 +# アーカイブ生成に使う外部ツール (macOS で GNU tar を使う場合は `make package TAR=gtar`)。 +# zip の変数は ZIP_BIN とする: Info-ZIP の zip は環境変数 $ZIP / $ZIPOPT を既定オプション +# として読むため、make 変数を ZIP と名付けて export すると引数解釈が壊れる (出力が作られない)。 +TAR ?= tar +ZIP_BIN ?= zip + BIN_NAME := deadman PKG := ./cmd/deadman BIN_DIR := bin @@ -18,6 +24,15 @@ EXE := $(shell $(GO) env GOEXE) # git から導出するバージョン文字列 (タグが無ければコミットハッシュ、未コミット差分は -dirty)。 VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +# アーカイブを決定的にするためコミット日時を SOURCE_DATE_EPOCH に固定する +# (tar の mtime 正規化・gzip -n と併用)。環境変数が設定されていればそちらを優先。 +SOURCE_DATE_EPOCH := $(or $(SOURCE_DATE_EPOCH),$(shell git log -1 --pretty=%ct 2>/dev/null || echo 0)) + +# zip は SOURCE_DATE_EPOCH を解さないため、固める前に touch で各ファイルの mtime を固定する。 +# touch -d @epoch は GNU 専用なので、GNU/BSD 双方が解せる touch -t 形式 (ホスト TZ) へ変換する。 +# date が epoch を扱えない環境でも決定性を失わないよう、最終手段として DOS 下限の 1980-01-01 に倒す。 +SOURCE_DATE_TOUCH := $(or $(shell date -d @$(SOURCE_DATE_EPOCH) +%Y%m%d%H%M.%S 2>/dev/null || date -r $(SOURCE_DATE_EPOCH) +%Y%m%d%H%M.%S 2>/dev/null),198001010000.00) + # リリースビルド用フラグ。-s -w でシンボルを除去、-trimpath で絶対パスを排除して再現性を上げる。 # -X main.version で git 由来のバージョン文字列をバイナリへ埋め込み、TUI のタイトル表示に反映する。 LDFLAGS := -s -w -X main.version=$(VERSION) @@ -48,6 +63,54 @@ cross: ## 全プラットフォーム向けにクロスコンパイル (dist/ $(GO) build $(BUILDFLAGS) -o $$out $(PKG) || exit 1; \ done +.PHONY: package +package: ## リリース用アーカイブ(.tar.gz/.zip)と SHA256SUMS を dist/ に生成 (要 GNU tar/gzip/zip/sha256sum) + @command -v $(ZIP_BIN) >/dev/null 2>&1 || { echo "error: zip が見つかりません (Debian/Ubuntu: apt install zip / macOS: brew install zip)" >&2; exit 1; } + @$(TAR) --version 2>/dev/null | grep -q 'GNU tar' || { echo "error: GNU tar が必要です (macOS: brew install gnu-tar してから make package TAR=gtar)" >&2; exit 1; } + @mkdir -p $(DIST_DIR) + @# 旧アーカイブ・make cross の素バイナリ・中断で残ったステージを一掃して、古い版が混ざらないようにする。 + @rm -rf $(DIST_DIR)/$(BIN_NAME)-* $(DIST_DIR)/SHA256SUMS + @case "$(VERSION)" in \ + *-dirty) echo "warning: VERSION=$(VERSION) — 作業ツリーが汚れています。クリーンなタグ付きの木で実行してください。" >&2 ;; \ + dev) echo "warning: VERSION=dev — git 情報がありません。リリースには git tag vX.Y.Z を推奨します。" >&2 ;; \ + *-[0-9]*-g[0-9a-f]*) echo "warning: VERSION=$(VERSION) はタグから先行したコミットです。リリースはタグ直上のコミットで実行してください。" >&2 ;; \ + v[0-9]*) : ;; \ + *) echo "$(VERSION)" | grep -Eq '^[0-9a-f]{7,40}$$' \ + && echo "warning: VERSION=$(VERSION) はコミットハッシュです。リリース前に git tag vX.Y.Z を付けてください。" >&2 || true ;; \ + esac + @for p in $(PLATFORMS); do \ + os=$${p%/*}; arch=$${p#*/}; \ + ext=; [ "$$os" = windows ] && ext=.exe; \ + name=$(BIN_NAME)-$(VERSION)-$$os-$$arch; \ + stage=$(DIST_DIR)/$$name; \ + echo ">> $$stage"; \ + rm -rf $$stage; mkdir -p $$stage; \ + CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch \ + $(GO) build $(BUILDFLAGS) -o $$stage/$(BIN_NAME)$$ext $(PKG) || exit 1; \ + cp deadman.conf LICENSE $$stage/ || exit 1; \ + sed '/](img\//d' README.md > $$stage/README.md || exit 1; \ + chmod 0755 $$stage/$(BIN_NAME)$$ext; \ + chmod 0644 $$stage/deadman.conf $$stage/README.md $$stage/LICENSE; \ + if [ "$$os" = windows ]; then \ + find $$stage -exec touch -t $(SOURCE_DATE_TOUCH) {} + 2>/dev/null || true; \ + ( cd $(DIST_DIR) && rm -f $$name.zip && ZIP= ZIPOPT= TZ=UTC $(ZIP_BIN) -qrX $$name.zip $$name ) || exit 1; \ + else \ + $(TAR) --sort=name --mtime="@$(SOURCE_DATE_EPOCH)" \ + --owner=0 --group=0 --numeric-owner \ + --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime \ + -C $(DIST_DIR) -cf $(DIST_DIR)/$$name.tar $$name || exit 1; \ + gzip -nf $(DIST_DIR)/$$name.tar || exit 1; \ + fi; \ + rm -rf $$stage; \ + done + @cd $(DIST_DIR) && \ + if command -v sha256sum >/dev/null 2>&1; then HASH="sha256sum"; \ + elif command -v shasum >/dev/null 2>&1; then HASH="shasum -a 256"; \ + else echo "error: sha256sum/shasum が見つかりません" >&2; exit 1; fi; \ + set --; for f in *.tar.gz *.zip; do [ -e "$$f" ] && set -- "$$@" "$$f"; done; \ + if [ "$$#" -gt 0 ]; then $$HASH "$$@" > SHA256SUMS && echo ">> $(DIST_DIR)/SHA256SUMS"; \ + else echo "warning: アーカイブが無いため SHA256SUMS を生成しません。" >&2; fi + .PHONY: install install: ## go install で $GOBIN/$GOPATH/bin に導入 $(GO) install $(BUILDFLAGS) $(PKG) diff --git a/README.md b/README.md index 1bdf48d..3ed7008 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,32 @@ go run ./cmd/deadman deadman.conf go install github.com/yuu61/deadman/cmd/deadman@latest ``` +## インストール(リリース版バイナリ) + +[Releases](https://github.com/yuu61/deadman/releases) から OS / アーキテクチャに合ったアーカイブをダウンロードして展開します。 +linux / macOS は `.tar.gz`、Windows は `.zip` で、ファイル名は `deadman---` 形式です(中の実行ファイル名は常に `deadman` / `deadman.exe`)。 +展開すると `deadman---/` という単一ディレクトリになり、実行ファイル・設定例 `deadman.conf`・README・LICENSE が入っています。 + +```sh +tar xzf deadman-v0.1.0-linux-amd64.tar.gz # Windows: deadman-v0.1.0-windows-amd64.zip を展開 +cd deadman-v0.1.0-linux-amd64 +./deadman deadman.conf # Windows: .\deadman.exe deadman.conf +``` + +同梱の `deadman.conf` は監視対象の**例**です。実際の監視先に書き換えて使ってください(既存の設定ファイルがある場合は上書きに注意)。設定の文法は「[設定ファイル](#設定ファイル)」を参照してください。 + +完全性は Releases に併載の `SHA256SUMS`で検証できます。アーカイブと一緒にダウンロードしてください。 + +```sh +# ダウンロードしたアーカイブと SHA256SUMS を同じディレクトリに置いて実行 +sha256sum --ignore-missing -c SHA256SUMS +# macOS: shasum -a 256 -c SHA256SUMS (全アーカイブを置くか、対象 1 件を手動照合) +``` + +Linux で raw ソケットや権限が必要な場合は「[権限とプラットフォームに関する注意](#権限とプラットフォームに関する注意)」を参照してください。 + +> **メンテナ向け**: 配布アーカイブは `make package` で `dist/` に生成されます(`.tar.gz` / `.zip` と `SHA256SUMS`、要 `zip` / GNU `tar`)。`git tag -a vX.Y.Z` を付けて `git push origin vX.Y.Z` すると `release` ワークフローが全プラットフォーム分をビルドして GitHub Release へ公開します。 + ## 使い方 ```sh