Build fdroidserver-ipfs image / build (push) Successful in 22s
Downstream of registry.gitlab.com/fdroid/docker-executable-fdroidserver:master,
patched for publishing F-Droid repos over IPFS:
- install Debian ipfs-cid so fdroidserver populates ipfsCIDv1 on every
package file (APKs, icons, screenshots), which fdroidclient with an
IPFS-gateway mirror routes via CID natively
- rehash.py wrapper that adds ipfsCIDv1 to entry["index"], renames the
index to a content-addressed filename, and re-signs entry.jar after
`fdroid update` — closes the cache-busting gap on the index file itself
- entrypoint.sh chains rehash automatically after any `fdroid update`,
so existing fdroid-via-docker wrappers need no changes
See SPEC.md for the rationale and Change 2's algorithm; README.md for
end-user usage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
125 lines
5.9 KiB
Markdown
125 lines
5.9 KiB
Markdown
# fdroidserver-ipfs
|
|
|
|
A drop-in replacement for `registry.gitlab.com/fdroid/docker-executable-fdroidserver:master`
|
|
tailored for publishing F-Droid repositories over IPFS.
|
|
|
|
The upstream image works for HTTP-served repos but has two gaps that break the
|
|
IPFS workflow. This image patches both.
|
|
|
|
## Problem
|
|
|
|
Pinning an F-Droid repo to IPFS and serving it through public HTTP gateways
|
|
(ipfs.io, dweb.link, cloudflare-ipfs.com, …) exposes two upstream limitations:
|
|
|
|
1. The `ipfs-cid` Python module isn't installed, so any fdroidserver code path
|
|
that computes a CIDv1 for repo artifacts fails on the stock image.
|
|
2. `index-v2.json` is published under a stable filename. Public IPFS gateways
|
|
cache aggressively by URL path, so clients keep getting the previous index
|
|
for hours after a republish even though the underlying CID has changed.
|
|
|
|
## Scope
|
|
|
|
This repo produces one OCI image — call it `fdroidserver-ipfs` — built `FROM`
|
|
the upstream `docker-executable-fdroidserver:master` and applying the minimum
|
|
set of changes needed for the IPFS workflow. **Everything not listed below is
|
|
intentionally inherited unchanged from upstream.**
|
|
|
|
### Change 1 — install `ipfs-cid`
|
|
|
|
Install the **Debian** `ipfs-cid` package (available in Trixie, which the
|
|
upstream image is based on):
|
|
|
|
```dockerfile
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends ipfs-cid \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
```
|
|
|
|
Note: the **PyPI** package of the same name is a Python library only and
|
|
ships no binary, so `pip install ipfs-cid` silently leaves CID generation
|
|
disabled. fdroidserver auto-detects the binary on PATH
|
|
(`fdroidserver/common.py::shutil.which('ipfs_cid')`) and, when present,
|
|
calls `calculate_IPFS_cid()` for every published file — APKs, source
|
|
tarballs, icons, screenshots, etc. — writing the resulting CIDv1 into
|
|
each file's `ipfsCIDv1` JSON field (`fdroidserver/update.py:1661-1663`,
|
|
`:1717-1719`). On the stock upstream image the binary is missing, so the
|
|
field is silently omitted and the IPFS-routing code path on the client is
|
|
never exercised.
|
|
|
|
**Why this matters for the client.** fdroidclient has native IPFS support:
|
|
`org.fdroid.download.MirrorChooser` (in `libs/download/`) reads the
|
|
`ipfsCIDv1` field on each file and, when a configured mirror is flagged
|
|
`isIpfsGateway`, fetches the file by CID via that gateway instead of by
|
|
URL. So once `ipfs-cid` is installed, every per-package asset (APK, icon,
|
|
…) is automatically reachable via any IPFS gateway by any client with one
|
|
configured — no patching required.
|
|
|
|
### Change 2 — add `ipfsCIDv1` to the index entry + cache-busted filename
|
|
|
|
Change 1 fixes per-package files but **does not** fix the index itself.
|
|
The `index` block inside `entry.json` is built by
|
|
`fdroidserver/common.py::file_entry()`, which only emits `name` / `sha256`
|
|
/ `size` and never adds `ipfsCIDv1` — even when one could be computed. So
|
|
fdroidclient has no CID for `index-v2.json` to feed into `MirrorChooser`,
|
|
and falls back to the stable HTTP path → public IPFS gateways serve a
|
|
stale cached copy for hours after a republish.
|
|
|
|
The post-process wrapper closes both halves of this gap in one pass:
|
|
|
|
1. Computes the IPFS CIDv1 of `repo/index-v2.json` (using `calculate_IPFS_cid`
|
|
from `fdroidserver.common`, which is the same code path fdroidserver
|
|
itself uses for APKs).
|
|
2. Renames `repo/index-v2.json` → `repo/index-v2-<h>.json`, where `<h>` is
|
|
the **last 12 hex characters of the SHA-256** of the file's content.
|
|
This image is **IPFS-only** — no stable copy is kept alongside.
|
|
3. Rewrites `repo/entry.json` so that `entry["index"]`:
|
|
- Updates `name` to `/index-v2-<h>.json`
|
|
- Adds `ipfsCIDv1` with the value computed in step 1
|
|
- Leaves `sha256` and `size` unchanged (the bytes didn't move)
|
|
4. Re-signs `entry.jar` by calling
|
|
`fdroidserver.signindex.sign_index(repodir, "entry.json")` — reuses the
|
|
existing `config.yml` keystore + `FDROID_KEY_STORE_PASS` /
|
|
`FDROID_KEY_PASS` env vars; no extra config surface.
|
|
5. If GPG signing is enabled in `config.yml`, regenerates `entry.json.asc`.
|
|
6. Applies the same CID/rename/rewrite to any `diff/*.json` files published
|
|
alongside (each is referenced by its own `name` field in entry.json).
|
|
|
|
Two-layer cache strategy, on purpose:
|
|
|
|
- **IPFS-aware clients** (any user with an IPFS gateway configured as a
|
|
mirror) follow the `ipfsCIDv1` and fetch the index by CID. Always fresh,
|
|
never gateway-cached by URL.
|
|
- **Plain-HTTP clients** still hit a new URL on every publish thanks to
|
|
the `<h>` suffix → public gateway path caches naturally miss → fresh
|
|
index without waiting for TTLs.
|
|
|
|
`entry.jar` itself stays at the fixed `/entry.jar` URL — gateways will
|
|
still cache it, but it's tiny and clients fetch it first to discover both
|
|
the current index filename and its CID, which are the cache-busted
|
|
references we actually care about.
|
|
|
|
`entry.json` does not currently carry an `ipfsCIDv1` for the index entry,
|
|
but fdroidclient's parser is configured with `ignoreUnknownKeys = true`
|
|
(`libs/index/.../IndexParser.kt`) and `EntryFileV2` already declares the
|
|
field (`libs/index/.../IndexV2.kt`), so adding it is both backwards-safe
|
|
and natively consumed by current client versions.
|
|
|
|
Delivery: a small Python script (`fdroidserver-ipfs-rehash`, or similar)
|
|
installed into the image's PATH that imports `fdroidserver.signindex` /
|
|
`fdroidserver.common` directly. Either invoked manually after `fdroid update`,
|
|
or wired into a container entrypoint that runs both. Entrypoint design is
|
|
deferred until we know how the image gets driven in production.
|
|
|
|
## Out of scope
|
|
|
|
- Changes to `index-v1.*` or the legacy `index.jar` flow.
|
|
- Pinning to IPFS, gateway selection, DNSLink updates — handled by whatever
|
|
drives the container, not by the image itself.
|
|
- Anything unrelated to the two issues above.
|
|
|
|
## Non-goals for this spec
|
|
|
|
This document captures *what* the image needs to do and *why*. The concrete
|
|
Dockerfile layering, the exact rehash/re-sign script, and the workflow YAML
|
|
are implementation details and land in follow-up changes.
|