fix: docker, docs, workflows

This commit is contained in:
Artemy 2024-05-16 16:44:49 +03:00
parent 50a8c0aed8
commit b3f6fb715d
13 changed files with 503 additions and 366 deletions

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist

View file

@ -1,5 +1,9 @@
HOST="0.0.0.0" HOST="0.0.0.0"
PORT=8080 PORT=8080
TIMEOUT=0 TIMEOUT=0 # Max timout for server response, 0 means no timeout
WAIT_FOR_PAGE_LOAD_TIMEOUT=2000 REVERSE_PROXY=false # Set true if app running behind reverse_proxy
REVERSE_PROXY=false
# Maximum timeout for waiting for page load, once the page is
# loaded or the timeout is exceeded, rendering is terminated
# and the page is rendered
WAIT_FOR_PAGE_LOAD_TIMEOUT=2000

View file

@ -5,15 +5,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '20'
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies - name: Install dependencies
run: npm install run: pnpm install
- name: Start build - name: Start build
run: npm run build run: pnpm run build

View file

@ -3,6 +3,10 @@ name: Create and publish a Docker image
# Configures this workflow to run every time a change is pushed to the branch called `release`. # Configures this workflow to run every time a change is pushed to the branch called `release`.
on: on:
push:
branches:
- main
- dev
release: release:
types: types:
- published - published
@ -20,13 +24,13 @@ jobs:
permissions: permissions:
contents: read contents: read
packages: write packages: write
#
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@ -34,14 +38,14 @@ jobs:
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 uses: docker/build-push-action@v5
with: with:
context: . context: .
push: true push: true

View file

@ -5,15 +5,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '20'
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies - name: Install dependencies
run: npm install run: pnpm install
- name: Check formatting - name: Start build
run: npm run format:check run: pnpm run format:check

View file

@ -1,12 +1,27 @@
FROM node:18-alpine as build FROM node:20-alpine as base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
COPY . /app
WORKDIR /app WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
FROM node:18-alpine as run FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build
FROM node:20-alpine as run
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app WORKDIR /app
COPY --from=build /app/dist/ /app/package*.json ./
RUN npm install --omit=dev COPY --from=prod-deps /app/node_modules /app/node_modules
CMD npm run start:docker COPY --from=build /app/dist /app/dist/
COPY --from=build /app/package.json /app/package.json
EXPOSE 8080 EXPOSE 8080
CMD [ "pnpm", "start" ]

View file

@ -2,8 +2,9 @@
Proxy that renders client-side JavaScript apps (e.g. React apps) on server and returns the resulting HTML code. Proxy that renders client-side JavaScript apps (e.g. React apps) on server and returns the resulting HTML code.
- JS is disabled/unsupported, no proxy: blank page or "Enable JS to continue" | Response without proxy | With webder |
- With webder: all desired content, no need to use JS interpreter | ----------------------------------------------------------------- | -------------------------------------------------- |
| JS is disabled/unsupported, blank page or "Enable JS to continue" | all desired content, no need to use JS interpreter |
> [!WARNING] > [!WARNING]
> No HTML purification is performed, so passing code directly from webder can lead to XSS attacks. Local network requests are not blocked too, check domains/IPs to avoid SSRF attacks. > No HTML purification is performed, so passing code directly from webder can lead to XSS attacks. Local network requests are not blocked too, check domains/IPs to avoid SSRF attacks.
@ -11,13 +12,13 @@ Proxy that renders client-side JavaScript apps (e.g. React apps) on server and r
## Usage ## Usage
`/render?url=...` The proxy is accessible at `/render?url=...` by default.
For available config fields, check `.env.example`. For available config fields, check `.env.example`.
Docker is supported. Docker is supported.
```bash ```bash
npm install pnpm install
npm run build pnpm run build
npm start pnpm start
``` ```

View file

@ -1,10 +1,10 @@
version: "3" version: '3'
services: services:
txtdot: txtdot:
image: ghcr.io/txtdot/webder:latest image: ghcr.io/txtdot/webder:latest
ports: ports:
- "8080:8080" - '8080:8080'
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- ".env:/app/.env" - '.env:/app/.env'

View file

@ -1,6 +1,6 @@
{ {
"name": "webder", "name": "webder",
"version": "1.0.0", "version": "1.0.1",
"description": "Http/s proxy that render pages with js and returns HTML", "description": "Http/s proxy that render pages with js and returns HTML",
"main": "dist/app.js", "main": "dist/app.js",
"private": true, "private": true,
@ -23,19 +23,20 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/node": "^20.11.30", "@types/node": "^20.12.12",
"@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.3.1", "@typescript-eslint/parser": "^7.9.0",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"tsc-watch": "^6.0.4", "tsc-watch": "^6.2.0",
"typescript": "^5.4.3" "typescript": "^5.4.5"
}, },
"dependencies": { "dependencies": {
"@fastify/one-line-logger": "^1.3.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"fastify": "^4.26.2", "fastify": "^4.27.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"puppeteer": "^22.6.0", "puppeteer": "^22.9.0",
"puppeteer-extra": "^3.3.6", "puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-adblocker": "^2.13.6", "puppeteer-extra-plugin-adblocker": "^2.13.6",
"puppeteer-extra-plugin-stealth": "^2.11.2" "puppeteer-extra-plugin-stealth": "^2.11.2"

710
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
import { ConfigService } from "./config/config.service"; import { ConfigService } from './config/config.service';
import Fastify from "fastify"; import Fastify from 'fastify';
import getConfig from "./config/main"; import getConfig from './config/main';
import { puppeteer } from "./puppeteer"; import { puppeteer } from './puppeteer';
class App { class App {
config: ConfigService; config: ConfigService;
@ -10,21 +10,25 @@ class App {
} }
listen() { listen() {
const fastify = Fastify({ const fastify = Fastify({
logger: true, logger: {
transport: {
target: '@fastify/one-line-logger',
},
},
trustProxy: this.config.reverse_proxy, trustProxy: this.config.reverse_proxy,
connectionTimeout: this.config.timeout, connectionTimeout: this.config.timeout,
}); });
fastify.get<{ Querystring: { url: string } }>( fastify.get<{ Querystring: { url: string } }>(
"/render", '/render',
{ {
schema: { schema: {
querystring: { querystring: {
type: "object", type: 'object',
properties: { properties: {
url: { type: "string" }, url: { type: 'string' },
}, },
required: ["url"], required: ['url'],
}, },
}, },
}, },
@ -40,14 +44,14 @@ class App {
idleTime: 100, idleTime: 100,
}); });
} catch { } catch {
reply.header("x-message", "Page load timeout"); reply.header('x-message', 'Page load timeout');
} }
const data = await page.content(); const data = await page.content();
browser.close(); browser.close();
reply.type("text/html"); reply.type('text/html');
return data; return data;
} }
); );

View file

@ -1,4 +1,4 @@
import { config } from "dotenv"; import { config } from 'dotenv';
export class ConfigService { export class ConfigService {
public readonly host: string; public readonly host: string;
@ -9,7 +9,7 @@ export class ConfigService {
constructor() { constructor() {
config(); config();
this.host = process.env.HOST || "0.0.0.0"; this.host = process.env.HOST || '0.0.0.0';
this.port = Number(process.env.PORT) || 8080; this.port = Number(process.env.PORT) || 8080;
this.timeout = Number(process.env.TIMEOUT) || 0; this.timeout = Number(process.env.TIMEOUT) || 0;
@ -21,6 +21,6 @@ export class ConfigService {
parseBool(value: string | undefined, def: boolean): boolean { parseBool(value: string | undefined, def: boolean): boolean {
if (!value) return def; if (!value) return def;
return value === "true" || value === "1"; return value === 'true' || value === '1';
} }
} }

View file

@ -1,4 +1,4 @@
import { ConfigService } from "./config.service"; import { ConfigService } from './config.service';
let configSvc: ConfigService | undefined; let configSvc: ConfigService | undefined;