Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ docs-developers/ Developer pages (architecture, server{,/api}, frontend, b
sidebars-*.ts canonical page lists (hand-ordered)
screenshots/ capture pipeline: manifest.mjs (source of truth), run.sh,
capture-web.mjs, capture-admin.mjs, capture-setup.mjs, capture-meta.mjs,
placeholders.mjs, .cache/ (gitignored)
capture-bridge.mjs, placeholders.mjs, .cache/ (gitignored)
static/img/screenshots/ generated images (committed)
src/pages/index.tsx landing page; src/css/custom.css theme
.github/workflows/ ci.yml (build gate), deploy.yml (GitHub Pages on main)
Expand Down
16 changes: 11 additions & 5 deletions docs-developers/architecture/cross-repo-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@ client.

**Server:** the scanner records each book's `codec` (ffprobe `codec_name`,
migration `0008_book_codec`); `item`/`chapters` expose `direct_playable` via
`media.DirectPlayable`; `GET .../stream?path=…&transcode=1` pipes through ffmpeg
`media.DirectPlayable` (from 2.4.0 the container counts too: a book with an
`.mka` file is always `false`, so the web player transcodes it and a native app
can't play it); `GET .../stream?path=…&transcode=1` pipes through ffmpeg
to MP3 (`media.Transcode`), with `&t=<seconds>` to start mid-file (transcoded
output isn't byte-seekable). All gated by the `--ffmpeg` flag and reflected in the
`transcode` capability (seam 8).
Expand Down Expand Up @@ -246,7 +248,7 @@ page captions such chapters (an older app shows them as ordinary chapters), and
saved chapters with a newer server answer while its audio files are unchanged
(same paths, order and sizes; `startChapterRefresh`). Podcasts additionally
return `id3_chap` from the server's CHAP attachments; the player (2.2.0 and
later) captions it "Chapters from the episode file". See [podcasts](#17-podcast-shows-feeds-and-client-visibility).
later, first released in 2.4.0) captions it "Chapters from the episode file". See [podcasts](#17-podcast-shows-feeds-and-client-visibility).

**A change requires:** any change to chapter normalization changes the queue
builder and the timeline math with it. And always: **stream the file, not the
Expand All @@ -266,7 +268,10 @@ Universal/App Links on claimed domains, else the web player) and `uri`
token → device-scoped session).
**Frontend:** the `audiosilo` scheme in `app.json`; the connect/pairing parser and
the `/web/connect` route consume both carriers; `client.ts`
`redeemCode()`/`exchange()`.
`redeemCode()`/`exchange()`. The app's scanner and the web player's paste field also
read the console's invite link (`inviteURL`, `<base>/connect#code=…`; `parseInviteScan`
from player 2.4.0) and sign in with its code through the same redeem and exchange, so
a change to that link's shape changes both repos.

**Home and away addresses (`addresses` capability).** Every pairing payload
(`/auth/redeem`, `/auth/pair`, the demo session's `pairing`) may carry
Expand Down Expand Up @@ -826,8 +831,9 @@ security-critical: allowed and denied tests on the server.
## 17. Podcast shows, feeds and client visibility

Phase 2c-2 extends the server/Bridge seam with episodes on disk, retention
and on-demand fetching/status/Retry. Phase 2d-1 ships the player's views to
players from 2.2.0; Phase 2d-2 adds discovery, follow by URL, OPML and Check
and on-demand fetching/status/Retry. Phase 2d-1 ships the player's views
(shown to players the gate admits, 2.2.0 and later; first released in the
2.4.0 player); Phase 2d-2 adds discovery, follow by URL, OPML and Check
for new episodes to the player.
See [Podcasts](../bridge/podcasts.md) and the
[API reference](../server/api/reference.md#podcasts) for exact fields.
Expand Down
16 changes: 13 additions & 3 deletions docs-developers/bridge/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,16 @@ for example, `openssl rand -hex 32`. The startup log says where the key came fro

The page's own password is not a secret in this sense: it is hashed (argon2id) in
the database, generated and **printed once** to the log on the first start, and
changed on the page's Settings tab.
changed on the page's Settings tab. An owner who lost it runs
`audiosilo-bridge reset-password [--data DIR]` (from v0.3.0; in Docker
`docker exec -u PUID:PGID <container> audiosilo-bridge reset-password`): it
prints a new generated password once and signs every session out, changing
only the password hash and the sessions (`app.ResetPassword`), so the pairing,
the mappings, the jobs and the sealed secrets stay, and a running Bridge takes
it at once. It refuses a folder with no Bridge database (a mistyped `--data`)
and, as root, a data folder another user owns (SQLite's new files would be
root's), naming the user to run it as. The command goes before its flags:
`--data DIR reset-password` is refused rather than starting the Bridge.

## PUID and PGID

Expand All @@ -222,8 +231,9 @@ user's. The entrypoint warns when it isn't writable.

## The images

`ghcr.io/kodestar/audiosilo-bridge` (private while the repository is), built from
one Dockerfile with four targets:
`ghcr.io/kodestar/audiosilo-bridge`, public from v0.3.0 (pulled with no login;
the source repository stays private), built from one Dockerfile with four
targets:

| Tag | Target | What's in it | Arch |
|---|---|---|---|
Expand Down
14 changes: 9 additions & 5 deletions docs-developers/bridge/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ description: "Working in audiosilo-bridge: the gate and CI, the package layout a

`audiosilo-bridge` is a Go module (`github.com/kodestar/audiosilo-bridge`, Go 1.26,
AGPL-3.0) with a small web page in `web/` (Vite, React 19, TypeScript, Tailwind v4,
Node 24). It is **private**; its own `CLAUDE.md` and `PACKAGES.md` (the
Node 24). The repository is **private** (its images are public from v0.3.0);
its own `CLAUDE.md` and `PACKAGES.md` (the
per-package rules and history) are authoritative, and the seam with the server is
section 23 of the workspace `CROSS-REPO.md`.

Expand All @@ -31,15 +32,17 @@ docker build --target slim . # a Dockerfile change: slim | cuda | full | agent
CI (`.github/workflows/ci.yml`) runs both sides on every pull request and every
push to `main`: a **go** job (build, vet, `go test -race`, golangci-lint) and a
**web** job (`npm ci`, typecheck, lint, the prettier check, the tests and a
production build). `image.yml` builds and pushes the three images on `v*` tags and
on manual dispatch (`full` and `slim` for amd64 and arm64, `cuda` for amd64); which
tags each publishes is in [the images](configuration.md#the-images).
production build). `image.yml` builds and pushes the four images to the public
GHCR package on `v*` tags and on manual dispatch (`full`, `slim` and `agent` for
amd64 and arm64, `cuda` for amd64); which tags each publishes is in
[the images](configuration.md#the-images).

To run it locally:

```sh
scripts/build-web.sh # build the page and embed it into bin/audiosilo-bridge
./bin/audiosilo-bridge --data ./data # the first start prints the page's password ONCE
./bin/audiosilo-bridge reset-password --data ./data # lost it: a new one, printed once
```

Then open `http://localhost:8091`, sign in, and pair it with a local server (see
Expand Down Expand Up @@ -129,7 +132,8 @@ Conventions that carry the design:
sends on every call, `AudioSilo Bridge/<version> (<os>)`, must parse with the
server's `auth.ParseClient`; a test holds the server's pattern.
- **Secrets are never logged or echoed.** The page's password is printed once at the
first start (to standard error, never through the logger), the server token, the
first start (to standard error, never through the logger) and once by
`reset-password` (to standard output), the server token, the
GitHub sign-in and the Accounts tab's agent credentials are sealed (the page API
says only whether one is set), and the log ring redacts attributes whose key looks like a secret
(`token`, `password`, `code`, `key`, `secret`, `cookie`, `authorization`).
Expand Down
13 changes: 9 additions & 4 deletions docs-developers/bridge/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ stay read-only and safe to expose to the internet. It is a separate program in i
own repository, `audiosilo-bridge`, and runs beside the server, usually as a second
container.

:::info[Private repository, built in phases]
The `audiosilo-bridge` repository (and its images, `ghcr.io/kodestar/audiosilo-bridge`)
is **private** for now, and the Bridge is being built in phases. **Phase 0** laid
:::info[Private repository, public images, built in phases]
The `audiosilo-bridge` repository is **private**; its images,
`ghcr.io/kodestar/audiosilo-bridge`, are **public from v0.3.0** and pull with no
login. Bridge v0.3.0 is the release that speaks to server 2.4.0 (podcasts,
contributions, generating and detecting chapters): run v0.3.0 or later with a
2.4.0 server. Nothing checks the version: the server offers each feature by the
job kinds the Bridge's heartbeat reports, so an older Bridge pairs but lacks
them. The Bridge is being built in phases. **Phase 0** laid
the foundations: pairing, the heartbeat and the capability, the job queue with
leases, the library mapping checks and one proof job, `bridge.verify`. **Phase 1a**
added the first job that makes it useful,
Expand All @@ -38,7 +43,7 @@ fetching on play, fetch status/Retry, one-ahead queue preparation and a
reserved download slot for play requests. **Phase 2c-3** adds server-side
podcast [directory search and OPML import/export](podcasts.md#discovery-and-opml-transfer)
with no Bridge change. **Phase 2d-1** adds the player's
[podcast views](podcasts.md#player-phase-2d-1) (players from 2.2.0), again
[podcast views](podcasts.md#player-phase-2d-1) (first released in player 2.4.0), again
with no Bridge change, and **Phase 2d-2** its
[discovery, follow by URL and OPML screens](podcasts.md#player-discovery-and-opml-phase-2d-2)
(a CORS change on the server only). Read-along, Audible backups and file moves
Expand Down
13 changes: 12 additions & 1 deletion docs-developers/bridge/pairing-and-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,10 @@ handlers at priority at least 100 (`InteractiveHandler`, `podcast.fetch`
today), never background work. Ordinary slots can take interactive jobs too;
total enclosure concurrency stays at two. Without negotiated support,
`interactive` becomes zero and `download` two, so an older server ignoring an
unknown query cannot fill the reserve with background jobs.
unknown query cannot fill the reserve with background jobs. The lanes the page
lists (`Runner.Lanes`, in `GET /api/status`) leave out a lane with no capacity
and nothing running, so that folded `interactive` lane no longer shows as
"0 of 0" (v0.3.0).

A change to eligible kinds, pairing, negotiated reserve or capacities cancels
and joins the outstanding claim, then reissues immediately without cancellation
Expand Down Expand Up @@ -348,6 +351,14 @@ they were:
reported again, every 30 seconds, until the server takes it or says it has moved
on.
- A running job of a kind the Bridge no longer runs is failed.
- **A parked job** (`needs_attention`) resumes parked, and its first report
sends the progress it last recorded, park code and all (v0.3.0). While it
stays parked, a report whose progress names no park keeps the one before it
(`Reporter.Progress`), since a resumed job reports its first steps before it
hears its engine's park again. The server keeps the stored park too when an
update to a parked job names none (`carryPark` in `internal/bridge`, server
2.4.0), so a restart no longer reads as a park dropped and a new one raised:
no spurious `bridge_job_failed` events, and the console keeps the reason.

When the server refuses the token (a `401`), a running job stops where it is,
without a final report, and is kept aside. When a link to the same server works
Expand Down
Loading
Loading