Phase 1 of docs/nostr-vpn-integration-plan.md's Phase 0->4 plan (seller-side
rootless feasibility already confirmed there). Two apps, one image:
- apps/nostr-vpn: the daemon. Own network namespace (container.network:
pasta), NET_ADMIN+NET_RAW scoped to that netns, /dev/net/tun, and the
net.ipv4.ip_forward sysctl via the primitive added in e42bd26. UDP 51822
(not upstream's default 51820, which collides with archipelago-wg on
fleet nodes per the Phase 0 log). Seller mode stays off until an operator
explicitly enables it (paid_exit.enabled defaults to false upstream).
- apps/nostr-vpn-web: the control panel, gated behind 127.0.0.1:38080,
talking to the daemon only through the shared /data volume (state-file
status + shelling out to the nvpn CLI) -- no network link between the
two containers, matching upstream's own umbrel/docker-compose.yml.
- docker/nostr-vpn: upstream's umbrel/Dockerfile, unchanged except for how
the pinned commit arrives (shallow git fetch of a verified SHA, since
codeload.github.com archive tarballs 404 from this environment and
GitHub won't fetch an arbitrary SHA directly). Entrypoint seeds a minimal
config.toml with the chosen listen_port on first boot only -- every
AppConfig field is `serde(default = ...)`, confirmed by reading
nostr-vpn-core directly, so this merges with nvpn's own identity/wallet
bootstrap instead of needing a generated_secrets entry or full config
template, and never touches a config that already exists.
Both volumes point at /var/lib/archipelago/nostr-vpn, adopting state from
the old root-mode install. Build and the seed-config path were verified
against the real `nvpn daemon` binary, not just read -- see the plan doc's
Phase 1 log for what that caught (a fabricated commit SHA, the codeload
404, wrong default branch name, and confirming identity/wallet persistence
actually survives container recreation).
Not done here, flagged in the plan doc instead: removing the old root-mode
path (rpc/vpn.rs, rpc/auth.rs's auto-enable-on-login) touches live
onboarding on every node, not just this app -- needs explicit sign-off.
Also missing: a stop-hook/uninstall-guard manifest primitive (doesn't
exist yet -- LifecycleHooks only has post_install/pre_start) for the
collect-due-on-stop and non-zero-wallet uninstall guard, and registry
mirroring + catalog signing (need credentials this pass doesn't have).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
99 lines
4.2 KiB
Docker
99 lines
4.2 KiB
Docker
# syntax=docker/dockerfile:1.7
|
|
#
|
|
# Packages nostr-vpn (github.com/mmalmi/nostr-vpn) as the paid-exit seller
|
|
# daemon + its web control panel. Both apps/nostr-vpn and apps/nostr-vpn-web
|
|
# build from this one image (same binaries, different entrypoint/command),
|
|
# mirroring upstream's own umbrel/docker-compose.yml, which runs `daemon`
|
|
# and `web` as two containers sharing one /data volume with no network link
|
|
# between them — reviewed directly, not assumed.
|
|
#
|
|
# This is upstream's own umbrel/Dockerfile, unchanged except for how the
|
|
# source arrives (a pinned commit tarball here, instead of a local checkout
|
|
# in their build context) — see docs/nostr-vpn-integration-plan.md for why
|
|
# the pin exists and what was verified against this exact commit.
|
|
ARG NVPN_COMMIT=87f19447741998ab5a06aadc701abc7ae021004b
|
|
|
|
FROM debian:bookworm-slim AS source
|
|
ARG NVPN_COMMIT
|
|
# git clone, not a codeload.github.com/archive/<sha>.tar.gz tarball: the
|
|
# latter 404s from this environment even for refs/heads/main HEAD (network
|
|
# policy on that specific endpoint, not a real upstream 404 — plain
|
|
# `git clone https://github.com/...` works fine).
|
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates git \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
WORKDIR /src
|
|
# GitHub's anonymous smart-HTTP upload-pack refuses to fetch an arbitrary
|
|
# SHA directly (only advertised refs) — fetch main by name and verify the
|
|
# pinned commit is actually what we land on, so a force-push to main can't
|
|
# silently swap out the reviewed code.
|
|
RUN git init -q . \
|
|
&& git remote add origin https://github.com/mmalmi/nostr-vpn.git \
|
|
&& git fetch -q --depth 1 origin master \
|
|
&& git checkout -q FETCH_HEAD \
|
|
&& test "$(git rev-parse HEAD)" = "${NVPN_COMMIT}" \
|
|
&& rm -rf .git
|
|
|
|
FROM node:24-bookworm AS web-builder
|
|
WORKDIR /work/web/control-panel
|
|
COPY --from=source /src/web/control-panel/package.json /src/web/control-panel/pnpm-lock.yaml ./
|
|
RUN --mount=type=cache,id=nostr-vpn-pnpm-store,target=/pnpm/store \
|
|
corepack enable \
|
|
&& corepack prepare pnpm@10.28.2 --activate \
|
|
&& pnpm install --frozen-lockfile --store-dir /pnpm/store
|
|
COPY --from=source /src/web/control-panel ./
|
|
RUN pnpm run build
|
|
|
|
FROM rust:1.94-bookworm AS rust-builder
|
|
ARG TARGETPLATFORM
|
|
WORKDIR /work
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
clang \
|
|
libclang-dev \
|
|
libdbus-1-dev \
|
|
pkg-config \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
COPY --from=source /src/Cargo.toml /src/Cargo.lock ./
|
|
COPY --from=source /src/crates ./crates
|
|
COPY --from=source /src/vendor ./vendor
|
|
RUN --mount=type=cache,id=nostr-vpn-cargo-registry-${TARGETPLATFORM},target=/usr/local/cargo/registry \
|
|
--mount=type=cache,id=nostr-vpn-cargo-git-${TARGETPLATFORM},target=/usr/local/cargo/git \
|
|
--mount=type=cache,id=nostr-vpn-cargo-target-${TARGETPLATFORM},target=/work/target \
|
|
cargo build --release -p nvpn -p nostr-vpn-web \
|
|
&& mkdir -p /out \
|
|
&& cp /work/target/release/nvpn /out/nvpn \
|
|
&& cp /work/target/release/nostr-vpn-web /out/nvpn-web
|
|
|
|
FROM debian:bookworm-slim AS runtime
|
|
LABEL org.opencontainers.image.source="https://github.com/mmalmi/nostr-vpn" \
|
|
org.opencontainers.image.description="nostr-vpn, packaged as an Archipelago paid-exit seller app" \
|
|
org.opencontainers.image.licenses="MIT"
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
ca-certificates \
|
|
iproute2 \
|
|
iptables \
|
|
iputils-ping \
|
|
libdbus-1-3 \
|
|
procps \
|
|
wireguard-tools \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
COPY --from=rust-builder /out/nvpn /usr/local/bin/nvpn
|
|
COPY --from=rust-builder /out/nvpn-web /usr/local/bin/nvpn-web
|
|
COPY --from=web-builder /work/web/control-panel/dist /usr/share/nostr-vpn/web
|
|
COPY docker-entrypoint.sh /usr/local/bin/archy-nvpn-entrypoint.sh
|
|
RUN chmod +x /usr/local/bin/archy-nvpn-entrypoint.sh
|
|
|
|
ENV HOME=/data/home \
|
|
XDG_CONFIG_HOME=/data/config \
|
|
NVPN_CLI_PATH=/usr/local/bin/nvpn \
|
|
RUST_LOG=info
|
|
|
|
EXPOSE 38080
|
|
VOLUME ["/data"]
|
|
|
|
# No image-level ENTRYPOINT/CMD: apps/nostr-vpn and apps/nostr-vpn-web set
|
|
# their own entrypoint/custom_args in their manifests (daemon vs. web),
|
|
# both pointing at archy-nvpn-entrypoint.sh — see that script for why the
|
|
# seed-config step has to run before either binary starts.
|