Skip to content

Commit 8d4760d

Browse files
authored
Add ATProto episode publishing with GitHub Actions (#46)
* Add standard.site support via @bryanguffey/astro-standard-site - Install @bryanguffey/astro-standard-site package - Add optional standardSite config to StarpodConfig type - Configure standardSite in starpod.config.ts with placeholder values - Create .well-known/site.standard.publication verification endpoint * Use environment variables for standard.site config instead of starpod.config.ts Moves DID and publication rkey to STANDARD_SITE_DID and STANDARD_SITE_PUBLICATION_RKEY env vars, matching the existing pattern (DISCORD_WEBHOOK). Removes StandardSiteConfig type and placeholder values from starpod.config.ts — values are only written once in .env. * Add standard.site documentation to README Explains what standard.site federation is, why you'd want it, how to configure the env vars, and links to further resources. Clarifies the feature is optional and the site works fine without it. * Add ATProto episode publishing with GitHub Actions for auto-publish and backfill * Add permissions blocks to ATProto publishing workflows * Fix pagination: fetch all existing ATProto documents via cursor-based pagination * Make itunes_episodeType optional in publish-episodes FeedSchema for CI resilience * Fix type errors and remove unused interface in publishing scripts * Move env var reads inside main() for proper type narrowing, remove string casts --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 3e955fc commit 8d4760d

9 files changed

Lines changed: 561 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Backfill Episodes to ATProto
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
confirm:
7+
description: 'Type "backfill" to confirm publishing all episodes to ATProto'
8+
required: true
9+
type: string
10+
11+
jobs:
12+
backfill:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
if: ${{ github.event.inputs.confirm == 'backfill' }}
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: 22
22+
- uses: pnpm/action-setup@v4
23+
- name: Get pnpm store directory
24+
shell: bash
25+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
26+
- uses: actions/cache@v4
27+
name: Setup pnpm cache
28+
with:
29+
path: ${{ env.STORE_PATH }}
30+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
31+
restore-keys: |
32+
${{ runner.os }}-pnpm-store-
33+
- name: Install dependencies
34+
run: pnpm install
35+
- name: Backfill all episodes
36+
run: pnpm tsx scripts/publish-episodes.ts --backfill
37+
env:
38+
ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }}
39+
ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }}
40+
STANDARD_SITE_URL: ${{ secrets.STANDARD_SITE_URL }}
41+
STANDARD_SITE_PUBLICATION_RKEY: ${{ secrets.STANDARD_SITE_PUBLICATION_RKEY }}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Publish Episodes to ATProto
2+
3+
on:
4+
# Run after the daily site rebuild to catch new episodes
5+
workflow_run:
6+
workflows: ["Rebuild Astro Site"]
7+
types: [completed]
8+
# Allow manual trigger
9+
workflow_dispatch:
10+
11+
jobs:
12+
publish:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
# Only run if the triggering workflow succeeded (or manual dispatch)
17+
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: 22
23+
- uses: pnpm/action-setup@v4
24+
- name: Get pnpm store directory
25+
shell: bash
26+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
27+
- uses: actions/cache@v4
28+
name: Setup pnpm cache
29+
with:
30+
path: ${{ env.STORE_PATH }}
31+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
32+
restore-keys: |
33+
${{ runner.os }}-pnpm-store-
34+
- name: Install dependencies
35+
run: pnpm install
36+
- name: Publish new episodes
37+
run: pnpm tsx scripts/publish-episodes.ts
38+
env:
39+
ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }}
40+
ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }}
41+
STANDARD_SITE_URL: ${{ secrets.STANDARD_SITE_URL }}
42+
STANDARD_SITE_PUBLICATION_RKEY: ${{ secrets.STANDARD_SITE_PUBLICATION_RKEY }}

CLAUDE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,13 @@ configured for Preact (`jsxImportSource: "preact"`).
8888
- `ASTRO_DB_REMOTE_URL` — Turso/libSQL database URL (e.g.,
8989
`libsql://your-db.turso.io`).
9090
- `ASTRO_DB_APP_TOKEN` — Authentication token for Turso database.
91+
- `STANDARD_SITE_DID` — Your ATProto DID for standard.site verification (e.g.,
92+
`did:plc:abc123`). Find yours at https://bsky.app/settings.
93+
- `STANDARD_SITE_PUBLICATION_RKEY` — The publication record key returned when
94+
creating a publication via `scripts/create-publication.ts`.
95+
- `ATPROTO_HANDLE` — Your Bluesky handle (e.g., `you.bsky.social`) for
96+
publishing episodes to ATProto.
97+
- `ATPROTO_APP_PASSWORD` — App password for ATProto API access. Create at
98+
https://bsky.app/settings/app-passwords.
99+
- `STANDARD_SITE_URL` — Your podcast website URL (e.g., `https://whiskey.fm`)
100+
used as the publication site when publishing documents.

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,94 @@ environment variable it should work for you. Of course, feel free to customize
106106
the code [here](./src/pages/api/contact.ts) to send the data elsewhere as you
107107
see fit.
108108

109+
#### standard.site (ATProto Federation)
110+
111+
Starpod supports [standard.site](https://standard.site/) — a specification that
112+
connects your podcast website to [ATProto](https://atproto.com/) (the protocol
113+
behind Bluesky). Each episode is published as an individual document on the
114+
federated web. Enabling this allows:
115+
116+
- **Verified ownership** — Cryptographically prove you own your content across
117+
the federated web
118+
- **Cross-platform discovery** — Your podcast appears on ATProto readers like
119+
[Leaflet](https://leaflet.pub/) and [Pckt](https://pckt.blog)
120+
- **Federated engagement** — Comments and interactions from Bluesky and other
121+
ATProto apps can connect back to your site
122+
- **Episode-level publishing** — Each episode is a standalone document in ATProto
123+
124+
This feature is entirely optional. The site works perfectly without it — the
125+
verification endpoint simply returns a 404 when unconfigured. No changes to
126+
`astro.config.mjs` are needed.
127+
128+
##### Initial Setup
129+
130+
1. Create an [app password](https://bsky.app/settings/app-passwords) on Bluesky
131+
2. Create your publication record (run once):
132+
133+
```bash
134+
ATPROTO_HANDLE=you.bsky.social \
135+
ATPROTO_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx \
136+
STANDARD_SITE_URL=https://your-podcast.com \
137+
pnpm tsx scripts/create-publication.ts
138+
```
139+
140+
3. Save the output values as environment variables
141+
142+
##### Environment Variables
143+
144+
Set these in your `.env` file for local development and as **GitHub Actions
145+
secrets** for automated publishing:
146+
147+
| Variable | Description | Where to find it |
148+
|----------|-------------|------------------|
149+
| `STANDARD_SITE_DID` | Your ATProto DID (decentralized identifier) | [bsky.app/settings](https://bsky.app/settings) → scroll to "DID" |
150+
| `STANDARD_SITE_PUBLICATION_RKEY` | Record key for your publication | Returned by `scripts/create-publication.ts` |
151+
| `ATPROTO_HANDLE` | Your Bluesky handle (e.g., `you.bsky.social`) | Your Bluesky username |
152+
| `ATPROTO_APP_PASSWORD` | App password for ATProto API access | [bsky.app/settings/app-passwords](https://bsky.app/settings/app-passwords) |
153+
| `STANDARD_SITE_URL` | Your podcast website URL (e.g., `https://whiskey.fm`) | Your deployed site URL |
154+
155+
##### GitHub Actions Secrets
156+
157+
Add the following secrets to your repository at **Settings → Secrets and
158+
variables → Actions → New repository secret**:
159+
160+
- `ATPROTO_HANDLE`
161+
- `ATPROTO_APP_PASSWORD`
162+
- `STANDARD_SITE_URL`
163+
- `STANDARD_SITE_PUBLICATION_RKEY`
164+
- `STANDARD_SITE_DID`
165+
166+
##### Publishing Episodes
167+
168+
Episodes are published to ATProto as individual documents automatically:
169+
170+
- **Automatic** — The `Publish Episodes to ATProto` workflow runs after each
171+
daily site rebuild and publishes any new episodes
172+
- **Manual** — Trigger the workflow manually from the Actions tab
173+
- **Backfill** — Use the `Backfill Episodes to ATProto` workflow (Actions tab →
174+
Run workflow → type "backfill") to publish all existing episodes
175+
176+
You can also publish locally:
177+
178+
```bash
179+
# Publish only new episodes
180+
pnpm publish:episodes
181+
182+
# Backfill all episodes
183+
pnpm publish:episodes:backfill
184+
```
185+
186+
##### Verification
187+
188+
After deploying, verify the well-known endpoint with:
189+
190+
```bash
191+
curl https://your-site.com/.well-known/site.standard.publication
192+
```
193+
194+
For full setup instructions (creating a publication, syncing posts, etc.), see
195+
the [`@bryanguffey/astro-standard-site` README](https://github.com/musicjunkieg/astro-standard-site#readme).
196+
109197
#### Configuring guests
110198

111199
We use Turso and Astro DB to setup guests per episode. If you would also like to

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
"lint:fix": "eslint . --fix",
1919
"preview": "astro preview",
2020
"start": "astro dev",
21+
"publish:episodes": "tsx scripts/publish-episodes.ts",
22+
"publish:episodes:backfill": "tsx scripts/publish-episodes.ts --backfill",
2123
"test": "concurrently \"pnpm:test:*(!fix)\" --names \"test:\"",
2224
"test:e2e": "pnpm exec playwright test",
2325
"test:unit": "vitest"
2426
},
2527
"dependencies": {
2628
"@astrojs/preact": "^5.1.4",
2729
"@astrojs/vercel": "^10.0.8",
30+
"@bryanguffey/astro-standard-site": "^1.0.3",
2831
"@libsql/client": "^0.17.3",
2932
"@preact/signals": "^2.9.1",
3033
"@vercel/analytics": "^1.6.1",

0 commit comments

Comments
 (0)