diff --git a/.gitea/workflows/helm-release.yaml b/.gitea/workflows/helm-release.yaml index 8f0289a..9146dda 100644 --- a/.gitea/workflows/helm-release.yaml +++ b/.gitea/workflows/helm-release.yaml @@ -1,6 +1,14 @@ # .gitea/workflows/helm-release.yaml -# 汎用Helmチャート公開ワークフロー(全リポジトリ共通) - +# ------------------------------------------------------------------------- +# secret-manager 用のチャート公開ワークフロー。 +# +# 他リポジトリの「毎 push で無条件公開」する共通版とは異なり、この版は +# 「Chart.yaml の version が Package Registry に未公開のときだけリリースする」。 +# 既に同じバージョンが公開済みなら何もしない(README や examples だけの +# 変更で push してもリリースは発生しない)。 +# +# リリース = Package Registry への発行 + Git タグ + Gitea リリース(tgz 添付)。 +# ------------------------------------------------------------------------- name: Helm Chart Release on: @@ -8,6 +16,12 @@ on: branches: - main - master + paths: + - 'Chart.yaml' + - 'values.yaml' + - 'values.schema.json' + - 'templates/**' + - '.gitea/workflows/helm-release.yaml' workflow_dispatch: env: @@ -19,88 +33,132 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - run: | - echo "🔄 Cloning repository..." - git clone --depth 1 $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git . - echo "✅ Repository cloned" + uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Install Helm - run: | - echo "📦 Installing Helm..." - curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - helm version - echo "✅ Helm installed" + uses: azure/setup-helm@v3 + with: + version: 'v3.12.0' - - name: Get Chart Info - id: chart_info + - name: Ensure jq run: | - # Chart.yamlからチャート名とバージョンを取得 - CHART_NAME=$(grep '^name:' Chart.yaml | awk '{print $2}') - CHART_VERSION=$(grep '^version:' Chart.yaml | awk '{print $2}') - - echo "Chart Name: ${CHART_NAME}" - echo "Chart Version: ${CHART_VERSION}" - - # GitHub Actionsの環境変数として設定 - echo "CHART_NAME=${CHART_NAME}" >> $GITHUB_ENV - echo "CHART_VERSION=${CHART_VERSION}" >> $GITHUB_ENV + command -v jq >/dev/null 2>&1 || { apt-get update -qq && apt-get install -y -qq jq; } + jq --version - - name: Validate Chart + - name: Get chart info + id: info run: | - echo "🔍 Validating Helm chart..." - helm lint . - echo "✅ Chart validation passed" + set -e + CHART_NAME=$(grep '^name:' Chart.yaml | head -1 | sed 's/^name:[[:space:]]*//; s/"//g; s/[[:space:]]*$//') + CHART_VERSION=$(grep '^version:' Chart.yaml | head -1 | sed 's/^version:[[:space:]]*//; s/"//g; s/[[:space:]]*$//') + echo "name=${CHART_NAME}" >> $GITHUB_OUTPUT + echo "version=${CHART_VERSION}" >> $GITHUB_OUTPUT + echo "Chart: ${CHART_NAME} v${CHART_VERSION}" - - name: Package Chart + - name: Validate chart + run: helm lint --strict . + + - name: Check whether this version is already released + id: check run: | - echo "📦 Packaging Helm chart..." + set -e + NAME="${{ steps.info.outputs.name }}" + VERSION="${{ steps.info.outputs.version }}" + + helm repo add cafepieters "${REGISTRY_URL}/api/packages/${OWNER}/helm" \ + --username "${{ secrets.REGISTRY_USER }}" \ + --password "${{ secrets.REGISTRY_TOKEN }}" >/dev/null 2>&1 || true + helm repo update cafepieters >/dev/null 2>&1 || true + + FOUND=$(helm search repo "cafepieters/${NAME}" --version "${VERSION}" --output json 2>/dev/null | jq 'length' 2>/dev/null || echo 0) + echo "Registry entries for ${NAME} ${VERSION}: ${FOUND}" + + if [ "${FOUND:-0}" -gt 0 ]; then + echo "release_needed=false" >> $GITHUB_OUTPUT + echo "✅ v${VERSION} は既にリリース済み。処理をスキップします。" + else + echo "release_needed=true" >> $GITHUB_OUTPUT + echo "🆕 v${VERSION} は未リリース。リリースを実行します。" + fi + + - name: Package chart + if: steps.check.outputs.release_needed == 'true' + run: | + set -e helm package . - - CHART_FILE=$(ls *.tgz) - echo "✅ Packaged: ${CHART_FILE}" + CHART_FILE=$(ls "${{ steps.info.outputs.name }}"-*.tgz | head -1) echo "CHART_FILE=${CHART_FILE}" >> $GITHUB_ENV + echo "Packaged: ${CHART_FILE}" - name: Publish to Gitea Package Registry + if: steps.check.outputs.release_needed == 'true' run: | - echo "🚀 Publishing ${CHART_FILE} to Gitea Package Registry..." - + set -e curl --fail-with-body \ -u "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_TOKEN }}" \ -X POST \ --upload-file "${CHART_FILE}" \ "${REGISTRY_URL}/api/packages/${OWNER}/helm/api/charts" - - echo "✅ Chart published successfully!" + echo "✅ Published ${CHART_FILE} to Package Registry" + + - name: Create Git tag + if: steps.check.outputs.release_needed == 'true' + run: | + set -e + VERSION="${{ steps.info.outputs.version }}" + git config user.name "Claude" + git config user.email "claude@cafepieters.com" + if git rev-parse "v${VERSION}" >/dev/null 2>&1; then + echo "Tag v${VERSION} already exists, skipping" + else + git tag -a "v${VERSION}" -m "Release ${VERSION}" + git push origin "v${VERSION}" + echo "Git tag v${VERSION} created" + fi + + - name: Create Gitea Release + if: steps.check.outputs.release_needed == 'true' + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -e + VERSION="${{ steps.info.outputs.version }}" + NAME="${{ steps.info.outputs.name }}" + PACKAGE_FILE="${NAME}-${VERSION}.tgz" + + EXISTING=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/v${VERSION}" | jq -r '.id // empty') + + if [ -n "${EXISTING}" ]; then + echo "Release v${VERSION} already exists (id=${EXISTING}), skipping" + else + RELEASE_ID=$(curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"v${VERSION}\",\"name\":\"v${VERSION}\",\"body\":\"secret-manager Helm Chart v${VERSION}\"}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" | jq -r '.id') + + curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/gzip" \ + --data-binary "@${PACKAGE_FILE}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${PACKAGE_FILE}" + + echo "Gitea release v${VERSION} created with asset ${PACKAGE_FILE}" + fi - name: Summary - if: success() run: | - echo "================================" - echo "✅ Deployment Successful!" - echo "================================" - echo "Repository: ${{ github.repository }}" - echo "Chart Name: ${CHART_NAME}" - echo "Chart Version: ${CHART_VERSION}" - echo "Chart File: ${CHART_FILE}" - echo "Branch: ${{ github.ref }}" - echo "Commit: ${{ github.sha }}" - echo "================================" - echo "" - echo "📦 Install with:" - echo "helm repo add cafepieters ${REGISTRY_URL}/api/packages/${OWNER}/helm" - echo "helm repo update" - echo "helm install my-${CHART_NAME} cafepieters/${CHART_NAME}" - - - name: Error Report - if: failure() - run: | - echo "================================" - echo "❌ Deployment Failed!" - echo "================================" - echo "Check the logs above for details" - echo "Common issues:" - echo "- Missing Chart.yaml" - echo "- Invalid Helm chart structure" - echo "- Missing REGISTRY_USER or REGISTRY_TOKEN secrets" - echo "- Insufficient permissions on Personal Access Token" - echo "================================" \ No newline at end of file + if [ "${{ steps.check.outputs.release_needed }}" = "true" ]; then + echo "================================" + echo "✅ Released secret-manager v${{ steps.info.outputs.version }}" + echo "================================" + echo "helm repo add cafepieters ${REGISTRY_URL}/api/packages/${OWNER}/helm" + echo "helm repo update" + echo "helm upgrade --install secrets cafepieters/secret-manager" + else + echo "ℹ️ No release: v${{ steps.info.outputs.version }} is already published." + echo " バージョンを上げたい場合は Chart.yaml の version / appVersion を更新して push してください。" + fi diff --git a/.gitea/workflows/image-update-and-release.yaml b/.gitea/workflows/image-update-and-release.yaml new file mode 100644 index 0000000..cf20213 --- /dev/null +++ b/.gitea/workflows/image-update-and-release.yaml @@ -0,0 +1,188 @@ +# .gitea/workflows/image-update-and-release.yaml +# ------------------------------------------------------------------------- +# 週次の自動更新 & リリース ワークフロー(毎週月曜 3:00 AM / JST 12:00 PM)。 +# +# ■ 他チャートとの違い +# phpfpm / n8n などは「Docker Hub の上流イメージ最新版」を毎週チェックし、 +# 更新があれば values.yaml / Chart.yaml のバージョンを上げてリリースする。 +# secret-manager は Secret を生成するだけで **追従すべき上流イメージが無い** +# ため、バージョンの自動採番は行わない(バージョンは常に手動で Chart.yaml +# を更新する)。 +# +# ■ このワークフローの役割 +# 1. (将来用)追従対象が増えたときに上流チェックを追加する場所を用意する +# (下部のコメントアウト済みサンプルを参照)。 +# 2. リリースの取りこぼし対策 — Chart.yaml の version が Package Registry に +# 未公開なら、週次でリリースを実行する(push トリガの helm-release.yaml が +# レジストリ障害等で失敗していた場合のセーフティネット)。 +# ------------------------------------------------------------------------- +name: Weekly Update and Release + +on: + schedule: + - cron: '0 3 * * 1' # 毎週月曜 3:00 AM UTC(JST 12:00 PM) + workflow_dispatch: + +env: + REGISTRY_URL: https://git.cafepieters.com + OWNER: helmchart + +jobs: + update-and-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: 'v3.12.0' + + - name: Ensure jq + run: | + command -v jq >/dev/null 2>&1 || { apt-get update -qq && apt-get install -y -qq jq; } + jq --version + + # ------------------------------------------------------------------- + # ▼▼▼ 将来、追従対象(イメージ / サブチャート等)が増えたらここに追加 ▼▼▼ + # + # 例: 上流コンテナの最新タグを Docker Hub から取得して values.yaml を書き換え、 + # 必要なら Chart.yaml の version / appVersion も更新する。 + # + # - name: Check upstream image + # id: upstream + # run: | + # set -e + # CURRENT=$(grep 'tag:' values.yaml | head -1 | sed 's/.*tag: *"\([^"]*\)".*/\1/') + # LATEST=$(curl -s "https://registry.hub.docker.com/v2/repositories//tags?page_size=100" \ + # | jq -r '.results[].name' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) + # echo "current=$CURRENT" >> $GITHUB_OUTPUT + # echo "latest=$LATEST" >> $GITHUB_OUTPUT + # + # - name: Apply update + # if: steps.upstream.outputs.current != steps.upstream.outputs.latest + # run: | + # set -e + # sed -i "s/tag: \"${{ steps.upstream.outputs.current }}\"/tag: \"${{ steps.upstream.outputs.latest }}\"/" values.yaml + # sed -i "s/^version: .*/version: ${{ steps.upstream.outputs.latest }}/" Chart.yaml + # sed -i "s/^appVersion: .*/appVersion: \"${{ steps.upstream.outputs.latest }}\"/" Chart.yaml + # git config user.name "Claude"; git config user.email "claude@cafepieters.com" + # git add values.yaml Chart.yaml + # git commit -m "chore: update upstream to ${{ steps.upstream.outputs.latest }}" + # git push origin main + # + # ▲▲▲ ここまで ▲▲▲ + # ------------------------------------------------------------------- + + - name: Get chart info + id: info + run: | + set -e + CHART_NAME=$(grep '^name:' Chart.yaml | head -1 | sed 's/^name:[[:space:]]*//; s/"//g; s/[[:space:]]*$//') + CHART_VERSION=$(grep '^version:' Chart.yaml | head -1 | sed 's/^version:[[:space:]]*//; s/"//g; s/[[:space:]]*$//') + echo "name=${CHART_NAME}" >> $GITHUB_OUTPUT + echo "version=${CHART_VERSION}" >> $GITHUB_OUTPUT + echo "Chart: ${CHART_NAME} v${CHART_VERSION}" + + - name: Validate chart + run: helm lint --strict . + + - name: Check whether this version is already released + id: check + run: | + set -e + NAME="${{ steps.info.outputs.name }}" + VERSION="${{ steps.info.outputs.version }}" + + helm repo add cafepieters "${REGISTRY_URL}/api/packages/${OWNER}/helm" \ + --username "${{ secrets.REGISTRY_USER }}" \ + --password "${{ secrets.REGISTRY_TOKEN }}" >/dev/null 2>&1 || true + helm repo update cafepieters >/dev/null 2>&1 || true + + FOUND=$(helm search repo "cafepieters/${NAME}" --version "${VERSION}" --output json 2>/dev/null | jq 'length' 2>/dev/null || echo 0) + echo "Registry entries for ${NAME} ${VERSION}: ${FOUND}" + + if [ "${FOUND:-0}" -gt 0 ]; then + echo "release_needed=false" >> $GITHUB_OUTPUT + echo "✅ v${VERSION} は公開済み。リリース不要。" + else + echo "release_needed=true" >> $GITHUB_OUTPUT + echo "🆕 v${VERSION} が未公開。週次リリースを実行します。" + fi + + - name: Package chart + if: steps.check.outputs.release_needed == 'true' + run: | + set -e + helm package . + CHART_FILE=$(ls "${{ steps.info.outputs.name }}"-*.tgz | head -1) + echo "CHART_FILE=${CHART_FILE}" >> $GITHUB_ENV + echo "Packaged: ${CHART_FILE}" + + - name: Publish to Gitea Package Registry + if: steps.check.outputs.release_needed == 'true' + run: | + set -e + curl --fail-with-body \ + -u "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_TOKEN }}" \ + -X POST \ + --upload-file "${CHART_FILE}" \ + "${REGISTRY_URL}/api/packages/${OWNER}/helm/api/charts" + echo "✅ Published ${CHART_FILE} to Package Registry" + + - name: Create Git tag + if: steps.check.outputs.release_needed == 'true' + run: | + set -e + VERSION="${{ steps.info.outputs.version }}" + git config user.name "Claude" + git config user.email "claude@cafepieters.com" + if git rev-parse "v${VERSION}" >/dev/null 2>&1; then + echo "Tag v${VERSION} already exists, skipping" + else + git tag -a "v${VERSION}" -m "Release ${VERSION}" + git push origin "v${VERSION}" + echo "Git tag v${VERSION} created" + fi + + - name: Create Gitea Release + if: steps.check.outputs.release_needed == 'true' + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -e + VERSION="${{ steps.info.outputs.version }}" + NAME="${{ steps.info.outputs.name }}" + PACKAGE_FILE="${NAME}-${VERSION}.tgz" + + EXISTING=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/v${VERSION}" | jq -r '.id // empty') + + if [ -n "${EXISTING}" ]; then + echo "Release v${VERSION} already exists (id=${EXISTING}), skipping" + else + RELEASE_ID=$(curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"v${VERSION}\",\"name\":\"v${VERSION}\",\"body\":\"secret-manager Helm Chart v${VERSION}(週次リリース)\"}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" | jq -r '.id') + + curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/gzip" \ + --data-binary "@${PACKAGE_FILE}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${PACKAGE_FILE}" + + echo "Gitea release v${VERSION} created with asset ${PACKAGE_FILE}" + fi + + - name: Summary + run: | + if [ "${{ steps.check.outputs.release_needed }}" = "true" ]; then + echo "✅ Weekly release done: secret-manager v${{ steps.info.outputs.version }}" + else + echo "ℹ️ Nothing to do: v${{ steps.info.outputs.version }} already published." + fi diff --git a/CLAUDE.md b/CLAUDE.md index 3024930..552504f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,14 +29,31 @@ そのため `Chart.yaml` の `version` / `appVersion` は **チャート独自に SemVer で採番**する。 (他チャートの「主アプリのバージョンと同一」ルールは適用対象外) - 破壊的変更 = メジャー、機能追加 = マイナー、修正 = パッチ。 -- 改修作業で臨時の手動リリースが必要な場合は末尾にアルファベットを付与する - (例: `0.2.0` → `0.2.0-a`)。 ## リリースフロー -- `main` への push で `.gitea/workflows/helm-release.yaml` が発火し、 - `helm lint` → `helm package` → Gitea Package Registry へ発行する。 -- 正式リリース時は `Chart.yaml` のバージョンを上げ、Git タグと Gitea リリースも作成する。 +新バージョンを出す手順は「`Chart.yaml` の `version` / `appVersion` を上げて `main` に push」だけ。 +以降は CI が処理する。 + +### `.gitea/workflows/helm-release.yaml`(push トリガ) +- `main` への push(`Chart.yaml` / `values.yaml` / `values.schema.json` / `templates/**` 変更時) + および手動実行で発火。 +- `helm lint --strict` → **現在の `version` が Package Registry に未公開のときだけ** + リリースする(`helm search repo --version` で存在確認)。 +- 他リポジトリの「毎 push で無条件公開」する共通版とは意図的に異なる。README・examples + だけの変更で push してもリリースは発生しない。 +- リリース内容 = Package Registry 発行 + Git タグ `v` + Gitea リリース(`.tgz` 添付)。 + タグ・リリースは既存なら skip(冪等)。 + +### `.gitea/workflows/image-update-and-release.yaml`(週次) +- 毎週月曜 03:00 UTC(JST 12:00)+ 手動実行。 +- **上流イメージの自動追従はしない**(このチャートは Secret を生成するだけで + 追従対象イメージが無い)。バージョンは常に手動で `Chart.yaml` を更新する。 +- 役割は「push 側の取りこぼし対策」— `version` が未公開なら週次でリリースを実行する。 +- 将来サブチャート等の追従対象が増えたときの追加ポイントは同ファイル内にコメントで用意済み。 + +### 手動リリース(臨時) +- 改修作業で臨時リリースが必要なら `version` 末尾にアルファベットを付与(例: `0.2.0` → `0.2.0-a`)。 ## 必要な Gitea Secrets @@ -74,6 +91,19 @@ ## チャート改修履歴 +### 自動更新・リリースワークフローの整備(2026-09-03, v0.1.0) + +`.gitea/workflows/` を 2 本体制に。`helm-release.yaml` を「無条件公開」から +「未公開バージョンのみリリース」に変更(`helm search repo --version` で存在確認し、 +未公開時のみ Package Registry 発行 + Git タグ + Gitea リリース)。`paths` フィルタで +`Chart.yaml`/`values.yaml`/`values.schema.json`/`templates/**` の変更時のみ発火。 +`image-update-and-release.yaml` を新設(週次)。上流イメージが無いチャートのため +バージョン自動採番はせず、push 側の取りこぼし対策(未公開バージョンの週次リリース) +として動作。将来の追従対象追加用コメントブロックを同梱。 + +**対象ファイル**: `.gitea/workflows/helm-release.yaml`, +`.gitea/workflows/image-update-and-release.yaml`, `README.md`, `CLAUDE.md` + ### 初版設計・実装(2026-08-31, v0.1.0) 空リポジトリから初期実装。`distribution`(namespaces 複製 / reflector / reloader)、 diff --git a/README.md b/README.md index c46b9df..a8f728d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Secret として配布** します。 - [他プロジェクトからの参照方法](#他プロジェクトからの参照方法) - [代表的なメールプロバイダの設定値](#代表的なメールプロバイダの設定値) - [変更の反映(伝播)とライフサイクル](#変更の反映伝播とライフサイクル) +- [自動化ワークフロー(Gitea Actions)](#自動化ワークフローgitea-actions) - [reflector / Reloader 連携](#reflector--reloader-連携) - [トラブルシューティング](#トラブルシューティング) @@ -307,6 +308,29 @@ distribution: が付きます。参照側 Deployment に `reloader.stakater.com/auto: "true"` を付けておくと、 Secret 更新時に自動でローリング再起動されます。 +## 自動化ワークフロー(Gitea Actions) + +`.gitea/workflows/` に 2 本の CI がある。どちらも「`Chart.yaml` の `version` が +Package Registry に未公開のときだけリリースする」方式で、リリース内容は +**Package Registry への発行 + Git タグ `v` + Gitea リリース(`.tgz` 添付)**。 + +| ワークフロー | 契機 | 動作 | +|---|---|---| +| `helm-release.yaml` | `main` への push(`Chart.yaml` / `values.yaml` / `values.schema.json` / `templates/**` 変更時)、手動実行 | `helm lint --strict` → 現在の `version` が未公開ならリリース。公開済みなら何もしない(README・examples だけの変更ではリリースされない)。 | +| `image-update-and-release.yaml` | 毎週月曜 03:00 UTC(JST 12:00)、手動実行 | 週次のセーフティネット。push 側がレジストリ障害等で取りこぼしていた場合に、未公開バージョンを週次でリリースする。 | + +> **上流イメージの自動追従はしない** — 他チャート(phpfpm・n8n 等)の +> `image-update-and-release.yaml` は Docker Hub の最新イメージを取得してバージョンを +> 自動採番するが、secret-manager は Secret を生成するだけで追従対象の上流イメージが +> 無いため、バージョンは常に手動で `Chart.yaml` を更新する。将来サブチャート等の +> 追従対象が増えた場合の追加ポイントは同ファイル内にコメントで用意してある。 + +新しいバージョンを出す手順: `Chart.yaml` の `version` と `appVersion` を上げて +`main` に push するだけ。あとは `helm-release.yaml` がリリースする。 + +必要な Gitea Secrets: `GITEA_TOKEN`(リリース・タグ)、`REGISTRY_USER` / +`REGISTRY_TOKEN`(Package Registry)。 + ## トラブルシューティング | 症状 | 原因 / 対処 | @@ -328,5 +352,7 @@ Secret 更新時に自動でローリング再起動されます。 - 初版。`distribution`(namespaces 複製 / reflector / reloader)、`common`、 `mail.providers`(SMTP/IMAP/POP3 + `smtp.env` + 任意 `msmtprc`)、`api.credentials`、 - `generic.secrets`(任意 type)を実装。`values.schema.json`・`examples/`・ - Gitea リリースワークフローを同梱。 + `generic.secrets`(任意 type)を実装。`values.schema.json`・`examples/` を同梱。 +- CI: `helm-release.yaml`(push 時・未公開バージョンのみリリース)と + `image-update-and-release.yaml`(週次リリースセーフティネット)を追加。 + どちらも Package Registry 発行 + Git タグ + Gitea リリースまで実施。