diff --git a/.env.example b/.env.example index 5de0c88..2ccd49e 100644 --- a/.env.example +++ b/.env.example @@ -16,3 +16,7 @@ MAX_FILE_SIZE='2 MB' # the hostname or ip of the upstream nginx proxy cache NGINX_CACHE_DIR='/var/nginx/cache' + +# screenshots require Puppeteer to be setup https://pptr.dev/troubleshooting#setting-up-chrome-linux-sandbox +ENABLE_SCREENSHOTS="false" +SCREENSHOTS_IDR="./screenshots" diff --git a/Dockerfile b/Dockerfile index daa932f..d04766e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,38 @@ # syntax=docker/dockerfile:1 -FROM node:20-alpine AS base +FROM node:20-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable -RUN apk update && apk add --no-cache nginx supervisor +# Setup nsite user +RUN groupadd -r nsite && useradd -r -g nsite -G audio,video nsite && usermod -d /app nsite + +# Install nginx and supervisor +RUN apt-get update && apt-get install -y nginx supervisor + +# setup supervisor COPY supervisord.conf /etc/supervisord.conf +# Setup nginx +COPY nginx/nginx.conf /etc/nginx/nginx.conf +COPY nginx/default.conf /etc/nginx/conf.d/default.conf +RUN chown nsite:nsite -R /etc/nginx + +# install google chrome for screenshots. copied from (https://pptr.dev/troubleshooting#running-puppeteer-in-docker) + +# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) +# Note: this installs the necessary libs to make the bundled version of Chrome for Testing that Puppeteer +# installs, work. +RUN apt-get update \ + && apt-get install -y wget gnupg \ + && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ + && apt-get update \ + && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \ + --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* + WORKDIR /app COPY package.json . COPY pnpm-lock.yaml . @@ -23,14 +48,6 @@ RUN pnpm build FROM base AS main -# Setup user -RUN addgroup -S nsite && adduser -S nsite -G nsite -RUN chown -R nsite:nsite /app - -# Setup nginx -COPY nginx/nginx.conf /etc/nginx/nginx.conf -COPY nginx/default.conf /etc/nginx/conf.d/default.conf - # setup nsite COPY --from=prod-deps /app/node_modules /app/node_modules COPY --from=build ./app/build ./build @@ -44,10 +61,16 @@ VOLUME [ "/screenshots" ] EXPOSE 80 3000 ENV NSITE_PORT="3000" ENV NGINX_CACHE_DIR="/var/cache/nginx" +ENV ENABLE_SCREENSHOTS="true" ENV SCREENSHOTS_DIR="/screenshots" +ENV PUPPETEER_SKIP_DOWNLOAD="true" COPY docker-entrypoint.sh / RUN chmod +x /docker-entrypoint.sh -ENTRYPOINT ["/docker-entrypoint.sh"] +# change ownership of app +RUN chown nsite:nsite -R /app + +# Run /docker-entrypoint as root so supervisor can run +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index dcb5821..51c1863 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,5 +1,7 @@ #!/bin/sh -chown -R nginx:nginx /var/cache/nginx +echo Changing permission on volumes +chown -R nsite:nsite /var/cache/nginx +chown -R nsite:nsite /screenshots exec "$@" diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 5a19bdf..9d16ade 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,4 +1,4 @@ -user nginx; +user nsite; worker_processes auto; error_log /dev/stderr notice; diff --git a/src/env.ts b/src/env.ts index c1c6ccb..dc356af 100644 --- a/src/env.ts +++ b/src/env.ts @@ -24,6 +24,7 @@ const NSITE_HOST = process.env.NSITE_HOST || "0.0.0.0"; const NSITE_PORT = process.env.NSITE_PORT ? parseInt(process.env.NSITE_PORT) : 3000; const HOST = `${NSITE_HOST}:${NSITE_PORT}`; +const ENABLE_SCREENSHOTS = process.env.SCREENSHOTS_DIR !== "false"; const SCREENSHOTS_DIR = process.env.SCREENSHOTS_DIR || "./screenshots"; export { @@ -39,5 +40,6 @@ export { NSITE_HOST, NSITE_PORT, HOST, + ENABLE_SCREENSHOTS, SCREENSHOTS_DIR, }; diff --git a/src/index.ts b/src/index.ts index 821dc56..dd8068c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,15 @@ import send from "koa-send"; import { resolveNpubFromHostname } from "./helpers/dns.js"; import { getNsiteBlobs, parseNsiteEvent } from "./events.js"; import { downloadFile, getUserBlossomServers } from "./blossom.js"; -import { BLOSSOM_SERVERS, HOST, NGINX_CACHE_DIR, NSITE_HOST, NSITE_PORT, SUBSCRIPTION_RELAYS } from "./env.js"; +import { + BLOSSOM_SERVERS, + ENABLE_SCREENSHOTS, + HOST, + NGINX_CACHE_DIR, + NSITE_HOST, + NSITE_PORT, + SUBSCRIPTION_RELAYS, +} from "./env.js"; import { userDomains, userRelays, userServers } from "./cache.js"; import { invalidatePubkeyPath } from "./nginx.js"; import pool, { getUserOutboxes, subscribeForEvents } from "./nostr.js"; @@ -151,17 +159,19 @@ try { } // get screenshots for websites -app.use(async (ctx, next) => { - if (ctx.method === "GET" && ctx.path.startsWith("/screenshot")) { - const [pubkey, etx] = basename(ctx.path).split("."); +if (ENABLE_SCREENSHOTS) { + app.use(async (ctx, next) => { + if (ctx.method === "GET" && ctx.path.startsWith("/screenshot")) { + const [pubkey, etx] = basename(ctx.path).split("."); - if (pubkey) { - if (!(await hasScreenshot(pubkey))) await takeScreenshot(pubkey); + if (pubkey) { + if (!(await hasScreenshot(pubkey))) await takeScreenshot(pubkey); - await send(ctx, getScreenshotPath(pubkey)); - } else throw Error("Missing pubkey"); - } else return next(); -}); + await send(ctx, getScreenshotPath(pubkey)); + } else throw Error("Missing pubkey"); + } else return next(); + }); +} app.listen({ host: NSITE_HOST, port: NSITE_PORT }, () => { console.log("Started on port", HOST); @@ -180,7 +190,7 @@ if (SUBSCRIPTION_RELAYS.length > 0) { } // invalidate screenshot for nsite - if (nsite.path === "/" || nsite.path === "/index.html") { + if ((ENABLE_SCREENSHOTS && nsite.path === "/") || nsite.path === "/index.html") { await removeScreenshot(nsite.pubkey); } } diff --git a/src/screenshots.ts b/src/screenshots.ts index c04e3bd..48e64a8 100644 --- a/src/screenshots.ts +++ b/src/screenshots.ts @@ -1,5 +1,5 @@ import { nip19 } from "nostr-tools"; -import puppeteer from "puppeteer"; +import puppeteer, { PuppeteerLaunchOptions } from "puppeteer"; import { join } from "path"; import pfs from "fs/promises"; @@ -25,7 +25,12 @@ export async function hasScreenshot(pubkey: string) { export async function takeScreenshot(pubkey: string) { console.log(`${pubkey}: Generating screenshot`); - const browser = await puppeteer.launch(); + const opts: PuppeteerLaunchOptions = { + args: ["--no-sandbox"], + }; + if (process.env.PUPPETEER_SKIP_DOWNLOAD) opts.executablePath = "google-chrome-stable"; + + const browser = await puppeteer.launch(opts); const page = await browser.newPage(); const url = new URL(`http://${nip19.npubEncode(pubkey)}.localhost:${NSITE_PORT}`); await page.goto(url.toString()); @@ -36,5 +41,6 @@ export async function takeScreenshot(pubkey: string) { export async function removeScreenshot(pubkey: string) { try { await pfs.rm(getScreenshotPath(pubkey)); + console.log(`${pubkey}: Removed screenshot`); } catch (error) {} } diff --git a/supervisord.conf b/supervisord.conf index e720627..7487f9f 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -12,10 +12,11 @@ stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:nsite] +user=nsite +group=nsite command=node /app autostart=true autorestart=true -user=root stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr