自動更新・リリースワークフローを2本体制に整備
Helm Chart Release / release-chart (push) Successful in 7s

他チャート(phpfpm/n8n)を参照し、リリース自動化を実装。

- helm-release.yaml(push トリガ): 「毎 push 無条件公開」から
  「Chart.yaml の version が Package Registry に未公開のときだけリリース」に変更。
  helm search repo --version で存在確認し、未公開時のみ
  Package Registry 発行 + Git タグ v<version> + Gitea リリース(tgz 添付)を実施。
  paths フィルタで Chart.yaml/values.yaml/values.schema.json/templates/** 変更時のみ発火。
  helm lint --strict でスキーマ検証。
- image-update-and-release.yaml(週次 月曜03:00 UTC): 新設。
  このチャートは Secret を生成するだけで追従すべき上流イメージが無いため
  バージョン自動採番はしない。push 側がレジストリ障害等で取りこぼした
  未公開バージョンを週次でリリースするセーフティネットとして動作。
  将来サブチャート等の追従対象が増えたときの追加ポイントをコメントで用意。
- タグ/リリース作成は既存なら skip(冪等)。jq 未導入環境向けのガードを追加。
- README.md / CLAUDE.md に自動化ワークフローの節を追加
This commit is contained in:
2026-09-03 10:29:53 +09:00
parent f374cd2e49
commit 257699fbff
4 changed files with 375 additions and 73 deletions
+124 -66
View File
@@ -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 "================================"
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
@@ -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/<repo>/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
+35 -5
View File
@@ -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<version>` + 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)、
+28 -2
View File
@@ -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<version>` + 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 リリースまで実施。