diff --git a/README.md b/README.md
index 504f1db3..aa248cc7 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,9 @@ curl -fsSL https://c0mpute.com/install.sh | sh
```
That is the whole setup. The installer puts `c0mpute`, `infernet` and `coinpay` in
-`~/.c0mpute/bin` and starts the worker for you (a systemd service where it can, a
-background daemon otherwise). Check it with `c0mpute worker status`, then run
+`~/.c0mpute/bin`, checks each release binary's minisign signature, and starts the worker
+for you as **your user, not root** (a systemd user service where it can, a background
+daemon otherwise; `--system-service` for a root-managed system service). Check it with `c0mpute worker status`, then run
`c0mpute bench` once so your node gets a score on the
[node board](https://c0mpute.com/nodes).
@@ -31,17 +32,23 @@ background daemon otherwise). Check it with `c0mpute worker status`, then run
- **What it does:** serves AI inference (through [Infernet](https://infernetprotocol.com)),
FFmpeg transcoding and storage jobs from the network.
- **What you get:** paid inference is billed through CoinPay (16 coins or PayPal) and you set
- your payout address with `infernet payout set `. Paid demand is early, so
- expect small, irregular earnings for now.
+ your payout address with `infernet payout set `. Paid demand is early and the
+ network is small, so expect small, irregular earnings for now. Storage payouts
+ ([CIP-006](docs/prds/006-challenges-metering-payouts.md)) are not paying yet, and CoinPay
+ settlement for hosting and ads is built but off by default.
- **House models (on by default):** the node pulls and serves the models Infernet publishes at
[infernetprotocol.com/house-models.json](https://infernetprotocol.com/house-models.json) that fit
- it (RAM, and always leaving 10% of the disk free), today Profullstack's
- `saas-business-strategist-agent` and the uncensored `huihui_ai/qwen3.5-abliterated:4b`/`:9b`.
- `c0mpute worker status` lists them and the disk they use. Opt out at install with
- `--no-house-models` (uncensored only: `--no-uncensored-models`), or later with
- `infernet house-models off` / `infernet house-models uncensored off`; cap the disk with
+ it (RAM, and always leaving 10% of the disk free), today Profullstack's agent models.
+ The uncensored `huihui_ai/qwen3.5-abliterated:4b`/`:9b` are on the list too, but a node
+ serves them **only if you opt in** (`--uncensored-models` at install, or
+ `infernet house-models uncensored on`). `c0mpute worker status` lists them and the disk
+ they use. Opt out of house models at install with `--no-house-models`, or later with
+ `infernet house-models off`; cap the disk with
`infernet house-models cap `. Models you pulled yourself are never removed.
Details: [infernetprotocol.com/docs/house-models](https://infernetprotocol.com/docs/house-models).
+- **Trust:** the worker runs code from the network on your machine. What it can touch, how
+ releases are signed, and how to remove it: [docs/node-safety.md](docs/node-safety.md). If you
+ would rather not trust a new project with your main machine, run it in a VM or on a spare box.
- **Invite people:** share `https://c0mpute.com/r/`. Nodes installed from it
show your handle on the board (`C0MPUTE_REF=` does the same by hand).
diff --git a/apps/web/src/app/providers/page.tsx b/apps/web/src/app/providers/page.tsx
index b40e9718..691f5420 100644
--- a/apps/web/src/app/providers/page.tsx
+++ b/apps/web/src/app/providers/page.tsx
@@ -108,19 +108,40 @@ $ c0mpute worker status # the installer already started it`}
that fit it, so a request for one can reach any node, not only ours. A model is pulled
only when the machine has its listed RAM and the download leaves 10% of the disk free;
one at a time, never while a job runs, and checked against its pinned digest. Models
- you pulled yourself are never removed.
+ you pulled yourself are never removed. Uncensored models on that list are{" "}
+ off unless you opt in.
{`$ c0mpute worker status # lists house models + disk used
$ infernet house-models off # opt out (removes what it pulled)
-$ infernet house-models uncensored off # skip uncensored models only
+$ infernet house-models uncensored on # opt in to uncensored models (off by default)
$ infernet house-models cap 20 # at most 20 GB`}
- At install: --no-house-models or --no-uncensored-models.
+ At install: --no-house-models, or --uncensored-models to opt in.
The list and how to add a model:{" "}
infernetprotocol.com/docs/house-models.
+
+
[ before you install ]
+
+ A node runs code from this network on your machine, so here is exactly what it can do.
+ The worker runs as your user, not root:
+ the installer writes to ~/.c0mpute and starts a systemd user service
+ (--system-service for a root-managed one, which needs sudo). Release
+ binaries are signed with minisign and the installer checks the signature before it
+ installs anything (public key: minisign.pub).
+ Jobs are the workloads your node advertises, run by those signed binaries as child
+ processes with CPU and GPU limits. That is not a sandbox: they can read what your user
+ can read. The code is MIT and on GitHub.
+
+
+ If you would rather not trust a new project with your main machine, run it in a VM or on a
+ spare box first. That is a reasonable call, and the node earns the same there.
+ Full details: docs/node-safety.md.
+
+
+
[ CPU voice inference ]
@@ -159,8 +180,11 @@ $ c0mpute worker start --roles tts,stt`}
itself by default (it learns the winning price, undercuts it, and asks
more when busy) and never goes under the minimum you set with{" "}
c0mpute pricing set --min 0.05; or ask one fixed price.
- When you win, your node runs the job and publishes a receipt; signed
- receipts and automatic settlement are not live yet. A direct protocol deal has{" "}
+ When you win, your node runs the job and publishes a receipt. Settlement
+ through CoinPay is built for hosting and ads but{" "}
+ off by default while it is
+ tested; storage payouts (CIP-006)
+ are not paying yet. A direct protocol deal has{" "}
no protocol fee. Jobs
bought through the market will pay you{" "}
95% once market
diff --git a/docs/node-safety.md b/docs/node-safety.md
new file mode 100644
index 00000000..8b85a9eb
--- /dev/null
+++ b/docs/node-safety.md
@@ -0,0 +1,83 @@
+# Running a node: what it can touch
+
+A c0mpute node runs work for strangers on your machine. That deserves a plain
+answer to "what am I trusting?", so here it is. If anything below is wrong,
+open an issue; this page is meant to be checkable against the code.
+
+## What the installer does
+
+`curl -fsSL https://c0mpute.com/install.sh | sh` (source: [`scripts/install.sh`](../scripts/install.sh)):
+
+- Downloads the `c0mpute`, `infernet` and `coinpay` release binaries and
+ **verifies each one's minisign signature** against the public key in
+ [`minisign.pub`](../minisign.pub) before installing it. A bad signature aborts the install.
+- Installs everything under your home directory: binaries in `~/.c0mpute/bin`,
+ data in `~/.local/share/c0mpute` (or a bigger volume it symlinks, unless
+ `NO_STORAGE_RELOCATE=1`).
+- Starts the worker as **your user, not root**: a systemd *user* service
+ (`~/.config/systemd/user/c0mpute-worker.service`), with systemd lingering so
+ it starts at boot. Without systemd it runs as a background daemon.
+- Uses `sudo` only if it is already configured, and only for conveniences:
+ a `/usr/local/bin` symlink and a `/etc/profile.d` PATH hook, plus the
+ system service if you ask for one with `--system-service`. It never runs the
+ worker as root. Skip all of it by running the installer without sudo.
+
+## What the worker runs
+
+- Jobs are the workload types your node advertises (`c0mpute worker status`
+ lists them): inference through Infernet, FFmpeg transcodes, storage shards,
+ LoRA training, speech, music and image generation. They are run by the
+ installed, signed binaries; a buyer sends data and parameters, not code to execute.
+ Container workloads (video, game servers) start only pinned, well-known images, and only
+ when Docker or Podman already works on the box.
+- Jobs run as child processes of the worker with CPU-thread and GPU limits.
+ **This is not a sandbox.** A job runs with your user's permissions, so a
+ bug in a workload runtime could read files your user can read. If that is
+ not acceptable for your main machine, run the node in a VM, a container or
+ on a spare box. It earns the same there.
+- The node keeps itself up to date (`C0MPUTE_NO_AUTO_UPDATE=1` turns that
+ off). Updates are verified against the same minisign key before the binary
+ is swapped.
+
+## Models it serves
+
+- **House models** (on by default): the models Infernet lists at
+ [infernetprotocol.com/house-models.json](https://infernetprotocol.com/house-models.json)
+ that fit your RAM and leave 10% of the disk free. Turn them off with
+ `infernet house-models off`, or `--no-house-models` at install.
+- **Uncensored house models are off unless you opt in** (`--uncensored-models`
+ at install, or `infernet house-models uncensored on`).
+
+## Network
+
+Outbound connections only; no port forwarding needed. Some workloads make
+outbound HTTP requests on a buyer's behalf from your IP (`crawl.fetch`,
+`workflow.step` http calls). Those are limited to public addresses, capped in size and
+time, and are listed in `c0mpute worker status` like every other workload. The node joins the
+libp2p mesh and advertises its capabilities, benchmark score and (if you set
+them) a nickname and referral handle, which appear on the public
+[node board](https://c0mpute.com/nodes).
+
+## Money: what pays today
+
+- **Paid inference** through Infernet pays out via CoinPay to the address you
+ set with `infernet payout set `.
+- **Hosting and ads** settlement through CoinPay is built (PRs #162, #163,
+ #166) but **off by default** while it is tested.
+- **Storage payouts** ([CIP-006](prds/006-challenges-metering-payouts.md)) are
+ not paying yet.
+
+The network is small and paid demand is early. Expect small, irregular
+earnings for now, and budget for the electricity and wear your hardware takes.
+
+## Removing it
+
+```sh
+systemctl --user disable --now c0mpute-worker # or: c0mpute worker stop
+rm -rf ~/.c0mpute ~/.local/share/c0mpute ~/.config/systemd/user/c0mpute-worker.service
+loginctl disable-linger "$USER" # if nothing else of yours needs it
+```
+
+With a system service, also run
+`sudo systemctl disable --now c0mpute-worker && sudo rm /etc/systemd/system/c0mpute-worker.service`.
+Remove Infernet with `infernet uninstall`.
diff --git a/docs/prds/006-challenges-metering-payouts.md b/docs/prds/006-challenges-metering-payouts.md
index 936a0faa..b16c27fa 100644
--- a/docs/prds/006-challenges-metering-payouts.md
+++ b/docs/prds/006-challenges-metering-payouts.md
@@ -1,11 +1,11 @@
---
cip: 006
title: "Storage challenges, metering, and provider payouts"
-status: Draft
+status: In progress
authors:
- anthony@profullstack.com
created: 2026-08-29
-updated: 2026-08-29
+updated: 2026-11-09
implements: DIP-0012 (0012-storage-plugin.md) Phase 5, DIP-0007 (CoinPay DID)
depends-on: 003, 004
blocks:
@@ -13,6 +13,25 @@ implementation:
estimate: "4–6 weeks"
---
+## Status (2026-11-09)
+
+Storage payouts are **not paying yet**. What exists today:
+
+| Piece | State |
+|---|---|
+| Shared payment path (`node/crates/c0mpute-core/src/payments.rs`: `Rail`, idempotent `Ledger`, per-round USD cap) on CoinPay payouts | Merged (#163), **off by default** |
+| Hosting marketplace: proof of storage + settlement for hosted sites | Merged (#163), **off by default** |
+| Ads billing + settlement through the same path | Merged (#162, #166), **off by default** |
+| Storage challenges issued continuously by the network, storage metering, monthly provider statements (this CIP) | Not started beyond `c0mpute-verify`'s `StorageChallenge` primitive |
+| Paid inference payouts (Infernet, via CoinPay) | Live today; not part of this CIP |
+
+The planned storage rate is $0.0015/GB-month, so 1 TB earns about $1.50 a
+month. The network is still small (live count on
+[c0mpute.com/nodes](https://c0mpute.com/nodes)), so nobody should run a node
+for storage income yet. The README, `/providers` and
+[`docs/node-safety.md`](../node-safety.md) say this in plain words; keep them in step
+with this table.
+
## Summary
Pay people for the disk they contribute, and make sure they are actually
diff --git a/docs/prds/README.md b/docs/prds/README.md
index ea67804b..2549c9e9 100644
--- a/docs/prds/README.md
+++ b/docs/prds/README.md
@@ -69,7 +69,7 @@ Delivering read/write network storage for c0mpute, implementing
| [003](003-shard-placement-transport.md) | Cross-node shard placement and streaming transport | 002 | In progress |
| [004](004-metadata-durability.md) | Metadata durability: manifests, volumes, and the root pointer | 002 | Draft |
| [005](005-repair-daemon.md) | Auto-repair daemon | 003, 004 | In progress |
-| [006](006-challenges-metering-payouts.md) | Storage challenges, metering, and provider payouts | 003, 004 | Draft |
+| [006](006-challenges-metering-payouts.md) | Storage challenges, metering, and provider payouts | 003, 004 | In progress (payment rail merged, off by default; storage payouts not live) |
| [007](007-c0mputefs-filesystem.md) | c0mputefs: mutable filesystem over immutable content | 004 | Draft |
| [008](008-write-path-consistency.md) | Write path: chunking, journal, and crash consistency | 007 | Draft |
| [009](009-mount-cli.md) | `c0mpute storage` CLI and the FUSE mount | 008 | Draft |
diff --git a/scripts/install.sh b/scripts/install.sh
index 1b96e6ff..95a6945d 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -26,8 +26,13 @@
# INFERNET_NO_HOUSE_MODELS=1). They are ON by default:
# the node pulls and serves the models listed at
# https://infernetprotocol.com/house-models.json that fit it.
-# --no-uncensored-models Skip uncensored house models only
+# --uncensored-models Also serve uncensored house models. They are OFF
+# unless you opt in (same as INFERNET_UNCENSORED_MODELS=1)
+# --no-uncensored-models Keep uncensored house models off
# (same as INFERNET_NO_UNCENSORED_MODELS=1)
+# --system-service Run the worker as a root-managed systemd system
+# service (needs sudo). Default: a systemd *user*
+# service under your account, no root involved.
# --no-exec Don't auto-exec a fresh shell at the end
# (CI-friendly; default is to drop you into a new
# shell with c0mpute already on $PATH)
@@ -59,6 +64,7 @@ WORKER_MODE=0
DEVELOPER_MODE=0
FORCE=0
NO_START=0
+SYSTEM_SERVICE="${C0MPUTE_SYSTEM_SERVICE:-0}"
say() { printf '\033[1;36m→\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!\033[0m %s\n' "$*" >&2; }
@@ -91,9 +97,11 @@ while [ $# -gt 0 ]; do
--name) shift; C0MPUTE_NAME="${1:-}" ;;
--name=*) C0MPUTE_NAME="${1#--name=}" ;;
--no-house-models) INFERNET_NO_HOUSE_MODELS=1 ;;
+ --uncensored-models) INFERNET_UNCENSORED_MODELS=1 ;;
--no-uncensored-models) INFERNET_NO_UNCENSORED_MODELS=1 ;;
+ --system-service) SYSTEM_SERVICE=1 ;;
--help|-h)
- sed -n '2,33p' "$0"
+ sed -n '2,39p' "$0"
exit 0
;;
*)
@@ -331,13 +339,17 @@ house_models_notice() {
fi
if [ "${INFERNET_NO_UNCENSORED_MODELS:-}" = "1" ]; then
infernet house-models uncensored off >/dev/null 2>&1 || true
+ elif [ "${INFERNET_UNCENSORED_MODELS:-}" = "1" ]; then
+ infernet house-models uncensored on >/dev/null 2>&1 || true
fi
say "house models: ON — this node pulls and serves the models listed at"
say " https://infernetprotocol.com/house-models.json that fit it (RAM; keeps 10% of disk free)."
- if [ "${INFERNET_NO_UNCENSORED_MODELS:-}" = "1" ]; then
- say " uncensored house models: off (--no-uncensored-models)"
+ if [ "${INFERNET_UNCENSORED_MODELS:-}" = "1" ] && [ "${INFERNET_NO_UNCENSORED_MODELS:-}" != "1" ]; then
+ say " uncensored house models: ON (you opted in with --uncensored-models)"
+ else
+ say " uncensored house models: off (opt in: infernet house-models uncensored on)"
fi
- say " opt out any time: infernet house-models off (uncensored only: infernet house-models uncensored off)"
+ say " opt out any time: infernet house-models off"
}
# transcode is in-process (no separate binary), but it depends on
@@ -701,20 +713,14 @@ EOF
}
# ────────────────────────────────────────────────────────────────────────
-# Auto-start the worker so a node self-onboards on install/upgrade. Prefers a
-# systemd system service (survives reboots, restarts on crash); falls back to a
-# background daemon. Idempotent: restarts onto the freshly installed binary.
+# Auto-start the worker so a node self-onboards on install/upgrade. Default:
+# a systemd *user* service under your own account (no root, nothing written
+# outside $HOME), with lingering so it starts at boot. A root-managed system
+# service only with --system-service, or when a previous install made one.
+# Falls back to a background daemon. Idempotent: restarts onto the new binary.
# ────────────────────────────────────────────────────────────────────────
-start_worker() {
- [ "$NO_START" -eq 1 ] && return 0
- bin="$C0MPUTE_HOME/bin/c0mpute"
- [ -x "$bin" ] || return 0
- say "starting the c0mpute worker (auto-registers + serves its models)"
-
- if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ] \
- && { [ -n "$SUDO" ] || [ "$(id -u)" = "0" ]; }; then
- tmp=$(mktemp)
- cat > "$tmp" < [User= line]
+ cat </dev/null; then
- rm -f "$tmp"
- $SUDO systemctl daemon-reload 2>/dev/null
- $SUDO systemctl enable c0mpute-worker >/dev/null 2>&1
- if $SUDO systemctl restart c0mpute-worker 2>/dev/null; then
- ok "worker running as a systemd service (auto-starts on boot, restarts on crash)"
- return 0
- fi
- fi
+}
+
+start_system_service() {
+ { [ -n "$SUDO" ] || [ "$(id -u)" = "0" ]; } || return 1
+ tmp=$(mktemp)
+ worker_unit multi-user.target "User=$(id -un)" > "$tmp"
+ if $SUDO cp "$tmp" /etc/systemd/system/c0mpute-worker.service 2>/dev/null; then
rm -f "$tmp"
- warn "systemd setup failed; falling back to a background daemon"
+ $SUDO systemctl daemon-reload 2>/dev/null
+ $SUDO systemctl enable c0mpute-worker >/dev/null 2>&1
+ if $SUDO systemctl restart c0mpute-worker 2>/dev/null; then
+ ok "worker running as a systemd system service as $(id -un) (auto-starts on boot, restarts on crash)"
+ return 0
+ fi
+ fi
+ rm -f "$tmp"
+ return 1
+}
+
+start_user_service() {
+ [ "$(id -u)" = "0" ] && return 1
+ systemctl --user show-environment >/dev/null 2>&1 || return 1
+ unit_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
+ mkdir -p "$unit_dir" || return 1
+ worker_unit default.target > "$unit_dir/c0mpute-worker.service" || return 1
+ "$C0MPUTE_HOME/bin/c0mpute" worker stop >/dev/null 2>&1 # a daemon from an earlier install
+ systemctl --user daemon-reload 2>/dev/null
+ systemctl --user enable c0mpute-worker >/dev/null 2>&1
+ systemctl --user restart c0mpute-worker 2>/dev/null || return 1
+ ok "worker running as a systemd user service as $(id -un) (no root; restarts on crash)"
+ # Lingering starts user services at boot, before anyone logs in. logind
+ # lets a user enable it for themselves without sudo on most distros.
+ if [ "$(loginctl show-user "$(id -un)" -p Linger --value 2>/dev/null)" = "yes" ] \
+ || loginctl enable-linger "$(id -un)" >/dev/null 2>&1; then
+ ok " starts at boot (systemd lingering on for $(id -un); undo: loginctl disable-linger)"
+ else
+ warn " starts when you log in. To start at boot too: sudo loginctl enable-linger $(id -un)"
+ fi
+ return 0
+}
+
+start_worker() {
+ [ "$NO_START" -eq 1 ] && return 0
+ bin="$C0MPUTE_HOME/bin/c0mpute"
+ [ -x "$bin" ] || return 0
+ say "starting the c0mpute worker (auto-registers + serves its models)"
+
+ if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
+ # Keep a system service an earlier install (or --system-service) set up,
+ # so an upgrade never leaves two workers running.
+ if [ -f /etc/systemd/system/c0mpute-worker.service ]; then
+ start_system_service && return 0
+ # Never start a second worker next to the system service.
+ warn "a c0mpute-worker system service from an earlier install exists and needs sudo to restart:"
+ warn " sudo systemctl restart c0mpute-worker"
+ warn " (or switch to a user service: sudo systemctl disable --now c0mpute-worker &&"
+ warn " sudo rm /etc/systemd/system/c0mpute-worker.service, then re-run this installer)"
+ return 0
+ fi
+ if [ "$SYSTEM_SERVICE" = "1" ]; then
+ start_system_service && return 0
+ warn "systemd system service setup failed (needs sudo); using a user service instead"
+ fi
+ start_user_service && return 0
+ if [ "$(id -u)" = "0" ]; then
+ start_system_service && return 0
+ fi
fi
# Fallback: detached daemon. Restart onto the new binary on upgrade.
@@ -804,6 +866,7 @@ main() {
# House-model opt-outs reach infernet's installer through the environment.
export INFERNET_NO_HOUSE_MODELS="${INFERNET_NO_HOUSE_MODELS:-}"
export INFERNET_NO_UNCENSORED_MODELS="${INFERNET_NO_UNCENSORED_MODELS:-}"
+ export INFERNET_UNCENSORED_MODELS="${INFERNET_UNCENSORED_MODELS:-}"
chain_install infernet "$INFERNET_INSTALL_URL" || true
house_models_notice
fi