commits
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the Forgejo workflow's behavior — test on every push/PR, build
image on pushes to main — but uses GitHub-native idioms: stock
actions/checkout, astral-sh/setup-uv, docker/login-action, and
docker/build-push-action. Auth is the built-in GITHUB_TOKEN with
packages: write permission, so no secrets or repo vars need to be set
on the mirrored GitHub repo. Image lands at
ghcr.io/<owner>/spacebee:latest (and :sha); the first publish is
private by default and can be flipped to public in GitHub's package
settings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regression from the security-hardening commit: switching to USER
spacebee (uid 10001) meant Passthrough.__init__'s implicit `/data`
parent mkdir could no longer succeed — UID 10001 has no write access
to /. Pre-create /data/passthrough at build time and chown to the
runtime user so the container boots with or without a mounted volume.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five fixes from the pre-publish review:
1. PROPFIND XML: escape `<D:displayname>` values (bookhive filenames and
local file names). Unescaped `&`/`<` would break the response XML for
any book with those chars in its title, and authenticated clients
could poison directory listings by PUT-ing crafted filenames.
2. Passthrough path check: replace str.startswith with is_relative_to so
a sibling directory sharing a prefix with $PASSTHROUGH_ROOT can't be
reached via `../sibling`.
3. Cover-download SSRF guard: require https + reject localhost and
RFC1918 / loopback / link-local IPs before fetching a URL returned
by the bookhive catalog.
4. Dockerfile: run as UID 10001 (non-root).
5. docker-compose: bind port to 127.0.0.1 by default, with a comment
explaining when to switch to 0.0.0.0.
New tests/test_security_guards.py covers all three code-level fixes.
Full suite: 68 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the vars.REGISTRY gate and the REGISTRY/IMAGE_NAME repo-var
references that have been silently skipping the image job since the
spacebee rename. Registry host is derived from GITHUB_SERVER_URL and
image name from GITHUB_REPOSITORY, lowercased for docker safety. Only
REGISTRY_USER / REGISTRY_TOKEN secrets need to be set — forks on any
Forgejo instance work with no repo-var configuration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New atproto/identity.py does handle → DID → PDS in two HTTPS hops
(public bsky appview + PLC directory, with did:web fallback). Config
no longer requires PDS; ATProtoClient resolves it lazily inside
_ensure_session under the existing session lock, so the first auth
call pays the cost and subsequent requests see the cached value.
No new deps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Package renamed (src/waggle -> src/spacebee, imports, pyproject,
Dockerfile CMD). CI no longer hardcodes git.brads.house; registry
coordinates come from REGISTRY/IMAGE_NAME repo vars and REGISTRY_USER/
REGISTRY_TOKEN secrets, and the image build is gated on `vars.REGISTRY`
so forks stay test-only by default. docker-compose.yml takes its image
tag from $IMAGE (defaults to local spacebee:latest). README, CLAUDE.md,
and .env.example scrubbed of personal hosts/handles. Dropped the empty
readest adapter stub to narrow scope to Moon+ Reader WebDAV.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dashboard and cover proxy no longer require basic auth; the data
they expose is already public on bookhive.buzz. WebDAV verbs on every
path (including PROPFIND on /) stay gated.
Reviews stored on finished books now render under the book-meta line
on both the current-year and archive lists.
The PDS returns 400 with `{"error": "ExpiredToken"}` when an access
token has aged out mid-session; the previous code only refreshed on
401 (unauthenticated) and forwarded the 400 to the caller, which
surfaced to Moon+ Reader as a sync failure after ~2h of activity.
Serves a single-user HTML view of the books waggle has records for,
styled after brad.quest's reading page: Currently Reading / Want to
Read / Finished in <year> / previous-years archive. Covers come from
a new /blob/{cid} proxy gated on CIDs we actually track (not an open
blob proxy). Profile + book cards link out to bookhive.buzz.
The plain `docker` runner label doesn't expose the host Docker socket, so
`docker build` fails with "Cannot connect to the Docker daemon". Split the
pipeline: test job stays on `docker` (slim uv image, manual git clone),
image job moves to `docker-host` with node:22-bookworm + apt-installed
docker.io. Matches the pattern already working in brad/vikunja-brad.
Also renames registry auth secrets to REGISTRY_USER / REGISTRY_TOKEN to
match the rest of Brad's Forgejo setup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WebDAV shim that impersonates Moon+ Reader's sync backend and translates
PROPFIND/GET/PUT into ATProto reads/writes against buzz.bookhive.book
records. Replaces the moon2hive cron script with a bidirectional service.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mirrors the Forgejo workflow's behavior — test on every push/PR, build
image on pushes to main — but uses GitHub-native idioms: stock
actions/checkout, astral-sh/setup-uv, docker/login-action, and
docker/build-push-action. Auth is the built-in GITHUB_TOKEN with
packages: write permission, so no secrets or repo vars need to be set
on the mirrored GitHub repo. Image lands at
ghcr.io/<owner>/spacebee:latest (and :sha); the first publish is
private by default and can be flipped to public in GitHub's package
settings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regression from the security-hardening commit: switching to USER
spacebee (uid 10001) meant Passthrough.__init__'s implicit `/data`
parent mkdir could no longer succeed — UID 10001 has no write access
to /. Pre-create /data/passthrough at build time and chown to the
runtime user so the container boots with or without a mounted volume.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five fixes from the pre-publish review:
1. PROPFIND XML: escape `<D:displayname>` values (bookhive filenames and
local file names). Unescaped `&`/`<` would break the response XML for
any book with those chars in its title, and authenticated clients
could poison directory listings by PUT-ing crafted filenames.
2. Passthrough path check: replace str.startswith with is_relative_to so
a sibling directory sharing a prefix with $PASSTHROUGH_ROOT can't be
reached via `../sibling`.
3. Cover-download SSRF guard: require https + reject localhost and
RFC1918 / loopback / link-local IPs before fetching a URL returned
by the bookhive catalog.
4. Dockerfile: run as UID 10001 (non-root).
5. docker-compose: bind port to 127.0.0.1 by default, with a comment
explaining when to switch to 0.0.0.0.
New tests/test_security_guards.py covers all three code-level fixes.
Full suite: 68 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the vars.REGISTRY gate and the REGISTRY/IMAGE_NAME repo-var
references that have been silently skipping the image job since the
spacebee rename. Registry host is derived from GITHUB_SERVER_URL and
image name from GITHUB_REPOSITORY, lowercased for docker safety. Only
REGISTRY_USER / REGISTRY_TOKEN secrets need to be set — forks on any
Forgejo instance work with no repo-var configuration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New atproto/identity.py does handle → DID → PDS in two HTTPS hops
(public bsky appview + PLC directory, with did:web fallback). Config
no longer requires PDS; ATProtoClient resolves it lazily inside
_ensure_session under the existing session lock, so the first auth
call pays the cost and subsequent requests see the cached value.
No new deps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Package renamed (src/waggle -> src/spacebee, imports, pyproject,
Dockerfile CMD). CI no longer hardcodes git.brads.house; registry
coordinates come from REGISTRY/IMAGE_NAME repo vars and REGISTRY_USER/
REGISTRY_TOKEN secrets, and the image build is gated on `vars.REGISTRY`
so forks stay test-only by default. docker-compose.yml takes its image
tag from $IMAGE (defaults to local spacebee:latest). README, CLAUDE.md,
and .env.example scrubbed of personal hosts/handles. Dropped the empty
readest adapter stub to narrow scope to Moon+ Reader WebDAV.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Serves a single-user HTML view of the books waggle has records for,
styled after brad.quest's reading page: Currently Reading / Want to
Read / Finished in <year> / previous-years archive. Covers come from
a new /blob/{cid} proxy gated on CIDs we actually track (not an open
blob proxy). Profile + book cards link out to bookhive.buzz.
The plain `docker` runner label doesn't expose the host Docker socket, so
`docker build` fails with "Cannot connect to the Docker daemon". Split the
pipeline: test job stays on `docker` (slim uv image, manual git clone),
image job moves to `docker-host` with node:22-bookworm + apt-installed
docker.io. Matches the pattern already working in brad/vikunja-brad.
Also renames registry auth secrets to REGISTRY_USER / REGISTRY_TOKEN to
match the rest of Brad's Forgejo setup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>