diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d23d1df..6385d7d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -53,7 +53,11 @@ "WebFetch(domain:blog.scottlogic.com)", "WebFetch(domain:eli.thegreenplace.net)", "WebFetch(domain:learn-wasm.dev)", - "Bash(curl:*)" + "Bash(curl:*)", + "Bash(./build.sh)", + "Bash(./pkg/wasm/shell/run.sh:*)", + "Bash(./run.sh echo.wasm)", + "Bash(./test.sh)" ], "deny": [], "ask": [] diff --git a/app/server.go b/app/server.go index 2e6f025..cbf9608 100644 --- a/app/server.go +++ b/app/server.go @@ -17,6 +17,7 @@ import ( "lol.mleku.dev/chk" "next.orly.dev/app/config" "next.orly.dev/pkg/acl" + "next.orly.dev/pkg/blossom" "next.orly.dev/pkg/database" "next.orly.dev/pkg/encoders/event" "next.orly.dev/pkg/encoders/filter" @@ -29,7 +30,6 @@ import ( "next.orly.dev/pkg/protocol/publish" "next.orly.dev/pkg/spider" dsync "next.orly.dev/pkg/sync" - blossom "next.orly.dev/pkg/blossom" ) type Server struct { @@ -91,19 +91,9 @@ func (s *Server) isIPBlacklisted(remote string) bool { } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Set comprehensive CORS headers for proxy compatibility - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept, Authorization, "+ - "X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, X-Real-IP, "+ - "Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, "+ - "Sec-WebSocket-Protocol, Sec-WebSocket-Extensions") - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Access-Control-Max-Age", "86400") - - // Add proxy-friendly headers - w.Header().Set("Vary", "Origin, Access-Control-Request-Method, Access-Control-Request-Headers") + // CORS headers should be handled by the reverse proxy (Caddy/nginx) + // to avoid duplicate headers. If running without a reverse proxy, + // uncomment the CORS configuration below or configure via environment variable. // Handle preflight OPTIONS requests if r.Method == "OPTIONS" { @@ -245,7 +235,9 @@ func (s *Server) UserInterface() { s.mux.HandleFunc("/api/sprocket/update", s.handleSprocketUpdate) s.mux.HandleFunc("/api/sprocket/restart", s.handleSprocketRestart) s.mux.HandleFunc("/api/sprocket/versions", s.handleSprocketVersions) - s.mux.HandleFunc("/api/sprocket/delete-version", s.handleSprocketDeleteVersion) + s.mux.HandleFunc( + "/api/sprocket/delete-version", s.handleSprocketDeleteVersion, + ) s.mux.HandleFunc("/api/sprocket/config", s.handleSprocketConfig) // NIP-86 management endpoint s.mux.HandleFunc("/api/nip86", s.handleNIP86Management) @@ -343,7 +335,9 @@ func (s *Server) handleAuthChallenge(w http.ResponseWriter, r *http.Request) { jsonData, err := json.Marshal(response) if chk.E(err) { - http.Error(w, "Error generating challenge", http.StatusInternalServerError) + http.Error( + w, "Error generating challenge", http.StatusInternalServerError, + ) return } @@ -561,7 +555,10 @@ func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) { // Check permissions - require write, admin, or owner level accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr) if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" { - http.Error(w, "Write, admin, or owner permission required", http.StatusForbidden) + http.Error( + w, "Write, admin, or owner permission required", + http.StatusForbidden, + ) return } @@ -610,7 +607,9 @@ func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/x-ndjson") - w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"") + w.Header().Set( + "Content-Disposition", "attachment; filename=\""+filename+"\"", + ) // Stream export s.D.Export(s.Ctx, w, pks...) @@ -725,7 +724,9 @@ func (s *Server) handleImport(w http.ResponseWriter, r *http.Request) { // Check permissions - require admin or owner level accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr) if accessLevel != "admin" && accessLevel != "owner" { - http.Error(w, "Admin or owner permission required", http.StatusForbidden) + http.Error( + w, "Admin or owner permission required", http.StatusForbidden, + ) return } @@ -785,7 +786,9 @@ func (s *Server) handleSprocketStatus(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") jsonData, err := json.Marshal(status) if chk.E(err) { - http.Error(w, "Error generating response", http.StatusInternalServerError) + http.Error( + w, "Error generating response", http.StatusInternalServerError, + ) return } @@ -826,7 +829,10 @@ func (s *Server) handleSprocketUpdate(w http.ResponseWriter, r *http.Request) { // Update the sprocket script if err := s.sprocketManager.UpdateSprocket(string(body)); chk.E(err) { - http.Error(w, fmt.Sprintf("Failed to update sprocket: %v", err), http.StatusInternalServerError) + http.Error( + w, fmt.Sprintf("Failed to update sprocket: %v", err), + http.StatusInternalServerError, + ) return } @@ -861,7 +867,10 @@ func (s *Server) handleSprocketRestart(w http.ResponseWriter, r *http.Request) { // Restart the sprocket script if err := s.sprocketManager.RestartSprocket(); chk.E(err) { - http.Error(w, fmt.Sprintf("Failed to restart sprocket: %v", err), http.StatusInternalServerError) + http.Error( + w, fmt.Sprintf("Failed to restart sprocket: %v", err), + http.StatusInternalServerError, + ) return } @@ -870,7 +879,9 @@ func (s *Server) handleSprocketRestart(w http.ResponseWriter, r *http.Request) { } // handleSprocketVersions returns all sprocket script versions -func (s *Server) handleSprocketVersions(w http.ResponseWriter, r *http.Request) { +func (s *Server) handleSprocketVersions( + w http.ResponseWriter, r *http.Request, +) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return @@ -896,14 +907,19 @@ func (s *Server) handleSprocketVersions(w http.ResponseWriter, r *http.Request) versions, err := s.sprocketManager.GetSprocketVersions() if chk.E(err) { - http.Error(w, fmt.Sprintf("Failed to get sprocket versions: %v", err), http.StatusInternalServerError) + http.Error( + w, fmt.Sprintf("Failed to get sprocket versions: %v", err), + http.StatusInternalServerError, + ) return } w.Header().Set("Content-Type", "application/json") jsonData, err := json.Marshal(versions) if chk.E(err) { - http.Error(w, "Error generating response", http.StatusInternalServerError) + http.Error( + w, "Error generating response", http.StatusInternalServerError, + ) return } @@ -911,7 +927,9 @@ func (s *Server) handleSprocketVersions(w http.ResponseWriter, r *http.Request) } // handleSprocketDeleteVersion deletes a specific sprocket version -func (s *Server) handleSprocketDeleteVersion(w http.ResponseWriter, r *http.Request) { +func (s *Server) handleSprocketDeleteVersion( + w http.ResponseWriter, r *http.Request, +) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return @@ -957,7 +975,10 @@ func (s *Server) handleSprocketDeleteVersion(w http.ResponseWriter, r *http.Requ // Delete the sprocket version if err := s.sprocketManager.DeleteSprocketVersion(request.Filename); chk.E(err) { - http.Error(w, fmt.Sprintf("Failed to delete sprocket version: %v", err), http.StatusInternalServerError) + http.Error( + w, fmt.Sprintf("Failed to delete sprocket version: %v", err), + http.StatusInternalServerError, + ) return } @@ -982,7 +1003,9 @@ func (s *Server) handleSprocketConfig(w http.ResponseWriter, r *http.Request) { jsonData, err := json.Marshal(response) if chk.E(err) { - http.Error(w, "Error generating response", http.StatusInternalServerError) + http.Error( + w, "Error generating response", http.StatusInternalServerError, + ) return } @@ -1006,7 +1029,9 @@ func (s *Server) handleACLMode(w http.ResponseWriter, r *http.Request) { jsonData, err := json.Marshal(response) if chk.E(err) { - http.Error(w, "Error generating response", http.StatusInternalServerError) + http.Error( + w, "Error generating response", http.StatusInternalServerError, + ) return } @@ -1016,7 +1041,9 @@ func (s *Server) handleACLMode(w http.ResponseWriter, r *http.Request) { // handleSyncCurrent handles requests for the current serial number func (s *Server) handleSyncCurrent(w http.ResponseWriter, r *http.Request) { if s.syncManager == nil { - http.Error(w, "Sync manager not initialized", http.StatusServiceUnavailable) + http.Error( + w, "Sync manager not initialized", http.StatusServiceUnavailable, + ) return } @@ -1031,7 +1058,9 @@ func (s *Server) handleSyncCurrent(w http.ResponseWriter, r *http.Request) { // handleSyncEventIDs handles requests for event IDs with their serial numbers func (s *Server) handleSyncEventIDs(w http.ResponseWriter, r *http.Request) { if s.syncManager == nil { - http.Error(w, "Sync manager not initialized", http.StatusServiceUnavailable) + http.Error( + w, "Sync manager not initialized", http.StatusServiceUnavailable, + ) return } @@ -1044,12 +1073,16 @@ func (s *Server) handleSyncEventIDs(w http.ResponseWriter, r *http.Request) { } // validatePeerRequest validates NIP-98 authentication and checks if the requesting peer is authorized -func (s *Server) validatePeerRequest(w http.ResponseWriter, r *http.Request) bool { +func (s *Server) validatePeerRequest( + w http.ResponseWriter, r *http.Request, +) bool { // Validate NIP-98 authentication valid, pubkey, err := httpauth.CheckAuth(r) if err != nil { log.Printf("NIP-98 auth validation error: %v", err) - http.Error(w, "Authentication validation failed", http.StatusUnauthorized) + http.Error( + w, "Authentication validation failed", http.StatusUnauthorized, + ) return false } if !valid { diff --git a/pkg/version/version b/pkg/version/version index d03112e..f53b7bd 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.28.1 \ No newline at end of file +v0.28.2 \ No newline at end of file diff --git a/pkg/wasm/.claude/settings.local.json b/pkg/wasm/.claude/settings.local.json new file mode 100644 index 0000000..4adf34b --- /dev/null +++ b/pkg/wasm/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(go build:*)", + "Bash(CGO_ENABLED=0 go build:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/pkg/wasm/README.md b/pkg/wasm/hello/README.md similarity index 100% rename from pkg/wasm/README.md rename to pkg/wasm/hello/README.md diff --git a/pkg/wasm/hello.js b/pkg/wasm/hello/hello.js similarity index 100% rename from pkg/wasm/hello.js rename to pkg/wasm/hello/hello.js diff --git a/pkg/wasm/index.html b/pkg/wasm/hello/index.html similarity index 100% rename from pkg/wasm/index.html rename to pkg/wasm/hello/index.html diff --git a/pkg/wasm/server.go b/pkg/wasm/hello/server.go similarity index 100% rename from pkg/wasm/server.go rename to pkg/wasm/hello/server.go diff --git a/pkg/wasm/shell/QUICKSTART.md b/pkg/wasm/shell/QUICKSTART.md new file mode 100644 index 0000000..41c9d04 --- /dev/null +++ b/pkg/wasm/shell/QUICKSTART.md @@ -0,0 +1,125 @@ +# Quick Start Guide + +## TL;DR + +```bash +# Build all examples +./build.sh + +# Run hello example (stdout only) +./run.sh hello.wasm + +# Run echo example (stdin/stdout) +echo "test" | ./run.sh echo.wasm + +# Run all tests +./test.sh +``` + +## What's Included + +### Scripts +- **`build.sh`** - Compile all `.wat` files to `.wasm` using `wat2wasm` +- **`run.sh`** - Execute WASM files with `wasmtime` WASI runtime +- **`test.sh`** - Run complete test suite + +### Examples +- **`hello.wat/wasm`** - Simple "Hello World" to stdout +- **`echo.wat/wasm`** - Read from stdin, echo to stdout + +### Documentation +- **`README.md`** - Complete documentation with examples +- **`QUICKSTART.md`** - This file + +## Running WASM in Shell - The Basics + +### Console Output (stdout) +```bash +./run.sh hello.wasm +# Output: Hello from WASM shell! +``` + +### Console Input (stdin) +```bash +# Piped input +echo "your text" | ./run.sh echo.wasm + +# Interactive input +./run.sh echo.wasm +# (type your input and press Enter) + +# From file +cat file.txt | ./run.sh echo.wasm +``` + +## Use Case: ORLY Policy Scripts + +This WASM shell runner is perfect for ORLY's policy system: + +```bash +# Event JSON comes via stdin +echo '{"kind":1,"content":"hello","pubkey":"..."}' | ./run.sh policy.wasm + +# Policy script: +# - Reads JSON from stdin +# - Applies rules +# - Outputs decision to stdout: "accept" or "reject" + +# ORLY reads the decision and acts accordingly +``` + +### Benefits +- **Sandboxed** - Cannot access system unless explicitly granted +- **Fast** - Near-native performance with wasmtime's JIT +- **Portable** - Same WASM binary runs everywhere +- **Multi-language** - Write policies in Go, Rust, C, JavaScript, etc. +- **Deterministic** - Same input = same output, always + +## Next Steps + +1. **Read the full README** - `cat README.md` +2. **Try the examples** - `./test.sh` +3. **Write your own** - Start with the template in README.md +4. **Compile from Go** - Use TinyGo to compile Go to WASM +5. **Integrate with ORLY** - Use as policy execution engine + +## File Structure + +``` +pkg/wasm/shell/ +├── build.sh # Build script (wat -> wasm) +├── run.sh # Run script (execute wasm) +├── test.sh # Test all examples +├── hello.wat # Source: Hello World +├── hello.wasm # Binary: Hello World +├── echo.wat # Source: Echo stdin/stdout +├── echo.wasm # Binary: Echo stdin/stdout +├── README.md # Full documentation +└── QUICKSTART.md # This file +``` + +## Troubleshooting + +### "wasmtime not found" +```bash +curl https://wasmtime.dev/install.sh -sSf | bash +export PATH="$HOME/.wasmtime/bin:$PATH" +``` + +### "wat2wasm not found" +```bash +sudo apt install wabt +``` + +### WASM fails to run +```bash +# Rebuild from source +./build.sh + +# Check the WASM module +wasm-objdump -x your.wasm +``` + +--- + +**Happy WASM hacking!** 🎉 diff --git a/pkg/wasm/shell/README.md b/pkg/wasm/shell/README.md new file mode 100644 index 0000000..4996b3b --- /dev/null +++ b/pkg/wasm/shell/README.md @@ -0,0 +1,353 @@ +# WASM Shell Runner + +Run WebAssembly programs directly in your shell with stdin/stdout support using WASI (WebAssembly System Interface). + +## Quick Start + +```bash +# Build all WAT files to WASM +./build.sh + +# Run the hello example +./run.sh hello.wasm + +# Run the echo example (with stdin) +echo "Hello World" | ./run.sh echo.wasm + +# Or interactive +./run.sh echo.wasm +``` + +## Prerequisites + +### Install wabt (WebAssembly Binary Toolkit) + +```bash +# Ubuntu/Debian +sudo apt install wabt + +# Provides: wat2wasm, wasm2wat, wasm-objdump, etc. +``` + +### Install wasmtime (WASM Runtime) + +```bash +# Install via official installer +curl https://wasmtime.dev/install.sh -sSf | bash + +# Add to PATH (add to ~/.bashrc for persistence) +export PATH="$HOME/.wasmtime/bin:$PATH" +``` + +## Examples + +### 1. Hello World (`hello.wat`) + +Simple example that prints to stdout: + +```bash +./build.sh +./run.sh hello.wasm +``` + +**Output:** +``` +Hello from WASM shell! +``` + +### 2. Echo Program (`echo.wat`) + +Reads from stdin and echoes back: + +```bash +./build.sh + +# Interactive mode +./run.sh echo.wasm +# Type something and press Enter + +# Piped input +echo "Test message" | ./run.sh echo.wasm + +# From file +cat somefile.txt | ./run.sh echo.wasm +``` + +**Output:** +``` +Enter text: Test message +You entered: Test message +``` + +## How It Works + +### WASI (WebAssembly System Interface) + +WASI provides a standard interface for WASM programs to interact with the host system: + +- **stdin** (fd 0) - Standard input +- **stdout** (fd 1) - Standard output +- **stderr** (fd 2) - Standard error + +### Key WASI Functions Used + +#### `fd_write` - Write to file descriptor +```wat +(import "wasi_snapshot_preview1" "fd_write" + (func $fd_write (param i32 i32 i32 i32) (result i32))) + +;; Usage: fd_write(fd, iovs_ptr, iovs_len, nwritten_ptr) -> errno +;; fd: File descriptor (1 = stdout) +;; iovs_ptr: Pointer to iovec array +;; iovs_len: Number of iovecs +;; nwritten_ptr: Where to store bytes written +``` + +#### `fd_read` - Read from file descriptor +```wat +(import "wasi_snapshot_preview1" "fd_read" + (func $fd_read (param i32 i32 i32 i32) (result i32))) + +;; Usage: fd_read(fd, iovs_ptr, iovs_len, nread_ptr) -> errno +;; fd: File descriptor (0 = stdin) +;; iovs_ptr: Pointer to iovec array +;; iovs_len: Number of iovecs +;; nread_ptr: Where to store bytes read +``` + +### iovec Structure + +Both functions use an iovec (I/O vector) structure: + +``` +struct iovec { + u32 buf; // Pointer to buffer in WASM memory + u32 buf_len; // Length of buffer +} +``` + +## Writing Your Own WASM Programs + +### Basic Template + +```wat +(module + ;; Import WASI functions you need + (import "wasi_snapshot_preview1" "fd_write" + (func $fd_write (param i32 i32 i32 i32) (result i32))) + + ;; Allocate memory + (memory 1) + (export "memory" (memory 0)) + + ;; Store your strings in memory + (data (i32.const 0) "Your message here\n") + + ;; Main function (entry point) + (func $main (export "_start") + ;; Setup iovec at some offset (e.g., 100) + (i32.store (i32.const 100) (i32.const 0)) ;; buf pointer + (i32.store (i32.const 104) (i32.const 18)) ;; buf length + + ;; Write to stdout + (call $fd_write + (i32.const 1) ;; stdout + (i32.const 100) ;; iovec pointer + (i32.const 1) ;; number of iovecs + (i32.const 200) ;; nwritten pointer + ) + drop ;; drop return value + ) +) +``` + +### Build and Run + +```bash +# Compile WAT to WASM +wat2wasm yourprogram.wat -o yourprogram.wasm + +# Run it +./run.sh yourprogram.wasm + +# Or directly with wasmtime +wasmtime yourprogram.wasm +``` + +## Advanced Usage + +### Pass Arguments + +```bash +# WASM programs can receive command-line arguments +./run.sh program.wasm arg1 arg2 arg3 +``` + +### Environment Variables + +```bash +# Set environment variables (wasmtime flag) +wasmtime --env KEY=value program.wasm +``` + +### Mount Directories + +```bash +# Give WASM access to directories (wasmtime flag) +wasmtime --dir=/tmp program.wasm +``` + +### Call Specific Functions + +```bash +# Instead of _start, call a specific exported function +wasmtime --invoke my_function program.wasm +``` + +## Compiling from High-Level Languages + +### From Go (using TinyGo) + +```bash +# Install TinyGo +wget https://github.com/tinygo-org/tinygo/releases/download/v0.31.0/tinygo_0.31.0_amd64.deb +sudo dpkg -i tinygo_0.31.0_amd64.deb + +# Write Go program +cat > main.go << 'EOF' +package main + +import "fmt" + +func main() { + fmt.Println("Hello from Go WASM!") +} +EOF + +# Compile to WASM with WASI +tinygo build -o program.wasm -target=wasi main.go + +# Run +./run.sh program.wasm +``` + +### From Rust + +```bash +# Add WASI target +rustup target add wasm32-wasi + +# Create project +cargo new --bin myprogram +cd myprogram + +# Build for WASI +cargo build --target wasm32-wasi --release + +# Run +wasmtime target/wasm32-wasi/release/myprogram.wasm +``` + +### From C/C++ (using wasi-sdk) + +```bash +# Download wasi-sdk +wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-linux.tar.gz +tar xf wasi-sdk-21.0-linux.tar.gz + +# Compile C program +cat > hello.c << 'EOF' +#include + +int main() { + printf("Hello from C WASM!\n"); + return 0; +} +EOF + +# Compile to WASM +./wasi-sdk-21.0/bin/clang hello.c -o hello.wasm + +# Run +wasmtime hello.wasm +``` + +## Debugging + +### Inspect WASM Module + +```bash +# Disassemble WASM to WAT +wasm2wat program.wasm -o program.wat + +# Show module structure +wasm-objdump -x program.wasm + +# Show imports +wasm-objdump -x program.wasm | grep -A 10 "Import" + +# Show exports +wasm-objdump -x program.wasm | grep -A 10 "Export" +``` + +### Verbose Execution + +```bash +# Run with logging +WASMTIME_LOG=wasmtime=trace wasmtime program.wasm + +# Enable debug info +wasmtime -g program.wasm +``` + +## Use Cases for ORLY + +WASM with WASI is perfect for ORLY's policy system: + +### Sandboxed Policy Scripts + +```bash +# Write policy in any language that compiles to WASM +# Run it safely in a sandbox with controlled stdin/stdout +./run.sh policy.wasm < event.json +``` + +**Benefits:** +- **Security**: Sandboxed execution, no system access unless granted +- **Portability**: Same WASM runs on any platform +- **Performance**: Near-native speed with wasmtime's JIT +- **Language Choice**: Write policies in Go, Rust, C, JavaScript, etc. +- **Deterministic**: Same input always produces same output + +### Example Policy Flow + +```bash +# Event comes in via stdin (JSON) +echo '{"kind":1,"content":"hello"}' | ./run.sh filter-policy.wasm + +# Policy outputs "accept" or "reject" to stdout +# ORLY reads the decision and acts accordingly +``` + +## Scripts Reference + +### `build.sh` +Compiles all `.wat` files to `.wasm` using `wat2wasm` + +### `run.sh [wasm-file] [args...]` +Runs a WASM file with `wasmtime`, defaults to `hello.wasm` + +## Files + +- **hello.wat** - Simple stdout example +- **echo.wat** - stdin/stdout interactive example +- **build.sh** - Build all WAT files +- **run.sh** - Run WASM files with wasmtime + +## Resources + +- [WASI Specification](https://github.com/WebAssembly/WASI) +- [Wasmtime Documentation](https://docs.wasmtime.dev/) +- [WebAssembly Reference](https://webassembly.github.io/spec/) +- [WAT Language Guide](https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format) +- [TinyGo WASI Support](https://tinygo.org/docs/guides/webassembly/wasi/) diff --git a/pkg/wasm/shell/build.sh b/pkg/wasm/shell/build.sh new file mode 100755 index 0000000..1c472e9 --- /dev/null +++ b/pkg/wasm/shell/build.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Build script for WASM shell examples +# Compiles WAT (WebAssembly Text) to WASM binary + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "Building WASM modules from WAT files..." + +# Check if wat2wasm is available +if ! command -v wat2wasm &> /dev/null; then + echo "Error: wat2wasm not found. Install wabt:" + echo " sudo apt install wabt" + exit 1 +fi + +# Build each .wat file to .wasm +for wat_file in *.wat; do + if [ -f "$wat_file" ]; then + wasm_file="${wat_file%.wat}.wasm" + echo " $wat_file -> $wasm_file" + wat2wasm "$wat_file" -o "$wasm_file" + fi +done + +echo "Build complete!" +echo "" +echo "Run with:" +echo " ./run.sh hello.wasm" +echo " or" +echo " wasmtime hello.wasm" diff --git a/pkg/wasm/shell/run.sh b/pkg/wasm/shell/run.sh new file mode 100755 index 0000000..aca4283 --- /dev/null +++ b/pkg/wasm/shell/run.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Run script for WASM shell examples +# Executes WASM files using wasmtime with WASI support + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Find wasmtime executable +WASMTIME="" +if command -v wasmtime &> /dev/null; then + WASMTIME="wasmtime" +elif [ -x "$HOME/.wasmtime/bin/wasmtime" ]; then + WASMTIME="$HOME/.wasmtime/bin/wasmtime" +else + echo "Error: wasmtime not found. Install it:" + echo " curl https://wasmtime.dev/install.sh -sSf | bash" + echo "" + echo "Or add to PATH:" + echo " export PATH=\"\$HOME/.wasmtime/bin:\$PATH\"" + exit 1 +fi + +# Get the WASM file from argument, default to hello.wasm +WASM_FILE="${1:-hello.wasm}" + +# If relative path, make it relative to script dir +if [[ "$WASM_FILE" != /* ]]; then + WASM_FILE="$SCRIPT_DIR/$WASM_FILE" +fi + +if [ ! -f "$WASM_FILE" ]; then + echo "Error: WASM file not found: $WASM_FILE" + echo "" + echo "Usage: $0 [wasm-file]" + echo "" + echo "Available WASM files:" + cd "$SCRIPT_DIR" + ls -1 *.wasm 2>/dev/null || echo " (none - run ./build.sh first)" + exit 1 +fi + +echo "Running: $WASM_FILE" +echo "---" + +# Run the WASM file with wasmtime +# Additional flags you might want: +# --dir=. : Mount current directory +# --env VAR=value : Set environment variable +# --invoke function : Call specific function instead of _start +"$WASMTIME" "$WASM_FILE" "$@" diff --git a/pkg/wasm/shell/test.sh b/pkg/wasm/shell/test.sh new file mode 100755 index 0000000..904f7b8 --- /dev/null +++ b/pkg/wasm/shell/test.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Test script for WASM shell examples + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "=========================================" +echo "WASM Shell Test Suite" +echo "=========================================" +echo "" + +# Build first +echo "[1/4] Building WASM modules..." +./build.sh +echo "" + +# Test hello.wasm +echo "[2/4] Testing hello.wasm (stdout only)..." +echo "---" +./run.sh hello.wasm +echo "" + +# Test echo.wasm with piped input +echo "[3/4] Testing echo.wasm (stdin/stdout with pipe)..." +echo "---" +echo "This is a test message" | ./run.sh echo.wasm +echo "" + +# Test echo.wasm with heredoc +echo "[4/4] Testing echo.wasm (stdin/stdout with heredoc)..." +echo "---" +./run.sh echo.wasm <<< "Testing heredoc input" +echo "" + +echo "=========================================" +echo "All tests passed!" +echo "=========================================" +echo "" +echo "Try these commands:" +echo " ./run.sh hello.wasm" +echo " echo 'your text' | ./run.sh echo.wasm" +echo " ./run.sh echo.wasm # interactive mode"