diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8b87d2f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +dist +gearbox.db* +uploads/* +!uploads/.gitkeep +.git +.idea +.claude +.gitea +.planning diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..69e50ba --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [develop] + pull_request: + branches: [develop] + +jobs: + ci: + runs-on: docker + container: + image: oven/bun:1 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint + run: bun run lint + + - name: Test + run: bun test + + - name: Build + run: bun run build diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..f902f46 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,108 @@ +name: Release + +on: + workflow_dispatch: + inputs: + bump: + description: "Version bump type" + required: true + default: "patch" + type: choice + options: + - patch + - minor + - major + +jobs: + ci: + runs-on: docker + container: + image: oven/bun:1 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint + run: bun run lint + + - name: Test + run: bun test + + - name: Build + run: bun run build + + release: + needs: ci + runs-on: dind + steps: + - name: Clone repository + run: | + apk add --no-cache git curl jq + git clone https://${{ secrets.GITEA_TOKEN }}@gitea.jeanlucmakiola.de/${{ gitea.repository }}.git repo + cd repo + git checkout ${{ gitea.ref_name }} + + - name: Compute version + working-directory: repo + run: | + LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -n1) + if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="v0.0.0" + fi + MAJOR=$(echo "$LATEST_TAG" | sed 's/v//' | cut -d. -f1) + MINOR=$(echo "$LATEST_TAG" | sed 's/v//' | cut -d. -f2) + PATCH=$(echo "$LATEST_TAG" | sed 's/v//' | cut -d. -f3) + case "${{ gitea.event.inputs.bump }}" in + major) MAJOR=$((MAJOR+1)); MINOR=0; PATCH=0 ;; + minor) MINOR=$((MINOR+1)); PATCH=0 ;; + patch) PATCH=$((PATCH+1)) ;; + esac + NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}" + echo "VERSION=$NEW_VERSION" >> "$GITHUB_ENV" + echo "PREV_TAG=$LATEST_TAG" >> "$GITHUB_ENV" + echo "New version: $NEW_VERSION" + + - name: Generate changelog + working-directory: repo + run: | + if [ "$PREV_TAG" = "v0.0.0" ]; then + CHANGELOG=$(git log --pretty=format:"- %s" HEAD) + else + CHANGELOG=$(git log --pretty=format:"- %s" "${PREV_TAG}..HEAD") + fi + echo "CHANGELOG<> "$GITHUB_ENV" + echo "$CHANGELOG" >> "$GITHUB_ENV" + echo "CHANGELOG_EOF" >> "$GITHUB_ENV" + + - name: Create and push tag + working-directory: repo + run: | + git config user.name "Gitea Actions" + git config user.email "actions@gitea.jeanlucmakiola.de" + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" + + - name: Build and push Docker image + working-directory: repo + run: | + REGISTRY="gitea.jeanlucmakiola.de" + IMAGE="${REGISTRY}/${{ gitea.repository_owner }}/gearbox" + docker build -t "${IMAGE}:${VERSION}" -t "${IMAGE}:latest" . + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY" -u "${{ gitea.repository_owner }}" --password-stdin + docker push "${IMAGE}:${VERSION}" + docker push "${IMAGE}:latest" + + - name: Create Gitea release + run: | + API_URL="${GITHUB_SERVER_URL}/api/v1/repos/${{ gitea.repository }}/releases" + curl -s -X POST "$API_URL" \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg tag "$VERSION" \ + --arg name "$VERSION" \ + --arg body "$CHANGELOG" \ + '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4ba933b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM oven/bun:1 AS deps +WORKDIR /app +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +FROM deps AS build +COPY . . +RUN bun run build + +FROM oven/bun:1-slim AS production +WORKDIR /app +ENV NODE_ENV=production +COPY --from=deps /app/node_modules ./node_modules +COPY --from=build /app/dist/client ./dist/client +COPY src/server ./src/server +COPY src/db ./src/db +COPY src/shared ./src/shared +COPY drizzle.config.ts package.json ./ +COPY drizzle ./drizzle +COPY entrypoint.sh ./ +RUN chmod +x entrypoint.sh && mkdir -p data uploads +EXPOSE 3000 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD bun -e "fetch('http://localhost:3000/api/health').then(r=>r.ok?process.exit(0):process.exit(1)).catch(()=>process.exit(1))" +ENTRYPOINT ["./entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5c1e704 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +services: + gearbox: + build: . + container_name: gearbox + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - DATABASE_PATH=./data/gearbox.db + volumes: + - gearbox-data:/app/data + - gearbox-uploads:/app/uploads + restart: unless-stopped + +volumes: + gearbox-data: + gearbox-uploads: diff --git a/drizzle.config.ts b/drizzle.config.ts index 56d638b..fcbc462 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,10 +1,10 @@ import { defineConfig } from "drizzle-kit"; export default defineConfig({ - out: "./drizzle", - schema: "./src/db/schema.ts", - dialect: "sqlite", - dbCredentials: { - url: "gearbox.db", - }, + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: "sqlite", + dbCredentials: { + url: process.env.DATABASE_PATH || "gearbox.db", + }, }); diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..5aec280 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +bun run db:push +exec bun run src/server/index.ts diff --git a/src/db/index.ts b/src/db/index.ts index 6b9bd4e..f50ccf4 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -2,7 +2,7 @@ import { Database } from "bun:sqlite"; import { drizzle } from "drizzle-orm/bun-sqlite"; import * as schema from "./schema.ts"; -const sqlite = new Database("gearbox.db"); +const sqlite = new Database(process.env.DATABASE_PATH || "gearbox.db"); sqlite.run("PRAGMA journal_mode = WAL"); sqlite.run("PRAGMA foreign_keys = ON");