diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..2d154b3 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 9da644f..eca664f 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,17 +1,20 @@ -name: Build & Push Docker Image +name: Build & Deploy on: push: branches: - main + - staging + - dev env: REGISTRY: git.severostudio.ru IMAGE_NAME: ${{ github.repository }} jobs: - docker: - runs-on: ubuntu-latest + build: + name: Build & Push + runs-on: master-builder steps: - name: Checkout @@ -33,5 +36,85 @@ jobs: context: . push: true tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + + deploy: + name: Deploy + runs-on: master-builder + needs: build + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Encode Docker Stack + id: stack + run: | + echo "content=$(base64 -w 0 docker-stack.yml)" >> "$GITHUB_OUTPUT" + + - name: Deploy via SSH + uses: appleboy/ssh-action@v1 + env: + BRANCH: ${{ github.ref_name }} + REGISTRY: ${{ env.REGISTRY }} + IMAGE_NAME: ${{ env.IMAGE_NAME }} + REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + STACK_FILE: ${{ steps.stack.outputs.content }} + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_SSH_KEY }} + envs: BRANCH,REGISTRY,IMAGE_NAME,REGISTRY_USERNAME,REGISTRY_PASSWORD,STACK_FILE + script: | + set -e + + IMAGE="${REGISTRY}/${IMAGE_NAME}:${BRANCH}" + DEPLOY_DIR="/opt/${IMAGE_NAME}/${BRANCH}" + STACK_NAME="$(echo "${IMAGE_NAME}" | tr '/' '-')-${BRANCH}" + + echo "=== Deployment ===" + echo "Branch: ${BRANCH}" + echo "Image: ${IMAGE}" + echo "Directory: ${DEPLOY_DIR}" + echo "Stack: ${STACK_NAME}" + + mkdir -p "${DEPLOY_DIR}" + + echo "${STACK_FILE}" | base64 -d > "${DEPLOY_DIR}/docker-stack.yml" + + echo "${REGISTRY_PASSWORD}" | docker login "${REGISTRY}" \ + -u "${REGISTRY_USERNAME}" \ + --password-stdin + + docker pull "${IMAGE}" + + export IMAGE="${IMAGE}" + + docker stack deploy \ + --compose-file "${DEPLOY_DIR}/docker-stack.yml" \ + --with-registry-auth \ + "${STACK_NAME}" + + echo "=== Services ===" + docker stack services "${STACK_NAME}" + + gc: + name: Docker GC + runs-on: master-builder + needs: + - build + - deploy + if: always() + + steps: + - name: Cleanup builder + run: | + docker container prune -f + docker image prune -af + docker builder prune -af || true + + echo "=== Docker disk usage ===" + docker system df + df -h / diff --git a/.npmrc b/.npmrc deleted file mode 100644 index cf04042..0000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -shamefully-hoist=true -strict-peer-dependencies=false diff --git a/Dockerfile b/Dockerfile index c072fea..83db45d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,35 @@ -FROM node:22-alpine +FROM node:22-alpine AS base -WORKDIR /usr/src/app +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" -COPY package*.json ./ +RUN corepack enable -RUN npm install --production +WORKDIR /app + + +FROM base AS deps + +COPY package.json pnpm-lock.yaml ./ + +RUN pnpm install --frozen-lockfile + + +FROM base AS build + +COPY --from=deps /app/node_modules ./node_modules COPY . . -RUN npm run build +RUN pnpm build + + +FROM base AS production + +ENV NODE_ENV=production + +COPY --from=build /app/.output ./.output EXPOSE 3000 -CMD [ "npm", "run", "start" ] +CMD ["node", ".output/server/index.mjs"] diff --git a/docker-stack.yml b/docker-stack.yml new file mode 100644 index 0000000..89af28f --- /dev/null +++ b/docker-stack.yml @@ -0,0 +1,24 @@ +services: + app: + image: ${IMAGE_TAG} + environment: + - NODE_ENV=production + - PORT=3000 + - NUXT_PUBLIC_API_BASE=${NUXT_PUBLIC_API_BASE:-https://api.main.aeterna.id/v1} + - NUXT_PUBLIC_WS_URL=${NUXT_PUBLIC_WS_URL:-https://api.main.aeterna.id} + - NUXT_PUBLIC_EDITOR_BASE=${NUXT_PUBLIC_EDITOR_BASE:-https://editor.main.aeterna.id} + networks: + - traefik_proxy + deploy: + replicas: 1 + labels: + - traefik.enable=true + - traefik.docker.network=traefik_proxy + - traefik.http.routers.dashboard.rule=Host(`dashboard.main.aeterna.id`) + - traefik.http.routers.dashboard.entrypoints=websecure + - traefik.http.services.dashboard.loadbalancer.server.port=3000 + - traefik.http.routers.dashboard.tls.certresolver=le + +networks: + traefik_proxy: + external: true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b088b8..59c29bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11449,7 +11449,7 @@ snapshots: '@petamoriken/float16': 3.9.2 debug: 4.4.3 env-paths: 3.0.0 - semver: 7.7.3 + semver: 7.7.4 shell-quote: 1.8.3 which: 4.0.0 transitivePeerDependencies: @@ -12298,7 +12298,7 @@ snapshots: node-abi@3.77.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 optional: true node-addon-api@7.1.1: {}