diff --git a/scrapper/README.md b/scrapper/README.md index 10b03ed..be92da3 100644 --- a/scrapper/README.md +++ b/scrapper/README.md @@ -18,14 +18,14 @@ No runtime dependencies. It uses Bun's built-in SQLite, HTTP server and HTMLRewr ```sh bun install -bun start # http://localhost:3000 +bun start # http://127.0.0.1:3000 bun start --port 8080 # or -p 8080 ``` The server scrapes every 24h. On startup it waits out the rest of the 24h since the last scrape (or scrapes right away if none happened in the last 24h). You can also click **Scrape now** in the UI, or run `bun run scrape` once (e.g. from cron). -Env vars: `PORT` (3000; `--port` overrides it), `SCRAPE_INTERVAL_HOURS` (24), `DB_PATH` (`data/jobs.sqlite`). +Env vars: `PORT` (3000; `--port` overrides it), `JOB_BOARD_HOST` (127.0.0.1; `--host` overrides it), `SCRAPE_INTERVAL_HOURS` (24), `DB_PATH` (`data/jobs.sqlite`). ## Build a binary for a server @@ -41,11 +41,15 @@ bun run build:linux-arm64 # bin/job-board-linux-arm64 (e.g. Raspberry Pi, ARM On the server: ```sh -./job-board # web server on :3000, scrapes every 24h +./job-board # http://127.0.0.1:3000, scrapes every 24h ./job-board --port 8080 +./job-board --host 0.0.0.0 --port 8080 # listen on all interfaces ./job-board scrape # scrape once and exit (exit code 1 if a scraper failed) ``` +It listens on loopback (`127.0.0.1`) only by default, so it's meant to sit behind a reverse proxy +(nginx, Caddy). There is no login: only use `--host 0.0.0.0` on a trusted network. + The database goes to `data/jobs.sqlite` next to the binary (override with `DB_PATH`). The binaries target glibc Linux; for Alpine use `--target=bun-linux-x64-musl`, and for old CPUs without AVX2 use `--target=bun-linux-x64-baseline`. @@ -58,7 +62,7 @@ Description=Job board After=network-online.target [Service] -ExecStart=/opt/job-board/job-board --port 3000 +ExecStart=/opt/job-board/job-board --host 127.0.0.1 --port 3000 Restart=on-failure User=jobboard diff --git a/scrapper/src/main.ts b/scrapper/src/main.ts index a233682..0d85f28 100644 --- a/scrapper/src/main.ts +++ b/scrapper/src/main.ts @@ -1,10 +1,10 @@ // Entry point (also used by the compiled binary): -// job-board [serve] [--port ] start the web server (scrapes every 24h) -// job-board scrape run all scrapers once and exit (exit code 1 if any failed) +// job-board [serve] [--host ] [--port ] start the web server (scrapes every 24h) +// job-board scrape run all scrapers once and exit (exit code 1 if any failed) export {}; -const USAGE = "Usage: job-board [serve] [--port ] | job-board scrape"; +const USAGE = "Usage: job-board [serve] [--host ] [--port ] | job-board scrape\n --host address to listen on (default 127.0.0.1; 0.0.0.0 = all interfaces)\n --port port to listen on (default 3000)"; function fail(message: string): never { console.error(`${message}\n${USAGE}`); @@ -20,6 +20,10 @@ for (let i = 0; i < args.length; i++) { const port = Number(value); if (!Number.isInteger(port) || port < 1 || port > 65535) fail(`Invalid port "${value ?? ""}".`); process.env.PORT = String(port); // read by server.ts; overrides the PORT env var + } else if (arg === "--host" || arg.startsWith("--host=")) { + const value = arg.startsWith("--host=") ? arg.slice("--host=".length) : args[++i]; + if (!value || value.startsWith("-")) fail(`Invalid host "${value ?? ""}".`); + process.env.JOB_BOARD_HOST = value; // read by server.ts; overrides the env var } else if (arg === "--help" || arg === "-h") { console.log(USAGE); process.exit(0); diff --git a/scrapper/src/server.ts b/scrapper/src/server.ts index b793b0c..b6830d8 100644 --- a/scrapper/src/server.ts +++ b/scrapper/src/server.ts @@ -3,6 +3,9 @@ import { isScraping, runAllScrapers } from "./scrape"; import { scrapers } from "./scrapers"; const PORT = Number(process.env.PORT ?? 3000); +// Loopback only by default; use --host 0.0.0.0 (or JOB_BOARD_HOST) to expose it on the network. +// Not plain HOST: some shells/environments set that to the machine hostname. +const HOST = process.env.JOB_BOARD_HOST || "127.0.0.1"; const SCRAPE_INTERVAL_HOURS = Number(process.env.SCRAPE_INTERVAL_HOURS ?? 24); // --- Scheduler: scrape every SCRAPE_INTERVAL_HOURS. On startup, wait out whatever is left of the @@ -205,6 +208,7 @@ function page(url: URL) { const html = (body: string) => new Response(body, { headers: { "content-type": "text/html; charset=utf-8" } }); Bun.serve({ + hostname: HOST, port: PORT, routes: { "/": (req) => html(page(new URL(req.url))), @@ -227,4 +231,5 @@ Bun.serve({ fetch: () => new Response("Not found", { status: 404 }), }); -console.log(`Job board running at http://localhost:${PORT} (scraping every ${SCRAPE_INTERVAL_HOURS}h)`); +const shownHost = HOST.includes(":") ? `[${HOST}]` : HOST; +console.log(`Job board running at http://${shownHost}:${PORT} (scraping every ${SCRAPE_INTERVAL_HOURS}h)`);