From 569d3f140dfdbc6da22c821f6334162f55056af6 Mon Sep 17 00:00:00 2001 From: Admin Date: Wed, 23 Sep 2026 21:44:46 +0000 Subject: [PATCH] deploy: serve desktop installers from container --- .dockerignore | 1 - .env.example | 1 + Dockerfile | 52 +++++++++++++++++++++++++++++++ apps/api/.env.example | 1 + apps/api/src/config.ts | 3 +- apps/api/src/server.ts | 29 ++++++++++++++--- apps/web/src/DesktopDownloads.tsx | 4 +-- 7 files changed, 81 insertions(+), 10 deletions(-) diff --git a/.dockerignore b/.dockerignore index e129542..474095d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ node_modules **/node_modules **/dist -apps/print .git .env .env.* diff --git a/.env.example b/.env.example index 99210da..085f748 100644 --- a/.env.example +++ b/.env.example @@ -12,3 +12,4 @@ FIELDLENS_TENANT_ID=fieldlens-production WEB_ORIGIN=https://fieldlens.example.invalid FIELDLENS_PRINT_RELEASE_TAG=fieldlens-print-v0.1.0 FIELDLENS_PRINT_RELEASE_BASE_URL=https://git.example.invalid/owner/fieldlens/releases/download +FIELDLENS_PRINT_DOWNLOAD_DIR=downloads diff --git a/Dockerfile b/Dockerfile index 50335db..8705062 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,53 @@ +FROM node:24-bookworm AS desktop-build +WORKDIR /workspace + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + curl \ + file \ + libayatana-appindicator3-dev \ + libssl-dev \ + libwebkit2gtk-4.1-dev \ + libxdo-dev \ + lld \ + llvm \ + nsis \ + patchelf \ + pkg-config \ + librsvg2-dev \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --profile minimal +ENV PATH="/root/.cargo/bin:$PATH" + +RUN rustup target add x86_64-pc-windows-msvc \ + && cargo install --locked cargo-xwin + +COPY package.json ./ +COPY apps/api/package.json apps/api/package.json +COPY apps/web/package.json apps/web/package.json +COPY apps/print/package.json apps/print/package.json +COPY packages/contracts/package.json packages/contracts/package.json +COPY packages/fieldlens-core/package.json packages/fieldlens-core/package.json + +RUN npm install + +COPY apps/print apps/print + +RUN npm run bundle:windows --workspace @fieldlens/print \ + && npm run bundle:deb --workspace @fieldlens/print \ + && mkdir -p /desktop-artifacts \ + && windows_file="$(find apps/print/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis -maxdepth 1 -type f -name '*-setup.exe' -print -quit)" \ + && linux_file="$(find apps/print/src-tauri/target/release/bundle/deb -maxdepth 1 -type f -name '*.deb' -print -quit)" \ + && test -n "$windows_file" \ + && test -n "$linux_file" \ + && cp "$windows_file" /desktop-artifacts/fieldlens-print-windows-x64-setup.exe \ + && cp "$linux_file" /desktop-artifacts/fieldlens-print-linux-x64.deb + FROM node:24-alpine AS build WORKDIR /workspace @@ -29,6 +79,7 @@ WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 ENV STATIC_DIR=/app/web +ENV FIELDLENS_PRINT_DOWNLOAD_DIR=/app/downloads COPY --from=build /workspace/package.json /app/package.json COPY --from=build /workspace/node_modules /app/node_modules @@ -38,6 +89,7 @@ COPY --from=build /workspace/migrations /app/migrations COPY --from=build /workspace/packages/contracts/package.json /app/packages/contracts/package.json COPY --from=build /workspace/packages/contracts/dist /app/packages/contracts/dist COPY --from=build /workspace/apps/web/dist /app/web +COPY --from=desktop-build /desktop-artifacts /app/downloads USER node EXPOSE 3000 diff --git a/apps/api/.env.example b/apps/api/.env.example index 30014fa..ac229a3 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -13,3 +13,4 @@ WEB_ORIGIN=http://localhost:5173 STATIC_DIR= FIELDLENS_PRINT_RELEASE_TAG=fieldlens-print-v0.1.0 FIELDLENS_PRINT_RELEASE_BASE_URL=https://git.example.invalid/owner/fieldlens/releases/download +FIELDLENS_PRINT_DOWNLOAD_DIR=downloads diff --git a/apps/api/src/config.ts b/apps/api/src/config.ts index a12cf3d..4caf3e0 100644 --- a/apps/api/src/config.ts +++ b/apps/api/src/config.ts @@ -18,6 +18,5 @@ export const config = { logLevel: process.env.LOG_LEVEL?.trim() || 'info', webOrigin: process.env.WEB_ORIGIN?.trim() || 'http://localhost:5173', staticDir: process.env.STATIC_DIR?.trim() || '', - printReleaseTag: process.env.FIELDLENS_PRINT_RELEASE_TAG?.trim() || 'fieldlens-print-v0.1.0', - printReleaseBaseUrl: process.env.FIELDLENS_PRINT_RELEASE_BASE_URL?.trim() || 'https://git.arrowstack.dev/Admin/fieldlens/releases/download', + printDownloadDir: process.env.FIELDLENS_PRINT_DOWNLOAD_DIR?.trim() || 'downloads', } diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 8f02971..ae9869c 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -1,6 +1,6 @@ import path from 'node:path' import cors from 'cors' -import express from 'express' +import express, { type Response } from 'express' import helmet from 'helmet' import { FIELDLENS_SCHEMA_VERSION, isSyncRequest, type DeviceRegistration } from '@fieldlens/contracts' import { getAccessRequest, listAccessRequests, requestAccess, reviewAccessRequest } from './access.js' @@ -61,14 +61,33 @@ app.get('/api/health/live', (_req, res) => { res.json({ status: 'live', version: process.env.FIELDLENS_VERSION ?? 'dev' }) }) +const desktopDownloadDir = path.resolve(config.printDownloadDir) + +function sendDesktopDownload(res: Response, filename: string) { + const file = path.join(desktopDownloadDir, filename) + res.setHeader('Cache-Control', 'public, max-age=300') + res.download(file, filename, (error) => { + if (!error || res.headersSent) return + log('error', 'desktop_download_missing', { filename, file }) + res.status(404).json({ error: 'desktop_download_unavailable' }) + }) +} + +app.get('/downloads/fieldlens-print-windows-x64-setup.exe', (_req, res) => { + sendDesktopDownload(res, 'fieldlens-print-windows-x64-setup.exe') +}) + +app.get('/downloads/fieldlens-print-linux-x64.deb', (_req, res) => { + sendDesktopDownload(res, 'fieldlens-print-linux-x64.deb') +}) + +// Backward-compatible aliases used by the first downloads page. app.get('/downloads/fieldlens-print/windows-x64', (_req, res) => { - res.setHeader('Cache-Control', 'no-store') - res.redirect(302, `${config.printReleaseBaseUrl.replace(/\/$/, '')}/${encodeURIComponent(config.printReleaseTag)}/fieldlens-print-windows-x64-setup.exe`) + sendDesktopDownload(res, 'fieldlens-print-windows-x64-setup.exe') }) app.get('/downloads/fieldlens-print/linux-x64.deb', (_req, res) => { - res.setHeader('Cache-Control', 'no-store') - res.redirect(302, `${config.printReleaseBaseUrl.replace(/\/$/, '')}/${encodeURIComponent(config.printReleaseTag)}/fieldlens-print-linux-x64.deb`) + sendDesktopDownload(res, 'fieldlens-print-linux-x64.deb') }) app.get('/api/health/ready', async (_req, res) => { diff --git a/apps/web/src/DesktopDownloads.tsx b/apps/web/src/DesktopDownloads.tsx index ddb6a0b..305b553 100644 --- a/apps/web/src/DesktopDownloads.tsx +++ b/apps/web/src/DesktopDownloads.tsx @@ -2,13 +2,13 @@ import { ArrowLeft, Download, Monitor, Terminal } from 'lucide-react' const downloads = [ { - href: '/downloads/fieldlens-print/windows-x64', + href: '/downloads/fieldlens-print-windows-x64-setup.exe', label: 'Windows', detail: 'x64 · NSIS installer · current-user install', icon: Monitor, }, { - href: '/downloads/fieldlens-print/linux-x64.deb', + href: '/downloads/fieldlens-print-linux-x64.deb', label: 'Linux', detail: 'x64 · Debian / Ubuntu .deb package', icon: Terminal,