diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml
index 4b1b4a751..4af45586b 100644
--- a/.github/workflows/helm.yml
+++ b/.github/workflows/helm.yml
@@ -55,7 +55,7 @@ jobs:
# get current version
current_version=$(grep '^version:' "$chart_path/Chart.yaml" | awk '{print $2}')
# try to get current release version
- if oras manifest fetch "ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}:${current_version}" >/dev/null 2>&1; then
+ if oras manifest fetch "ghcr.io/${{ github.repository }}/${chart_name}:${current_version}" >/dev/null 2>&1; then
echo "No version change for $chart_name. Skipping."
else
helm dependency build "$chart_path"
@@ -87,8 +87,8 @@ jobs:
name: Publish to ghcr.io
runs-on: ubuntu-24.04
permissions:
- packages: write # needed for pushing to github registry
- id-token: write # needed for signing the images with GitHub OIDC Token
+ packages: write
+ id-token: write
needs: [package-helm-chart]
if: needs.package-helm-chart.outputs.has_artifacts == 'true'
steps:
@@ -128,17 +128,59 @@ jobs:
# push chart to OCI
chart_release_file=$(basename "$chart_path")
chart_name=${chart_release_file%-*}
- helm push ${chart_path} oci://ghcr.io/${GITHUB_REPOSITORY@L} |& tee helm-push-output.log
+ helm push ${chart_path} oci://ghcr.io/${{ github.repository }} |& tee helm-push-output.log
chart_digest=$(awk -F "[, ]+" '/Digest/{print $NF}' < helm-push-output.log)
# sign chart
- cosign sign "ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}@${chart_digest}"
+ cosign sign "ghcr.io/${{ github.repository }}/${chart_name}@${chart_digest}"
# push artifacthub-repo.yml to OCI
oras push \
- ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}:artifacthub.io \
+ ghcr.io/${{ github.repository }}/${chart_name}:artifacthub.io \
--config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \
charts/$chart_name/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml \
|& tee oras-push-output.log
artifacthub_digest=$(grep "Digest:" oras-push-output.log | awk '{print $2}')
# sign artifacthub-repo.yml
- cosign sign "ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}:artifacthub.io@${artifacthub_digest}"
+ cosign sign "ghcr.io/${{ github.repository }}/${chart_name}:artifacthub.io@${artifacthub_digest}"
+ done
+
+ verify:
+ name: Verify signatures for each chart tag
+ needs: [publish]
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ steps:
+ - name: Checkout
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Install Cosign
+ uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
+
+ - name: Downloads artifacts
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
+ with:
+ name: artifacts
+ path: .cr-release-packages/
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Verify signatures for each chart tag
+ run: |
+ for chart_path in $(find .cr-release-packages -name '*.tgz' -print); do
+ chart_release_file=$(basename "$chart_path")
+ chart_name=${chart_release_file%-*}
+ version=${chart_release_file#$chart_name-}
+ version=${version%.tgz}
+
+ cosign verify "ghcr.io/${{ github.repository }}/${chart_name}:${version}" \
+ --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
done
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 35188a3ce..53b806f3d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,3 +1,4 @@
+---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Seerr Release
diff --git a/.github/workflows/renovate-helm-custom-hooks.yml b/.github/workflows/renovate-helm-custom-hooks.yml
new file mode 100644
index 000000000..e833403a2
--- /dev/null
+++ b/.github/workflows/renovate-helm-custom-hooks.yml
@@ -0,0 +1,181 @@
+---
+# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
+name: Renovate Helm Hooks
+
+on:
+ pull_request:
+ branches:
+ - develop
+ paths:
+ - 'charts/**'
+
+permissions: {}
+
+concurrency:
+ group: renovate-helm-hooks-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ renovate-post-run:
+ name: Renovate Bump Chart Version
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ if: github.actor == 'renovate[bot]'
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
+ id: app-token
+ with:
+ app-id: 2138788
+ private-key: ${{ secrets.APP_SEERR_HELM_PRIVATE_KEY }}
+
+ - name: Set up chart-testing
+ uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0
+
+ - name: Run chart-testing (list-changed)
+ id: list-changed
+ run: |
+ changed="$(ct list-changed --target-branch ${TARGET_BRANCH})"
+ if [[ -n "$changed" ]]; then
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ echo "changed_list=${changed//$'\n'/ }" >> "$GITHUB_OUTPUT"
+ fi
+ env:
+ TARGET_BRANCH: ${{ github.event.repository.default_branch }}
+
+ - name: Bump chart version
+ if: steps.list-changed.outputs.changed == 'true'
+ env:
+ CHART: ${{ steps.list-changed.outputs.changed_list }}
+ run: |
+ if [[ ! -d "${CHART}" ]]; then
+ echo "${CHART} directory not found"
+ exit 0
+ fi
+
+ # Extract current appVersion and chart version from Chart.yaml
+ APP_VERSION=$(grep -e "^appVersion:" "$CHART/Chart.yaml" | cut -d ":" -f 2 | tr -d '[:space:]' | tr -d '"')
+ CHART_VERSION=$(grep -e "^version:" "$CHART/Chart.yaml" | cut -d ":" -f 2 | tr -d '[:space:]' | tr -d '"')
+
+ # Extract major, minor and patch versions of appVersion
+ APP_MAJOR_VERSION=$(printf '%s' "$APP_VERSION" | cut -d "." -f 1)
+ APP_MINOR_VERSION=$(printf '%s' "$APP_VERSION" | cut -d "." -f 2)
+ APP_PATCH_VERSION=$(printf '%s' "$APP_VERSION" | cut -d "." -f 3)
+
+ # Extract major, minor and patch versions of chart version
+ CHART_MAJOR_VERSION=$(printf '%s' "$CHART_VERSION" | cut -d "." -f 1)
+ CHART_MINOR_VERSION=$(printf '%s' "$CHART_VERSION" | cut -d "." -f 2)
+ CHART_PATCH_VERSION=$(printf '%s' "$CHART_VERSION" | cut -d "." -f 3)
+
+ # Get previous appVersion from the base commit of the pull request
+ BASE_COMMIT=$(git merge-base origin/main HEAD)
+ PREV_APP_VERSION=$(git show "$BASE_COMMIT":"$CHART/Chart.yaml" | grep -e "^appVersion:" | cut -d ":" -f 2 | tr -d '[:space:]' | tr -d '"')
+
+ # Extract major, minor and patch versions of previous appVersion
+ PREV_APP_MAJOR_VERSION=$(printf '%s' "$PREV_APP_VERSION" | cut -d "." -f 1)
+ PREV_APP_MINOR_VERSION=$(printf '%s' "$PREV_APP_VERSION" | cut -d "." -f 2)
+ PREV_APP_PATCH_VERSION=$(printf '%s' "$PREV_APP_VERSION" | cut -d "." -f 3)
+
+ # Check if the major, minor, or patch version of appVersion has changed
+ if [[ "$APP_MAJOR_VERSION" != "$PREV_APP_MAJOR_VERSION" ]]; then
+ # Bump major version of the chart and reset minor and patch versions to 0
+ CHART_MAJOR_VERSION=$((CHART_MAJOR_VERSION+1))
+ CHART_MINOR_VERSION=0
+ CHART_PATCH_VERSION=0
+ elif [[ "$APP_MINOR_VERSION" != "$PREV_APP_MINOR_VERSION" ]]; then
+ # Bump minor version of the chart and reset patch version to 0
+ CHART_MINOR_VERSION=$((CHART_MINOR_VERSION+1))
+ CHART_PATCH_VERSION=0
+ elif [[ "$APP_PATCH_VERSION" != "$PREV_APP_PATCH_VERSION" ]]; then
+ # Bump patch version of the chart
+ CHART_PATCH_VERSION=$((CHART_PATCH_VERSION+1))
+ fi
+
+ # Update the chart version in Chart.yaml
+ CHART_NEW_VERSION="${CHART_MAJOR_VERSION}.${CHART_MINOR_VERSION}.${CHART_PATCH_VERSION}"
+ sed -i "s/^version:.*/version: ${CHART_NEW_VERSION}/" "$CHART/Chart.yaml"
+
+ - name: Ensure documentation is updated
+ if: steps.list-changed.outputs.changed == 'true'
+ uses: docker://jnorwood/helm-docs:v1.14.2@sha256:7e562b49ab6b1dbc50c3da8f2dd6ffa8a5c6bba327b1c6335cc15ce29267979c
+
+ - name: Commit changes
+ if: steps.list-changed.outputs.changed == 'true'
+ env:
+ CHART: ${{ steps.list-changed.outputs.changed_list }}
+ GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ run: |
+ # Define the target directory
+ TARGET_DIR="$CHART"
+
+ # Fetch deleted files in the target directory
+ DELETED_FILES=$(git diff --diff-filter=D --name-only HEAD -- "$TARGET_DIR")
+
+ # Fetch added/modified files in the target directory
+ MODIFIED_FILES=$(git diff --diff-filter=ACM --name-only HEAD -- "$TARGET_DIR")
+
+ # Create a temporary file for JSON output
+ FILE_CHANGES_JSON_FILE=$(mktemp)
+
+ # Initialize JSON structure in the file
+ echo '{ "deletions": [], "additions": [] }' > "$FILE_CHANGES_JSON_FILE"
+
+ # Add deletions
+ for file in $DELETED_FILES; do
+ jq --arg path "$file" '.deletions += [{"path": $path}]' "$FILE_CHANGES_JSON_FILE" > "$FILE_CHANGES_JSON_FILE.tmp"
+ mv "$FILE_CHANGES_JSON_FILE.tmp" "$FILE_CHANGES_JSON_FILE"
+ done
+
+ # Add additions (new or modified files)
+ for file in $MODIFIED_FILES; do
+ BASE64_CONTENT=$(base64 -w 0 <"$file") # Encode file content
+ jq --arg path "$file" --arg content "$BASE64_CONTENT" \
+ '.additions += [{"path": $path, "contents": $content}]' "$FILE_CHANGES_JSON_FILE" > "$FILE_CHANGES_JSON_FILE.tmp"
+ mv "$FILE_CHANGES_JSON_FILE.tmp" "$FILE_CHANGES_JSON_FILE"
+ done
+
+ # Create a temporary file for the final JSON payload
+ JSON_PAYLOAD_FILE=$(mktemp)
+
+ # Construct the final JSON using jq and store it in a file
+ jq -n --arg repo "$GITHUB_REPOSITORY" \
+ --arg branch "$GITHUB_HEAD_REF" \
+ --arg message "fix: post upgrade changes from renovate" \
+ --arg expectedOid "$GITHUB_SHA" \
+ --slurpfile fileChanges "$FILE_CHANGES_JSON_FILE" \
+ '{
+ query: "mutation ($input: CreateCommitOnBranchInput!) {
+ createCommitOnBranch(input: $input) {
+ commit {
+ url
+ }
+ }
+ }",
+ variables: {
+ input: {
+ branch: {
+ repositoryNameWithOwner: $repo,
+ branchName: $branch
+ },
+ message: { headline: $message },
+ fileChanges: $fileChanges[0],
+ expectedHeadOid: $expectedOid
+ }
+ }
+ }' > "$JSON_PAYLOAD_FILE"
+
+ # Call GitHub API
+ curl https://api.github.com/graphql -f \
+ -sSf -H "Authorization: Bearer $GITHUB_TOKEN" \
+ --data "@$JSON_PAYLOAD_FILE"
+
+ # Clean up temporary files
+ rm "$FILE_CHANGES_JSON_FILE" "$JSON_PAYLOAD_FILE"
diff --git a/charts/seerr-chart/README.md b/charts/seerr-chart/README.md
index 318f90b3c..2b63be031 100644
--- a/charts/seerr-chart/README.md
+++ b/charts/seerr-chart/README.md
@@ -20,6 +20,10 @@ Seerr helm chart for Kubernetes
Kubernetes: `>=1.23.0-0`
+## Installation
+
+Refer to [https://docs.seerr.dev/getting-started/kubernetes](Seerr kubernetes documentation)
+
## Update Notes
### Updating to 3.0.0
diff --git a/charts/seerr-chart/README.md.gotmpl b/charts/seerr-chart/README.md.gotmpl
index cdf693740..15a45b064 100644
--- a/charts/seerr-chart/README.md.gotmpl
+++ b/charts/seerr-chart/README.md.gotmpl
@@ -14,11 +14,15 @@
{{ template "chart.requirementsSection" . }}
+## Installation
+
+Refer to [https://docs.seerr.dev/getting-started/kubernetes](Seerr kubernetes documentation)
+
## Update Notes
### Updating to 3.0.0
-Nothing change we just rebranded `jellyseerr` helm-chart to `seerr` :)
+Nothing has changed; we just rebranded the `jellyseerr` Helm chart to `seerr` 🥳.
### Updating to 2.7.0
diff --git a/docs/getting-started/docker.mdx b/docs/getting-started/docker.mdx
index 7efb5a2de..664882f8b 100644
--- a/docs/getting-started/docker.mdx
+++ b/docs/getting-started/docker.mdx
@@ -15,6 +15,12 @@ Refer to [Configuring Databases](/extending-jellyseerr/database-config#postgresq
An alternative Docker image is available on Docker Hub for this project. You can find it at [Docker Hub Repository Link](https://hub.docker.com/r/seerr/seerr)
:::
+:::info
+All official Seerr images are cryptographically signed and include a verified [Software Bill of Materials (SBOM)](https://cyclonedx.org/).
+
+To confirm that the container image you are using is authentic and unmodified, please refer to the [Verifying Signed Artifacts](/using-jellyseerr/advanced/verifying-signed-artifacts#verifying-signed-images) guide.
+:::
+
## Unix (Linux, macOS)
:::warning
Be sure to replace `/path/to/appdata/config` in the below examples with a valid host directory path. If this volume mount is not configured correctly, your Jellyseerr settings/data will not be persisted when the container is recreated (e.g., when updating the image or rebooting your machine).
@@ -72,11 +78,6 @@ Finally, run the container with the same parameters originally used to create th
```bash
docker run -d ...
```
-:::info
-All official Seerr images are cryptographically signed and include a verified [Software Bill of Materials (SBOM)](https://cyclonedx.org/).
-
-To confirm that the container image you are using is authentic and unmodified, please refer to the [Verifying Signed Artifacts](/using-jellyseerr/advanced/verifying-signed-artifacts) guide.
-:::
:::tip
You may alternatively use a third-party updating mechanism, such as [Watchtower](https://github.com/containrrr/watchtower) or [Ouroboros](https://github.com/pyouroboros/ouroboros), to keep Jellyseerr up-to-date automatically.
diff --git a/docs/getting-started/kubernetes.mdx b/docs/getting-started/kubernetes.mdx
index 4720abed9..1d5cb6ff0 100644
--- a/docs/getting-started/kubernetes.mdx
+++ b/docs/getting-started/kubernetes.mdx
@@ -8,6 +8,12 @@ sidebar_position: 5
This method is not recommended for most users. It is intended for advanced users who are using Kubernetes.
:::
+:::info
+All official Seerr charts are cryptographically signed and include a verified [Software Bill of Materials (SBOM)](https://cyclonedx.org/).
+
+To confirm that the chart you are using is authentic and unmodified, please refer to the [Verifying Signed Artifacts](/using-jellyseerr/advanced/verifying-signed-artifacts#verifying-signed-helm-charts) guide.
+:::
+
## Installation
```console
helm install jellyseerr oci://ghcr.io/fallenbagel/jellyseerr/jellyseerr-chart
diff --git a/docs/using-jellyseerr/advanced/verifying-signed-artifacts.mdx b/docs/using-jellyseerr/advanced/verifying-signed-artifacts.mdx
index c706fcab9..3ed328676 100644
--- a/docs/using-jellyseerr/advanced/verifying-signed-artifacts.mdx
+++ b/docs/using-jellyseerr/advanced/verifying-signed-artifacts.mdx
@@ -12,6 +12,7 @@ import TabItem from '@theme/TabItem';
These artifacts are cryptographically signed using [Sigstore Cosign](https://docs.sigstore.dev/quickstart/quickstart-cosign/):
- Container images
+- Helm charts
This ensures that the images you pull are authentic, tamper-proof, and built by the official Seerr release pipeline.
@@ -27,19 +28,11 @@ You will need the following tools installed:
To verify images:
-- [Docker](https://docs.docker.com/get-docker/) **or**
-- [Podman](https://podman.io/getting-started/installation) (including [Skopeo](https://github.com/containers/skopeo/blob/main/install.md))
+- [Docker](https://docs.docker.com/get-docker/) **or** [Podman](https://podman.io/getting-started/installation) (including [Skopeo](https://github.com/containers/skopeo/blob/main/install.md))
---
-# Verifying Signed Images
-
-All Seerr container images published to GitHub Container Registry (GHCR) are cryptographically signed using [Sigstore Cosign](https://docs.sigstore.dev/quickstart/quickstart-cosign/).
-This ensures that the images you pull are authentic, tamper-proof, and built by the official Seerr release pipeline.
-
-Each image also includes a CycloneDX SBOM (Software Bill of Materials) attestation, generated with [Trivy](https://aquasecurity.github.io/trivy/), providing transparency about all dependencies included in the image.
-
----
+## Verifying Signed Images
### Image Locations
@@ -227,17 +220,6 @@ This confirms that the image was:
---
-### Troubleshooting
-
-| Issue | Likely Cause | Suggested Fix |
-|-------|---------------|----------------|
-| `no matching signatures` | Incorrect digest or tag | Retrieve the digest again using Docker or Skopeo |
-| `certificate identity does not match expected` | Workflow reference changed | Ensure your `--certificate-identity` matches this documentation |
-| `cosign: command not found` | Cosign not installed | Install Cosign from the official release |
-| `certificate expired` | Old release | Verify a newer tag or digest |
-
----
-
### Example: Full Verification Flow
@@ -269,6 +251,127 @@ cosign verify ghcr.io/seerr-team/seerr@"$DIGEST" \
+## Verifying Signed Helm charts
+
+### Helm Chart Locations
+
+Official Seerr helm charts are available from:
+
+- GitHub Container Registry (GHCR): `ghcr.io/seerr-team/seerr/seerr-chart/seerr-chart:`
+
+You can view all available tags on the [Seerr Releases page](https://github.com/seerr-team/seerr/pkgs/container/seerr%2Fseerr-chart).
+
+---
+
+### Verifying a Specific Release Tag
+
+Each tagged release (for example `3.0.0`) is immutable and cryptographically signed.
+Verification should always be performed using the image digest (SHA256).
+
+#### Retrieve the Helm Chart Digest
+
+
+
+
+```bash
+docker buildx imagetools inspect ghcr.io/seerr-team/seerr/seerr-chart:3.0.0 --format '{{json .Manifest.Digest}}' | tr -d '"'
+```
+
+
+
+
+```bash
+skopeo inspect docker://ghcr.io/seerr-team/seerr/seerr-chart:3.0.0 --format '{{.Digest}}'
+```
+
+
+
+Example output:
+
+```
+sha256:abcd1234...
+```
+
+---
+
+#### Verify the Helm Chart Signature
+
+```bash
+cosign verify ghcr.io/seerr-team/seerr/seerr-chart@sha256:abcd1234... \
+ --certificate-identity "https://github.com/seerr-team/seerr/.github/workflows/helm.yml@refs/heads/main" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
+```
+
+:::info Successful Verification Example
+Verification for `ghcr.io/seerr-team/seerr/seerr-chart@sha256:abcd1234...`
+
+The following checks were performed:
+
+- Cosign claims validated
+- Signatures verified against the transparency log
+- Certificate issued by Fulcio to the expected workflow identity
+:::
+
+---
+
+### Expected Certificate Identity
+
+The expected certificate identity for all signed Seerr images is:
+
+```
+https://github.com/seerr-team/seerr/.github/workflows/helm.yml@refs/heads/main
+```
+
+This confirms that the image was:
+
+- Built by the official Seerr Release workflow
+- Produced from the seerr-team/seerr repository
+- Signed using GitHub’s OIDC identity via Sigstore Fulcio
+
+---
+
+### Example: Full Verification Flow
+
+
+
+
+```bash
+DIGEST=$(docker buildx imagetools inspect ghcr.io/seerr-team/seerr/seerr-chart:3.0.0 --format '{{json .Manifest.Digest}}' | tr -d '"')
+
+cosign verify ghcr.io/seerr-team/seerr/seerr-chart@"$DIGEST" \
+ --certificate-identity-regexp "https://github.com/seerr-team/seerr/.github/workflows/helm.yml@refs/heads/main" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
+
+cosign verify-attestation ghcr.io/seerr-team/seerr/seerr-chart@"$DIGEST" \
+ --type cyclonedx \
+ --certificate-identity-regexp "https://github.com/seerr-team/seerr/.github/workflows/helm.yml@refs/heads/main" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
+```
+
+
+
+
+```bash
+DIGEST=$(skopeo inspect docker://ghcr.io/seerr-team/seerr/seerr-chart:3.0.0 --format '{{.Digest}}')
+
+cosign verify ghcr.io/seerr-team/seerr/seerr-chart@"$DIGEST" \
+ --certificate-identity-regexp "https://github.com/seerr-team/seerr/.github/workflows/helm.yml@refs/heads/main" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
+```
+
+
+
+---
+
+## Troubleshooting
+
+| Issue | Likely Cause | Suggested Fix |
+|-------|---------------|----------------|
+| `no matching signatures` | Incorrect digest or tag | Retrieve the digest again using Docker or Skopeo |
+| `certificate identity does not match expected` | Workflow reference changed | Ensure your `--certificate-identity` matches this documentation |
+| `cosign: command not found` | Cosign not installed | Install Cosign from the official release |
+| `certificate expired` | Old release | Verify a newer tag or digest |
+
---
## Further Reading