From 0a693036044ea415b0baa4f548011ba0f3f722b1 Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Sat, 20 Jun 2026 20:49:13 -0500 Subject: [PATCH] feat(ci): publish container releases from main --- .dockerignore | 12 ++ .env.example | 1 + .gitea/workflows/release.yml | 235 +++++++++++++++++++++++++++++++++++ Dockerfile | 34 +++++ README.md | 20 +++ 5 files changed, 302 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/release.yml create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..39982e3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.gitea +data +tmp +dist +bin +*.log +*.db +*.db-shm +*.db-wal +coverage.out +node_modules diff --git a/.env.example b/.env.example index f1382a4..15b9f82 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ MAINTAINARR_ADDR=:8080 MAINTAINARR_DB_PATH=data/maintainarr.db +MAINTAINARR_LOG_ARCHIVE_DIR=data/log-archives MAINTAINARR_SESSION_KEY=change-me-session-key-please MAINTAINARR_ENCRYPTION_KEY=change-me-encryption-key-32bytes MAINTAINARR_ORG_NAME=Maintainarr diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..fcd284a --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,235 @@ +name: Release Container + +on: + push: + branches: + - main + +env: + APP_NAME: maintainarr + +jobs: + publish-container: + name: Build And Publish Container + runs-on: ubuntu-latest + + env: + GITEA_SERVER_URL: ${{ gitea.server_url }} + GITEA_REPOSITORY: ${{ gitea.repository }} + GITEA_SHA: ${{ gitea.sha }} + GITEA_ACTOR: ${{ gitea.actor }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_REGISTRY: ${{ secrets.GITEA_REGISTRY }} + GITEA_REGISTRY_USERNAME: ${{ secrets.GITEA_REGISTRY_USERNAME }} + GITEA_PACKAGE_NAMESPACE: ${{ secrets.GITEA_PACKAGE_NAMESPACE }} + + steps: + - name: Check out repository + uses: https://dock-it.dev/actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + fetch-depth: 0 + + - name: Prepare release metadata + shell: bash + run: | + set -euo pipefail + + short_sha="$(printf '%s' "$GITEA_SHA" | cut -c1-7)" + repo_owner="${GITEA_REPOSITORY%%/*}" + registry_host="${GITEA_REGISTRY}" + + if [ -z "$registry_host" ]; then + registry_host="$(printf '%s' "$GITEA_SERVER_URL" | sed -E 's#^https?://##; s#/$##')" + fi + + package_namespace="${GITEA_PACKAGE_NAMESPACE}" + if [ -z "$package_namespace" ]; then + package_namespace="${repo_owner}" + fi + + registry_username="${GITEA_REGISTRY_USERNAME}" + if [ -z "$registry_username" ]; then + registry_username="${GITEA_ACTOR}" + fi + + image_ref="${registry_host}/${package_namespace}/${APP_NAME}" + + echo "SHORT_SHA=${short_sha}" >> "$GITHUB_ENV" + echo "RELEASE_TAG=release-${short_sha}" >> "$GITHUB_ENV" + echo "RELEASE_NAME=Release ${short_sha}" >> "$GITHUB_ENV" + echo "REGISTRY_HOST=${registry_host}" >> "$GITHUB_ENV" + echo "REGISTRY_USERNAME=${registry_username}" >> "$GITHUB_ENV" + echo "PACKAGE_NAMESPACE=${package_namespace}" >> "$GITHUB_ENV" + echo "IMAGE_REF=${image_ref}" >> "$GITHUB_ENV" + + - name: Verify release token + shell: bash + run: | + set -euo pipefail + + if [ -z "$GITEA_TOKEN" ]; then + echo "The repository secret GITEA_TOKEN is required to publish releases and packages." + exit 1 + fi + + - name: Install release dependencies + shell: bash + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y curl jq + + - name: Log in to Gitea container registry + shell: bash + run: | + set -euo pipefail + printf '%s' "$GITEA_TOKEN" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin + + - name: Build container image + shell: bash + run: | + set -euo pipefail + docker build \ + --tag "${IMAGE_REF}:${SHORT_SHA}" \ + --tag "${IMAGE_REF}:main" \ + --tag "${IMAGE_REF}:latest" \ + . + + - name: Push container image + shell: bash + run: | + set -euo pipefail + docker push "${IMAGE_REF}:${SHORT_SHA}" + docker push "${IMAGE_REF}:main" + docker push "${IMAGE_REF}:latest" + + - name: Create release notes + shell: bash + run: | + set -euo pipefail + + git fetch --tags --force + + api="${GITEA_SERVER_URL%/}/api/v1/repos/${GITEA_REPOSITORY}" + repo_url="${GITEA_SERVER_URL%/}/${GITEA_REPOSITORY}" + + previous_tag="$( + curl --fail-with-body --silent --show-error \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${api}/releases?limit=50" | + jq -r '[.[] | select(.tag_name | startswith("release-"))][0].tag_name // empty' + )" + + if [ -n "$previous_tag" ]; then + range="${previous_tag}..${GITEA_SHA}" + else + range="${GITEA_SHA}" + fi + + { + if [ -n "$previous_tag" ]; then + echo "## Changes since ${previous_tag}" + else + echo "## Changes" + fi + + echo + + commit_count="$(git rev-list --count "$range")" + + if [ "$commit_count" -eq 0 ]; then + echo "- No commits found since the previous release." + else + git log "$range" \ + --reverse \ + --pretty=format:'%H%x1f%s' | + while IFS="$(printf '\037')" read -r hash subject; do + short="$(printf '%s' "$hash" | cut -c1-7)" + echo "- ([${short}](${repo_url}/commit/${hash})) ${subject}" + done + fi + + echo + echo "## Container Images" + echo + echo "- \`${IMAGE_REF}:${SHORT_SHA}\`" + echo "- \`${IMAGE_REF}:main\`" + echo "- \`${IMAGE_REF}:latest\`" + + echo + echo "## Authors" + echo + echo "Sorted by total lines added or removed." + echo + + git log "$range" --numstat --format='author:%an <%ae>' | + awk ' + /^author:/ { + author = substr($0, 8) + next + } + + NF >= 3 { + added = ($1 == "-" ? 0 : $1) + removed = ($2 == "-" ? 0 : $2) + + adds[author] += added + dels[author] += removed + churn[author] += added + removed + } + + END { + for (a in churn) { + printf "%d\t%d\t%d\t%s\n", churn[a], adds[a], dels[a], a + } + } + ' | + sort -nr | + while IFS="$(printf '\t')" read -r total added removed author; do + echo "- ${author} - ${total} lines changed (+${added} / -${removed})" + done + } > release-notes.md + + - name: Create Gitea release + shell: bash + run: | + set -euo pipefail + + api="${GITEA_SERVER_URL%/}/api/v1/repos/${GITEA_REPOSITORY}" + + existing_release_id="$( + curl --fail-with-body --silent --show-error \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${api}/releases/tags/${RELEASE_TAG}" | + jq -r '.id // empty' 2>/dev/null || true + )" + + payload="$(jq -n \ + --arg tag "$RELEASE_TAG" \ + --arg sha "$GITEA_SHA" \ + --arg name "$RELEASE_NAME" \ + --rawfile body release-notes.md \ + '{ + tag_name: $tag, + target_commitish: $sha, + name: $name, + body: $body, + draft: false, + prerelease: false + }')" + + if [ -n "$existing_release_id" ]; then + curl --fail-with-body --silent --show-error \ + -X PATCH \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + --data "$payload" \ + "${api}/releases/${existing_release_id}" >/dev/null + else + curl --fail-with-body --silent --show-error \ + -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + --data "$payload" \ + "${api}/releases" >/dev/null + fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..245e617 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM golang:1.25-bookworm AS build + +WORKDIR /src + +COPY go.mod go.sum ./ +RUN go mod download + +COPY cmd ./cmd +COPY internal ./internal +COPY web ./web + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/maintainarr ./cmd/maintainarr + +FROM debian:bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=build /out/maintainarr /app/maintainarr +COPY web/static /app/web/static + +RUN mkdir -p /app/data + +ENV MAINTAINARR_ADDR=:8080 +ENV MAINTAINARR_DB_PATH=/app/data/maintainarr.db +ENV MAINTAINARR_LOG_ARCHIVE_DIR=/app/data/log-archives +ENV MAINTAINARR_BASE_URL=http://localhost:8080 + +EXPOSE 8080 + +CMD ["/app/maintainarr"] diff --git a/README.md b/README.md index 5ce26a3..9fac299 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,13 @@ go run ./cmd/maintainarr Default address: `http://localhost:8080` +## Container + +```powershell +docker build -t maintainarr . +docker run --rm -p 8080:8080 -v ${PWD}/data:/app/data maintainarr +``` + ## First User The first registered user becomes the initial `admin`. @@ -77,6 +84,19 @@ MAINTAINARR_THEME_MODE=dark MAINTAINARR_REFRESH_CRON=@every 5s ``` +## Release Automation + +- Push or merge into `main` to trigger `.gitea/workflows/release.yml` +- The workflow builds a Docker image, publishes it to the Gitea container registry, and creates or updates a Gitea release +- It tags the image as `latest`, `main`, and the short commit SHA +- Required secret: `GITEA_TOKEN` +- Optional secret: `GITEA_REGISTRY` + Defaults to the host from `gitea.server_url` +- Optional secret: `GITEA_REGISTRY_USERNAME` + Defaults to `gitea.actor` +- Optional secret: `GITEA_PACKAGE_NAMESPACE` + Defaults to the repository owner from `gitea.repository` + ## Roles - `admin`: full access, intended for user management and future organization settings