1- # Web Worker Podcast
1+ # Web Worker Podcast Website
22
3- Astro SSR version of the Web Worker podcast website.
3+ This is the public source repository for the Web Worker podcast website.
44
5- The site renders from local Markdown files in ` content/episodes ` , so the
6- deployed container does not need an external database. The admin page can fetch
7- the upstream RSS feed, compare items by title, and import new episodes as
8- Markdown.
5+ The site is an Astro SSR application that renders podcast episodes from local
6+ Markdown files. It includes a small admin workflow for syncing an upstream RSS
7+ feed into those Markdown files. No external database is required for podcast
8+ content.
9+
10+ ## Public Repository Notice
11+
12+ This repository is public. Do not commit secrets, server IPs, private SSH host
13+ aliases, API keys, 1Panel tokens, certificate files, or production ` .env ` files.
14+
15+ Use ` .env.example ` as a template and keep real environment files outside git.
16+ Private deployment details should live in the server environment, 1Panel, or a
17+ private operations note.
18+
19+ ## Stack
20+
21+ - Astro SSR with the Node adapter
22+ - Vue components for interactive UI
23+ - Tailwind CSS
24+ - Markdown files in ` content/episodes ` as the content source of truth
25+ - Optional Redis for rate limiting and short JSON caches
26+ - Docker Compose for production-style deployment
927
1028## Local Development
1129
@@ -14,68 +32,133 @@ pnpm install
1432pnpm run dev
1533```
1634
17- Build and run the SSR server:
35+ Build and run the SSR server locally :
1836
1937``` bash
2038pnpm run build
2139HOST=127.0.0.1 PORT=4322 pnpm run start:ssr
2240```
2341
24- Admin defaults for local development:
25-
26- - URL: ` /admin `
27- - Username: ` admin `
28- - Password: ` change-me `
42+ The admin page is available at ` /admin ` . Local credentials come from
43+ environment variables; ` .env.example ` contains development placeholders only.
2944
3045## Content
3146
32- Episodes live in ` content/episodes/*.md ` . To regenerate the initial Markdown
33- files from the legacy JSON snapshot:
47+ Episodes live in:
48+
49+ ``` text
50+ content/episodes/*.md
51+ ```
52+
53+ To regenerate Markdown files from the legacy JSON snapshot:
3454
3555``` bash
3656pnpm run import:episodes
3757```
3858
39- Preview or import new remote RSS items into Markdown :
59+ To preview or import missing RSS episodes :
4060
4161``` bash
4262pnpm run fetch-rss -- --preview
4363pnpm run fetch-rss
4464```
4565
46- In Docker, mount the episode directory to ` /app/content/episodes ` .
66+ The RSS diff is intentionally simple: local and remote episodes are compared by
67+ normalized title.
68+
69+ ## Environment
70+
71+ Important runtime variables:
4772
48- Redis is optional. It is used only for rate limiting and short JSON caches; the
49- Markdown files remain the source of truth. Local Docker starts Redis
50- automatically. Production Compose also starts an internal Redis service; set
51- ` REDIS_PASSWORD ` and ` REDIS_URL=redis://:<password>@redis:6379 ` in the server
52- environment file.
73+ - ` RSS_URL ` : upstream podcast RSS feed.
74+ - ` EPISODES_DIR ` : Markdown episode directory.
75+ - ` PUBLIC_SITE_URL ` : canonical public site URL.
76+ - ` PUBLIC_ALLOW_INDEXING ` : set to ` false ` for staging or test domains.
77+ - ` JWT_SECRET ` : secret used for admin session tokens.
78+ - ` ADMIN_USERNAME ` : admin login username.
79+ - ` ADMIN_PASSWORD ` or ` ADMIN_PASSWORD_SHA256 ` : admin login password source.
80+ - ` REDIS_URL ` : optional Redis URL for shared rate-limit counters and short
81+ caches.
82+ - ` RSS_AUTO_SYNC ` : enables periodic RSS import in the Node process.
83+ - ` RSS_AUTO_SYNC_INTERVAL_HOURS ` : interval for automatic RSS sync.
5384
54- ## Staging Deployment
85+ For public deployments, prefer ` ADMIN_PASSWORD_SHA256 ` over storing a plaintext
86+ admin password when your runtime makes rotation manageable.
5587
56- Use ` new.webworker.tech ` as the staging domain. Keep indexing disabled:
88+ ## Docker
89+
90+ Check the local Compose config:
5791
5892``` bash
59- PUBLIC_ALLOW_INDEXING=false
93+ docker compose config
94+ ```
95+
96+ Run the app and Redis locally:
97+
98+ ``` bash
99+ docker compose up --build
60100```
61101
62- Production -style Compose config:
102+ Check the production -style Compose config with explicit placeholder values :
63103
64104``` bash
105+ JWT_SECRET=dev-secret \
106+ ADMIN_PASSWORD=dev-password \
107+ REDIS_PASSWORD=dev-redis-password \
108+ REDIS_URL=redis://:dev-redis-password@redis:6379 \
65109docker compose -f docker-compose.prod.yml config
66110```
67111
68- Deploy helper for the Aliyun host:
112+ In Docker, mount the episode directory to ` /app/content/episodes ` .
113+
114+ ## Deployment
115+
116+ The deployment helper is intentionally parameterized. Pass your own SSH host,
117+ remote paths, and public URL through environment variables:
69118
70119``` bash
120+ REMOTE_HOST=< ssh-host> \
121+ REMOTE_ROOT=/srv/webworker-tech \
122+ REMOTE_APP_DIR=/srv/webworker-tech/app \
123+ REMOTE_CONTENT_DIR=/srv/webworker-tech/content/episodes \
124+ ENV_FILE=/srv/webworker-tech/.env.production \
125+ PUBLIC_URL=https://staging.example.com/ \
71126sh scripts/deploy-new-webworker.sh
72127```
73128
74- The deploy script builds a linux/amd64 Docker image, uploads it to ` ssh aliyun ` ,
75- syncs ` content/episodes ` , starts Compose, and verifies the remote health
76- endpoint on port ` 4322 ` . After DNS and HTTPS are ready, pass
77- ` PUBLIC_URL=https://new.webworker.tech/ ` to verify the public URL as well.
129+ The script builds a ` linux/amd64 ` Docker image, copies the image and Compose
130+ file to the remote host, syncs Markdown episode files, starts Compose, and
131+ verifies ` /api/health ` .
132+
133+ Keep test domains out of search engines with:
134+
135+ ``` bash
136+ PUBLIC_ALLOW_INDEXING=false
137+ ```
138+
139+ ## Security
78140
79- Use the local 1Panel API skill for website, reverse proxy, DNS verification, and
80- HTTPS work. Do not hand-edit OpenResty config unless the 1Panel API is
81- unavailable and the fallback is explicitly accepted.
141+ Implemented defensive controls include:
142+
143+ - admin routes protected by session auth
144+ - Origin checks for write endpoints
145+ - rate limiting for public pages, admin login, and RSS admin APIs
146+ - Markdown HTML sanitization before rendering
147+ - short public cache headers for public GET/HEAD pages
148+ - query-string stripping on public pages to reduce cache bypass noise
149+ - optional Redis-backed shared rate-limit counters
150+ - ` robots.txt ` and ` X-Robots-Tag ` noindex controls for staging
151+
152+ For heavy abuse or distributed traffic, put the app behind a CDN/WAF or a
153+ reverse proxy with request limits. Redis is not a full-page cache in this app;
154+ it is used for shared counters and short data caches.
155+
156+ ## Verification
157+
158+ Useful checks before pushing or deploying:
159+
160+ ``` bash
161+ pnpm run build
162+ pnpm audit --audit-level moderate
163+ docker compose config
164+ ```
0 commit comments