laputa-mirror serves the Laputa PM repository format from an S3-compatible
bucket and exposes authenticated PUT publishing for:
index.jsonpackages/<arch>/<name>/<name>-<ver>-<rel>.tar.gzmetadata/<arch>/<name>/<name>-<ver>-<rel>.jsonsources/<name>/<name>-<ver>-<rel>-src.tar.gz
The mirror accepts the older packages/<name>/... object path for existing
arm64 packages, but new PM uploads use the arch-qualified path. Source mirrors
stay shared across architectures.
This guide stands up a new mirror at https://laputa.17166969.xyz/ using
Cloudflare R2 for object storage and a Cloudflare Tunnel for the only public
origin path. With this tunnel-only setup, leave R2_PUBLIC_URL unset so package
downloads are served through laputa-mirror instead of redirecting clients to a
public R2 URL.
Laputa PM clients
-> https://laputa.17166969.xyz
-> Cloudflare Tunnel
-> cloudflared on the server
-> http://127.0.0.1:3000
-> laputa-mirror
-> Cloudflare R2 bucket: laputa-mirror
The server does not need inbound ports open. cloudflared makes outbound
connections to Cloudflare and forwards the public hostname to the local mirror.
On the machine where you build the .deb:
sudo apt-get update
sudo apt-get install -y build-essential curl pkg-config libssl-dev nodejs npm dpkg-devInstall Rust with rustup if it is not already present:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. "$HOME/.cargo/env"
rustup default stableInstall cloudflared using Cloudflare's package instructions for Debian/Ubuntu,
or install the binary at /usr/bin/cloudflared on the server.
You already created the bucket:
laputa-mirror
Create an R2 API token with object read/write access to that bucket. You need:
S3_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com
S3_BUCKET=laputa-mirror
S3_ACCESS_KEY_ID=<r2_access_key_id>
S3_SECRET_ACCESS_KEY=<r2_secret_access_key>
S3_REGION=auto
Do not configure R2_PUBLIC_URL for the tunnel-only setup.
make deb builds debug x86_64-unknown-linux-musl binaries and packages them
as amd64. It requires cargo-zigbuild:
cd /path/to/laputa-systems/mirror
cargo install cargo-zigbuild
make debOutput:
laputa-mirror_0.1.0_amd64.deb
The package includes:
/usr/bin/laputa-mirror/usr/bin/laputa-mirror-publish/usr/share/laputa-mirror/static/lib/systemd/system/laputa-mirror.service/etc/laputa-mirror/env.example
The repository intentionally does not build release binaries from this target.
Copy the package to the server:
scp laputa-mirror_0.1.0_amd64.deb root@<server>:/tmp/Install the package:
sudo dpkg -i /tmp/laputa-mirror_0.1.0_amd64.debThe package post-install creates the laputa-mirror service user, creates
/var/lib/laputa-mirror, and copies /etc/laputa-mirror/env.example to
/etc/laputa-mirror/env if that file does not already exist.
Edit the env file:
sudo editor /etc/laputa-mirror/envMinimum tunnel-only config:
LISTEN_ADDR=127.0.0.1:3000
S3_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com
S3_BUCKET=laputa-mirror
S3_ACCESS_KEY_ID=<r2_access_key_id>
S3_SECRET_ACCESS_KEY=<r2_secret_access_key>
S3_REGION=auto
DB_PATH=/var/lib/laputa-mirror/auth.db
ALLOWED_USERS=josh
RP_ID=laputa.17166969.xyz
RP_ORIGIN=https://laputa.17166969.xyzSet ALLOWED_USERS to the usernames allowed to register passkeys. Comma
separate multiple users.
Start the local service:
sudo systemctl daemon-reload
sudo systemctl enable --now laputa-mirror
sudo systemctl status laputa-mirrorLocal check:
curl -fsSL http://127.0.0.1:3000/healthThis guide uses a locally-managed tunnel because it is easy to reproduce from the CLI. Cloudflare recommends remotely-managed tunnels for most production deployments; the origin service and hostname are the same either way.
Authenticate cloudflared:
cloudflared tunnel loginCreate the tunnel:
cloudflared tunnel create laputa-mirrorRecord the tunnel UUID from the output. The command also creates:
~/.cloudflared/<tunnel-uuid>.json
Install the tunnel credentials:
export TUNNEL_ID=<tunnel-uuid>
sudo install -d -m 755 /etc/cloudflared
sudo install -m 600 "$HOME/.cloudflared/$TUNNEL_ID.json" "/etc/cloudflared/$TUNNEL_ID.json"Create /etc/cloudflared/config.yml:
sudo tee /etc/cloudflared/config.yml >/dev/null <<EOF
tunnel: $TUNNEL_ID
credentials-file: /etc/cloudflared/$TUNNEL_ID.json
ingress:
- hostname: laputa.17166969.xyz
service: http://127.0.0.1:3000
- service: http_status:404
EOFCreate the DNS route:
cloudflared tunnel route dns laputa-mirror laputa.17166969.xyzInstall and start cloudflared as a service:
sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflaredPublic check:
curl -fsSL https://laputa.17166969.xyz/healthOpen:
https://laputa.17166969.xyz/auth
Register a passkey using a username from ALLOWED_USERS.
Then open:
https://laputa.17166969.xyz/auth/settings
Create a named token, for example github-actions, and store it once. Tokens
are shown only at creation time.
Set the publisher environment:
export LAPUTA_MIRROR_URL=https://laputa.17166969.xyz
export LAPUTA_MIRROR_TOKEN=<token_from_settings>Publish an existing exported PM repo:
cargo run --bin laputa-mirror-publish -- \
.out/laputa-bootstrap-build-essential-native-repoThe publisher:
- Uploads every package tarball from
packages/<arch>/. - Uploads package metadata sidecars from
metadata/<arch>/. - Maps
.out/source-mirrors/<pkg>-<ver>-<rel>.tar.gztosources/<pkg>/<pkg>-<ver>-<rel>-src.tar.gz. - Recomputes package/source sha256 and package size metadata.
- Uploads
index.jsonlast.
Large package and source uploads are split into smaller chunk requests to avoid
Cloudflare Tunnel request body limits. The publisher only talks to
laputa-mirror; the mirror service is the only component that writes to R2.
Verify:
curl -fsSL https://laputa.17166969.xyz/index.json | jq '.[].name'
curl -I https://laputa.17166969.xyz/packages/aarch64/build-essential-native/build-essential-native-1-2.tar.gz
curl -I https://laputa.17166969.xyz/metadata/aarch64/build-essential-native/build-essential-native-1-2.json
curl -I https://laputa.17166969.xyz/sources/linux/linux-7.0.5-5-src.tar.gzAdd repository secrets:
LAPUTA_MIRROR_TOKEN=<token_from_settings>
Run the manual workflow:
Laputa Mirror Publish ARM64
Use the workflow run ID that produced:
laputa-bootstrap-build-essential-native-repo-arm64
The workflow downloads that artifact, validates the expected repo layout,
publishes it, then checks the public index.json and a known package URL.
Use the mirror as the public repo:
export XSH_PM_PUBLIC_REPO=https://laputa.17166969.xyzFor uploads from local PM tooling, use:
export XSH_PM_REPO=https://laputa.17166969.xyz
export LAPUTA_TOKEN=<token_from_settings>Check services:
sudo systemctl status laputa-mirror
sudo systemctl status cloudflaredLogs:
sudo journalctl -u laputa-mirror -f
sudo journalctl -u cloudflared -fRestart after env changes:
sudo systemctl restart laputa-mirrorRestart after tunnel config changes:
sudo systemctl restart cloudflared- Keep
/etc/laputa-mirror/envmode0600; it contains R2 credentials. - Keep
/etc/cloudflared/<tunnel-uuid>.jsonmode0600; it is the tunnel credential. - Large package/source uploads are chunked through
laputa-mirror; CI does not receive or use R2 credentials.