fix CORS headers and a wasm experiment
This commit is contained in:
@@ -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": []
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1 +1 @@
|
||||
v0.28.1
|
||||
v0.28.2
|
||||
10
pkg/wasm/.claude/settings.local.json
Normal file
10
pkg/wasm/.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(go build:*)",
|
||||
"Bash(CGO_ENABLED=0 go build:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
125
pkg/wasm/shell/QUICKSTART.md
Normal file
125
pkg/wasm/shell/QUICKSTART.md
Normal file
@@ -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!** 🎉
|
||||
353
pkg/wasm/shell/README.md
Normal file
353
pkg/wasm/shell/README.md
Normal file
@@ -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 <stdio.h>
|
||||
|
||||
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/)
|
||||
34
pkg/wasm/shell/build.sh
Executable file
34
pkg/wasm/shell/build.sh
Executable file
@@ -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"
|
||||
52
pkg/wasm/shell/run.sh
Executable file
52
pkg/wasm/shell/run.sh
Executable file
@@ -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" "$@"
|
||||
45
pkg/wasm/shell/test.sh
Executable file
45
pkg/wasm/shell/test.sh
Executable file
@@ -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"
|
||||
Reference in New Issue
Block a user