diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6cda40b..be92a4b 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -3,7 +3,8 @@ name: Build & Publish Images on: push: branches: [ main, dev ] - tags: [ '*' ] + tags: + - '*' workflow_dispatch: env: @@ -12,93 +13,46 @@ env: IMAGE_FRONTEND: papercrate-dms/papercrate-frontend jobs: - publish: + build: runs-on: ubuntu-latest + strategy: + matrix: + component: [backend, frontend] + platform: [linux/amd64, linux/arm64] + permissions: contents: read packages: write + id-token: write steps: - name: Check out repository uses: actions/checkout@v4 - - name: Cache Cargo - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - backend/target - key: ${{ runner.os }}-cargo-${{ hashFiles('backend/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - - - name: Install system dependencies + - name: Set component metadata run: | - sudo apt-get update - sudo apt-get install -y pkg-config libssl-dev libpq-dev zig + if [ "${{ matrix.component }}" = "backend" ]; then + echo "COMPONENT_CONTEXT=./backend" >> "$GITHUB_ENV" + echo "COMPONENT_IMAGE=${{ env.IMAGE_BACKEND }}" >> "$GITHUB_ENV" + else + echo "COMPONENT_CONTEXT=./frontend" >> "$GITHUB_ENV" + echo "COMPONENT_IMAGE=${{ env.IMAGE_FRONTEND }}" >> "$GITHUB_ENV" + fi + echo "COMPONENT_NAME=${{ matrix.component }}" >> "$GITHUB_ENV" - - name: Install Rust targets and tools - run: | - rustup target add aarch64-unknown-linux-gnu - cargo install cargo-zigbuild cross - - - name: Build backend binaries (amd64) - working-directory: backend - run: | - cargo build --release --bin backend --bin worker --bin webdav --bin admin - cargo install diesel_cli --no-default-features --features postgres --root target/diesel-amd64 - - - name: Build backend binaries (arm64) - working-directory: backend - run: | - cargo zigbuild --target aarch64-unknown-linux-gnu --release --bin backend --bin worker --bin webdav --bin admin - cross install diesel_cli --no-default-features --features postgres --target aarch64-unknown-linux-gnu --root target/diesel-arm64 - - - name: Prepare backend artifacts - run: | - set -eux - mkdir -p build-artifacts/backend/amd64 build-artifacts/backend/arm64 - cp backend/target/release/backend build-artifacts/backend/amd64/papercrate-backend - cp backend/target/release/worker build-artifacts/backend/amd64/papercrate-worker - cp backend/target/release/webdav build-artifacts/backend/amd64/papercrate-webdav - cp backend/target/release/admin build-artifacts/backend/amd64/papercrate-admin - cp backend/target/diesel-amd64/bin/diesel build-artifacts/backend/amd64/diesel - cp backend/target/aarch64-unknown-linux-gnu/release/backend build-artifacts/backend/arm64/papercrate-backend - cp backend/target/aarch64-unknown-linux-gnu/release/worker build-artifacts/backend/arm64/papercrate-worker - cp backend/target/aarch64-unknown-linux-gnu/release/webdav build-artifacts/backend/arm64/papercrate-webdav - cp backend/target/aarch64-unknown-linux-gnu/release/admin build-artifacts/backend/arm64/papercrate-admin - cp backend/target/diesel-arm64/bin/diesel build-artifacts/backend/arm64/diesel - chmod +x build-artifacts/backend/*/* - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install frontend dependencies - working-directory: frontend - run: npm ci --no-audit --no-fund - - - name: Build frontend - working-directory: frontend - run: npm run build - - - name: Compute image tags - id: tags + - name: Compute tags env: GITHUB_SHA: ${{ github.sha }} GITHUB_REF: ${{ github.ref }} REGISTRY: ${{ env.REGISTRY }} + PLATFORM: ${{ matrix.platform }} run: | - SHORT_SHA=${GITHUB_SHA::7} - TAGS=("$SHORT_SHA") + ARCH=${PLATFORM#*/} + TAGS=("${GITHUB_SHA::7}") BRANCH=${GITHUB_REF##*/} if [ "$BRANCH" = "dev" ]; then - TAGS+=("${SHORT_SHA}-dev") + TAGS+=("${GITHUB_SHA::7}-dev") fi if [[ "$GITHUB_REF" == refs/tags/* ]]; then @@ -126,16 +80,12 @@ jobs: printf '%s\n' "$tag" done echo "EOF" - echo "BACKEND_TAGS<> "$GITHUB_ENV" - name: Set up QEMU @@ -148,27 +98,100 @@ jobs: uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} - username: papercrate-dms - password: ${{ secrets.GHCR_PAT }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build & push backend image + - name: Build & push ${{ env.COMPONENT_NAME }} (${{ matrix.platform }}) uses: docker/build-push-action@v5 with: - context: . - file: backend/Dockerfile + context: ${{ env.COMPONENT_CONTEXT }} push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ env.BACKEND_TAGS }} - cache-from: type=gha,scope=backend - cache-to: type=gha,scope=backend,mode=max + platforms: ${{ matrix.platform }} + tags: ${{ env.IMAGE_TAGS }} - - name: Build & push frontend image - uses: docker/build-push-action@v5 + manifest: + runs-on: ubuntu-latest + needs: build + strategy: + matrix: + component: [backend, frontend] + + permissions: + contents: read + packages: write + + env: + REGISTRY: ghcr.io + ARCHES: amd64 arm64 + + steps: + - name: Set component metadata + run: | + if [ "${{ matrix.component }}" = "backend" ]; then + echo "COMPONENT_IMAGE=${{ env.IMAGE_BACKEND }}" >> "$GITHUB_ENV" + else + echo "COMPONENT_IMAGE=${{ env.IMAGE_FRONTEND }}" >> "$GITHUB_ENV" + fi + echo "COMPONENT_NAME=${{ matrix.component }}" >> "$GITHUB_ENV" + + - name: Compute base tags + env: + GITHUB_SHA: ${{ github.sha }} + GITHUB_REF: ${{ github.ref }} + run: | + TAGS=("${GITHUB_SHA::7}") + + BRANCH=${GITHUB_REF##*/} + if [ "$BRANCH" = "dev" ]; then + TAGS+=("${GITHUB_SHA::7}-dev") + fi + + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + TAGS+=("${GITHUB_REF#refs/tags/}") + fi + + UNIQUE=() + for tag in "${TAGS[@]}"; do + [[ -z "$tag" ]] && continue + skip=false + for seen in "${UNIQUE[@]}"; do + if [ "$tag" = "$seen" ]; then + skip=true + break + fi + done + if [ "$skip" = false ]; then + UNIQUE+=("$tag") + fi + done + + { + echo "BASE_TAGS<> "$GITHUB_ENV" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 with: - context: . - file: frontend/Dockerfile - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ env.FRONTEND_TAGS }} - cache-from: type=gha,scope=frontend - cache-to: type=gha,scope=frontend,mode=max + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create manifests for ${{ env.COMPONENT_NAME }} + run: | + while IFS= read -r tag; do + [ -n "$tag" ] || continue + args=() + for arch in $ARCHES; do + args+=("$REGISTRY/$COMPONENT_IMAGE:${tag}-${arch}") + done + docker buildx imagetools create \ + --tag "$REGISTRY/$COMPONENT_IMAGE:$tag" \ + "${args[@]}" + done <<< "$BASE_TAGS" diff --git a/backend/Dockerfile b/backend/Dockerfile index 0a08478..262d52c 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,13 +1,33 @@ # syntax=docker/dockerfile:1 -ARG TARGETARCH -FROM debian:trixie-slim AS runtime -ARG TARGETARCH +FROM rust:1-slim AS builder WORKDIR /app -RUN set -eux; \ - apt-get update; \ - apt-get install -y --no-install-recommends \ +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + libssl-dev \ + libpq-dev \ + libjpeg-dev \ + libpng-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* + +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY migrations ./migrations +COPY tests ./tests +COPY diesel.toml ./ + +RUN cargo build --release --bin backend --bin worker --bin webdav --bin admin +RUN cargo install diesel_cli --no-default-features --features postgres + +FROM debian:trixie-slim AS runtime +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ libssl3 \ @@ -17,33 +37,29 @@ RUN set -eux; \ ocrmypdf \ tesseract-ocr \ ghostscript \ - qpdf; \ - rm -rf /var/lib/apt/lists/*; \ - case "$TARGETARCH" in \ - amd64) pdf_pkg=pdfium-linux-x64.tgz ;; \ - arm64) pdf_pkg=pdfium-linux-arm64.tgz ;; \ - *) echo "Unsupported architecture: $TARGETARCH" >&2; exit 1 ;; \ - esac; \ - curl -fsSL "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/${pdf_pkg}" -o /tmp/pdfium.tgz; \ - mkdir -p /tmp/pdfium; \ - tar -xzf /tmp/pdfium.tgz -C /tmp/pdfium --strip-components=1; \ - pdfium_so="$(find /tmp/pdfium -name libpdfium.so -type f | head -n1)"; \ - [ -n "$pdfium_so" ]; \ - mv "$pdfium_so" /usr/local/lib/libpdfium.so; \ - ldconfig; \ - rm -rf /tmp/pdfium /tmp/pdfium.tgz; \ - useradd --system --create-home --uid 10001 appuser - -COPY build-artifacts/backend/${TARGETARCH}/papercrate-backend /usr/local/bin/papercrate-backend -COPY build-artifacts/backend/${TARGETARCH}/papercrate-worker /usr/local/bin/papercrate-worker -COPY build-artifacts/backend/${TARGETARCH}/papercrate-webdav /usr/local/bin/papercrate-webdav -COPY build-artifacts/backend/${TARGETARCH}/papercrate-admin /usr/local/bin/papercrate-admin -COPY build-artifacts/backend/${TARGETARCH}/diesel /usr/local/bin/diesel + qpdf \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /usr/local/lib \ + && curl -fsSL https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-arm64.tgz -o /tmp/pdfium.tgz \ + && mkdir -p /tmp/pdfium \ + && tar -xzf /tmp/pdfium.tgz -C /tmp/pdfium --strip-components=1 \ + && pdfium_so="$(find /tmp/pdfium -name libpdfium.so -type f | head -n1)" \ + && [ -n "${pdfium_so}" ] \ + && mv "${pdfium_so}" /usr/local/lib/libpdfium.so \ + && ldconfig \ + && rm -rf /tmp/pdfium.tgz /tmp/pdfium \ + && useradd --system --create-home --uid 10001 appuser +COPY --from=builder /app/target/release/backend /usr/local/bin/papercrate-backend +COPY --from=builder /app/target/release/worker /usr/local/bin/papercrate-worker +COPY --from=builder /app/target/release/webdav /usr/local/bin/papercrate-webdav +COPY --from=builder /app/target/release/admin /usr/local/bin/papercrate-admin +COPY --from=builder /usr/local/cargo/bin/diesel /usr/local/bin/diesel COPY migrations ./migrations COPY diesel.toml ./ ENV RUST_LOG=info USER appuser EXPOSE 3000 + ENTRYPOINT ["/usr/local/bin/papercrate-backend"] diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6a4809e..b05cba1 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,18 @@ # syntax=docker/dockerfile:1 +FROM node:20-alpine AS build +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --no-audit --no-fund + +COPY . . +RUN npm run build + FROM nginx:alpine WORKDIR /usr/share/nginx/html -COPY dist ./ +COPY --from=build /app/dist ./ ENV API_PROXY_PASS=""