Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ jobs:

- name: Run host workspace tests (hot cache from build job)
run: cargo test --workspace
env:
WW_TEST_REQUIRE_KUBO: "1"

- name: Smoke-test Chess authority proof command
run: |
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]

### Changed
- **Wetware now owns default IPNS signing and can follow an IPNS deployment
Stem.** `ww run --ipns-stem <name>` uses locally verified raw records as the
authoritative deployment source. Signed EOL revokes the current generation
even during a routing outage, while durable raw-record watermarks reject
rollback across restarts. The installed daemon derives its default IPNS name
from `~/.ww/identity`, signs locally, persists before publication, and
republishes through Kubo HTTP Routing V1. Kubo 0.33 operators must enable
`Gateway.ExposeRoutingAPI` and use the Gateway listener, which defaults to
`http://localhost:8080` and can be set with `IPFS_ROUTING_API` or
`--ipns-routing-url`. Wetware no longer creates or requires Kubo's `"ww"`
signing key. Guest `Routing.publish` is unchanged. `rust-ipns` is temporarily
pinned to reviewed commit `02c5ae7bf3f9568c7dbbb1308ae9299cfc7ba2d9`
pending upstream PR #503 or a release that contains its V2-only validation
fix.
- **Default daemon images no longer publish private host state.** Installed
daemons import only `~/.ww/fhs` as their default deployment image. Identity
and namespace configuration remain host-side. Updates rewrite vulnerable
Expand Down
94 changes: 91 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ authority = { package = "wetware-authority", path = "crates/authority" }
atom = { path = "crates/atom" }
async-trait = { workspace = true }
reqwest = { workspace = true, features = ["rustls-tls", "json"] }
# Temporary exact pin for V2-only record verification. Remove after
# https://github.com/dariusc93/rust-ipfs/pull/503 ships in a rust-ipns release.
rust-ipns = { git = "https://github.com/wetware/rust-ipfs", rev = "02c5ae7bf3f9568c7dbbb1308ae9299cfc7ba2d9", default-features = false, features = ["ed25519", "rsa"] }
cache = { package = "ww-cache", path = "crates/cache" }
cell = { path = "crates/cell" }
rpc = { path = "crates/rpc" }
Expand Down
16 changes: 5 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,12 @@ IPNS_KEY ?=

# Best-effort publish: runs as part of `make all`. If Kubo isn't running,
# the build continues without a CID (HostPathLoader fallback).
# Reads IPNS key from ~/.ww/etc/ns/ww if available (provisioned by `ww perform install`).
# Default IPNS publication is host-owned and runs through `ww perform update`
# plus the daemon republisher. This build target only imports the tree.
try-publish-std: std
@KEY=$$(grep '^ipns=' ~/.ww/etc/ns/ww 2>/dev/null | cut -d= -f2 | tr -d ' '); \
if [ -n "$$KEY" ]; then \
$(MAKE) publish-std IPNS_KEY=ww 2>/dev/null \
&& echo " std namespace published to IPFS" \
|| echo " std namespace publish skipped (Kubo not running)"; \
else \
$(MAKE) publish-std 2>/dev/null \
&& echo " std namespace published to IPFS (no IPNS key)" \
|| echo " std namespace publish skipped (Kubo not running)"; \
fi
@$(MAKE) publish-std 2>/dev/null \
&& echo " std namespace published to IPFS (host IPNS publish is daemon-owned)" \
|| echo " std namespace publish skipped (Kubo not running)"

publish-std: std
@echo "Assembling std namespace tree..."
Expand Down
53 changes: 0 additions & 53 deletions crates/ipfs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -692,59 +692,6 @@ impl HttpClient {
.map(|s| s.to_string())
.ok_or_else(|| anyhow::anyhow!("name publish response missing Name field"))
}

/// List IPNS key names on the local Kubo node.
pub async fn key_list(&self) -> anyhow::Result<Vec<String>> {
let url = format!("{}/api/v0/key/list", self.base_url);
let response = self
.http_client
.post(&url)
.send()
.await
.context("IPFS key list request failed")?;
let status = response.status();
let body: serde_json::Value = response
.json()
.await
.context("Failed to parse key list response")?;
if !status.is_success() {
let msg = body["Message"].as_str().unwrap_or("unknown error");
anyhow::bail!("IPFS key list failed ({}): {}", status, msg);
}
let keys = body["Keys"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|k| k["Name"].as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
Ok(keys)
}

/// Generate a new Ed25519 IPNS key. Returns the key's peer ID.
pub async fn key_gen(&self, name: &str) -> anyhow::Result<String> {
let url = format!("{}/api/v0/key/gen?arg={}&type=ed25519", self.base_url, name);
let response = self
.http_client
.post(&url)
.send()
.await
.context("IPFS key gen request failed")?;
let status = response.status();
let body: serde_json::Value = response
.json()
.await
.context("Failed to parse key gen response")?;
if !status.is_success() {
let msg = body["Message"].as_str().unwrap_or("unknown error");
anyhow::bail!("IPFS key gen failed ({}): {}", status, msg);
}
body["Id"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| anyhow::anyhow!("key gen response missing Id field"))
}
}

#[cfg(test)]
Expand Down
3 changes: 3 additions & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ tracing = { workspace = true }
auth = { path = "../guest/auth" }
ipfs = { path = "../ipfs" }
authority = { package = "wetware-authority", path = "../authority" }

[dev-dependencies]
tempfile = { workspace = true }
Loading