diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..1cb30ce50 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.github +.vscode +.devcontainer +_output +_temp +_tests +_artifacts +node_modules +Logo +*.md +!LICENSE diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84bf288ab..211ad8643 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ -name: Build & Test +name: Build, Test & Docker on: push: - branches: [develop, master] + branches: [develop, master, feature/**] paths-ignore: - 'src/NzbDrone.Core/Localization/Core/**' - 'src/Prowlarr.Api.*/openapi.json' @@ -11,12 +11,20 @@ on: paths-ignore: - 'src/NzbDrone.Core/Localization/Core/**' - 'src/Prowlarr.Api.*/openapi.json' + workflow_dispatch: + inputs: + version: + description: "Version tag (e.g. 2.3.3.1234)" + required: false + type: string env: DOTNET_VERSION: '8.0.x' DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true NODE_VERSION: '20' + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: backend: @@ -138,3 +146,67 @@ jobs: - name: Build run: yarn run build --env production + + docker: + name: Build & Push Docker Image + runs-on: ubuntu-latest + needs: [backend, unit-tests, frontend] + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Determine version + id: version + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + if [ -n "$INPUT_VERSION" ]; then + echo "version=$INPUT_VERSION" >> "$GITHUB_OUTPUT" + else + echo "version=$(git describe --tags 2>/dev/null || echo "dev-${GITHUB_SHA::8}")" >> "$GITHUB_OUTPUT" + fi + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=sha,prefix= + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/develop' }} + type=raw,value=${{ steps.version.outputs.version }},enable=${{ inputs.version != '' }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ steps.version.outputs.version }} + VERSION_BRANCH=${{ github.ref_name }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..ef2d05981 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,74 @@ +# Stage 1: Build backend +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS backend +WORKDIR /repo + +ARG TARGETPLATFORM +RUN case "${TARGETPLATFORM}" in \ + "linux/arm64") echo "linux-musl-arm64" > /tmp/rid ;; \ + "linux/arm/v7") echo "linux-musl-arm" > /tmp/rid ;; \ + *) echo "linux-musl-x64" > /tmp/rid ;; \ + esac + +# Copy solution and project files first for layer caching on restore +COPY src/Prowlarr.sln src/Directory.Build.props src/Directory.Build.targets src/NuGet.config src/stylecop.json src/ +COPY src/Targets/ src/Targets/ +COPY src/Libraries/ src/Libraries/ +COPY src/NzbDrone/Prowlarr.csproj src/NzbDrone/ +COPY src/NzbDrone.Common/Prowlarr.Common.csproj src/NzbDrone.Common/ +COPY src/NzbDrone.Console/Prowlarr.Console.csproj src/NzbDrone.Console/ +COPY src/NzbDrone.Core/Prowlarr.Core.csproj src/NzbDrone.Core/ +COPY src/NzbDrone.Host/Prowlarr.Host.csproj src/NzbDrone.Host/ +COPY src/NzbDrone.Mono/Prowlarr.Mono.csproj src/NzbDrone.Mono/ +COPY src/NzbDrone.SignalR/Prowlarr.SignalR.csproj src/NzbDrone.SignalR/ +COPY src/NzbDrone.Update/Prowlarr.Update.csproj src/NzbDrone.Update/ +COPY src/NzbDrone.Windows/Prowlarr.Windows.csproj src/NzbDrone.Windows/ +COPY src/Prowlarr.Api.V1/Prowlarr.Api.V1.csproj src/Prowlarr.Api.V1/ +COPY src/Prowlarr.Http/Prowlarr.Http.csproj src/Prowlarr.Http/ +COPY src/ServiceHelpers/ServiceInstall/ServiceInstall.csproj src/ServiceHelpers/ServiceInstall/ +COPY src/ServiceHelpers/ServiceUninstall/ServiceUninstall.csproj src/ServiceHelpers/ServiceUninstall/ + +RUN dotnet restore src/Prowlarr.sln -r "$(cat /tmp/rid)" -p:SelfContained=true + +# Copy remaining source and build +COPY src/ src/ +COPY LICENSE LICENSE + +RUN dotnet publish src/NzbDrone/Prowlarr.csproj \ + -c Release \ + -r "$(cat /tmp/rid)" \ + --self-contained \ + --no-restore \ + -o /build/bin && \ + rm -rf /build/bin/Prowlarr.Update /build/bin/Prowlarr.Windows.* \ + /build/bin/ServiceInstall.* /build/bin/ServiceUninstall.* + +# Stage 2: Build frontend +FROM node:20-alpine AS frontend +WORKDIR /repo + +COPY package.json yarn.lock .yarnrc ./ +RUN yarn install --frozen-lockfile --network-timeout 120000 + +COPY frontend/ frontend/ +COPY tsconfig.json ./ +RUN yarn build --env production + +# Stage 3: Runtime on hotio base +FROM ghcr.io/hotio/base:alpinevpn + +EXPOSE 9696 +ENV WEBUI_PORTS="9696/tcp" + +RUN apk add --no-cache libintl sqlite-libs icu-libs + +COPY --from=backend /build/bin ${APP_DIR}/bin +COPY --from=frontend /repo/_output/UI ${APP_DIR}/bin/UI + +ARG VERSION +ARG VERSION_BRANCH=develop +RUN echo -e "PackageVersion=${VERSION:-local}\nPackageAuthor=[nitrobass24](https://github.com/nitrobass24)\nUpdateMethod=Docker\nBranch=${VERSION_BRANCH}" \ + > "${APP_DIR}/package_info" && \ + chmod -R u=rwX,go=rX "${APP_DIR}" + +COPY root/ / +RUN find /etc/s6-overlay/s6-rc.d -name "run*" -execdir chmod +x {} + diff --git a/root/etc/s6-overlay/s6-rc.d/init-setup-app/dependencies.d/init-setup b/root/etc/s6-overlay/s6-rc.d/init-setup-app/dependencies.d/init-setup new file mode 100644 index 000000000..e69de29bb diff --git a/root/etc/s6-overlay/s6-rc.d/init-setup-app/run b/root/etc/s6-overlay/s6-rc.d/init-setup-app/run new file mode 100644 index 000000000..9e782a7eb --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-setup-app/run @@ -0,0 +1,17 @@ +#!/command/with-contenv bash +# shellcheck shell=bash + +# shellcheck source=/dev/null +source /etc/s6-overlay/scripts/bash-functions + +set -e + +umask "${UMASK}" + +echo " +---------------------------------------------------------------------- +ENVIRONMENT APP +---------------------------------------------------------------------- +WEBUI_PORTS=${WEBUI_PORTS} +---------------------------------------------------------------------- +" diff --git a/root/etc/s6-overlay/s6-rc.d/init-setup-app/type b/root/etc/s6-overlay/s6-rc.d/init-setup-app/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-setup-app/type @@ -0,0 +1 @@ +oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-setup-app/up b/root/etc/s6-overlay/s6-rc.d/init-setup-app/up new file mode 100644 index 000000000..2338a3f94 --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-setup-app/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-setup-app/run diff --git a/root/etc/s6-overlay/s6-rc.d/service-prowlarr/dependencies.d/init-wireguard b/root/etc/s6-overlay/s6-rc.d/service-prowlarr/dependencies.d/init-wireguard new file mode 100644 index 000000000..e69de29bb diff --git a/root/etc/s6-overlay/s6-rc.d/service-prowlarr/run b/root/etc/s6-overlay/s6-rc.d/service-prowlarr/run new file mode 100644 index 000000000..cc958ea5d --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/service-prowlarr/run @@ -0,0 +1,7 @@ +#!/command/with-contenv bash +# shellcheck shell=bash + +umask "${UMASK}" + +cd "${APP_DIR}/bin" || exit 1 +exec s6-setuidgid hotio "${APP_DIR}/bin/Prowlarr" --nobrowser --data="${CONFIG_DIR}" diff --git a/root/etc/s6-overlay/s6-rc.d/service-prowlarr/type b/root/etc/s6-overlay/s6-rc.d/service-prowlarr/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/service-prowlarr/type @@ -0,0 +1 @@ +longrun diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup-app b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup-app new file mode 100644 index 000000000..e69de29bb diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/service-prowlarr b/root/etc/s6-overlay/s6-rc.d/user/contents.d/service-prowlarr new file mode 100644 index 000000000..e69de29bb