From f880aa59577179d9080018997b016d093e4db777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Sun, 8 Mar 2026 13:49:38 +0100 Subject: [PATCH] feat: externalize buttplug as separate nginx container - Add Dockerfile.buttplug: builds Rust/WASM + TS, serves via nginx - Add nginx.buttplug.conf: serves /dist and /wasm with correct MIME types - Add .gitea/workflows/docker-build-buttplug.yml: path-filtered CI workflow - Strip Rust toolchain and buttplug build from frontend Dockerfile - Move buttplug to devDependencies (types only at build time) - Remove vite-plugin-wasm from frontend (WASM now served by nginx) - Add /buttplug proxy in vite.config (dev: localhost:8080) - Add buttplug service to compose.yml - Load buttplug dynamically in play page via runtime import - Fix faq page: suppress no-unnecessary-state-wrap for reassigned SvelteSet Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/docker-build-buttplug.yml | 68 +++++++++++++++++++ Dockerfile | 42 ++---------- Dockerfile.buttplug | 65 ++++++++++++++++++ compose.yml | 18 +++++ nginx.buttplug.conf | 23 +++++++ packages/frontend/package.json | 2 +- packages/frontend/src/routes/faq/+page.svelte | 3 +- .../frontend/src/routes/play/+page.svelte | 44 ++++++------ packages/frontend/vite.config.ts | 13 +++- pnpm-lock.yaml | 6 +- 10 files changed, 220 insertions(+), 64 deletions(-) create mode 100644 .gitea/workflows/docker-build-buttplug.yml create mode 100644 Dockerfile.buttplug create mode 100644 nginx.buttplug.conf diff --git a/.gitea/workflows/docker-build-buttplug.yml b/.gitea/workflows/docker-build-buttplug.yml new file mode 100644 index 0000000..7b821d0 --- /dev/null +++ b/.gitea/workflows/docker-build-buttplug.yml @@ -0,0 +1,68 @@ +name: Build and Push Buttplug Image + +on: + push: + branches: + - main + - develop + tags: + - "v*.*.*" + paths: + - "packages/buttplug/**" + - "Dockerfile.buttplug" + - "nginx.buttplug.conf" + pull_request: + branches: + - main + paths: + - "packages/buttplug/**" + - "Dockerfile.buttplug" + - "nginx.buttplug.conf" + workflow_dispatch: + +env: + REGISTRY: dev.pivoine.art + IMAGE_NAME: valknar/sexy-buttplug + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + platforms: linux/amd64 + + - name: Log in to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ gitea.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=sha,prefix={{branch}}- + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile.buttplug + platforms: linux/amd64 + push: ${{ gitea.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max diff --git a/Dockerfile b/Dockerfile index 17bf670..074eb62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,48 +20,19 @@ RUN mkdir -p ./packages/frontend && \ printf 'PUBLIC_API_URL=\nPUBLIC_URL=\nPUBLIC_UMAMI_ID=\nPUBLIC_UMAMI_SCRIPT=\n' > ./packages/frontend/.env # ============================================================================ -# Builder stage - compile application with Rust/WASM support +# Builder stage - compile frontend # ============================================================================ FROM base AS builder ARG CI=false ENV CI=$CI -# Install build dependencies for Rust and native modules -RUN apt-get update && apt-get install -y \ - curl \ - build-essential \ - pkg-config \ - libssl-dev \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -# Install Rust toolchain -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ - --default-toolchain stable \ - --profile minimal \ - --target wasm32-unknown-unknown - -# Add Rust to PATH -ENV PATH="/root/.cargo/bin:${PATH}" - -# Install wasm-bindgen-cli -RUN cargo install wasm-bindgen-cli - # Copy source files COPY packages ./packages # Install all dependencies RUN pnpm install --frozen-lockfile -# Build packages in correct order with WASM support -# 1. Build buttplug WASM -RUN RUSTFLAGS='--cfg getrandom_backend="wasm_js" --cfg=web_sys_unstable_apis' \ - pnpm --filter @sexy.pivoine.art/buttplug build:wasm - -# 2. Build buttplug TypeScript -RUN pnpm --filter @sexy.pivoine.art/buttplug build - -# 3. Build frontend +# Build frontend RUN pnpm --filter @sexy.pivoine.art/frontend build # Prune dev dependencies for production @@ -91,19 +62,14 @@ COPY --from=builder --chown=node:node /app/package.json ./package.json COPY --from=builder --chown=node:node /app/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=builder --chown=node:node /app/pnpm-workspace.yaml ./pnpm-workspace.yaml -# Create package directories -RUN mkdir -p packages/frontend packages/buttplug +# Create package directory +RUN mkdir -p packages/frontend # Copy frontend artifacts COPY --from=builder --chown=node:node /app/packages/frontend/build ./packages/frontend/build COPY --from=builder --chown=node:node /app/packages/frontend/node_modules ./packages/frontend/node_modules COPY --from=builder --chown=node:node /app/packages/frontend/package.json ./packages/frontend/package.json -# Copy buttplug artifacts -COPY --from=builder --chown=node:node /app/packages/buttplug/dist ./packages/buttplug/dist -COPY --from=builder --chown=node:node /app/packages/buttplug/node_modules ./packages/buttplug/node_modules -COPY --from=builder --chown=node:node /app/packages/buttplug/package.json ./packages/buttplug/package.json - # Switch to non-root user USER node diff --git a/Dockerfile.buttplug b/Dockerfile.buttplug new file mode 100644 index 0000000..dd315ed --- /dev/null +++ b/Dockerfile.buttplug @@ -0,0 +1,65 @@ +# syntax=docker/dockerfile:1 + +# ============================================================================ +# Builder stage - compile Rust/WASM and TypeScript +# ============================================================================ +FROM node:22.11.0-slim AS builder + +# Install build dependencies for Rust +RUN apt-get update && apt-get install -y \ + curl \ + build-essential \ + pkg-config \ + libssl-dev \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Enable corepack for pnpm +RUN npm install -g corepack@latest && corepack enable + +# Install Rust toolchain +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + --default-toolchain stable \ + --profile minimal \ + --target wasm32-unknown-unknown + +ENV PATH="/root/.cargo/bin:${PATH}" + +# Install wasm-bindgen-cli +RUN cargo install wasm-bindgen-cli + +WORKDIR /app + +# Copy workspace configuration +COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./ +COPY packages/buttplug ./packages/buttplug + +# Install dependencies +RUN pnpm install --frozen-lockfile --filter @sexy.pivoine.art/buttplug + +# Build WASM +RUN RUSTFLAGS='--cfg getrandom_backend="wasm_js" --cfg=web_sys_unstable_apis' \ + pnpm --filter @sexy.pivoine.art/buttplug build:wasm + +# Build TypeScript +RUN pnpm --filter @sexy.pivoine.art/buttplug build + +# ============================================================================ +# Runner stage - nginx serving dist/ and wasm/ +# ============================================================================ +FROM nginx:1.27-alpine AS runner + +# Remove default nginx config +RUN rm /etc/nginx/conf.d/default.conf + +# Copy nginx config +COPY nginx.buttplug.conf /etc/nginx/conf.d/buttplug.conf + +# Copy built artifacts +COPY --from=builder /app/packages/buttplug/dist /usr/share/nginx/html/dist +COPY --from=builder /app/packages/buttplug/wasm /usr/share/nginx/html/wasm + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost/dist/index.js || exit 1 diff --git a/compose.yml b/compose.yml index 4951774..ae52a79 100644 --- a/compose.yml +++ b/compose.yml @@ -64,6 +64,21 @@ services: timeout: 10s retries: 3 start_period: 20s + buttplug: + build: + context: . + dockerfile: Dockerfile.buttplug + container_name: sexy_buttplug + restart: unless-stopped + ports: + - "8080:80" + healthcheck: + test: + ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/dist/index.js"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s frontend: build: context: . @@ -78,9 +93,12 @@ services: HOST: 0.0.0.0 PUBLIC_API_URL: http://sexy_backend:4000 PUBLIC_URL: http://localhost:3000 + BUTTPLUG_URL: http://sexy_buttplug:80 depends_on: backend: condition: service_healthy + buttplug: + condition: service_healthy volumes: uploads_data: diff --git a/nginx.buttplug.conf b/nginx.buttplug.conf new file mode 100644 index 0000000..4fef1aa --- /dev/null +++ b/nginx.buttplug.conf @@ -0,0 +1,23 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + + # WASM MIME type + include /etc/nginx/mime.types; + types { + application/wasm wasm; + } + + # Cache JS and WASM aggressively (content-addressed by build) + location ~* \.(js|wasm)$ { + add_header Cache-Control "public, max-age=31536000, immutable"; + add_header Cross-Origin-Resource-Policy "cross-origin"; + add_header Cross-Origin-Embedder-Policy "require-corp"; + } + + location / { + try_files $uri =404; + } +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index bbd2945..ce98662 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -12,6 +12,7 @@ "check": "svelte-check --tsconfig ./tsconfig.json --threshold warning" }, "devDependencies": { + "@sexy.pivoine.art/buttplug": "workspace:*", "@iconify-json/ri": "^1.2.10", "@iconify/tailwind4": "^1.2.1", "@internationalized/date": "^3.11.0", @@ -42,7 +43,6 @@ "vite-plugin-wasm": "3.5.0" }, "dependencies": { - "@sexy.pivoine.art/buttplug": "workspace:*", "@sexy.pivoine.art/types": "workspace:*", "graphql": "^16.11.0", "graphql-request": "^7.1.2", diff --git a/packages/frontend/src/routes/faq/+page.svelte b/packages/frontend/src/routes/faq/+page.svelte index c3b42e0..65a7dbb 100644 --- a/packages/frontend/src/routes/faq/+page.svelte +++ b/packages/frontend/src/routes/faq/+page.svelte @@ -8,7 +8,8 @@ import Meta from "$lib/components/meta/meta.svelte"; let searchQuery = $state(""); - let expandedItems = new SvelteSet(); + // eslint-disable-next-line svelte/no-unnecessary-state-wrap -- variable is reassigned, $state is required + let expandedItems = $state(new SvelteSet()); const faqCategories = [ { diff --git a/packages/frontend/src/routes/play/+page.svelte b/packages/frontend/src/routes/play/+page.svelte index bde4dff..49a0387 100644 --- a/packages/frontend/src/routes/play/+page.svelte +++ b/packages/frontend/src/routes/play/+page.svelte @@ -1,14 +1,7 @@ diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 952447c..2b5f03c 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -2,13 +2,17 @@ import path from "path"; import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; import { sveltekit } from "@sveltejs/kit/vite"; -import wasm from "vite-plugin-wasm"; export default defineConfig({ - plugins: [sveltekit(), tailwindcss(), wasm()], + plugins: [sveltekit(), tailwindcss()], resolve: { alias: { $lib: path.resolve("./src/lib"), "@": path.resolve("./src/lib") }, }, + build: { + rollupOptions: { + external: ["@sexy.pivoine.art/buttplug"], + }, + }, server: { port: 3000, proxy: { @@ -19,6 +23,11 @@ export default defineConfig({ secure: false, ws: true, }, + "/buttplug": { + rewrite: (path) => path.replace(/^\/buttplug/, ""), + target: "http://localhost:8080", + changeOrigin: true, + }, }, }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87f624f..1c53906 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,9 +151,6 @@ importers: packages/frontend: dependencies: - '@sexy.pivoine.art/buttplug': - specifier: workspace:* - version: link:../buttplug '@sexy.pivoine.art/types': specifier: workspace:* version: link:../types @@ -188,6 +185,9 @@ importers: '@lucide/svelte': specifier: ^0.561.0 version: 0.561.0(svelte@5.53.7) + '@sexy.pivoine.art/buttplug': + specifier: workspace:* + version: link:../buttplug '@sveltejs/adapter-node': specifier: ^5.5.4 version: 5.5.4(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)))