From e315ecb316d71b83cd3289a10c5da3ebd7f759e6 Mon Sep 17 00:00:00 2001 From: Leon Morival Date: Sun, 24 May 2026 21:43:09 +0200 Subject: [PATCH] feat: ci/cd --- .dockerignore | 14 ++++++ .gitea/workflows/deploy.yml | 97 +++++++++++++++++++++++++++++++++++++ Dockerfile | 38 +++++++++++++++ config/deploy.yml | 44 +++++++++++++++++ next.config.ts | 2 +- src/content/site.ts | 3 +- 6 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/deploy.yml create mode 100644 Dockerfile create mode 100644 config/deploy.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..14e4340 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +.git +.gitea +.idea +.next +node_modules +coverage +.env +.env.* +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* +*.tsbuildinfo +README.md diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..208135b --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,97 @@ +name: Next CI-CD + +on: + push: + branches: ["main"] + +env: + BUN_IMAGE: oven/bun:1-alpine + KAMAL_IMAGE: ghcr.io/basecamp/kamal:v2.11.0 + +jobs: + deploy: + name: Lint, Build and Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Lint and build + run: | + set -eu + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -e HOME=/tmp \ + -e NEXT_TELEMETRY_DISABLED=1 \ + -v "$PWD:/app" \ + -w /app \ + "$BUN_IMAGE" \ + sh -lc "bun install --frozen-lockfile && bun run lint && bun run build" + + - name: Deploy with Kamal + env: + GITEA_TOKEN: ${{ secrets.TOKEN_GITEA }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + run: | + set -eu + + : "${GITEA_TOKEN:?TOKEN_GITEA secret is required}" + : "${SSH_PRIVATE_KEY:?SSH_PRIVATE_KEY secret is required}" + : "${SSH_HOST:=89.167.35.217}" + : "${SSH_USER:=root}" + export SSH_HOST SSH_USER + + WORKSPACE="${GITHUB_WORKSPACE:-$PWD}" + test -f "$WORKSPACE/config/deploy.yml" + + tar \ + --exclude=.git \ + --exclude=.next \ + --exclude=.kamal/secrets \ + --exclude=.kamal/secrets.* \ + --exclude=.env \ + --exclude=.env.* \ + --exclude=node_modules \ + -C "$WORKSPACE" -cf - . | docker run --rm -i \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e GITEA_TOKEN \ + -e SSH_HOST \ + -e SSH_USER \ + -e SSH_PRIVATE_KEY \ + --entrypoint /bin/sh \ + "$KAMAL_IMAGE" -lc ' + set -eu + + mkdir -p /workdir /root/.ssh + tar -xf - -C /workdir + cd /workdir + + chmod 700 /root/.ssh + printf "%s\n" "$SSH_PRIVATE_KEY" | tr -d "\r" > /root/.ssh/id_ed25519 + chmod 600 /root/.ssh/id_ed25519 + + mkdir -p /workdir/.ssh + cat > /workdir/.ssh/config <<'"'"'SSH_CONFIG'"'"' + Host * + IdentityFile /root/.ssh/id_ed25519 + IdentitiesOnly yes + StrictHostKeyChecking yes + UserKnownHostsFile /workdir/.ssh/known_hosts + GlobalKnownHostsFile /dev/null + CheckHostIP no + SSH_CONFIG + ssh-keyscan -H "$SSH_HOST" > /workdir/.ssh/known_hosts + chmod 600 /workdir/.ssh/config /workdir/.ssh/known_hosts + + mkdir -p .kamal + cat > .kamal/secrets <<'"'"'SECRETS'"'"' + GITEA_TOKEN=$GITEA_TOKEN + SSH_PRIVATE_KEY=$SSH_PRIVATE_KEY + SECRETS + chmod 600 .kamal/secrets + + /kamal/bin/kamal deploy + ' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cc4a971 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM oven/bun:1-alpine AS deps + +WORKDIR /app + +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +FROM oven/bun:1-alpine AS builder + +WORKDIR /app + +ENV NEXT_TELEMETRY_DISABLED=1 + +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN bun run build + +FROM node:22-alpine AS runner + +WORKDIR /app + +ENV HOSTNAME=0.0.0.0 +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production +ENV PORT=3000 + +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/config/deploy.yml b/config/deploy.yml new file mode 100644 index 0000000..00d5655 --- /dev/null +++ b/config/deploy.yml @@ -0,0 +1,44 @@ +service: bowli-landing +image: daily_meal/bowli-landing +deploy_timeout: 120 + +x-hosts: &hosts + - <%= ENV.fetch("SSH_HOST", "89.167.35.217") %> + +servers: + web: + hosts: *hosts + +proxy: + host: landing.leonmorival.com + ssl: false + app_port: 3000 + run: + http_port: 8081 + https_port: 8443 + healthcheck: + path: / + +registry: + server: gitea.leonmorival.com + username: leon.morival@gmail.com + password: + - GITEA_TOKEN + +ssh: + user: <%= ENV.fetch("SSH_USER", "root") %> + keys_only: true + key_data: + - SSH_PRIVATE_KEY + config: false + +builder: + arch: amd64 + +env: + clear: + NODE_ENV: production + NEXT_TELEMETRY_DISABLED: "1" + NEXT_PUBLIC_SITE_URL: https://landing.leonmorival.com + NEXT_PUBLIC_API_URL: https://bemeal.leonmorival.com/api + NEXT_PUBLIC_SUPPORT_EMAIL: support@bowli.app diff --git a/next.config.ts b/next.config.ts index e9ffa30..68a6c64 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "standalone", }; export default nextConfig; diff --git a/src/content/site.ts b/src/content/site.ts index 783bcdf..5bc47bc 100644 --- a/src/content/site.ts +++ b/src/content/site.ts @@ -6,7 +6,8 @@ export const siteConfig = { description: "Bowli aide à suivre ses repas, ses objectifs nutritionnels et ses activités sportives depuis une application mobile.", supportEmail: process.env.NEXT_PUBLIC_SUPPORT_EMAIL ?? "support@bowli.app", - siteUrl: process.env.NEXT_PUBLIC_SITE_URL ?? "https://bowli.app", + siteUrl: + process.env.NEXT_PUBLIC_SITE_URL ?? "https://landing.leonmorival.com", legalPublishedAt: "2026-05-24T00:00:00+02:00", legalUpdatedLabel: "24 mai 2026", } as const;