Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
114170b
system: add Read/Write methods to Endpoint for nil-safe I/O
lthibault Sep 19, 2025
77da08f
feat: add dual-mode support to ProcConfig
lthibault Sep 19, 2025
71d18f2
feat: update echo example for dual-mode support
lthibault Sep 19, 2025
31eae6f
test: update tests for dual-mode implementation
lthibault Sep 19, 2025
a6a400b
docs: add comprehensive documentation for dual-mode design
lthibault Sep 19, 2025
e7b789d
refactor: improve Endpoint I/O handling for dual-mode
lthibault Sep 19, 2025
75aa5d0
refactor: update command-line interface for dual-mode support
lthibault Sep 19, 2025
64ec091
deps: update dependencies for dual-mode implementation
lthibault Sep 19, 2025
169780c
refactor: remove deprecated shell and export examples
lthibault Sep 19, 2025
8c2076a
refactor: remove deprecated system components
lthibault Sep 19, 2025
0b24939
refactor: remove deprecated run components
lthibault Sep 19, 2025
f3f4d00
docs: update main README for dual-mode architecture
lthibault Sep 19, 2025
2e4ddbf
fix: update Endpoint.String() test to expect name only
lthibault Sep 19, 2025
c2c5ad0
feat: remove cell functionality
lthibault Sep 19, 2025
0d601a5
fix: add nil check for Src in ProcConfig.New
lthibault Sep 19, 2025
e09e20a
fix: enable stdin/stdout in sync mode and add async flag
lthibault Sep 19, 2025
10f330c
refactor: simplify sync mode stdin/stdout handling
lthibault Sep 19, 2025
48fbeab
feat: add IPFS file loading and update tests for Src field
lthibault Sep 19, 2025
eff1f9a
feat: add ww cat command for remote stream connection
lthibault Sep 19, 2025
029360e
feat: implement client-only ww cat command with better logging
lthibault Sep 19, 2025
bacd82d
refactor: simplify cat command to use minimal libp2p host
lthibault Sep 19, 2025
6a034b9
feat: implement ww cat command with libp2p client mode
lthibault Sep 20, 2025
c32b424
util: add DHT and host management utilities
lthibault Sep 23, 2025
4f3de9f
cmd/ww/cat: refactor to use DHT for peer discovery
lthibault Sep 23, 2025
39fa0d3
cmd/ww/run: add DHT support and event monitoring
lthibault Sep 23, 2025
25e1954
cmd/internal/flags: remove mDNS and discovery flags
lthibault Sep 24, 2025
dd3cb50
go.mod: clean up unused dependencies
lthibault Sep 24, 2025
bc2c863
examples/echo: add stdout sync for better output handling
lthibault Sep 24, 2025
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
55 changes: 52 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,64 @@ Go implementation of the Wetware distributed computing system.
go install github.com/wetware/go/cmd/ww
```

## Quick Start

Run WASM binaries directly with `ww run`:

```bash
# Run a local WASM file
ww run ./myapp.wasm

# Run from IPFS
ww run /ipfs/QmHash/myapp.wasm

# Run from $PATH
ww run myapp

# Run with debug info
ww run --wasm-debug ./myapp.wasm
```

## Commands

- `ww run <binary>` - Execute binaries in isolated cells with IPFS support
- `ww run <binary>` - Execute WASM binaries with libp2p networking
- `ww shell` - Interactive LISP shell with IPFS and P2P capabilities
- `ww export <path>` - Add files/directories to IPFS
- `ww import <ipfs-path>` - Download content from IPFS
- `ww idgen` - Generate Ed25519 private keys

## Architecture

Wetware provides capability-based security through isolated execution environments (cells) with controlled access to IPFS and other distributed services. Each cell runs in a jailed process with file descriptor-based capability passing.
Wetware provides capability-based security through WASM-based execution environments with controlled access to IPFS and other distributed services. Each WASM module runs with its `poll()` export served on libp2p streams at `/ww/0.1.0/{proc-id}`.

### WASM Process Model

- **Binary Resolution**: Supports local files, $PATH binaries, and IPFS paths
- **WASM Runtime**: Uses wazero for secure WASM execution
- **libp2p Integration**: Serves WASM `poll()` export on network streams
- **IPFS Support**: Direct access to IPFS for distributed content

## Examples

### Hello World WASM

Build and run a simple WASM module:

```bash
# Install tinygo
go install tinygo.org/x/tinygo@latest

# Build example
cd examples/hello
tinygo build -target wasi -o hello.wasm main.go

# Run with ww
ww run hello.wasm
```

See [examples/hello/README.md](examples/hello/README.md) for more details.

## Documentation

See [spec/cell.md](spec/cell.md) for the cell API specification.
- [Cell API Specification](spec/cell.md) - Complete API reference
- [Shell Documentation](cmd/ww/shell/README.md) - Interactive shell guide
22 changes: 0 additions & 22 deletions cmd/internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ func CapabilityFlags() []cli.Flag {
Usage: "grant process execution capability",
EnvVars: []string{"WW_WITH_EXEC"},
},
&cli.BoolFlag{
Name: "with-mdns",
Category: "CAPABILITIES",
Usage: "grant mDNS peer discovery capability",
EnvVars: []string{"WW_WITH_MDNS"},
},
&cli.BoolFlag{
Name: "with-p2p",
Category: "CAPABILITIES",
Expand All @@ -58,22 +52,6 @@ func P2PFlags() []cli.Flag {
Usage: "connect to cluster through specified peers",
EnvVars: []string{"WW_JOIN"},
},
&cli.StringFlag{
Name: "discover",
Category: "P2P",
Aliases: []string{"d"},
Usage: "automatic peer discovery settings",
Value: "/mdns",
EnvVars: []string{"WW_DISCOVER"},
},
&cli.StringFlag{
Name: "namespace",
Category: "P2P",
Aliases: []string{"ns"},
Usage: "cluster namespace (must match dial host)",
Value: "ww",
EnvVars: []string{"WW_NAMESPACE"},
},
&cli.BoolFlag{
Name: "dial",
Category: "P2P",
Expand Down
29 changes: 0 additions & 29 deletions cmd/ww/args/args.go

This file was deleted.

184 changes: 184 additions & 0 deletions cmd/ww/cat/cat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package cat

import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"os"
"syscall"

"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/urfave/cli/v2"
"github.com/wetware/go/cmd/internal/flags"
"github.com/wetware/go/util"
)

var env util.IPFSEnv

func Command() *cli.Command {
return &cli.Command{
Name: "cat",
ArgsUsage: "<peer> <proc>",
Usage: "Connect to a peer and execute a procedure over a stream",
Description: `Connect to a specified peer and execute a procedure over a custom protocol stream.
The command will:
1. Initialize IPFS environment for stream forwarding
2. Use IPFS to establish connection to the specified peer
3. Forward the stream using the /ww/0.1.0/<proc> protocol
4. Bind the stream to stdin/stdout for communication

Examples:
ww cat QmPeer123 /echo
ww cat 12D3KooW... /myproc`,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "ipfs",
EnvVars: []string{"WW_IPFS"},
Value: "/ip4/127.0.0.1/tcp/5001/http",
Usage: "IPFS API endpoint",
},
}, append(flags.CapabilityFlags(), flags.P2PFlags()...)...),

Before: func(c *cli.Context) error {
return env.Boot(c.String("ipfs"))
},
After: func(c *cli.Context) error {
return env.Close()
},

Action: Main,
}
}

func Main(c *cli.Context) error {
ctx, cancel := context.WithTimeout(c.Context, c.Duration("timeout"))
defer cancel()

if c.NArg() != 2 {
return cli.Exit("cat requires exactly two arguments: <peer> <proc>", 1)
}

peerIDStr := c.Args().Get(0)
procName := c.Args().Get(1)

// Parse peer ID
peerID, err := peer.Decode(peerIDStr)
if err != nil {
return fmt.Errorf("invalid peer ID %s: %w", peerIDStr, err)
}

// Construct protocol ID
protocolID := protocol.ID("/ww/0.1.0/" + procName)

// Create libp2p host in client mode
h, err := util.NewClient()
if err != nil {
return fmt.Errorf("failed to create host: %w", err)
}
defer h.Close()

dht, err := env.NewDHT(ctx, h)
if err != nil {
return fmt.Errorf("failed to create DHT client: %w", err)
}
defer dht.Close()

// Set up DHT readiness monitoring BEFORE bootstrapping
slog.DebugContext(ctx, "setting up DHT readiness monitoring")
readyChan := make(chan error, 1)
go func() {
readyChan <- util.WaitForDHTReady(ctx, dht)
}()

// Bootstrap the DHT to populate routing table with IPFS peers
slog.DebugContext(ctx, "bootstrapping DHT")
if err := dht.Bootstrap(ctx); err != nil {
return fmt.Errorf("failed to bootstrap DHT: %w", err)
}

// Wait for DHT to be ready
slog.DebugContext(ctx, "waiting for DHT routing table to populate")
if err := <-readyChan; err != nil {
slog.WarnContext(ctx, "DHT may not be fully ready", "error", err)
}

// Use DHT for peer discovery
slog.DebugContext(ctx, "searching for peer via DHT", "peer", peerID.String()[:12])

// Try to find the peer using DHT
peerInfo, err := dht.FindPeer(ctx, peerID)
if err != nil {
return fmt.Errorf("failed to find peer %s via DHT: %w", peerID, err)
}

slog.DebugContext(ctx, "target peer found via DHT", "peer", peerInfo.ID.String()[:12])
if err := h.Connect(ctx, peerInfo); err != nil {
return fmt.Errorf("failed to connect to peer %s: %w", peerInfo.ID, err)
}

// Open stream to peer
stream, err := h.NewStream(ctx, peerID, protocolID)
if err != nil {
return fmt.Errorf("failed to open stream to peer %s: %w", peerID, err)
}
defer stream.Close()

// Display connection banner
fmt.Printf("⚗️ Wetware Stream Connected\n")
fmt.Printf(" Peer: %s...\n", peerID.String()[:12])
fmt.Printf(" Endpoint: %s\n", protocolID)
fmt.Printf(" Ctrl+C to exit\n\n")

// Bind stream to stdin/stdout
return bindStreamToStdio(ctx, stream)
}

func bindStreamToStdio(ctx context.Context, stream network.Stream) error {
// Copy data between stream and stdin/stdout
readDone := make(chan error, 1)
writeDone := make(chan error, 1)

// Copy from stream to stdout
go func() {
_, err := io.Copy(os.Stdout, stream)
readDone <- err
}()

// Copy from stdin to stream
go func() {
_, err := io.Copy(stream, os.Stdin)
writeDone <- err
}()

// Wait for stdin to close (Ctrl+D)
err := <-writeDone
if err != nil && err != io.EOF {
// Check if it's a broken pipe error (expected when stdin closes)
if !errors.Is(err, syscall.EPIPE) {
return err
}
}

// Close the write end to signal EOF to remote peer
// This will trigger the echo server to respond and then close
stream.CloseWrite()

// Wait for the remote peer to finish processing and send response
// The echo server should now process the input and send back the echoed text
select {
case <-ctx.Done():
return ctx.Err()
case err := <-readDone:
// Check if this is a graceful closure (EOF) or an error
if err == nil || errors.Is(err, io.EOF) {
// Graceful closure - this is expected
return nil
}
// Any other error should be reported
return err
}
}
10 changes: 3 additions & 7 deletions cmd/ww/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import (
"github.com/lmittmann/tint"
"github.com/urfave/cli/v2"

"github.com/wetware/go/cmd/ww/args"
"github.com/wetware/go/cmd/ww/cat"
"github.com/wetware/go/cmd/ww/export"
"github.com/wetware/go/cmd/ww/idgen"
importcmd "github.com/wetware/go/cmd/ww/import"
"github.com/wetware/go/cmd/ww/run"
"github.com/wetware/go/cmd/ww/shell"
)

func main() {
Expand Down Expand Up @@ -47,18 +46,15 @@ func main() {
},
},
Commands: []*cli.Command{
cat.Command(),
idgen.Command(),
run.Command(),
shell.Command(),
export.Command(),
importcmd.Command(),
},
}

hostArgs, guestArgs := args.SplitArgs(os.Args)
ctx = context.WithValue(ctx, args.GuestArgs, guestArgs)

err := app.RunContext(ctx, hostArgs)
err := app.RunContext(ctx, os.Args)
if err != nil {
slog.ErrorContext(ctx, err.Error())
os.Exit(1)
Expand Down
Loading
Loading