mirror of
https://github.com/TxtDot/webder.git
synced 2024-11-05 13:13:59 +03:00
fix: docker, docs, workflows
This commit is contained in:
parent
50a8c0aed8
commit
b3f6fb715d
13 changed files with 503 additions and 366 deletions
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
12
.env.example
12
.env.example
|
@ -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
|
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
|
@ -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
|
||||||
|
|
14
.github/workflows/deploy.yml
vendored
14
.github/workflows/deploy.yml
vendored
|
@ -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
|
||||||
|
|
16
.github/workflows/format-check.yml
vendored
16
.github/workflows/format-check.yml
vendored
|
@ -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
|
||||||
|
|
31
Dockerfile
31
Dockerfile
|
@ -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" ]
|
13
README.md
13
README.md
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
@ -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'
|
||||||
|
|
17
package.json
17
package.json
|
@ -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
710
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
26
src/app.ts
26
src/app.ts
|
@ -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;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConfigService } from "./config.service";
|
import { ConfigService } from './config.service';
|
||||||
|
|
||||||
let configSvc: ConfigService | undefined;
|
let configSvc: ConfigService | undefined;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue