implement stripe and idenfy webhooks support
This commit is contained in:
parent
7dfd54a20a
commit
93977bad7a
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
@ -0,0 +1,5 @@
|
||||
[env]
|
||||
# Set the C compiler for the wasm32 target. This ensures that build scripts
|
||||
# which compile C code (like the one in secp256k1-sys) use the correct
|
||||
# version of clang that can target WebAssembly.
|
||||
CC_wasm32-unknown-unknown = "/opt/homebrew/opt/llvm/bin/clang"
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ target
|
||||
dump.rdb
|
||||
worker_rhai_temp_db
|
||||
launch_data
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.env
|
460
ARCHITECTURE.md
Normal file
460
ARCHITECTURE.md
Normal file
@ -0,0 +1,460 @@
|
||||
# Circles: Peer-Centric Application Layer Architecture
|
||||
|
||||
This document provides a comprehensive overview of the Circles project architecture - a **distributed, peer-contextual application layer runtime** where peers can send scripts to each other for execution within isolated contexts.
|
||||
|
||||
## 1. System Overview
|
||||
|
||||
### 1.1. Core Concept
|
||||
|
||||
Circles implements a **peer-centric backend architecture** where each user is treated as an autonomous execution context rather than a passive database entry. The system enables **cross-peer script execution** where:
|
||||
|
||||
- **Peer A** sends a script to **Peer B**
|
||||
- The script executes within **Peer B's context** (filesystem namespace, data isolation)
|
||||
- The script runs with awareness that **Peer A** initiated the execution
|
||||
- **Peer B** controls what scripts it accepts and how they execute
|
||||
|
||||
### 1.2. Key Characteristics
|
||||
|
||||
- **Peer-Centric Execution**: Each peer is an isolated context, not a row in a global database
|
||||
- **Context-Based Routing**: Execution flows are scoped by peer identity (public keys)
|
||||
- **Decentralizable**: Circle nodes can be deployed independently and serve separate peer sets
|
||||
- **Script-Triggered Workflows**: Cross-peer interactions happen via script execution
|
||||
- **Namespace Isolation**: Workers enforce isolation through filesystem namespacing
|
||||
|
||||
## 2. Core Components
|
||||
|
||||
### 2.1. Peer Context
|
||||
|
||||
A **peer context** represents an isolated execution environment identified by a secp256k1 public key:
|
||||
|
||||
- **Identity**: Unique secp256k1 public key
|
||||
- **Filesystem Namespace**: Isolated root directory (`DB_PATH/{peer_public_key}/`)
|
||||
- **Execution Environment**: Rhai script engine with peer-specific variables
|
||||
- **Policy Boundaries**: Controls which peers can execute scripts and what operations are allowed
|
||||
|
||||
### 2.2. Circle (WebSocket Gateway)
|
||||
|
||||
The [`server`](src/server) crate provides the WebSocket server that acts as a gateway for peer interactions:
|
||||
|
||||
- **Peer Authentication**: secp256k1 signature-based authentication via JSON-RPC
|
||||
- **Script Routing**: Routes incoming script execution requests to Redis queues
|
||||
- **Session Management**: Maintains authenticated WebSocket connections per peer
|
||||
- **Multi-Peer Support**: Single server instance can handle multiple peer contexts
|
||||
|
||||
**Key Features:**
|
||||
- Built on Actix Web with WebSocket actors ([`CircleWs`](src/server/src/lib.rs:100))
|
||||
- JSON-RPC 2.0 API for authentication and script execution
|
||||
- Redis integration for decoupled worker communication
|
||||
|
||||
### 2.3. Worker Engine
|
||||
|
||||
The [`worker`](src/worker) crate implements the script execution engine:
|
||||
|
||||
- **Context-Aware Execution**: Loads and executes scripts within specific peer contexts
|
||||
- **Namespace Isolation**: Each peer gets isolated filesystem access
|
||||
- **Caller Awareness**: Scripts execute with knowledge of both caller and target peer
|
||||
- **Multiplexing Support**: Single worker can serve multiple peer contexts
|
||||
|
||||
**Execution Environment Variables:**
|
||||
```rhai
|
||||
CALLER_PUBLIC_KEY // The peer who sent the script
|
||||
CIRCLE_PUBLIC_KEY // The peer in whose context the script runs
|
||||
DB_PATH // Namespaced database path for this peer
|
||||
```
|
||||
|
||||
### 2.4. Client Library
|
||||
|
||||
The [`client_ws`](src/client_ws) crate provides cross-platform WebSocket client functionality:
|
||||
|
||||
- **Cross-Platform**: Native (tokio-tungstenite) and WASM (gloo-net) support
|
||||
- **Authentication**: Automated secp256k1 signature-based authentication
|
||||
- **Script Execution**: High-level API for sending scripts to other peers
|
||||
- **Builder Pattern**: Flexible client construction with optional authentication
|
||||
|
||||
## 3. Communication Topology
|
||||
|
||||
### 3.1. Peer-to-Peer Script Execution Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant PeerA as Peer A (Client)
|
||||
participant Circle as Circle Node (WebSocket)
|
||||
participant Redis as Redis Queue
|
||||
participant Worker as Worker Engine
|
||||
participant FS as Peer B Filesystem
|
||||
|
||||
Note over PeerA,FS: Peer A sends script to execute in Peer B's context
|
||||
|
||||
PeerA->>Circle: WebSocket: JSON-RPC "play" request
|
||||
Note right of PeerA: Script + Target Peer B's public key
|
||||
|
||||
Circle->>Circle: Validate authentication
|
||||
Circle->>Redis: LPUSH to peer B's queue
|
||||
Note right of Circle: Queue: rhailib:{peer_b_pubkey}
|
||||
|
||||
Worker->>Redis: BLPOP from peer B's queue
|
||||
Worker->>Worker: Load Peer B's context
|
||||
Note right of Worker: Set CALLER_PUBLIC_KEY=peer_a<br/>Set CIRCLE_PUBLIC_KEY=peer_b<br/>Set DB_PATH=peer_b_namespace/
|
||||
|
||||
Worker->>FS: Execute script in Peer B's namespace
|
||||
FS-->>Worker: Script execution result
|
||||
|
||||
Worker->>Redis: LPUSH result to reply queue
|
||||
Redis-->>Circle: Result notification
|
||||
Circle-->>PeerA: WebSocket: JSON-RPC response
|
||||
```
|
||||
|
||||
### 3.2. System Component Diagram
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Peer A Environment"
|
||||
ClientA[Client A<br/>client_ws]
|
||||
KeyA[Private Key A]
|
||||
ClientA -.-> KeyA
|
||||
end
|
||||
|
||||
subgraph "Peer B Environment"
|
||||
ClientB[Client B<br/>client_ws]
|
||||
KeyB[Private Key B]
|
||||
ClientB -.-> KeyB
|
||||
end
|
||||
|
||||
subgraph "Circle Infrastructure"
|
||||
Circle[Circle Node<br/>server]
|
||||
Redis[(Redis<br/>Task Queues)]
|
||||
Worker1[Worker Engine<br/>Peer A Context]
|
||||
Worker2[Worker Engine<br/>Peer B Context]
|
||||
|
||||
Circle <--> Redis
|
||||
Redis <--> Worker1
|
||||
Redis <--> Worker2
|
||||
end
|
||||
|
||||
subgraph "Isolated Storage"
|
||||
FSA[Peer A Namespace<br/>DB_PATH/peer_a/]
|
||||
FSB[Peer B Namespace<br/>DB_PATH/peer_b/]
|
||||
|
||||
Worker1 <--> FSA
|
||||
Worker2 <--> FSB
|
||||
end
|
||||
|
||||
ClientA <-->|WebSocket<br/>Authenticated| Circle
|
||||
ClientB <-->|WebSocket<br/>Authenticated| Circle
|
||||
|
||||
style ClientA fill:#e1f5fe
|
||||
style ClientB fill:#e8f5e8
|
||||
style Circle fill:#fff3e0
|
||||
style Worker1 fill:#e1f5fe
|
||||
style Worker2 fill:#e8f5e8
|
||||
```
|
||||
|
||||
### 3.3. Multi-Circle Deployment Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Launcher Configuration"
|
||||
Launcher[Launcher<br/>circles.json]
|
||||
Config[["OurWorld: port 8090<br/>Threefold: port 8091<br/>Sikana: port 8092<br/>..."]]
|
||||
Launcher --> Config
|
||||
end
|
||||
|
||||
subgraph "Circle Nodes"
|
||||
Circle1[Circle 1<br/>:8090]
|
||||
Circle2[Circle 2<br/>:8091]
|
||||
Circle3[Circle 3<br/>:8092]
|
||||
end
|
||||
|
||||
subgraph "Worker Pool"
|
||||
Worker1[Worker<br/>OurWorld Context]
|
||||
Worker2[Worker<br/>Threefold Context]
|
||||
Worker3[Worker<br/>Sikana Context]
|
||||
end
|
||||
|
||||
subgraph "Shared Infrastructure"
|
||||
Redis[(Redis<br/>Coordination)]
|
||||
Storage[(Namespaced Storage<br/>peer_contexts/)]
|
||||
end
|
||||
|
||||
Launcher --> Circle1
|
||||
Launcher --> Circle2
|
||||
Launcher --> Circle3
|
||||
|
||||
Circle1 <--> Redis
|
||||
Circle2 <--> Redis
|
||||
Circle3 <--> Redis
|
||||
|
||||
Redis <--> Worker1
|
||||
Redis <--> Worker2
|
||||
Redis <--> Worker3
|
||||
|
||||
Worker1 <--> Storage
|
||||
Worker2 <--> Storage
|
||||
Worker3 <--> Storage
|
||||
```
|
||||
|
||||
## 4. Authentication and Security
|
||||
|
||||
### 4.1. Peer Authentication Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Peer Client
|
||||
participant Circle as Circle Node
|
||||
|
||||
Note over Client: Has secp256k1 keypair
|
||||
|
||||
Client->>+Circle: JSON-RPC "fetch_nonce" (pubkey)
|
||||
Circle->>Circle: generate_nonce()
|
||||
Circle->>Circle: store_nonce(pubkey, nonce)
|
||||
Circle-->>-Client: nonce + expiration
|
||||
|
||||
Client->>Client: sign(nonce, private_key)
|
||||
|
||||
Client->>+Circle: JSON-RPC "authenticate" (pubkey, signature)
|
||||
Circle->>Circle: verify_signature(nonce, signature, pubkey)
|
||||
|
||||
alt Signature Valid
|
||||
Circle->>Circle: Mark session as authenticated
|
||||
Circle-->>Client: {"authenticated": true}
|
||||
Note over Circle: Subsequent "play" requests include authenticated pubkey
|
||||
else Signature Invalid
|
||||
Circle-->>-Client: JSON-RPC Error: Invalid Credentials
|
||||
Circle->>Circle: Close connection
|
||||
end
|
||||
```
|
||||
|
||||
### 4.2. Context Isolation Model
|
||||
|
||||
Each peer context is isolated through:
|
||||
|
||||
1. **Filesystem Namespacing**: Each peer gets a dedicated root directory
|
||||
2. **Environment Variables**: Scripts execute with peer-specific context variables
|
||||
3. **Redis Queue Isolation**: Each peer has dedicated task queues
|
||||
4. **Authentication Boundaries**: Only authenticated peers can send scripts
|
||||
|
||||
```
|
||||
worker_rhai_temp_db/
|
||||
├── peer_a_public_key/ # Peer A's isolated namespace
|
||||
│ ├── data/
|
||||
│ ├── config/
|
||||
│ └── logs/
|
||||
├── peer_b_public_key/ # Peer B's isolated namespace
|
||||
│ ├── data/
|
||||
│ ├── config/
|
||||
│ └── logs/
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 5. Script Execution Model
|
||||
|
||||
### 5.1. Cross-Peer Script Execution
|
||||
|
||||
When Peer A sends a script to Peer B:
|
||||
|
||||
1. **Authentication**: Peer A authenticates with their private key
|
||||
2. **Script Routing**: Circle routes the script to Peer B's Redis queue
|
||||
3. **Context Loading**: Worker loads Peer B's execution context
|
||||
4. **Environment Setup**: Worker sets context variables:
|
||||
```rhai
|
||||
CALLER_PUBLIC_KEY = "peer_a_public_key"
|
||||
CIRCLE_PUBLIC_KEY = "peer_b_public_key"
|
||||
DB_PATH = "/path/to/peer_b_namespace"
|
||||
```
|
||||
5. **Script Execution**: Rhai engine executes script in Peer B's context
|
||||
6. **Result Return**: Execution result is returned to Peer A via Redis/WebSocket
|
||||
|
||||
### 5.2. Example Script Execution
|
||||
|
||||
```rhai
|
||||
// Script sent by Peer A to Peer B
|
||||
print("Script running in context of: " + CIRCLE_PUBLIC_KEY);
|
||||
print("Initiated by: " + CALLER_PUBLIC_KEY);
|
||||
|
||||
// Access Peer B's data (isolated namespace)
|
||||
let peer_b_data = load_data("user_preferences.json");
|
||||
|
||||
// Perform operations in Peer B's context
|
||||
let result = process_data(peer_b_data);
|
||||
|
||||
// Return result to Peer A
|
||||
result
|
||||
```
|
||||
|
||||
## 6. API Reference
|
||||
|
||||
### 6.1. JSON-RPC Methods
|
||||
|
||||
The Circle WebSocket API provides three core methods:
|
||||
|
||||
#### `fetch_nonce`
|
||||
Requests a cryptographic nonce for authentication.
|
||||
|
||||
**Parameters:**
|
||||
- `pubkey` (string): Client's secp256k1 public key
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"nonce": "base64_encoded_nonce",
|
||||
"expires_at": 1234567890
|
||||
}
|
||||
```
|
||||
|
||||
#### `authenticate`
|
||||
Authenticates the client using a signed nonce.
|
||||
|
||||
**Parameters:**
|
||||
- `pubkey` (string): Client's public key
|
||||
- `signature` (string): Nonce signed with client's private key
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"authenticated": true
|
||||
}
|
||||
```
|
||||
|
||||
#### `play`
|
||||
Executes a script in a target peer's context.
|
||||
|
||||
**Parameters:**
|
||||
- `script` (string): Rhai script to execute
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"output": "script_execution_result"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2. Client Library Usage
|
||||
|
||||
```rust
|
||||
use circle_client_ws::{CircleWsClientBuilder, CircleWsClient};
|
||||
|
||||
// Create authenticated client
|
||||
let mut client = CircleWsClientBuilder::new("ws://localhost:8090/peer_b_pubkey".to_string())
|
||||
.with_keypair(private_key)
|
||||
.build();
|
||||
|
||||
// Connect and authenticate
|
||||
client.connect().await?;
|
||||
client.authenticate().await?;
|
||||
|
||||
// Send script to execute in peer B's context
|
||||
let result = client.play(r#"
|
||||
print("Hello from " + CALLER_PUBLIC_KEY + " to " + CIRCLE_PUBLIC_KEY);
|
||||
"Script executed successfully"
|
||||
"#.to_string()).await?;
|
||||
|
||||
println!("Result: {}", result.output);
|
||||
```
|
||||
|
||||
## 7. Deployment Models
|
||||
|
||||
### 7.1. Single-Peer Worker
|
||||
|
||||
Each peer runs in its own dedicated worker process:
|
||||
|
||||
```bash
|
||||
# Start worker for specific peer
|
||||
./worker --circle-public-key peer_a_public_key --redis-url redis://localhost:6379
|
||||
```
|
||||
|
||||
### 7.2. Multi-Peer Worker
|
||||
|
||||
Single worker serves multiple peer contexts with namespace isolation:
|
||||
|
||||
```bash
|
||||
# Worker handles multiple peers with context switching
|
||||
./worker --redis-url redis://localhost:6379
|
||||
```
|
||||
|
||||
### 7.3. Launcher-Managed Deployment
|
||||
|
||||
Use the launcher to manage multiple circles:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "OurWorld",
|
||||
"port": 8090,
|
||||
"script_path": "scripts/ourworld.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Threefold",
|
||||
"port": 8091,
|
||||
"script_path": "scripts/threefold.rhai"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```bash
|
||||
./launcher --config circles.json
|
||||
```
|
||||
|
||||
## 8. Technical Implementation Details
|
||||
|
||||
### 8.1. Redis Protocol
|
||||
|
||||
**Task Queue Pattern:**
|
||||
- Queue Key: `rhailib:{peer_public_key}`
|
||||
- Task Details: `rhailib:{task_id}` (hash)
|
||||
- Reply Queue: `rhailib:reply:{task_id}`
|
||||
|
||||
**Task Lifecycle:**
|
||||
1. Client submits script → Redis queue
|
||||
2. Worker picks up task → Updates status to "processing"
|
||||
3. Worker executes script → Updates status to "completed"/"error"
|
||||
4. Worker publishes result → Reply queue
|
||||
5. Client receives result → Task cleanup (optional)
|
||||
|
||||
### 8.2. Worker Context Management
|
||||
|
||||
```rust
|
||||
// Worker sets up peer context before script execution
|
||||
let mut db_config = rhai::Map::new();
|
||||
db_config.insert("DB_PATH".into(), db_path.clone().into());
|
||||
db_config.insert("CALLER_PUBLIC_KEY".into(), caller_id.clone().into());
|
||||
db_config.insert("CIRCLE_PUBLIC_KEY".into(), circle_public_key.clone().into());
|
||||
engine.set_default_tag(Dynamic::from(db_config));
|
||||
```
|
||||
|
||||
### 8.3. Filesystem Isolation
|
||||
|
||||
Each peer context gets an isolated filesystem namespace:
|
||||
|
||||
```rust
|
||||
// Example namespace structure
|
||||
let peer_namespace = format!("{}/{}", base_db_path, peer_public_key);
|
||||
std::fs::create_dir_all(&peer_namespace)?;
|
||||
|
||||
// All file operations are relative to this namespace
|
||||
let data_file = format!("{}/data/user_data.json", peer_namespace);
|
||||
```
|
||||
|
||||
## 9. Comparison to Traditional Architectures
|
||||
|
||||
| Feature | Traditional Backend | Circles (Peer-Centric) |
|
||||
|---------|-------------------|------------------------|
|
||||
| User Representation | Row in database | Autonomous execution context |
|
||||
| Request Routing | Global endpoints | Peer-scoped script execution |
|
||||
| State Management | Shared application state | Isolated peer contexts |
|
||||
| Communication | HTTP/REST, JSON-RPC | WebSocket + Redis coordination |
|
||||
| Distribution | Centralized | Decentralized, per-peer scalable |
|
||||
| Execution Model | Threaded request handlers | Context-aware script processors |
|
||||
| Data Isolation | Application-level | Filesystem namespacing |
|
||||
| Cross-User Operations | Database transactions | Cross-peer script execution |
|
||||
|
||||
## 10. Summary
|
||||
|
||||
Circles implements a **distributed, peer-contextual application layer runtime** where:
|
||||
|
||||
- Each peer is an isolated execution context identified by secp256k1 public keys
|
||||
- Peers can send scripts to other peers for execution within the target's context
|
||||
- Workers enforce isolation through filesystem namespacing and environment variables
|
||||
- The system supports both single-peer and multi-peer worker deployments
|
||||
- Communication flows through WebSocket gateways coordinated via Redis queues
|
||||
|
||||
This architecture enables **decentralized application logic** where peers maintain autonomy over their execution environments while supporting secure cross-peer interactions through script-based workflows.
|
27
CONTRIBUTING.md
Normal file
27
CONTRIBUTING.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Contributing
|
||||
|
||||
|
||||
## Development Pipeline
|
||||
|
||||
### Before committing
|
||||
|
||||
1.
|
||||
|
||||
## Cargo.toml
|
||||
|
||||
## Folder, crate, library, and binary naming
|
||||
|
||||
As every crate in this repository is part of the same `circles` workspace, it would be reduntant to name every folder circles_*. However for naming the libraries, for external projects, simply the folder name in this workspace would not be unique, so we use circles_* (following rust naming conventions). Finally, for crate names, as underscores are reserved for rust crates, we use circles-*.
|
||||
|
||||
For example, we have a websocket server crate for circles:
|
||||
- folder: `server`
|
||||
- library: `circles_server_ws`
|
||||
- crate: `circles-server-ws`
|
||||
|
||||
## Development with LLMs
|
||||
|
||||
Each project is specified with three files:
|
||||
|
||||
- `README.md`: A high-level overview of the project, its purpose, and how to use it.
|
||||
- `ARCHITECTURE.md`: A detailed description of the system's architecture, including its components and how they interact.
|
||||
- `CONTRIBUTING.md`: Guidelines for contributing to the project, including how to set up the development environment and how to submit pull requests.
|
542
Cargo.lock
generated
542
Cargo.lock
generated
@ -11,7 +11,7 @@ dependencies = [
|
||||
"actix-macros",
|
||||
"actix-rt",
|
||||
"actix_derive",
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"bytes",
|
||||
"crossbeam-channel",
|
||||
"futures-core",
|
||||
@ -33,7 +33,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@ -55,8 +55,8 @@ dependencies = [
|
||||
"actix-service",
|
||||
"actix-tls",
|
||||
"actix-utils",
|
||||
"base64",
|
||||
"bitflags",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.1",
|
||||
"brotli",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
@ -131,7 +131,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"mio",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@ -213,7 +213,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"smallvec",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
@ -445,6 +445,12 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@ -486,7 +492,7 @@ version = "0.69.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
@ -519,6 +525,12 @@ dependencies = [
|
||||
"hex-conservative",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
@ -634,6 +646,9 @@ name = "circle_client_ws"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"circle_ws_lib",
|
||||
"clap",
|
||||
"dotenv",
|
||||
"env_logger",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"gloo-console 0.3.0",
|
||||
@ -644,14 +659,13 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"secp256k1 0.29.1",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-tungstenite 0.19.0",
|
||||
"tokio-tungstenite 0.23.1",
|
||||
"url",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
@ -666,25 +680,32 @@ dependencies = [
|
||||
"actix",
|
||||
"actix-web",
|
||||
"actix-web-actors",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"clap",
|
||||
"engine",
|
||||
"dotenv",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"heromodels",
|
||||
"hex",
|
||||
"hmac",
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"redis",
|
||||
"redis 0.23.3",
|
||||
"redis 0.25.4",
|
||||
"rhai_client",
|
||||
"rhailib_engine",
|
||||
"rhailib_worker",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"secp256k1 0.29.1",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.19.0",
|
||||
"url",
|
||||
@ -697,15 +718,12 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"circle_client_ws",
|
||||
"circle_ws_lib",
|
||||
"engine",
|
||||
"env_logger",
|
||||
"heromodels",
|
||||
"hex",
|
||||
"launcher",
|
||||
"log",
|
||||
"redis",
|
||||
"rhailib_worker",
|
||||
"secp256k1 0.29.1",
|
||||
"redis 0.25.4",
|
||||
"secp256k1",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
@ -717,8 +735,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"circle_client_ws",
|
||||
"common_models",
|
||||
"engine",
|
||||
"futures",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -735,7 +751,8 @@ dependencies = [
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rhai",
|
||||
"secp256k1 0.29.1",
|
||||
"rhailib_engine",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
@ -838,25 +855,6 @@ dependencies = [
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "comfy-table"
|
||||
version = "7.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "common_models"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
@ -947,28 +945,6 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
@ -1000,6 +976,14 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.0.1"
|
||||
@ -1029,6 +1013,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1042,6 +1027,12 @@ dependencies = [
|
||||
"syn 2.0.103",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
@ -1069,17 +1060,6 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"heromodels",
|
||||
"heromodels-derive",
|
||||
"heromodels_core",
|
||||
"rhai",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
@ -1712,6 +1692,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode 2.0.1",
|
||||
"chrono",
|
||||
"derive",
|
||||
"heromodels-derive",
|
||||
"heromodels_core",
|
||||
"ourdb",
|
||||
@ -1754,6 +1735,15 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
@ -1785,6 +1775,17 @@ dependencies = [
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
@ -1803,6 +1804,43 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
@ -1979,6 +2017,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.16"
|
||||
@ -2046,35 +2090,6 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||
|
||||
[[package]]
|
||||
name = "launcher"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"circle_ws_lib",
|
||||
"clap",
|
||||
"comfy-table",
|
||||
"engine",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"heromodels",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ourdb",
|
||||
"rand 0.8.5",
|
||||
"redis",
|
||||
"rhai",
|
||||
"rhai_client",
|
||||
"rhailib_worker",
|
||||
"secp256k1 0.28.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.23.1",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@ -2154,6 +2169,16 @@ version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heromodels",
|
||||
"heromodels_core",
|
||||
"rhai",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@ -2244,7 +2269,7 @@ version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
@ -2333,7 +2358,7 @@ version = "0.10.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@ -2649,6 +2674,27 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"combine",
|
||||
"futures-util",
|
||||
"itoa",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"ryu",
|
||||
"sha1_smol",
|
||||
"socket2 0.4.10",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "0.25.4"
|
||||
@ -2664,7 +2710,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"ryu",
|
||||
"sha1_smol",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"url",
|
||||
@ -2676,7 +2722,7 @@ version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2730,13 +2776,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.22.2"
|
||||
name = "reqwest"
|
||||
version = "0.11.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249"
|
||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"instant",
|
||||
"no-std-compat",
|
||||
"num-traits",
|
||||
@ -2753,8 +2839,10 @@ name = "rhai_client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
"redis",
|
||||
"redis 0.25.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@ -2782,19 +2870,49 @@ dependencies = [
|
||||
"syn 2.0.103",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhailib_dsl"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive",
|
||||
"dotenv",
|
||||
"heromodels",
|
||||
"heromodels-derive",
|
||||
"heromodels_core",
|
||||
"macros",
|
||||
"reqwest",
|
||||
"rhai",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhailib_engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"heromodels",
|
||||
"heromodels-derive",
|
||||
"heromodels_core",
|
||||
"rhai",
|
||||
"rhailib_dsl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhailib_worker"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"engine",
|
||||
"env_logger",
|
||||
"heromodels",
|
||||
"log",
|
||||
"redis",
|
||||
"redis 0.25.4",
|
||||
"rhai",
|
||||
"rhai_client",
|
||||
"rhailib_engine",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@ -2843,7 +2961,7 @@ version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
@ -2856,7 +2974,7 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
@ -2878,6 +2996,15 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
@ -2920,7 +3047,7 @@ version = "13.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"clipboard-win",
|
||||
"fd-lock",
|
||||
@ -2932,7 +3059,7 @@ dependencies = [
|
||||
"radix_trie",
|
||||
"rustyline-derive",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"winapi",
|
||||
]
|
||||
@ -2978,16 +3105,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.28.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
|
||||
dependencies = [
|
||||
"rand 0.8.5",
|
||||
"secp256k1-sys 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.29.1"
|
||||
@ -2996,16 +3113,7 @@ checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"rand 0.8.5",
|
||||
"secp256k1-sys 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1-sys"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"secp256k1-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3023,7 +3131,7 @@ version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@ -3123,6 +3231,17 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.10.8"
|
||||
@ -3180,6 +3299,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
@ -3246,6 +3375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@ -3260,6 +3390,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
@ -3271,6 +3407,27 @@ dependencies = [
|
||||
"syn 2.0.103",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
@ -3391,7 +3548,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@ -3496,6 +3653,12 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
@ -3558,6 +3721,12 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tst"
|
||||
version = "0.1.0"
|
||||
@ -3644,12 +3813,6 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
@ -3749,6 +3912,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
@ -3982,6 +4154,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@ -4000,6 +4181,21 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@ -4032,6 +4228,12 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@ -4044,6 +4246,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@ -4056,6 +4264,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@ -4080,6 +4294,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@ -4092,6 +4312,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@ -4104,6 +4330,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@ -4116,6 +4348,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
@ -4137,13 +4375,23 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
10
Cargo.toml
10
Cargo.toml
@ -7,8 +7,7 @@ edition = "2021"
|
||||
resolver = "2"
|
||||
members = [
|
||||
"src/client_ws",
|
||||
"src/server_ws",
|
||||
"src/launcher",
|
||||
"src/server",
|
||||
"src/ui_repl",
|
||||
"src/app",
|
||||
]
|
||||
@ -46,9 +45,9 @@ uuid = { version = "1.6", features = ["v4", "serde", "js"] }
|
||||
thiserror = "1.0"
|
||||
# Path dependencies to other local crates from outside this repo
|
||||
heromodels = { path = "../db/heromodels" }
|
||||
engine = { path = "../rhailib/src/engine" }
|
||||
rhailib_engine = { path = "../rhailib/src/engine" }
|
||||
rhailib_worker = { path = "../rhailib/src/worker" }
|
||||
circle_ws_lib = { path = "src/server_ws" }
|
||||
circle_ws_lib = { path = "src/server" }
|
||||
|
||||
|
||||
# Dev dependencies
|
||||
@ -59,12 +58,9 @@ tempfile = "3.10.1"
|
||||
log = "0.4"
|
||||
circle_ws_lib = { workspace = true }
|
||||
heromodels = { workspace = true }
|
||||
engine = { workspace = true }
|
||||
rhailib_worker = { workspace = true }
|
||||
redis = { workspace = true }
|
||||
secp256k1 = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
launcher = { path = "src/launcher" }
|
||||
|
||||
|
||||
|
||||
|
23
README.md
23
README.md
@ -1,21 +1,28 @@
|
||||
# Circles Project
|
||||
# Circles
|
||||
|
||||
Welcome to the `circles` project, a full-stack system featuring a WebSocket server, a cross-platform client, and a launcher to manage multiple instances. This project is designed for executing Rhai scripts in isolated environments, with an optional layer of `secp256k1` cryptographic authentication.
|
||||
Welcome to `circles`, a system designed to accomodate a peer-centric backend infrastructure for
|
||||
|
||||
This system is a distributed, context-aware application layer runtime where peers trigger script executions in each other’s logical environments. Execution always occurs within the target peer’s context, with the caller peer’s identity provided for authorization and provenance. Workers — whether serving single or multiple peers — enforce context isolation via namespaced filesystems and runtime boundaries. The system enables cross-peer logic to run safely and scalably without assuming global state or centralized coordination.
|
||||
|
||||
|
||||
|
||||
|
||||
featuring a WebSocket server, a cross-platform client, and a launcher to manage multiple instances. This project is designed for executing Rhai scripts in isolated environments, with an optional layer of `secp256k1` cryptographic authentication.
|
||||
|
||||
## Overview
|
||||
|
||||
The `circles` project provides two core library crates and a utility application:
|
||||
|
||||
- **`server_ws`**: The core WebSocket server library, built with `Actix`. It handles client connections, processes JSON-RPC messages, and executes Rhai scripts.
|
||||
- **`server`**: The core WebSocket server library, built with `Actix`. It handles client connections, processes JSON-RPC messages, and executes Rhai scripts.
|
||||
- **`client_ws`**: The core cross-platform WebSocket client library, compatible with both native Rust and WebAssembly (WASM) environments.
|
||||
- **`launcher`**: A convenient command-line utility that uses the `server_ws` library to read a `circles.json` configuration file and spawn multiple, isolated "Circle" instances.
|
||||
- **`launcher`**: A convenient command-line utility that uses the `server` library to read a `circles.json` configuration file and spawn multiple, isolated "Circle" instances.
|
||||
- **`openrpc.json`**: An OpenRPC specification that formally defines the JSON-RPC 2.0 API used for client-server communication.
|
||||
|
||||
## Architecture
|
||||
|
||||
The system is designed around a client-server model, with `client_ws` and `server_ws` as the core components. The `launcher` is provided as a utility for orchestrating multiple server instances, each configured as an isolated "Circle" environment.
|
||||
The system is designed around a client-server model, with `client_ws` and `server` as the core components. The `launcher` is provided as a utility for orchestrating multiple server instances, each configured as an isolated "Circle" environment.
|
||||
|
||||
Clients connect to a `server_ws` instance via WebSocket and interact with it using the JSON-RPC protocol. The server can be configured to require authentication, in which case the client must complete a signature-based challenge-response flow over the WebSocket connection before it can execute protected methods like `play`.
|
||||
Clients connect to a `server` instance via WebSocket and interact with it using the JSON-RPC protocol. The server can be configured to require authentication, in which case the client must complete a signature-based challenge-response flow over the WebSocket connection before it can execute protected methods like `play`.
|
||||
|
||||
For a more detailed explanation of the system's design, please see the [ARCHITECTURE.md](ARCHITECTURE.md) file.
|
||||
|
||||
@ -54,10 +61,10 @@ For a complete definition of the API, including request parameters and response
|
||||
|
||||
## Crates
|
||||
|
||||
- **[server_ws](server_ws/README.md)**: Detailed documentation for the server library.
|
||||
- **[server](server/README.md)**: Detailed documentation for the server library.
|
||||
- **[client_ws](client_ws/README.md)**: Detailed documentation for the client library.
|
||||
- **[launcher](launcher/README.md)**: Detailed documentation for the launcher utility.
|
||||
- **[app](src/app/README.md)**: A Yew frontend application that uses the `client_ws` to interact with the `server_ws`.
|
||||
- **[app](src/app/README.md)**: A Yew frontend application that uses the `client_ws` to interact with the `server`.
|
||||
|
||||
## Running the App
|
||||
|
||||
|
189
cmd/dispatcher.rs
Normal file
189
cmd/dispatcher.rs
Normal file
@ -0,0 +1,189 @@
|
||||
use clap::Parser;
|
||||
use rhai_client::{RhaiClient, RhaiClientBuilder};
|
||||
use log::{error, info};
|
||||
use std::io::{self, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about = "Circles Client - Rhai script execution client", long_about = None)]
|
||||
struct Args {
|
||||
/// Caller public key (caller ID)
|
||||
#[arg(short = 'c', long = "caller-key", help = "Caller public key (your identity)")]
|
||||
caller_public_key: String,
|
||||
|
||||
/// Circle public key (context ID)
|
||||
#[arg(short = 'k', long = "circle-key", help = "Circle public key (execution context)")]
|
||||
circle_public_key: String,
|
||||
|
||||
/// Worker public key (defaults to circle public key if not provided)
|
||||
#[arg(short = 'w', long = "worker-key", help = "Worker public key (defaults to circle key)")]
|
||||
worker_public_key: Option<String>,
|
||||
|
||||
/// Redis URL
|
||||
#[arg(short, long, default_value = "redis://localhost:6379", help = "Redis connection URL")]
|
||||
redis_url: String,
|
||||
|
||||
/// Rhai script to execute
|
||||
#[arg(short, long, help = "Rhai script to execute")]
|
||||
script: Option<String>,
|
||||
|
||||
/// Path to Rhai script file
|
||||
#[arg(short, long, help = "Path to Rhai script file")]
|
||||
file: Option<String>,
|
||||
|
||||
/// Timeout for script execution (in seconds)
|
||||
#[arg(short, long, default_value = "30", help = "Timeout for script execution in seconds")]
|
||||
timeout: u64,
|
||||
|
||||
/// Increase verbosity (can be used multiple times)
|
||||
#[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity (-v for debug, -vv for trace)")]
|
||||
verbose: u8,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
// Configure logging based on verbosity level
|
||||
let log_config = match args.verbose {
|
||||
0 => "warn,circles_client=info,rhai_client=info",
|
||||
1 => "info,circles_client=debug,rhai_client=debug",
|
||||
2 => "debug",
|
||||
_ => "trace",
|
||||
};
|
||||
|
||||
std::env::set_var("RUST_LOG", log_config);
|
||||
env_logger::init();
|
||||
|
||||
// Use worker key or default to circle key
|
||||
let worker_key = args.worker_public_key.unwrap_or_else(|| args.circle_public_key.clone());
|
||||
|
||||
info!("🔗 Starting Circles Client");
|
||||
info!("📋 Configuration:");
|
||||
info!(" Caller Key: {}", args.caller_public_key);
|
||||
info!(" Circle Key: {}", args.circle_public_key);
|
||||
info!(" Worker Key: {}", worker_key);
|
||||
info!(" Redis URL: {}", args.redis_url);
|
||||
info!(" Timeout: {}s", args.timeout);
|
||||
info!();
|
||||
|
||||
// Create the Rhai client
|
||||
let client = RhaiClientBuilder::new()
|
||||
.caller_id(&args.caller_public_key)
|
||||
.redis_url(&args.redis_url)
|
||||
.build()?;
|
||||
|
||||
info!("✅ Connected to Redis at {}", args.redis_url);
|
||||
|
||||
// Determine execution mode
|
||||
if let Some(script_content) = args.script {
|
||||
// Execute inline script
|
||||
info!("📜 Executing inline script");
|
||||
execute_script(&client, &worker_key, script_content, args.timeout).await?;
|
||||
} else if let Some(file_path) = args.file {
|
||||
// Execute script from file
|
||||
info!("📁 Loading script from file: {}", file_path);
|
||||
let script_content = std::fs::read_to_string(&file_path)
|
||||
.map_err(|e| format!("Failed to read script file '{}': {}", file_path, e))?;
|
||||
execute_script(&client, &worker_key, script_content, args.timeout).await?;
|
||||
} else {
|
||||
// Interactive mode
|
||||
info!("🎮 Entering interactive mode");
|
||||
info!("Type Rhai scripts and press Enter to execute. Type 'exit' or 'quit' to close.");
|
||||
run_interactive_mode(&client, &worker_key, args.timeout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_script(
|
||||
client: &RhaiClient,
|
||||
worker_key: &str,
|
||||
script: String,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("⚡ Executing script: {:.50}...", script);
|
||||
|
||||
let timeout = Duration::from_secs(timeout_secs);
|
||||
|
||||
match client
|
||||
.new_play_request()
|
||||
.recipient_id(worker_key)
|
||||
.script(&script)
|
||||
.timeout(timeout)
|
||||
.await_response()
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
info!("✅ Script execution completed");
|
||||
println!("Status: {}", result.status);
|
||||
if let Some(output) = result.output {
|
||||
println!("Output: {}", output);
|
||||
}
|
||||
if let Some(error) = result.error {
|
||||
println!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Script execution failed: {}", e);
|
||||
return Err(Box::new(e));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_interactive_mode(
|
||||
client: &RhaiClient,
|
||||
worker_key: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let timeout = Duration::from_secs(timeout_secs);
|
||||
|
||||
loop {
|
||||
print!("rhai> ");
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
let input = input.trim();
|
||||
|
||||
if input.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
info!("👋 Goodbye!");
|
||||
break;
|
||||
}
|
||||
|
||||
info!("⚡ Executing: {}", input);
|
||||
|
||||
match client
|
||||
.new_play_request()
|
||||
.recipient_id(worker_key)
|
||||
.script(input)
|
||||
.timeout(timeout)
|
||||
.await_response()
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
println!("Status: {}", result.status);
|
||||
if let Some(output) = result.output {
|
||||
println!("Output: {}", output);
|
||||
}
|
||||
if let Some(error) = result.error {
|
||||
println!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Execution failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
println!(); // Add blank line for readability
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
# System Architecture
|
||||
|
||||
This document provides a detailed overview of the `circles` project architecture. The project is composed of two core library crates, `server_ws` and `client_ws`, and a convenient `launcher` utility.
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
The `circles` project provides the core components for a client-server system designed to execute Rhai scripts in isolated environments. The `launcher` application is a utility that demonstrates how to use the `server_ws` and `client_ws` libraries to manage multiple server instances, but the libraries themselves are the fundamental building blocks.
|
||||
|
||||
The core functionality revolves around:
|
||||
- **Orchestration**: The `launcher` starts and stops multiple, independent WebSocket servers.
|
||||
- **Client-Server Communication**: A JSON-RPC 2.0 API over WebSockets allows clients to execute scripts and authenticate.
|
||||
- **Authentication**: An optional, robust `secp256k1` signature-based authentication mechanism secures the script execution endpoint.
|
||||
|
||||
## 2. Component Architecture
|
||||
|
||||
### 2.1. `server_ws` (Library)
|
||||
|
||||
The `server_ws` crate provides the WebSocket server that handles client connections and API requests. Its key features include:
|
||||
- **Web Framework**: Built using `Actix`, a powerful actor-based web framework for Rust.
|
||||
- **WebSocket Handling**: Uses `actix-web-actors` to manage individual WebSocket sessions. Each client connection is handled by a `CircleWs` actor, ensuring that sessions are isolated from one another.
|
||||
- **JSON-RPC API**: Exposes a JSON-RPC 2.0 API with methods for script execution (`play`) and authentication (`fetch_nonce`, `authenticate`).
|
||||
- **Authentication Service**: The authentication flow is handled entirely within the WebSocket connection using the dedicated JSON-RPC methods.
|
||||
|
||||
### 2.2. `client_ws` (Library)
|
||||
|
||||
The `client_ws` crate is a WebSocket client library designed for interacting with the `server_ws`. It is engineered to be cross-platform:
|
||||
- **Native**: For native Rust applications, it uses `tokio-tungstenite` for WebSocket communication.
|
||||
- **WebAssembly (WASM)**: For browser-based applications, it uses `gloo-net` to integrate with the browser's native WebSocket API.
|
||||
- **API**: Provides a flexible builder pattern for client construction and a high-level API (`CircleWsClient`) that abstracts the complexities of the WebSocket connection and the JSON-RPC protocol.
|
||||
|
||||
### 2.3. `launcher` (Utility)
|
||||
|
||||
The `launcher` is a command-line utility that demonstrates how to use the `server_ws` library. It is responsible for:
|
||||
- **Configuration**: Reading a `circles.json` file that defines a list of Circle instances to run.
|
||||
- **Orchestration**: Spawning a dedicated `server_ws` instance for each configured circle.
|
||||
- **Lifecycle Management**: Managing the lifecycle of all spawned servers and their associated Rhai workers.
|
||||
|
||||
### 2.2. `server_ws`
|
||||
|
||||
The `server_ws` crate provides the WebSocket server that handles client connections and API requests. Its key features include:
|
||||
- **Web Framework**: Built using `Actix`, a powerful actor-based web framework for Rust.
|
||||
- **WebSocket Handling**: Uses `actix-web-actors` to manage individual WebSocket sessions. Each client connection is handled by a `CircleWs` actor, ensuring that sessions are isolated from one another.
|
||||
- **JSON-RPC API**: Exposes a JSON-RPC 2.0 API with methods for script execution (`play`) and authentication (`fetch_nonce`, `authenticate`).
|
||||
- **Authentication Service**: The authentication flow is handled entirely within the WebSocket connection using the dedicated JSON-RPC methods.
|
||||
|
||||
### 2.3. `client_ws`
|
||||
|
||||
The `client_ws` crate is a WebSocket client library designed for interacting with the `server_ws`. It is engineered to be cross-platform:
|
||||
- **Native**: For native Rust applications, it uses `tokio-tungstenite` for WebSocket communication.
|
||||
- **WebAssembly (WASM)**: For browser-based applications, it uses `gloo-net` to integrate with the browser's native WebSocket API.
|
||||
- **API**: Provides a flexible builder pattern for client construction and a high-level API (`CircleWsClient`) that abstracts the complexities of the WebSocket connection and the JSON-RPC protocol.
|
||||
|
||||
## 3. Communication and Protocols
|
||||
|
||||
### 3.1. JSON-RPC 2.0
|
||||
|
||||
All client-server communication, including authentication, uses the JSON-RPC 2.0 protocol over the WebSocket connection. This provides a unified, lightweight, and well-defined structure for all interactions. The formal API contract is defined in the [openrpc.json](openrpc.json) file.
|
||||
|
||||
### 3.2. Authentication Flow
|
||||
|
||||
The authentication mechanism is designed to verify that a client possesses the private key corresponding to a given public key, without ever exposing the private key. The entire flow happens over the established WebSocket connection.
|
||||
|
||||
**Sequence of Events:**
|
||||
1. **Keypair**: The client is instantiated with a `secp256k1` keypair.
|
||||
2. **Nonce Request**: The client sends a `fetch_nonce` JSON-RPC request containing its public key.
|
||||
3. **Nonce Issuance**: The server generates a unique, single-use nonce, stores it in the actor's state, and returns it to the client in a JSON-RPC response.
|
||||
4. **Signature Creation**: The client signs the received nonce with its private key.
|
||||
5. **Authentication Request**: The client sends an `authenticate` JSON-RPC message, containing the public key and the generated signature.
|
||||
6. **Signature Verification**: The server's WebSocket actor retrieves the stored nonce for the given public key and cryptographically verifies the signature.
|
||||
7. **Session Update**: If verification is successful, the server marks the client's WebSocket session as "authenticated," granting it access to protected methods like `play`.
|
||||
|
||||
## 4. Diagrams
|
||||
|
||||
### 4.1. System Component Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "User Machine"
|
||||
Launcher[🚀 launcher]
|
||||
CirclesConfig[circles.json]
|
||||
Launcher -- Reads --> CirclesConfig
|
||||
end
|
||||
|
||||
subgraph "Spawned Processes"
|
||||
direction LR
|
||||
subgraph "Circle 1"
|
||||
Server1[🌐 server_ws on port 9001]
|
||||
end
|
||||
subgraph "Circle 2"
|
||||
Server2[🌐 server_ws on port 9002]
|
||||
end
|
||||
end
|
||||
|
||||
Launcher -- Spawns & Manages --> Server1
|
||||
Launcher -- Spawns & Manages --> Server2
|
||||
|
||||
subgraph "Clients"
|
||||
Client1[💻 client_ws]
|
||||
Client2[💻 client_ws]
|
||||
end
|
||||
|
||||
Client1 -- Connects via WebSocket --> Server1
|
||||
Client2 -- Connects via WebSocket --> Server2
|
||||
```
|
||||
|
||||
### 4.2. Authentication Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as client_ws
|
||||
participant WsActor as CircleWs Actor (WebSocket)
|
||||
|
||||
Client->>Client: Instantiate with keypair
|
||||
|
||||
Note over Client: Has public_key, private_key
|
||||
|
||||
Client->>+WsActor: JSON-RPC "fetch_nonce" (pubkey)
|
||||
WsActor->>WsActor: generate_nonce()
|
||||
WsActor->>WsActor: store_nonce(pubkey, nonce)
|
||||
WsActor-->>-Client: JSON-RPC Response ({"nonce": "..."})
|
||||
|
||||
Client->>Client: sign(nonce, private_key)
|
||||
|
||||
Note over Client: Has signature
|
||||
|
||||
Client->>+WsActor: JSON-RPC "authenticate" (pubkey, signature)
|
||||
WsActor->>WsActor: retrieve_nonce(pubkey)
|
||||
WsActor->>WsActor: verify_signature(nonce, signature, pubkey)
|
||||
|
||||
alt Signature is Valid
|
||||
WsActor->>WsActor: Set session as authenticated
|
||||
WsActor-->>-Client: JSON-RPC Response ({"authenticated": true})
|
||||
else Signature is Invalid
|
||||
WsActor-->>-Client: JSON-RPC Error (Invalid Credentials)
|
||||
end
|
||||
|
||||
Note over WsActor: Subsequent "play" requests will include the authenticated public key.
|
351
docs/aidocs/IMPLEMENTATION_PLAN.md
Normal file
351
docs/aidocs/IMPLEMENTATION_PLAN.md
Normal file
@ -0,0 +1,351 @@
|
||||
# Multi-Circle Single-Server Implementation Plan
|
||||
|
||||
## Overview
|
||||
Transform the current launcher from a JSON-config based multi-server system to a command-line driven single-server system that handles multiple circles via path-based routing.
|
||||
|
||||
## Architecture Changes
|
||||
|
||||
### Current Architecture:
|
||||
```
|
||||
Launcher → circles.json → Multiple Servers (one per circle, different ports)
|
||||
```
|
||||
|
||||
### New Architecture:
|
||||
```
|
||||
Launcher → CLI args → Single Server (one port, path-based routing)
|
||||
```
|
||||
|
||||
## Phase 1: Update Launcher Interface
|
||||
|
||||
### 1.1 New Command Line Interface
|
||||
```bash
|
||||
# New launcher usage
|
||||
cargo run --bin launcher -- --port 8080 --public-keys "pk1,pk2,pk3"
|
||||
# or with multiple -k flags
|
||||
cargo run --bin launcher -- --port 8080 -k "pk1" -k "pk2" -k "pk3"
|
||||
```
|
||||
|
||||
### 1.2 Updated Args Structure
|
||||
Replace current `Args` struct with:
|
||||
```rust
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// Port for the WebSocket server
|
||||
#[arg(short, long, default_value = "8080")]
|
||||
pub port: u16,
|
||||
|
||||
/// Circle public keys (hex format, can be specified multiple times)
|
||||
#[arg(short = 'k', long = "public-key")]
|
||||
pub public_keys: Vec<String>,
|
||||
|
||||
/// Redis URL
|
||||
#[arg(long, default_value = "redis://127.0.0.1:6379")]
|
||||
pub redis_url: String,
|
||||
|
||||
/// Enable authentication
|
||||
#[arg(long)]
|
||||
pub enable_auth: bool,
|
||||
|
||||
/// Enable debug mode
|
||||
#[arg(short, long)]
|
||||
pub debug: bool,
|
||||
|
||||
/// Verbosity level
|
||||
#[arg(short, long, action = clap::ArgAction::Count)]
|
||||
pub verbose: u8,
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 Remove Obsolete Code
|
||||
- Remove `CircleConfig` struct
|
||||
- Remove `SimpleArgs` struct
|
||||
- Remove `run_simple_launcher` function
|
||||
- Remove JSON parsing logic from `main.rs`
|
||||
- Remove `simple.rs` binary entirely
|
||||
|
||||
## Phase 2: Multi-Circle Server Architecture
|
||||
|
||||
### 2.1 New Server Configuration
|
||||
Update `ServerConfig` to support multiple circles:
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct MultiCircleServerConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub redis_url: String,
|
||||
pub circle_public_keys: Vec<String>, // List of allowed circles
|
||||
pub enable_auth: bool,
|
||||
pub enable_tls: bool,
|
||||
pub cert_path: Option<String>,
|
||||
pub key_path: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Path-Based Routing
|
||||
Update WebSocket routing from `/ws` to `/{circle_pk}`:
|
||||
```rust
|
||||
// New route pattern in spawn_circle_server
|
||||
.route("/{circle_pk}", web::get().to(ws_handler))
|
||||
|
||||
// Updated handler signature
|
||||
async fn ws_handler(
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
path: web::Path<String>, // circle_pk from URL
|
||||
server_config: web::Data<MultiCircleServerConfig>,
|
||||
) -> Result<HttpResponse, Error>
|
||||
```
|
||||
|
||||
### 2.3 Circle Validation
|
||||
Add validation logic in `ws_handler`:
|
||||
```rust
|
||||
let circle_pk = path.into_inner();
|
||||
|
||||
// Validate circle_pk is in allowed list
|
||||
if !server_config.circle_public_keys.contains(&circle_pk) {
|
||||
return Ok(HttpResponse::NotFound()
|
||||
.json(json!({
|
||||
"error": "Circle not found",
|
||||
"message": format!("Circle '{}' is not available on this server", circle_pk)
|
||||
})));
|
||||
}
|
||||
```
|
||||
|
||||
## Phase 3: Per-Circle Authentication
|
||||
|
||||
### 3.1 Update CircleWs Actor
|
||||
Modify `CircleWs` to work with specific circle from URL:
|
||||
```rust
|
||||
struct CircleWs {
|
||||
circle_public_key: String, // From URL path
|
||||
redis_url: String,
|
||||
nonce_store: HashMap<String, NonceResponse>,
|
||||
auth_enabled: bool,
|
||||
authenticated_pubkey: Option<String>,
|
||||
}
|
||||
|
||||
impl CircleWs {
|
||||
fn new_for_circle(
|
||||
circle_public_key: String,
|
||||
redis_url: String,
|
||||
auth_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
circle_public_key,
|
||||
redis_url,
|
||||
nonce_store: HashMap::new(),
|
||||
auth_enabled,
|
||||
authenticated_pubkey: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Circle-Specific Authentication
|
||||
Update authentication logic in `handle_authenticate`:
|
||||
- Authentication challenges are specific to the circle from URL path
|
||||
- Signature verification uses the circle's public key context
|
||||
- Each circle maintains separate authentication state
|
||||
|
||||
## Phase 4: Worker Communication Updates
|
||||
|
||||
### 4.1 Update Launcher Worker Spawning
|
||||
Modify `setup_and_spawn_circles` to:
|
||||
- Accept list of public keys instead of CircleConfig
|
||||
- Spawn one worker per public key
|
||||
- Launch single server instance with all public keys
|
||||
|
||||
### 4.2 Play Request Routing
|
||||
Update `handle_play` to route to correct worker:
|
||||
```rust
|
||||
// Use circle_public_key from URL path for worker routing
|
||||
rhai_client
|
||||
.new_play_request()
|
||||
.recipient_id(&self.circle_public_key) // From URL path
|
||||
.script_path(&script_content)
|
||||
.timeout(TASK_TIMEOUT_DURATION)
|
||||
.await_response()
|
||||
.await
|
||||
```
|
||||
|
||||
## Phase 5: Updated Launcher Logic
|
||||
|
||||
### 5.1 New Setup Function
|
||||
Replace `setup_and_spawn_circles` with:
|
||||
```rust
|
||||
pub async fn setup_multi_circle_server(
|
||||
public_keys: Vec<String>,
|
||||
port: u16,
|
||||
redis_url: String,
|
||||
enable_auth: bool,
|
||||
) -> Result<(Vec<JoinHandle<_>>, ServerHandle), Box<dyn std::error::Error>>
|
||||
```
|
||||
|
||||
### 5.2 Single Server Spawn
|
||||
Launch one server instance that handles all circles:
|
||||
```rust
|
||||
let server_config = MultiCircleServerConfig {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port,
|
||||
redis_url: redis_url.clone(),
|
||||
circle_public_keys: public_keys.clone(),
|
||||
enable_auth,
|
||||
enable_tls: false,
|
||||
cert_path: None,
|
||||
key_path: None,
|
||||
};
|
||||
|
||||
let (server_task, server_handle) = spawn_multi_circle_server(server_config)?;
|
||||
```
|
||||
|
||||
### 5.3 Worker Spawning Per Circle
|
||||
Spawn one worker per public key:
|
||||
```rust
|
||||
let mut worker_handles = Vec::new();
|
||||
for public_key in &public_keys {
|
||||
let worker_handle = spawn_rhai_worker(
|
||||
public_key.clone(),
|
||||
public_key.clone(),
|
||||
engine.clone(),
|
||||
redis_url.clone(),
|
||||
worker_shutdown_rx,
|
||||
preserve_tasks,
|
||||
);
|
||||
worker_handles.push(worker_handle);
|
||||
}
|
||||
```
|
||||
|
||||
## Phase 6: File Structure Changes
|
||||
|
||||
### 6.1 Remove Files
|
||||
- Delete `src/launcher/src/cmd/simple.rs`
|
||||
- Remove simple binary from `Cargo.toml`
|
||||
|
||||
### 6.2 Update Main Binary
|
||||
Update `src/launcher/src/cmd/main.rs` to use new CLI args instead of JSON config
|
||||
|
||||
### 6.3 Update Library Exports
|
||||
Remove exports for:
|
||||
- `SimpleArgs`
|
||||
- `run_simple_launcher`
|
||||
- `CircleConfig`
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
CLI[Launcher CLI<br/>--port 8080<br/>-k pk1 -k pk2 -k pk3<br/>--enable-auth]
|
||||
|
||||
CLI --> L[Launcher Process]
|
||||
|
||||
L --> W1[Worker: pk1<br/>Redis Queue: rhai_tasks:pk1]
|
||||
L --> W2[Worker: pk2<br/>Redis Queue: rhai_tasks:pk2]
|
||||
L --> W3[Worker: pk3<br/>Redis Queue: rhai_tasks:pk3]
|
||||
|
||||
L --> S[Single Server Instance<br/>Port 8080<br/>MultiCircleServerConfig]
|
||||
|
||||
C1[Client 1] --> |wss://127.0.0.1:8080/pk1| S
|
||||
C2[Client 2] --> |wss://127.0.0.1:8080/pk2| S
|
||||
C3[Client 3] --> |wss://127.0.0.1:8080/pk3| S
|
||||
|
||||
S --> |Validate pk1 in allowed list| V1[Circle Validation]
|
||||
S --> |Validate pk2 in allowed list| V2[Circle Validation]
|
||||
S --> |Validate pk3 in allowed list| V3[Circle Validation]
|
||||
|
||||
V1 --> |Create CircleWs for pk1| WS1[CircleWs Actor: pk1]
|
||||
V2 --> |Create CircleWs for pk2| WS2[CircleWs Actor: pk2]
|
||||
V3 --> |Create CircleWs for pk3| WS3[CircleWs Actor: pk3]
|
||||
|
||||
WS1 --> |Route play requests| W1
|
||||
WS2 --> |Route play requests| W2
|
||||
WS3 --> |Route play requests| W3
|
||||
|
||||
subgraph "Redis Queues"
|
||||
R1[rhai_tasks:pk1]
|
||||
R2[rhai_tasks:pk2]
|
||||
R3[rhai_tasks:pk3]
|
||||
end
|
||||
|
||||
W1 <--> R1
|
||||
W2 <--> R2
|
||||
W3 <--> R3
|
||||
|
||||
subgraph "Authentication Per Circle"
|
||||
A1[Auth State: pk1]
|
||||
A2[Auth State: pk2]
|
||||
A3[Auth State: pk3]
|
||||
end
|
||||
|
||||
WS1 <--> A1
|
||||
WS2 <--> A2
|
||||
WS3 <--> A3
|
||||
```
|
||||
|
||||
## Implementation Steps Summary
|
||||
|
||||
### Step 1: Update Launcher Args
|
||||
- Replace `Args` struct with new CLI interface
|
||||
- Remove JSON config dependencies
|
||||
- Add public key validation
|
||||
|
||||
### Step 2: Create Multi-Circle Server Config
|
||||
- Replace `ServerConfig` with `MultiCircleServerConfig`
|
||||
- Support list of circle public keys
|
||||
- Maintain TLS and auth options
|
||||
|
||||
### Step 3: Implement Path-Based Routing
|
||||
- Update WebSocket route from `/ws` to `/{circle_pk}`
|
||||
- Add circle validation before WebSocket upgrade
|
||||
- Return HTTP 404 for invalid circles
|
||||
|
||||
### Step 4: Update CircleWs Actor
|
||||
- Extract circle public key from URL path
|
||||
- Implement per-circle authentication
|
||||
- Route to correct worker based on circle
|
||||
|
||||
### Step 5: Update Launcher Logic
|
||||
- Remove JSON parsing
|
||||
- Spawn single server with multiple circles
|
||||
- Spawn one worker per circle public key
|
||||
|
||||
### Step 6: Clean Up Code
|
||||
- Remove `SimpleArgs`, `CircleConfig`, `run_simple_launcher`
|
||||
- Delete simple binary
|
||||
- Update exports and documentation
|
||||
|
||||
## Benefits of This Architecture
|
||||
|
||||
1. **Simplified Deployment**: Single command, single port, multiple circles
|
||||
2. **Resource Efficiency**: Shared server infrastructure
|
||||
3. **Clear Separation**: Per-circle authentication and worker routing
|
||||
4. **Scalable**: Easy to add/remove circles via CLI
|
||||
5. **Maintainable**: Less complex than multi-server approach
|
||||
|
||||
## Example Usage
|
||||
|
||||
```bash
|
||||
# Launch server with 3 circles on port 8080 with auth enabled
|
||||
cargo run --bin launcher -- \
|
||||
--port 8080 \
|
||||
--enable-auth \
|
||||
-k "02a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab" \
|
||||
-k "03b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abc1" \
|
||||
-k "02c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abcd12"
|
||||
|
||||
# Clients connect to specific circles:
|
||||
# wss://127.0.0.1:8080/02a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab
|
||||
# wss://127.0.0.1:8080/03b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abc1
|
||||
# wss://127.0.0.1:8080/02c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abcd12
|
||||
```
|
||||
|
||||
## Key Requirements Satisfied
|
||||
|
||||
1. ✅ **Remove JSON config functionality** - Completely replaced with CLI args
|
||||
2. ✅ **Accept list of circle public keys** - Via `-k` flags or comma-separated
|
||||
3. ✅ **Single port for multiple circles** - One server handles all circles
|
||||
4. ✅ **Path-based routing** - `wss://127.0.0.1:port/circle_pk`
|
||||
5. ✅ **Per-circle authentication** - Auth against specific circle's public key
|
||||
6. ✅ **Route to appropriate worker** - Based on circle public key from URL
|
||||
7. ✅ **Use hex format everywhere** - Consistent secp256k1 hex encoding
|
||||
8. ✅ **HTTP 404 for invalid circles** - Before WebSocket upgrade
|
1
docs/aidocs/README.md
Normal file
1
docs/aidocs/README.md
Normal file
@ -0,0 +1 @@
|
||||
Docs generated by ai that need to be organized.
|
382
docs/aidocs/WSS_IMPLEMENTATION_PLAN.md
Normal file
382
docs/aidocs/WSS_IMPLEMENTATION_PLAN.md
Normal file
@ -0,0 +1,382 @@
|
||||
# WSS (WebSocket Secure) Implementation Plan
|
||||
|
||||
## Overview
|
||||
This document outlines the complete implementation plan for adding WSS support to both the server and client_ws components, with optional configuration and comprehensive examples.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Server (src/server)
|
||||
- ✅ Basic TLS infrastructure with `rustls` and `rustls-pemfile`
|
||||
- ✅ Certificate loading functionality (`load_rustls_config`)
|
||||
- ✅ Optional TLS configuration in `ServerConfig`
|
||||
- ✅ Existing cert.pem and key.pem files for testing
|
||||
- ⚠️ TLS configuration could be enhanced with better error handling
|
||||
- ⚠️ Missing WSS-specific examples and tests
|
||||
|
||||
### Client (src/client_ws)
|
||||
- ✅ Basic TLS support with `native-tls` and `tokio-native-tls`
|
||||
- ✅ Cross-platform WebSocket support (native + WASM)
|
||||
- ⚠️ TLS connector accepts invalid certificates (development mode)
|
||||
- ⚠️ No automatic WSS URL detection
|
||||
- ⚠️ Missing WSS-specific configuration options
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Server WSS Enhancement ✅ READY TO IMPLEMENT
|
||||
|
||||
#### 1.1 Enhanced ServerConfig
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct ServerConfig {
|
||||
pub circle_name: String,
|
||||
pub circle_public_key: String,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub redis_url: String,
|
||||
pub enable_auth: bool,
|
||||
|
||||
// Enhanced TLS Configuration
|
||||
pub enable_tls: bool, // Explicit TLS enable flag
|
||||
pub cert_path: Option<String>, // Path to certificate file
|
||||
pub key_path: Option<String>, // Path to private key file
|
||||
pub tls_port: Option<u16>, // Optional separate TLS port
|
||||
pub tls_accept_invalid_certs: bool, // For development
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 Improved TLS Loading
|
||||
- Better error messages for certificate loading failures
|
||||
- Validation of certificate and key file existence
|
||||
- Support for different certificate formats
|
||||
- Logging of TLS configuration status
|
||||
|
||||
#### 1.3 Enhanced spawn_circle_server
|
||||
- Clear logging of HTTP vs HTTPS mode
|
||||
- Better error handling for TLS configuration
|
||||
- Support for running both HTTP and HTTPS simultaneously (if needed)
|
||||
|
||||
### Phase 2: Client WSS Enhancement
|
||||
|
||||
#### 2.1 WSS URL Detection
|
||||
```rust
|
||||
impl CircleWsClient {
|
||||
fn is_wss_url(&self) -> bool {
|
||||
self.ws_url.starts_with("wss://")
|
||||
}
|
||||
|
||||
fn requires_tls(&self) -> bool {
|
||||
self.is_wss_url()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 Enhanced TLS Configuration
|
||||
```rust
|
||||
pub struct TlsConfig {
|
||||
pub accept_invalid_certs: bool,
|
||||
pub accept_invalid_hostnames: bool,
|
||||
pub ca_cert_path: Option<String>,
|
||||
pub client_cert_path: Option<String>,
|
||||
pub client_key_path: Option<String>,
|
||||
}
|
||||
|
||||
impl CircleWsClientBuilder {
|
||||
pub fn with_tls_config(mut self, tls_config: TlsConfig) -> Self {
|
||||
self.tls_config = Some(tls_config);
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 Cross-Platform WSS Support
|
||||
- Native: Enhanced `tokio-tungstenite` with `rustls` support
|
||||
- WASM: Ensure `gloo-net` handles WSS URLs correctly
|
||||
- Consistent error handling across platforms
|
||||
|
||||
### Phase 3: Examples and Testing
|
||||
|
||||
#### 3.1 Basic WSS Example
|
||||
```rust
|
||||
// examples/wss_basic_example.rs
|
||||
// Demonstrates basic WSS connection without authentication
|
||||
```
|
||||
|
||||
#### 3.2 WSS + Authentication Example
|
||||
```rust
|
||||
// examples/wss_auth_example.rs
|
||||
// Demonstrates WSS connection with secp256k1 authentication
|
||||
```
|
||||
|
||||
#### 3.3 End-to-End Secure Example
|
||||
```rust
|
||||
// examples/wss_end_to_end_example.rs
|
||||
// Complete server + client WSS with authentication
|
||||
```
|
||||
|
||||
#### 3.4 Certificate Generation Helper
|
||||
```rust
|
||||
// examples/wss_cert_generation.rs
|
||||
// Helper to generate self-signed certificates for development
|
||||
```
|
||||
|
||||
### Phase 4: Documentation and Integration
|
||||
|
||||
#### 4.1 Update README files
|
||||
- Server WSS configuration guide
|
||||
- Client WSS usage examples
|
||||
- Certificate management instructions
|
||||
|
||||
#### 4.2 Integration Tests
|
||||
- WSS connection establishment
|
||||
- WSS + authentication flow
|
||||
- Certificate validation scenarios
|
||||
|
||||
## Technical Implementation Details
|
||||
|
||||
### Server Enhancements
|
||||
|
||||
#### Enhanced Certificate Loading
|
||||
```rust
|
||||
fn load_rustls_config(
|
||||
cert_path: &str,
|
||||
key_path: &str,
|
||||
) -> Result<RustlsServerConfig, TlsConfigError> {
|
||||
// Validate file existence
|
||||
if !std::path::Path::new(cert_path).exists() {
|
||||
return Err(TlsConfigError::CertificateNotFound(cert_path.to_string()));
|
||||
}
|
||||
|
||||
if !std::path::Path::new(key_path).exists() {
|
||||
return Err(TlsConfigError::PrivateKeyNotFound(key_path.to_string()));
|
||||
}
|
||||
|
||||
// Enhanced error handling for certificate loading
|
||||
// Support for different key formats (PKCS8, RSA, etc.)
|
||||
// Validation of certificate chain
|
||||
}
|
||||
```
|
||||
|
||||
#### TLS Error Types
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TlsConfigError {
|
||||
#[error("Certificate file not found: {0}")]
|
||||
CertificateNotFound(String),
|
||||
#[error("Private key file not found: {0}")]
|
||||
PrivateKeyNotFound(String),
|
||||
#[error("Invalid certificate format: {0}")]
|
||||
InvalidCertificate(String),
|
||||
#[error("Invalid private key format: {0}")]
|
||||
InvalidPrivateKey(String),
|
||||
#[error("TLS configuration error: {0}")]
|
||||
ConfigurationError(String),
|
||||
}
|
||||
```
|
||||
|
||||
### Client Enhancements
|
||||
|
||||
#### WSS Connection Logic
|
||||
```rust
|
||||
impl CircleWsClient {
|
||||
async fn create_tls_connector(&self) -> Result<TlsConnector, CircleWsClientError> {
|
||||
let mut builder = TlsConnector::builder();
|
||||
|
||||
if let Some(tls_config) = &self.tls_config {
|
||||
builder.danger_accept_invalid_certs(tls_config.accept_invalid_certs);
|
||||
builder.danger_accept_invalid_hostnames(tls_config.accept_invalid_hostnames);
|
||||
|
||||
// Load custom CA certificates if provided
|
||||
if let Some(ca_cert_path) = &tls_config.ca_cert_path {
|
||||
// Load and add CA certificate
|
||||
}
|
||||
}
|
||||
|
||||
builder.build().map_err(|e| {
|
||||
CircleWsClientError::ConnectionError(format!("TLS configuration failed: {}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Structure
|
||||
|
||||
#### Basic WSS Example
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Start WSS server
|
||||
let config = ServerConfig {
|
||||
enable_tls: true,
|
||||
cert_path: Some("cert.pem".to_string()),
|
||||
key_path: Some("key.pem".to_string()),
|
||||
port: 8443,
|
||||
// ... other config
|
||||
};
|
||||
|
||||
let (server_task, _handle) = spawn_circle_server(config)?;
|
||||
|
||||
// Connect WSS client
|
||||
let mut client = CircleWsClientBuilder::new("wss://localhost:8443/ws".to_string())
|
||||
.with_tls_config(TlsConfig {
|
||||
accept_invalid_certs: true, // For development
|
||||
..Default::default()
|
||||
})
|
||||
.build();
|
||||
|
||||
client.connect().await?;
|
||||
|
||||
// Test basic functionality
|
||||
let result = client.play("print('Hello WSS!')".to_string()).await?;
|
||||
println!("Result: {}", result.output);
|
||||
|
||||
client.disconnect().await;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
#### WSS + Authentication Example
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Start authenticated WSS server
|
||||
let config = ServerConfig {
|
||||
enable_tls: true,
|
||||
enable_auth: true,
|
||||
cert_path: Some("cert.pem".to_string()),
|
||||
key_path: Some("key.pem".to_string()),
|
||||
port: 8443,
|
||||
// ... other config
|
||||
};
|
||||
|
||||
// Connect authenticated WSS client
|
||||
let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
|
||||
|
||||
let mut client = CircleWsClientBuilder::new("wss://localhost:8443/ws".to_string())
|
||||
.with_keypair(private_key.to_string())
|
||||
.with_tls_config(TlsConfig {
|
||||
accept_invalid_certs: true,
|
||||
..Default::default()
|
||||
})
|
||||
.build();
|
||||
|
||||
client.connect().await?;
|
||||
|
||||
// Authenticate over secure connection
|
||||
match client.authenticate().await? {
|
||||
true => println!("Authenticated successfully over WSS"),
|
||||
false => println!("Authentication failed"),
|
||||
}
|
||||
|
||||
// Test authenticated request over secure connection
|
||||
let result = client.play("print('Authenticated WSS request!')".to_string()).await?;
|
||||
println!("Secure result: {}", result.output);
|
||||
|
||||
client.disconnect().await;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- TLS configuration validation
|
||||
- Certificate loading error handling
|
||||
- WSS URL detection
|
||||
- TLS connector creation
|
||||
|
||||
### Integration Tests
|
||||
- WSS server startup with valid certificates
|
||||
- WSS client connection establishment
|
||||
- WSS + authentication flow
|
||||
- Error scenarios (invalid certificates, connection failures)
|
||||
|
||||
### End-to-End Tests
|
||||
- Complete WSS server + client communication
|
||||
- Authentication over WSS
|
||||
- Multiple concurrent WSS connections
|
||||
- Certificate validation scenarios
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Development vs Production
|
||||
- **Development**: Accept self-signed certificates, invalid hostnames
|
||||
- **Production**: Strict certificate validation, proper CA chains
|
||||
|
||||
### Certificate Management
|
||||
- Clear documentation for certificate generation
|
||||
- Support for Let's Encrypt certificates
|
||||
- Certificate rotation considerations
|
||||
|
||||
### TLS Configuration
|
||||
- Modern TLS versions (1.2+)
|
||||
- Secure cipher suites
|
||||
- HSTS headers for web clients
|
||||
|
||||
## File Changes Required
|
||||
|
||||
### New Files
|
||||
- `examples/wss_basic_example.rs`
|
||||
- `examples/wss_auth_example.rs`
|
||||
- `examples/wss_end_to_end_example.rs`
|
||||
- `examples/wss_cert_generation.rs`
|
||||
- `src/server/src/tls_config.rs` (optional)
|
||||
- `src/client_ws/src/tls_config.rs` (optional)
|
||||
|
||||
### Modified Files
|
||||
- `src/server/src/lib.rs` - Enhanced TLS support
|
||||
- `src/server/cmd/main.rs` - TLS CLI options
|
||||
- `src/client_ws/src/lib.rs` - WSS support
|
||||
- `src/client_ws/Cargo.toml` - Additional TLS dependencies
|
||||
- `src/server/Cargo.toml` - Enhanced TLS dependencies
|
||||
- `src/server/README.md` - WSS documentation
|
||||
- `src/client_ws/README.md` - WSS usage guide
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Server Additional Dependencies
|
||||
```toml
|
||||
# Enhanced TLS support
|
||||
rustls-webpki = "0.103"
|
||||
rustls-native-certs = "0.7"
|
||||
```
|
||||
|
||||
### Client Additional Dependencies
|
||||
```toml
|
||||
# Enhanced TLS support for native
|
||||
rustls = { version = "0.23", optional = true }
|
||||
tokio-rustls = { version = "0.26", optional = true }
|
||||
rustls-native-certs = { version = "0.7", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["crypto"]
|
||||
crypto = ["secp256k1", "sha3"]
|
||||
rustls-tls = ["rustls", "tokio-rustls", "rustls-native-certs"]
|
||||
```
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **Phase 1a**: Enhance server TLS configuration and error handling
|
||||
2. **Phase 1b**: Create basic WSS server example and test
|
||||
3. **Phase 1c**: Validate server WSS functionality with manual testing
|
||||
4. **Phase 2a**: Enhance client WSS support
|
||||
5. **Phase 2b**: Create client WSS examples
|
||||
6. **Phase 2c**: Test client WSS connectivity
|
||||
7. **Phase 3**: Create end-to-end WSS examples
|
||||
8. **Phase 4**: Integration tests and documentation
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- ✅ Server can start with WSS enabled using existing certificates
|
||||
- ✅ Client can connect to WSS server with proper TLS validation
|
||||
- ✅ Authentication works over WSS connections
|
||||
- ✅ Examples demonstrate all WSS functionality
|
||||
- ✅ Tests validate WSS behavior
|
||||
- ✅ Documentation explains WSS configuration
|
||||
- ✅ Cross-platform compatibility (native + WASM)
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Switch to Code mode for implementation
|
||||
2. Start with Phase 1a: Server TLS enhancements
|
||||
3. Create and test basic WSS server example
|
||||
4. Validate functionality before proceeding to client
|
59
docs/design.md
Normal file
59
docs/design.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Design
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines a system design that satisfies the specified requirements for decentralized backend ownership. It describes how to implement core capabilities like isolation, delegation, and open logic control — without introducing tight coupling or central dependencies.
|
||||
|
||||
## Design Principles
|
||||
|
||||
### 1. **Contextual Execution**
|
||||
- Define a runtime model where each peer context is a named environment.
|
||||
- Execution is scoped to a context, and all operations are resolved within it.
|
||||
|
||||
**Implementation Strategy:**
|
||||
- Use a unified worker engine that can load and execute within a namespaced peer context.
|
||||
- Contexts are mounted via a virtual filesystem abstraction, one directory per peer.
|
||||
|
||||
### 2. **Logical Isolation via Filesystem Namespacing**
|
||||
- Each peer's execution environment is backed by a namespaced root directory.
|
||||
- All storage operations are relative to that root.
|
||||
|
||||
**Advantages:**
|
||||
- Easy enforcement of data boundaries
|
||||
- Works across shared processes
|
||||
|
||||
### 3. **Script-Based Delegated Execution**
|
||||
- Scripts are the unit of cross-peer interaction.
|
||||
- A script includes the `caller` (originating peer), parameters, and logic.
|
||||
|
||||
**Design Feature:**
|
||||
- A script sent to another peer is evaluated with both `caller` and `target` contexts available to the runtime.
|
||||
- Target peer decides whether to accept and how to interpret it.
|
||||
|
||||
### 4. **Policy-Driven Acceptance**
|
||||
- Each context has policies determining:
|
||||
- Which peers may send scripts
|
||||
- Which actions are allowed
|
||||
|
||||
**Example:** Policies written as declarative access control rules, tied to peer IDs, namespaces, or capabilities.
|
||||
|
||||
### 5. **Open, Modifiable Logic**
|
||||
- Use an embedded domain-specific language (e.g. Rhai) that allows:
|
||||
- Peer owners to define and inspect their logic
|
||||
- Script modules to be composed, extended, or overridden
|
||||
|
||||
### 6. **Worker Multiplexing**
|
||||
- Use a single worker binary that can handle one or many peer contexts.
|
||||
- The context is dynamically determined at runtime.
|
||||
|
||||
**Design Note:**
|
||||
- All workers enforce namespacing, even when only one peer is active per process.
|
||||
- Supports both isolated (1 peer per worker) and shared (many peers per worker) deployments.
|
||||
|
||||
## Optional Enhancements
|
||||
|
||||
- Pluggable transport layer (WebSocket, HTTP/2, NATS, etc.)
|
||||
- Pluggable storage backends for namespace-mounting (FS, S3, SQLite, etc.)
|
||||
- Declarative schema binding between DSL and structured data
|
||||
|
||||
This design enables decentralized application runtime control while supporting a scalable and secure execution model.
|
34
docs/rationale.md
Normal file
34
docs/rationale.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Rethinking Backend Ownership
|
||||
|
||||
## Motivation
|
||||
|
||||
Modern applications are powered by backends that run on infrastructure and systems controlled by centralized entities. Whether it's social platforms, collaboration tools, or data-driven apps, the backend is almost always a black box — hosted, maintained, and operated by someone else.
|
||||
|
||||
This has profound implications:
|
||||
|
||||
- **Loss of autonomy:** Users are locked out of the logic, rules, and data structures that govern their digital experience.
|
||||
- **Opaque control:** Application behavior can change without the user’s consent — and often without visibility.
|
||||
- **Vendor lock-in:** Switching providers or migrating data is often non-trivial, risky, or impossible.
|
||||
- **Security and privacy risks:** Centralized backends present single points of failure and attack.
|
||||
|
||||
In this model, users are not participants in their computing environment — they are clients of someone else's backend.
|
||||
|
||||
## The Vision
|
||||
|
||||
The purpose of this initiative is to invert that dynamic. We aim to establish a paradigm where users and organizations **own and control their own backend logic and data**, without sacrificing connectivity, collaboration, or scalability.
|
||||
|
||||
This means:
|
||||
|
||||
- **Local authority:** Each user or organization should have full control over how their backend behaves — what code runs, what data is stored, and who can access it.
|
||||
- **Portable and interoperable:** Ownership must not mean isolation. User-owned backends should be able to interact with one another on equal footing.
|
||||
- **Transparent logic:** Application behavior should be visible, inspectable, and modifiable by the user.
|
||||
- **Delegation, not dependence:** Users should be able to cooperate and interact by delegating execution to each other — not by relying on a central server.
|
||||
|
||||
## What We Stand For
|
||||
|
||||
- **Agency:** You control your digital environment.
|
||||
- **Decentralization:** No central chokepoint for computation or data.
|
||||
- **Modularity:** Users compose their backend behavior, not inherit it from a monolith.
|
||||
- **Resilience:** Systems should degrade gracefully, fail independently, and recover without central orchestration.
|
||||
|
||||
This is about building a more equitable and open computing model — one where the backend serves you, not the other way around.
|
50
docs/system_requirements_specification.md
Normal file
50
docs/system_requirements_specification.md
Normal file
@ -0,0 +1,50 @@
|
||||
# System Requirements Specification
|
||||
|
||||
## Objective
|
||||
|
||||
To define the core requirements for a system that fulfills the goals of decentralized backend ownership — enabling individuals and organizations to control, operate, and interact through their own backend environments without relying on centralized infrastructure.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### 1. **Isolated Execution Contexts**
|
||||
- Each user or peer must operate within a distinct, logically isolated execution context.
|
||||
- Contexts must not be able to interfere with each other's state or runtime.
|
||||
|
||||
### 2. **Cross-Context Communication**
|
||||
- Peers must be able to initiate interactions with other peers.
|
||||
- Communication must include origin metadata (who initiated it), and be authorized by the target context.
|
||||
|
||||
### 3. **Delegated Execution**
|
||||
- A peer must be able to send code or instructions to another peer for execution, under the recipient's policies.
|
||||
- The recipient must treat the execution as contextualized by the caller, but constrained by its own local rules.
|
||||
|
||||
### 4. **Ownership of Logic and Data**
|
||||
- Users must be able to inspect, modify, and extend the logic that governs their backend.
|
||||
- Data storage and access policies must be under the control of the peer.
|
||||
|
||||
### 5. **Composability and Modifiability**
|
||||
- System behavior must be defined by open, composable modules or scripts.
|
||||
- Users must be able to override default behavior or extend it with minimal coupling.
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### 6. **Security and Isolation**
|
||||
- Scripts or instructions from external peers must be sandboxed and policy-checked.
|
||||
- Each execution context must enforce boundaries between data and logic.
|
||||
|
||||
### 7. **Resilience and Redundancy**
|
||||
- Failure of one peer or node must not impact others.
|
||||
- Communication must be asynchronous and fault-tolerant.
|
||||
|
||||
### 8. **Portability**
|
||||
- A peer’s logic and data must be portable across environments and host infrastructure.
|
||||
- No assumption of persistent centralized hosting.
|
||||
|
||||
### 9. **Transparency**
|
||||
- All logic must be auditable by its owner.
|
||||
- Communications between peers must be observable and traceable.
|
||||
|
||||
### 10. **Scalability**
|
||||
- The system must support large numbers of peer contexts, potentially hosted on shared infrastructure without compromising logical separation.
|
||||
|
||||
These requirements define the baseline for any system that claims to decentralize backend control and empower users to operate their own programmable, connected environments.
|
@ -1,68 +0,0 @@
|
||||
# OurWorld Example
|
||||
|
||||
This directory contains a complete example demonstrating a simulated "OurWorld" network, consisting of multiple interconnected "circles" (nodes). Each circle runs its own WebSocket server and a Rhai script worker, all managed by a central launcher.
|
||||
|
||||
This example is designed to showcase:
|
||||
1. **Multi-Circle Configuration**: How to define and configure multiple circles in a single `circles.json` file.
|
||||
2. **Programmatic Launching**: How to use the `launcher` library to start, manage, and monitor these circles from within a Rust application.
|
||||
3. **Dynamic Key Generation**: The launcher generates unique cryptographic keypairs for each circle upon startup.
|
||||
4. **Output Generation**: How to use the `--output` functionality to get a JSON file containing the connection details (public keys, WebSocket URLs, etc.) for each running circle.
|
||||
5. **Graceful Shutdown**: How the launcher handles a `Ctrl+C` signal to shut down all running circles cleanly.
|
||||
|
||||
## Directory Contents
|
||||
|
||||
- `circles.json`: The main configuration file that defines the 7 circles in the OurWorld network, including their names, ports, and associated Rhai scripts.
|
||||
- `scripts/`: This directory contains the individual Rhai scripts that define the behavior of each circle.
|
||||
- `ourworld_output.json` (Generated): This file is created after running the example and contains the runtime details of each circle.
|
||||
|
||||
## How to Run the Example
|
||||
|
||||
There are two ways to run this example, each demonstrating a different way to use the launcher.
|
||||
|
||||
### 1. As a Root Example (Recommended)
|
||||
|
||||
This method runs the launcher programmatically from the root of the workspace and is the simplest way to see the system in action. It uses the `examples/ourworld.rs` file.
|
||||
|
||||
```sh
|
||||
# From the root of the workspace
|
||||
cargo run --example ourworld
|
||||
```
|
||||
|
||||
### 2. As a Crate-Level Example
|
||||
|
||||
This method runs a similar launcher, but as an example *within* the `launcher` crate itself. It uses the `src/launcher/examples/ourworld/main.rs` file. This is useful for testing the launcher in a more isolated context.
|
||||
|
||||
```sh
|
||||
# Navigate to the launcher's crate directory
|
||||
cd src/launcher
|
||||
|
||||
# Run the 'ourworld' example using cargo
|
||||
cargo run --example ourworld
|
||||
```
|
||||
|
||||
### 3. Using the Launcher Binary
|
||||
|
||||
This method uses the main `launcher` binary to run the configuration, which is useful for testing the command-line interface.
|
||||
|
||||
```sh
|
||||
# From the root of the workspace
|
||||
cargo run -p launcher -- --config examples/ourworld/circles.json --output examples/ourworld/ourworld_output.json
|
||||
```
|
||||
|
||||
## What to Expect
|
||||
|
||||
When you run the example, you will see log output indicating that the launcher is starting up, followed by a table summarizing the running circles:
|
||||
|
||||
```
|
||||
+-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+
|
||||
| Name | Public Key | Worker Queue | WS URL |
|
||||
+=================+==================================================================+==========================================+=======================+
|
||||
| OurWorld | 02... | rhai_tasks:02... | ws://127.0.0.1:9000/ws|
|
||||
+-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+
|
||||
| Dunia Cybercity | 03... | rhai_tasks:03... | ws://127.0.0.1:9001/ws|
|
||||
+-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+
|
||||
| ... (and so on for all 7 circles) |
|
||||
+-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+
|
||||
```
|
||||
|
||||
The launcher will then wait for you to press `Ctrl+C` to initiate a graceful shutdown of all services.
|
@ -1,51 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Timur Gordon",
|
||||
"port": 9100,
|
||||
"script_path": "scripts/test_script.rhai",
|
||||
"public_key": "023b0a9d409506f41f5782353857dea6abc16ae4643661cd94d8155fdb498642e3",
|
||||
"secret_key": "7a7074c59ccfa3465686277e9e9da34867b36a1e256271b893b6c22fbc82929e"
|
||||
},
|
||||
{
|
||||
"name": "Kristof de Spiegeleer",
|
||||
"port": 9101,
|
||||
"script_path": "scripts/test_script.rhai",
|
||||
"public_key": "030b62236efa67855b3379a9d4add1facbe8a545bafa86e1d6fbac06caae5b5b12",
|
||||
"secret_key": "04225fbb41d8c397581d7ec19ded8aaf02d8b9daf27fed9617525e4f8114a382"
|
||||
},
|
||||
{
|
||||
"name": "OurWorld",
|
||||
"port": 9000,
|
||||
"script_path": "scripts/ourworld.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Dunia Cybercity",
|
||||
"port": 9001,
|
||||
"script_path": "scripts/dunia_cybercity.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Sikana",
|
||||
"port": 9002,
|
||||
"script_path": "scripts/sikana.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Threefold",
|
||||
"port": 9003,
|
||||
"script_path": "scripts/threefold.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Mbweni",
|
||||
"port": 9004,
|
||||
"script_path": "scripts/mbweni.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Geomind",
|
||||
"port": 9005,
|
||||
"script_path": "scripts/geomind.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Freezone",
|
||||
"port": 9006,
|
||||
"script_path": "scripts/freezone.rhai"
|
||||
}
|
||||
]
|
@ -1,90 +0,0 @@
|
||||
//! Example of launching multiple circles and outputting their details to a file.
|
||||
//!
|
||||
//! This example demonstrates how to use the launcher library to start circles
|
||||
//! programmatically, similar to how the `launcher` binary works.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ```sh
|
||||
//! cargo run --example ourworld
|
||||
//! ```
|
||||
//!
|
||||
//! This will:
|
||||
//! 1. Read the `circles.json` file in the `examples/ourworld` directory.
|
||||
//! 2. Launch all 7 circles defined in the config.
|
||||
//! 3. Create a `ourworld_output.json` file in the same directory with the details.
|
||||
//! 4. The launcher will run until you stop it with Ctrl+C.
|
||||
|
||||
use launcher::{run_launcher, Args, CircleConfig};
|
||||
use log::{error, info};
|
||||
use std::error::Error as StdError;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
println!("--- Launching OurWorld Example Programmatically ---");
|
||||
|
||||
// The example is now at the root of the `examples` directory,
|
||||
// so we can reference its assets directly.
|
||||
let example_dir = PathBuf::from("./examples/ourworld");
|
||||
let config_path = example_dir.join("circles.json");
|
||||
let output_path = example_dir.join("ourworld_output.json");
|
||||
|
||||
println!("Using config file: {:?}", config_path);
|
||||
println!("Output will be written to: {:?}", output_path);
|
||||
|
||||
// Manually construct the arguments instead of parsing from command line.
|
||||
// This is useful when embedding the launcher logic in another application.
|
||||
let args = Args {
|
||||
config_path: config_path.clone(),
|
||||
output: Some(output_path),
|
||||
debug: true, // Enable debug logging for the example
|
||||
verbose: 2, // Set verbosity to max
|
||||
};
|
||||
|
||||
if !config_path.exists() {
|
||||
let msg = format!("Configuration file not found at {:?}", config_path);
|
||||
error!("{}", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
|
||||
let config_content = fs::read_to_string(&config_path)?;
|
||||
|
||||
let mut circle_configs: Vec<CircleConfig> = match serde_json::from_str(&config_content) {
|
||||
Ok(configs) => configs,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to parse {}: {}. Ensure it's a valid JSON array of CircleConfig.",
|
||||
config_path.display(),
|
||||
e
|
||||
);
|
||||
return Err(Box::new(e) as Box<dyn StdError>);
|
||||
}
|
||||
};
|
||||
|
||||
// Make script paths relative to the project root by prepending the example directory path.
|
||||
for config in &mut circle_configs {
|
||||
if let Some(script_path) = &config.script_path {
|
||||
let full_script_path = example_dir.join(script_path);
|
||||
config.script_path = Some(full_script_path.to_string_lossy().into_owned());
|
||||
}
|
||||
}
|
||||
|
||||
if circle_configs.is_empty() {
|
||||
info!(
|
||||
"No circle configurations found in {}. Exiting.",
|
||||
config_path.display()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Starting launcher... Press Ctrl+C to exit.");
|
||||
|
||||
// The run_launcher function will setup logging, spawn circles, print the table,
|
||||
// and wait for a shutdown signal (Ctrl+C).
|
||||
run_launcher(args, circle_configs).await?;
|
||||
|
||||
println!("--- OurWorld Example Finished ---");
|
||||
Ok(())
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Timur Gordon",
|
||||
"public_key": "023b0a9d409506f41f5782353857dea6abc16ae4643661cd94d8155fdb498642e3",
|
||||
"secret_key": "7a7074c59ccfa3465686277e9e9da34867b36a1e256271b893b6c22fbc82929e",
|
||||
"worker_queue": "rhai_tasks:023b0a9d409506f41f5782353857dea6abc16ae4643661cd94d8155fdb498642e3",
|
||||
"ws_url": "ws://127.0.0.1:9100"
|
||||
},
|
||||
{
|
||||
"name": "Kristof de Spiegeleer",
|
||||
"public_key": "030b62236efa67855b3379a9d4add1facbe8a545bafa86e1d6fbac06caae5b5b12",
|
||||
"secret_key": "04225fbb41d8c397581d7ec19ded8aaf02d8b9daf27fed9617525e4f8114a382",
|
||||
"worker_queue": "rhai_tasks:030b62236efa67855b3379a9d4add1facbe8a545bafa86e1d6fbac06caae5b5b12",
|
||||
"ws_url": "ws://127.0.0.1:9101"
|
||||
},
|
||||
{
|
||||
"name": "OurWorld",
|
||||
"public_key": "02c9e7cb76e19adc3b579091d923ef273282070bc4864c1a07e94ca34a78aea8ef",
|
||||
"secret_key": "cb58d003eae0a5168b6f6fc4e822879b17e7b5987e5a76b870b75fe659e3cc60",
|
||||
"worker_queue": "rhai_tasks:02c9e7cb76e19adc3b579091d923ef273282070bc4864c1a07e94ca34a78aea8ef",
|
||||
"ws_url": "ws://127.0.0.1:9000"
|
||||
},
|
||||
{
|
||||
"name": "Dunia Cybercity",
|
||||
"public_key": "02a19f52bdde937a3f05c1bcc9b58c467d5084586a5a0b832617e131886c961771",
|
||||
"secret_key": "c2a9a0c35b7c85cadfaf9e3123829324e4b4b116239833123d73da14b13dfbde",
|
||||
"worker_queue": "rhai_tasks:02a19f52bdde937a3f05c1bcc9b58c467d5084586a5a0b832617e131886c961771",
|
||||
"ws_url": "ws://127.0.0.1:9001"
|
||||
},
|
||||
{
|
||||
"name": "Sikana",
|
||||
"public_key": "032db92879e51d5adf17a99df0cedba49d69e051ffb7b224e5a50e5d02a8f3c68f",
|
||||
"secret_key": "3718bc30c8d3ee1e88f1d88e06ed7637197c2f9b422a5757201acf572e9c7345",
|
||||
"worker_queue": "rhai_tasks:032db92879e51d5adf17a99df0cedba49d69e051ffb7b224e5a50e5d02a8f3c68f",
|
||||
"ws_url": "ws://127.0.0.1:9002"
|
||||
},
|
||||
{
|
||||
"name": "Threefold",
|
||||
"public_key": "021b452667f0c73a9f96c65dfceb0810e36109ad2408e0693a90fd4cdf7d8de0f6",
|
||||
"secret_key": "9e9532bdc279570f22b8bc853eadf8f7cdf5cacd3e506ae06b3a9f34778a372f",
|
||||
"worker_queue": "rhai_tasks:021b452667f0c73a9f96c65dfceb0810e36109ad2408e0693a90fd4cdf7d8de0f6",
|
||||
"ws_url": "ws://127.0.0.1:9003"
|
||||
},
|
||||
{
|
||||
"name": "Mbweni",
|
||||
"public_key": "029423b664660e9d9bcdbde244fda7d2064b17089463ddfb6eed301e34fb115969",
|
||||
"secret_key": "b70c510765d1a3e315871355fb6b902662f465ef317ddbabf146163ad8b83937",
|
||||
"worker_queue": "rhai_tasks:029423b664660e9d9bcdbde244fda7d2064b17089463ddfb6eed301e34fb115969",
|
||||
"ws_url": "ws://127.0.0.1:9004"
|
||||
},
|
||||
{
|
||||
"name": "Geomind",
|
||||
"public_key": "03af7abb8737dfb0d3e7eb22d4e57d5566a82be0ca7c4fe7f7cf1b37ad595044f7",
|
||||
"secret_key": "22a4b7514b3f88a697566f5c1aa12178e0974ae3f7ae6eb7054c4c9cbd30a8fa",
|
||||
"worker_queue": "rhai_tasks:03af7abb8737dfb0d3e7eb22d4e57d5566a82be0ca7c4fe7f7cf1b37ad595044f7",
|
||||
"ws_url": "ws://127.0.0.1:9005"
|
||||
},
|
||||
{
|
||||
"name": "Freezone",
|
||||
"public_key": "03a00761250dd79294ccbc4de916454736ff5a6488d8bb93c759d2dae5abf20b03",
|
||||
"secret_key": "a09d158e6f2b6706bca97a320bbc64b6278fe795820a0759f658f230fd071003",
|
||||
"worker_queue": "rhai_tasks:03a00761250dd79294ccbc4de916454736ff5a6488d8bb93c759d2dae5abf20b03",
|
||||
"ws_url": "ws://127.0.0.1:9006"
|
||||
}
|
||||
]
|
@ -1,249 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Dunia Cybercity")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:8091/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
||||
|
||||
let circle = get_circle();
|
||||
|
||||
print("--- Creating OurWorld Library ---");
|
||||
|
||||
// === IMAGES ===
|
||||
print("Creating images...");
|
||||
|
||||
let nature1 = save_image(new_image()
|
||||
.title("Mountain Sunrise")
|
||||
.description("Breathtaking sunrise over mountain peaks")
|
||||
.url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature2 = save_image(new_image()
|
||||
.title("Ocean Waves")
|
||||
.description("Powerful ocean waves crashing on rocks")
|
||||
.url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature3 = save_image(new_image()
|
||||
.title("Forest Path")
|
||||
.description("Peaceful path through ancient forest")
|
||||
.url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech1 = save_image(new_image()
|
||||
.title("Solar Panels")
|
||||
.description("Modern solar panel installation")
|
||||
.url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech2 = save_image(new_image()
|
||||
.title("Wind Turbines")
|
||||
.description("Wind turbines generating clean energy")
|
||||
.url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space1 = save_image(new_image()
|
||||
.title("Earth from Space")
|
||||
.description("Our beautiful planet from orbit")
|
||||
.url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space2 = save_image(new_image()
|
||||
.title("Galaxy Spiral")
|
||||
.description("Stunning spiral galaxy in deep space")
|
||||
.url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let city1 = save_image(new_image()
|
||||
.title("Smart City")
|
||||
.description("Futuristic smart city at night")
|
||||
.url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
// === PDFs ===
|
||||
print("Creating PDFs...");
|
||||
|
||||
let pdf1 = save_pdf(new_pdf()
|
||||
.title("Climate Action Report 2024")
|
||||
.description("Comprehensive analysis of global climate initiatives")
|
||||
.url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf")
|
||||
.page_count(42));
|
||||
|
||||
let pdf2 = save_pdf(new_pdf()
|
||||
.title("Sustainable Development Goals")
|
||||
.description("UN SDG implementation guide")
|
||||
.url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf")
|
||||
.page_count(35));
|
||||
|
||||
let pdf3 = save_pdf(new_pdf()
|
||||
.title("Renewable Energy Handbook")
|
||||
.description("Technical guide to renewable energy systems")
|
||||
.url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf")
|
||||
.page_count(280));
|
||||
|
||||
let pdf4 = save_pdf(new_pdf()
|
||||
.title("Blockchain for Good")
|
||||
.description("How blockchain technology can solve global challenges")
|
||||
.url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype")
|
||||
.page_count(24));
|
||||
|
||||
let pdf5 = save_pdf(new_pdf()
|
||||
.title("Future of Work Report")
|
||||
.description("Analysis of changing work patterns and remote collaboration")
|
||||
.url("https://www.mckinsey.com/featured-insights/future-of-work")
|
||||
.page_count(156));
|
||||
|
||||
// === MARKDOWN DOCUMENTS ===
|
||||
print("Creating markdown documents...");
|
||||
|
||||
let md1 = save_markdown(new_markdown()
|
||||
.title("OurWorld Mission Statement")
|
||||
.description("Our vision for a better world")
|
||||
.content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities"));
|
||||
|
||||
let md2 = save_markdown(new_markdown()
|
||||
.title("Getting Started Guide")
|
||||
.description("How to join the OurWorld movement")
|
||||
.content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)"));
|
||||
|
||||
let md3 = save_markdown(new_markdown()
|
||||
.title("Technology Roadmap 2024")
|
||||
.description("Our technical development plans")
|
||||
.content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training"));
|
||||
|
||||
let md4 = save_markdown(new_markdown()
|
||||
.title("Community Guidelines")
|
||||
.description("How we work together")
|
||||
.content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone"));
|
||||
|
||||
|
||||
let investor = new_contact()
|
||||
.name("Example Investor")
|
||||
.save_contact();
|
||||
|
||||
let investors = new_group()
|
||||
.name("Investors")
|
||||
.description("A group for example inverstors of ourworld");
|
||||
|
||||
investors.add_contact(investor.id)
|
||||
.save_group();
|
||||
|
||||
// === BOOKS ===
|
||||
print("Creating books...");
|
||||
|
||||
let sustainability_book = save_book(new_book()
|
||||
.title("Sustainability Handbook")
|
||||
.description("Complete guide to sustainable living and practices")
|
||||
.add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.")
|
||||
.add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances")
|
||||
.add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need")
|
||||
.add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively")
|
||||
.add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Waste Reduction").page(2))
|
||||
.add_toc_entry(new_toc_entry().title("Sustainable Food").page(3)));
|
||||
|
||||
let tech_guide_book = save_book(new_book()
|
||||
.title("Green Technology Guide")
|
||||
.description("Understanding and implementing green technologies")
|
||||
.add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities")
|
||||
.add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates")
|
||||
.add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems")
|
||||
.add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Solar Technology").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2)));
|
||||
|
||||
let community_book = save_book(new_book()
|
||||
.title("Building Communities")
|
||||
.description("Guide to creating sustainable and inclusive communities")
|
||||
.add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship")
|
||||
.add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles")
|
||||
.add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1)));
|
||||
|
||||
// === SLIDES ===
|
||||
print("Creating slides...");
|
||||
|
||||
let climate_slides = save_slides(new_slides()
|
||||
.title("Climate Change Awareness")
|
||||
.description("Visual presentation on climate change impacts and solutions")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200").title("Global Temperature Rise"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200").title("Melting Ice Caps"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200").title("Extreme Weather Events"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200").title("Renewable Energy Solutions"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200").title("Sustainable Transportation")));
|
||||
|
||||
let innovation_slides = save_slides(new_slides()
|
||||
.title("Innovation Showcase")
|
||||
.description("Cutting-edge technologies for a sustainable future")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200").title("AI and Machine Learning"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200").title("Blockchain Technology"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200").title("IoT and Smart Cities"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200").title("Quantum Computing"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200").title("Biotechnology Advances")));
|
||||
|
||||
let nature_slides = save_slides(new_slides()
|
||||
.title("Biodiversity Gallery")
|
||||
.description("Celebrating Earth's incredible biodiversity")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200").title("Tropical Rainforest"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200").title("Coral Reef Ecosystem"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200").title("Arctic Wildlife"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200").title("Mountain Ecosystems")));
|
||||
|
||||
// === COLLECTIONS ===
|
||||
print("Creating collections...");
|
||||
|
||||
let nature_collection = save_collection(new_collection()
|
||||
.title("Nature & Environment")
|
||||
.description("Beautiful images and resources about our natural world")
|
||||
.add_image(nature1.id)
|
||||
.add_image(nature2.id)
|
||||
.add_image(nature3.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id)
|
||||
.add_book(sustainability_book.id)
|
||||
.add_slides(nature_slides.id));
|
||||
|
||||
let technology_collection = save_collection(new_collection()
|
||||
.title("Sustainable Technology")
|
||||
.description("Innovations driving positive change")
|
||||
.add_image(tech1.id)
|
||||
.add_image(tech2.id)
|
||||
.add_pdf(pdf3.id)
|
||||
.add_pdf(pdf4.id)
|
||||
.add_markdown(md3.id)
|
||||
.add_book(tech_guide_book.id)
|
||||
.add_slides(innovation_slides.id));
|
||||
|
||||
let space_collection = save_collection(new_collection()
|
||||
.title("Space & Cosmos")
|
||||
.description("Exploring the universe and our place in it")
|
||||
.add_image(space1.id)
|
||||
.add_image(space2.id)
|
||||
.add_pdf(pdf2.id)
|
||||
.add_markdown(md2.id));
|
||||
|
||||
let community_collection = save_collection(new_collection()
|
||||
.title("Community & Collaboration")
|
||||
.description("Building better communities together")
|
||||
.add_image(city1.id)
|
||||
.add_pdf(pdf5.id)
|
||||
.add_markdown(md4.id)
|
||||
.add_book(community_book.id));
|
||||
|
||||
let climate_collection = save_collection(new_collection()
|
||||
.title("Climate Action")
|
||||
.description("Understanding and addressing climate change")
|
||||
.add_slides(climate_slides.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id));
|
||||
|
||||
print("✅ OurWorld library created successfully!");
|
||||
print("📚 Collections: 5");
|
||||
print("🖼️ Images: 8");
|
||||
print("📄 PDFs: 5");
|
||||
print("📝 Markdown docs: 4");
|
||||
print("📖 Books: 3");
|
||||
print("🎞️ Slide shows: 3");
|
@ -1,249 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Zanzibar Digital Freezone")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:8096/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
||||
|
||||
let circle = get_circle();
|
||||
|
||||
print("--- Creating OurWorld Library ---");
|
||||
|
||||
// === IMAGES ===
|
||||
print("Creating images...");
|
||||
|
||||
let nature1 = save_image(new_image()
|
||||
.title("Mountain Sunrise")
|
||||
.description("Breathtaking sunrise over mountain peaks")
|
||||
.url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature2 = save_image(new_image()
|
||||
.title("Ocean Waves")
|
||||
.description("Powerful ocean waves crashing on rocks")
|
||||
.url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature3 = save_image(new_image()
|
||||
.title("Forest Path")
|
||||
.description("Peaceful path through ancient forest")
|
||||
.url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech1 = save_image(new_image()
|
||||
.title("Solar Panels")
|
||||
.description("Modern solar panel installation")
|
||||
.url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech2 = save_image(new_image()
|
||||
.title("Wind Turbines")
|
||||
.description("Wind turbines generating clean energy")
|
||||
.url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space1 = save_image(new_image()
|
||||
.title("Earth from Space")
|
||||
.description("Our beautiful planet from orbit")
|
||||
.url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space2 = save_image(new_image()
|
||||
.title("Galaxy Spiral")
|
||||
.description("Stunning spiral galaxy in deep space")
|
||||
.url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let city1 = save_image(new_image()
|
||||
.title("Smart City")
|
||||
.description("Futuristic smart city at night")
|
||||
.url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
// === PDFs ===
|
||||
print("Creating PDFs...");
|
||||
|
||||
let pdf1 = save_pdf(new_pdf()
|
||||
.title("Climate Action Report 2024")
|
||||
.description("Comprehensive analysis of global climate initiatives")
|
||||
.url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf")
|
||||
.page_count(42));
|
||||
|
||||
let pdf2 = save_pdf(new_pdf()
|
||||
.title("Sustainable Development Goals")
|
||||
.description("UN SDG implementation guide")
|
||||
.url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf")
|
||||
.page_count(35));
|
||||
|
||||
let pdf3 = save_pdf(new_pdf()
|
||||
.title("Renewable Energy Handbook")
|
||||
.description("Technical guide to renewable energy systems")
|
||||
.url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf")
|
||||
.page_count(280));
|
||||
|
||||
let pdf4 = save_pdf(new_pdf()
|
||||
.title("Blockchain for Good")
|
||||
.description("How blockchain technology can solve global challenges")
|
||||
.url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype")
|
||||
.page_count(24));
|
||||
|
||||
let pdf5 = save_pdf(new_pdf()
|
||||
.title("Future of Work Report")
|
||||
.description("Analysis of changing work patterns and remote collaboration")
|
||||
.url("https://www.mckinsey.com/featured-insights/future-of-work")
|
||||
.page_count(156));
|
||||
|
||||
// === MARKDOWN DOCUMENTS ===
|
||||
print("Creating markdown documents...");
|
||||
|
||||
let md1 = save_markdown(new_markdown()
|
||||
.title("OurWorld Mission Statement")
|
||||
.description("Our vision for a better world")
|
||||
.content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities"));
|
||||
|
||||
let md2 = save_markdown(new_markdown()
|
||||
.title("Getting Started Guide")
|
||||
.description("How to join the OurWorld movement")
|
||||
.content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)"));
|
||||
|
||||
let md3 = save_markdown(new_markdown()
|
||||
.title("Technology Roadmap 2024")
|
||||
.description("Our technical development plans")
|
||||
.content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training"));
|
||||
|
||||
let md4 = save_markdown(new_markdown()
|
||||
.title("Community Guidelines")
|
||||
.description("How we work together")
|
||||
.content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone"));
|
||||
|
||||
|
||||
let investor = new_contact()
|
||||
.name("Example Investor")
|
||||
.save_contact();
|
||||
|
||||
let investors = new_group()
|
||||
.name("Investors")
|
||||
.description("A group for example inverstors of ourworld");
|
||||
|
||||
investors.add_contact(investor.id)
|
||||
.save_group();
|
||||
|
||||
// === BOOKS ===
|
||||
print("Creating books...");
|
||||
|
||||
let sustainability_book = save_book(new_book()
|
||||
.title("Sustainability Handbook")
|
||||
.description("Complete guide to sustainable living and practices")
|
||||
.add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.")
|
||||
.add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances")
|
||||
.add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need")
|
||||
.add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively")
|
||||
.add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Waste Reduction").page(2))
|
||||
.add_toc_entry(new_toc_entry().title("Sustainable Food").page(3)));
|
||||
|
||||
let tech_guide_book = save_book(new_book()
|
||||
.title("Green Technology Guide")
|
||||
.description("Understanding and implementing green technologies")
|
||||
.add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities")
|
||||
.add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates")
|
||||
.add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems")
|
||||
.add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Solar Technology").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2)));
|
||||
|
||||
let community_book = save_book(new_book()
|
||||
.title("Building Communities")
|
||||
.description("Guide to creating sustainable and inclusive communities")
|
||||
.add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship")
|
||||
.add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles")
|
||||
.add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1)));
|
||||
|
||||
// === SLIDES ===
|
||||
print("Creating slides...");
|
||||
|
||||
let climate_slides = save_slides(new_slides()
|
||||
.title("Climate Change Awareness")
|
||||
.description("Visual presentation on climate change impacts and solutions")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200").title("Global Temperature Rise"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200").title("Melting Ice Caps"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200").title("Extreme Weather Events"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200").title("Renewable Energy Solutions"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200").title("Sustainable Transportation")));
|
||||
|
||||
let innovation_slides = save_slides(new_slides()
|
||||
.title("Innovation Showcase")
|
||||
.description("Cutting-edge technologies for a sustainable future")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200").title("AI and Machine Learning"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200").title("Blockchain Technology"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200").title("IoT and Smart Cities"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200").title("Quantum Computing"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200").title("Biotechnology Advances")));
|
||||
|
||||
let nature_slides = save_slides(new_slides()
|
||||
.title("Biodiversity Gallery")
|
||||
.description("Celebrating Earth's incredible biodiversity")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200").title("Tropical Rainforest"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200").title("Coral Reef Ecosystem"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200").title("Arctic Wildlife"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200").title("Mountain Ecosystems")));
|
||||
|
||||
// === COLLECTIONS ===
|
||||
print("Creating collections...");
|
||||
|
||||
let nature_collection = save_collection(new_collection()
|
||||
.title("Nature & Environment")
|
||||
.description("Beautiful images and resources about our natural world")
|
||||
.add_image(nature1.id)
|
||||
.add_image(nature2.id)
|
||||
.add_image(nature3.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id)
|
||||
.add_book(sustainability_book.id)
|
||||
.add_slides(nature_slides.id));
|
||||
|
||||
let technology_collection = save_collection(new_collection()
|
||||
.title("Sustainable Technology")
|
||||
.description("Innovations driving positive change")
|
||||
.add_image(tech1.id)
|
||||
.add_image(tech2.id)
|
||||
.add_pdf(pdf3.id)
|
||||
.add_pdf(pdf4.id)
|
||||
.add_markdown(md3.id)
|
||||
.add_book(tech_guide_book.id)
|
||||
.add_slides(innovation_slides.id));
|
||||
|
||||
let space_collection = save_collection(new_collection()
|
||||
.title("Space & Cosmos")
|
||||
.description("Exploring the universe and our place in it")
|
||||
.add_image(space1.id)
|
||||
.add_image(space2.id)
|
||||
.add_pdf(pdf2.id)
|
||||
.add_markdown(md2.id));
|
||||
|
||||
let community_collection = save_collection(new_collection()
|
||||
.title("Community & Collaboration")
|
||||
.description("Building better communities together")
|
||||
.add_image(city1.id)
|
||||
.add_pdf(pdf5.id)
|
||||
.add_markdown(md4.id)
|
||||
.add_book(community_book.id));
|
||||
|
||||
let climate_collection = save_collection(new_collection()
|
||||
.title("Climate Action")
|
||||
.description("Understanding and addressing climate change")
|
||||
.add_slides(climate_slides.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id));
|
||||
|
||||
print("✅ OurWorld library created successfully!");
|
||||
print("📚 Collections: 5");
|
||||
print("🖼️ Images: 8");
|
||||
print("📄 PDFs: 5");
|
||||
print("📝 Markdown docs: 4");
|
||||
print("📖 Books: 3");
|
||||
print("🎞️ Slide shows: 3");
|
@ -1,249 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Geomind")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:8095/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
||||
|
||||
let circle = get_circle();
|
||||
|
||||
print("--- Creating OurWorld Library ---");
|
||||
|
||||
// === IMAGES ===
|
||||
print("Creating images...");
|
||||
|
||||
let nature1 = save_image(new_image()
|
||||
.title("Mountain Sunrise")
|
||||
.description("Breathtaking sunrise over mountain peaks")
|
||||
.url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature2 = save_image(new_image()
|
||||
.title("Ocean Waves")
|
||||
.description("Powerful ocean waves crashing on rocks")
|
||||
.url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature3 = save_image(new_image()
|
||||
.title("Forest Path")
|
||||
.description("Peaceful path through ancient forest")
|
||||
.url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech1 = save_image(new_image()
|
||||
.title("Solar Panels")
|
||||
.description("Modern solar panel installation")
|
||||
.url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech2 = save_image(new_image()
|
||||
.title("Wind Turbines")
|
||||
.description("Wind turbines generating clean energy")
|
||||
.url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space1 = save_image(new_image()
|
||||
.title("Earth from Space")
|
||||
.description("Our beautiful planet from orbit")
|
||||
.url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space2 = save_image(new_image()
|
||||
.title("Galaxy Spiral")
|
||||
.description("Stunning spiral galaxy in deep space")
|
||||
.url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let city1 = save_image(new_image()
|
||||
.title("Smart City")
|
||||
.description("Futuristic smart city at night")
|
||||
.url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
// === PDFs ===
|
||||
print("Creating PDFs...");
|
||||
|
||||
let pdf1 = save_pdf(new_pdf()
|
||||
.title("Climate Action Report 2024")
|
||||
.description("Comprehensive analysis of global climate initiatives")
|
||||
.url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf")
|
||||
.page_count(42));
|
||||
|
||||
let pdf2 = save_pdf(new_pdf()
|
||||
.title("Sustainable Development Goals")
|
||||
.description("UN SDG implementation guide")
|
||||
.url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf")
|
||||
.page_count(35));
|
||||
|
||||
let pdf3 = save_pdf(new_pdf()
|
||||
.title("Renewable Energy Handbook")
|
||||
.description("Technical guide to renewable energy systems")
|
||||
.url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf")
|
||||
.page_count(280));
|
||||
|
||||
let pdf4 = save_pdf(new_pdf()
|
||||
.title("Blockchain for Good")
|
||||
.description("How blockchain technology can solve global challenges")
|
||||
.url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype")
|
||||
.page_count(24));
|
||||
|
||||
let pdf5 = save_pdf(new_pdf()
|
||||
.title("Future of Work Report")
|
||||
.description("Analysis of changing work patterns and remote collaboration")
|
||||
.url("https://www.mckinsey.com/featured-insights/future-of-work")
|
||||
.page_count(156));
|
||||
|
||||
// === MARKDOWN DOCUMENTS ===
|
||||
print("Creating markdown documents...");
|
||||
|
||||
let md1 = save_markdown(new_markdown()
|
||||
.title("OurWorld Mission Statement")
|
||||
.description("Our vision for a better world")
|
||||
.content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities"));
|
||||
|
||||
let md2 = save_markdown(new_markdown()
|
||||
.title("Getting Started Guide")
|
||||
.description("How to join the OurWorld movement")
|
||||
.content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)"));
|
||||
|
||||
let md3 = save_markdown(new_markdown()
|
||||
.title("Technology Roadmap 2024")
|
||||
.description("Our technical development plans")
|
||||
.content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training"));
|
||||
|
||||
let md4 = save_markdown(new_markdown()
|
||||
.title("Community Guidelines")
|
||||
.description("How we work together")
|
||||
.content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone"));
|
||||
|
||||
|
||||
let investor = new_contact()
|
||||
.name("Example Investor")
|
||||
.save_contact();
|
||||
|
||||
let investors = new_group()
|
||||
.name("Investors")
|
||||
.description("A group for example inverstors of ourworld");
|
||||
|
||||
investors.add_contact(investor.id)
|
||||
.save_group();
|
||||
|
||||
// === BOOKS ===
|
||||
print("Creating books...");
|
||||
|
||||
let sustainability_book = save_book(new_book()
|
||||
.title("Sustainability Handbook")
|
||||
.description("Complete guide to sustainable living and practices")
|
||||
.add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.")
|
||||
.add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances")
|
||||
.add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need")
|
||||
.add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively")
|
||||
.add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Waste Reduction").page(2))
|
||||
.add_toc_entry(new_toc_entry().title("Sustainable Food").page(3)));
|
||||
|
||||
let tech_guide_book = save_book(new_book()
|
||||
.title("Green Technology Guide")
|
||||
.description("Understanding and implementing green technologies")
|
||||
.add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities")
|
||||
.add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates")
|
||||
.add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems")
|
||||
.add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Solar Technology").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2)));
|
||||
|
||||
let community_book = save_book(new_book()
|
||||
.title("Building Communities")
|
||||
.description("Guide to creating sustainable and inclusive communities")
|
||||
.add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship")
|
||||
.add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles")
|
||||
.add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1)));
|
||||
|
||||
// === SLIDES ===
|
||||
print("Creating slides...");
|
||||
|
||||
let climate_slides = save_slides(new_slides()
|
||||
.title("Climate Change Awareness")
|
||||
.description("Visual presentation on climate change impacts and solutions")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200").title("Global Temperature Rise"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200").title("Melting Ice Caps"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200").title("Extreme Weather Events"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200").title("Renewable Energy Solutions"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200").title("Sustainable Transportation")));
|
||||
|
||||
let innovation_slides = save_slides(new_slides()
|
||||
.title("Innovation Showcase")
|
||||
.description("Cutting-edge technologies for a sustainable future")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200").title("AI and Machine Learning"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200").title("Blockchain Technology"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200").title("IoT and Smart Cities"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200").title("Quantum Computing"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200").title("Biotechnology Advances")));
|
||||
|
||||
let nature_slides = save_slides(new_slides()
|
||||
.title("Biodiversity Gallery")
|
||||
.description("Celebrating Earth's incredible biodiversity")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200").title("Tropical Rainforest"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200").title("Coral Reef Ecosystem"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200").title("Arctic Wildlife"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200").title("Mountain Ecosystems")));
|
||||
|
||||
// === COLLECTIONS ===
|
||||
print("Creating collections...");
|
||||
|
||||
let nature_collection = save_collection(new_collection()
|
||||
.title("Nature & Environment")
|
||||
.description("Beautiful images and resources about our natural world")
|
||||
.add_image(nature1.id)
|
||||
.add_image(nature2.id)
|
||||
.add_image(nature3.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id)
|
||||
.add_book(sustainability_book.id)
|
||||
.add_slides(nature_slides.id));
|
||||
|
||||
let technology_collection = save_collection(new_collection()
|
||||
.title("Sustainable Technology")
|
||||
.description("Innovations driving positive change")
|
||||
.add_image(tech1.id)
|
||||
.add_image(tech2.id)
|
||||
.add_pdf(pdf3.id)
|
||||
.add_pdf(pdf4.id)
|
||||
.add_markdown(md3.id)
|
||||
.add_book(tech_guide_book.id)
|
||||
.add_slides(innovation_slides.id));
|
||||
|
||||
let space_collection = save_collection(new_collection()
|
||||
.title("Space & Cosmos")
|
||||
.description("Exploring the universe and our place in it")
|
||||
.add_image(space1.id)
|
||||
.add_image(space2.id)
|
||||
.add_pdf(pdf2.id)
|
||||
.add_markdown(md2.id));
|
||||
|
||||
let community_collection = save_collection(new_collection()
|
||||
.title("Community & Collaboration")
|
||||
.description("Building better communities together")
|
||||
.add_image(city1.id)
|
||||
.add_pdf(pdf5.id)
|
||||
.add_markdown(md4.id)
|
||||
.add_book(community_book.id));
|
||||
|
||||
let climate_collection = save_collection(new_collection()
|
||||
.title("Climate Action")
|
||||
.description("Understanding and addressing climate change")
|
||||
.add_slides(climate_slides.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id));
|
||||
|
||||
print("✅ OurWorld library created successfully!");
|
||||
print("📚 Collections: 5");
|
||||
print("🖼️ Images: 8");
|
||||
print("📄 PDFs: 5");
|
||||
print("📝 Markdown docs: 4");
|
||||
print("📖 Books: 3");
|
||||
print("🎞️ Slide shows: 3");
|
@ -1,15 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Kristof de Spiegeleer")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:9101/ws")
|
||||
.add_circle("ws://localhost:9000/ws")
|
||||
.add_circle("ws://localhost:9001/ws")
|
||||
.add_circle("ws://localhost:9002/ws")
|
||||
.add_circle("ws://localhost:9003/ws")
|
||||
.add_circle("ws://localhost:9004/ws")
|
||||
.add_circle("ws://localhost:9005/ws")
|
||||
.add_circle("ws://localhost:8096/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
@ -1,249 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Mbweni Ruins & Gardens")
|
||||
.description("Mbweni ruins and Gardens")
|
||||
.ws_url("ws://localhost:8094/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
||||
|
||||
let circle = get_circle();
|
||||
|
||||
print("--- Creating OurWorld Library ---");
|
||||
|
||||
// === IMAGES ===
|
||||
print("Creating images...");
|
||||
|
||||
let nature1 = save_image(new_image()
|
||||
.title("Mountain Sunrise")
|
||||
.description("Breathtaking sunrise over mountain peaks")
|
||||
.url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature2 = save_image(new_image()
|
||||
.title("Ocean Waves")
|
||||
.description("Powerful ocean waves crashing on rocks")
|
||||
.url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature3 = save_image(new_image()
|
||||
.title("Forest Path")
|
||||
.description("Peaceful path through ancient forest")
|
||||
.url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech1 = save_image(new_image()
|
||||
.title("Solar Panels")
|
||||
.description("Modern solar panel installation")
|
||||
.url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech2 = save_image(new_image()
|
||||
.title("Wind Turbines")
|
||||
.description("Wind turbines generating clean energy")
|
||||
.url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space1 = save_image(new_image()
|
||||
.title("Earth from Space")
|
||||
.description("Our beautiful planet from orbit")
|
||||
.url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space2 = save_image(new_image()
|
||||
.title("Galaxy Spiral")
|
||||
.description("Stunning spiral galaxy in deep space")
|
||||
.url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let city1 = save_image(new_image()
|
||||
.title("Smart City")
|
||||
.description("Futuristic smart city at night")
|
||||
.url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
// === PDFs ===
|
||||
print("Creating PDFs...");
|
||||
|
||||
let pdf1 = save_pdf(new_pdf()
|
||||
.title("Climate Action Report 2024")
|
||||
.description("Comprehensive analysis of global climate initiatives")
|
||||
.url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf")
|
||||
.page_count(42));
|
||||
|
||||
let pdf2 = save_pdf(new_pdf()
|
||||
.title("Sustainable Development Goals")
|
||||
.description("UN SDG implementation guide")
|
||||
.url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf")
|
||||
.page_count(35));
|
||||
|
||||
let pdf3 = save_pdf(new_pdf()
|
||||
.title("Renewable Energy Handbook")
|
||||
.description("Technical guide to renewable energy systems")
|
||||
.url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf")
|
||||
.page_count(280));
|
||||
|
||||
let pdf4 = save_pdf(new_pdf()
|
||||
.title("Blockchain for Good")
|
||||
.description("How blockchain technology can solve global challenges")
|
||||
.url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype")
|
||||
.page_count(24));
|
||||
|
||||
let pdf5 = save_pdf(new_pdf()
|
||||
.title("Future of Work Report")
|
||||
.description("Analysis of changing work patterns and remote collaboration")
|
||||
.url("https://www.mckinsey.com/featured-insights/future-of-work")
|
||||
.page_count(156));
|
||||
|
||||
// === MARKDOWN DOCUMENTS ===
|
||||
print("Creating markdown documents...");
|
||||
|
||||
let md1 = save_markdown(new_markdown()
|
||||
.title("OurWorld Mission Statement")
|
||||
.description("Our vision for a better world")
|
||||
.content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities"));
|
||||
|
||||
let md2 = save_markdown(new_markdown()
|
||||
.title("Getting Started Guide")
|
||||
.description("How to join the OurWorld movement")
|
||||
.content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)"));
|
||||
|
||||
let md3 = save_markdown(new_markdown()
|
||||
.title("Technology Roadmap 2024")
|
||||
.description("Our technical development plans")
|
||||
.content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training"));
|
||||
|
||||
let md4 = save_markdown(new_markdown()
|
||||
.title("Community Guidelines")
|
||||
.description("How we work together")
|
||||
.content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone"));
|
||||
|
||||
|
||||
let investor = new_contact()
|
||||
.name("Example Investor")
|
||||
.save_contact();
|
||||
|
||||
let investors = new_group()
|
||||
.name("Investors")
|
||||
.description("A group for example inverstors of ourworld");
|
||||
|
||||
investors.add_contact(investor.id)
|
||||
.save_group();
|
||||
|
||||
// === BOOKS ===
|
||||
print("Creating books...");
|
||||
|
||||
let sustainability_book = save_book(new_book()
|
||||
.title("Sustainability Handbook")
|
||||
.description("Complete guide to sustainable living and practices")
|
||||
.add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.")
|
||||
.add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances")
|
||||
.add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need")
|
||||
.add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively")
|
||||
.add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Waste Reduction").page(2))
|
||||
.add_toc_entry(new_toc_entry().title("Sustainable Food").page(3)));
|
||||
|
||||
let tech_guide_book = save_book(new_book()
|
||||
.title("Green Technology Guide")
|
||||
.description("Understanding and implementing green technologies")
|
||||
.add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities")
|
||||
.add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates")
|
||||
.add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems")
|
||||
.add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Solar Technology").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2)));
|
||||
|
||||
let community_book = save_book(new_book()
|
||||
.title("Building Communities")
|
||||
.description("Guide to creating sustainable and inclusive communities")
|
||||
.add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship")
|
||||
.add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles")
|
||||
.add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1)));
|
||||
|
||||
// === SLIDES ===
|
||||
print("Creating slides...");
|
||||
|
||||
let climate_slides = save_slides(new_slides()
|
||||
.title("Climate Change Awareness")
|
||||
.description("Visual presentation on climate change impacts and solutions")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200").title("Global Temperature Rise"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200").title("Melting Ice Caps"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200").title("Extreme Weather Events"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200").title("Renewable Energy Solutions"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200").title("Sustainable Transportation")));
|
||||
|
||||
let innovation_slides = save_slides(new_slides()
|
||||
.title("Innovation Showcase")
|
||||
.description("Cutting-edge technologies for a sustainable future")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200").title("AI and Machine Learning"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200").title("Blockchain Technology"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200").title("IoT and Smart Cities"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200").title("Quantum Computing"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200").title("Biotechnology Advances")));
|
||||
|
||||
let nature_slides = save_slides(new_slides()
|
||||
.title("Biodiversity Gallery")
|
||||
.description("Celebrating Earth's incredible biodiversity")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200").title("Tropical Rainforest"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200").title("Coral Reef Ecosystem"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200").title("Arctic Wildlife"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200").title("Mountain Ecosystems")));
|
||||
|
||||
// === COLLECTIONS ===
|
||||
print("Creating collections...");
|
||||
|
||||
let nature_collection = save_collection(new_collection()
|
||||
.title("Nature & Environment")
|
||||
.description("Beautiful images and resources about our natural world")
|
||||
.add_image(nature1.id)
|
||||
.add_image(nature2.id)
|
||||
.add_image(nature3.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id)
|
||||
.add_book(sustainability_book.id)
|
||||
.add_slides(nature_slides.id));
|
||||
|
||||
let technology_collection = save_collection(new_collection()
|
||||
.title("Sustainable Technology")
|
||||
.description("Innovations driving positive change")
|
||||
.add_image(tech1.id)
|
||||
.add_image(tech2.id)
|
||||
.add_pdf(pdf3.id)
|
||||
.add_pdf(pdf4.id)
|
||||
.add_markdown(md3.id)
|
||||
.add_book(tech_guide_book.id)
|
||||
.add_slides(innovation_slides.id));
|
||||
|
||||
let space_collection = save_collection(new_collection()
|
||||
.title("Space & Cosmos")
|
||||
.description("Exploring the universe and our place in it")
|
||||
.add_image(space1.id)
|
||||
.add_image(space2.id)
|
||||
.add_pdf(pdf2.id)
|
||||
.add_markdown(md2.id));
|
||||
|
||||
let community_collection = save_collection(new_collection()
|
||||
.title("Community & Collaboration")
|
||||
.description("Building better communities together")
|
||||
.add_image(city1.id)
|
||||
.add_pdf(pdf5.id)
|
||||
.add_markdown(md4.id)
|
||||
.add_book(community_book.id));
|
||||
|
||||
let climate_collection = save_collection(new_collection()
|
||||
.title("Climate Action")
|
||||
.description("Understanding and addressing climate change")
|
||||
.add_slides(climate_slides.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id));
|
||||
|
||||
print("✅ OurWorld library created successfully!");
|
||||
print("📚 Collections: 5");
|
||||
print("🖼️ Images: 8");
|
||||
print("📄 PDFs: 5");
|
||||
print("📝 Markdown docs: 4");
|
||||
print("📖 Books: 3");
|
||||
print("🎞️ Slide shows: 3");
|
@ -1,257 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Ourworld")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:9000/ws")
|
||||
.add_circle("ws://localhost:9001/ws")
|
||||
.add_circle("ws://localhost:9002/ws")
|
||||
.add_circle("ws://localhost:9003/ws")
|
||||
.add_circle("ws://localhost:9004/ws")
|
||||
.add_circle("ws://localhost:9005/ws")
|
||||
.add_circle("ws://localhost:8096/ws")
|
||||
.add_member("023b0a9d409506f41f5782353857dea6abc16ae4643661cd94d8155fdb498642e3")
|
||||
.add_member("030b62236efa67855b3379a9d4add1facbe8a545bafa86e1d6fbac06caae5b5b12")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
||||
|
||||
let circle = get_circle();
|
||||
|
||||
print("--- Creating OurWorld Library ---");
|
||||
|
||||
// === IMAGES ===
|
||||
print("Creating images...");
|
||||
|
||||
let nature1 = save_image(new_image()
|
||||
.title("Mountain Sunrise")
|
||||
.description("Breathtaking sunrise over mountain peaks")
|
||||
.url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature2 = save_image(new_image()
|
||||
.title("Ocean Waves")
|
||||
.description("Powerful ocean waves crashing on rocks")
|
||||
.url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature3 = save_image(new_image()
|
||||
.title("Forest Path")
|
||||
.description("Peaceful path through ancient forest")
|
||||
.url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech1 = save_image(new_image()
|
||||
.title("Solar Panels")
|
||||
.description("Modern solar panel installation")
|
||||
.url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech2 = save_image(new_image()
|
||||
.title("Wind Turbines")
|
||||
.description("Wind turbines generating clean energy")
|
||||
.url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space1 = save_image(new_image()
|
||||
.title("Earth from Space")
|
||||
.description("Our beautiful planet from orbit")
|
||||
.url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space2 = save_image(new_image()
|
||||
.title("Galaxy Spiral")
|
||||
.description("Stunning spiral galaxy in deep space")
|
||||
.url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let city1 = save_image(new_image()
|
||||
.title("Smart City")
|
||||
.description("Futuristic smart city at night")
|
||||
.url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
// === PDFs ===
|
||||
print("Creating PDFs...");
|
||||
|
||||
let pdf1 = save_pdf(new_pdf()
|
||||
.title("Climate Action Report 2024")
|
||||
.description("Comprehensive analysis of global climate initiatives")
|
||||
.url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf")
|
||||
.page_count(42));
|
||||
|
||||
let pdf2 = save_pdf(new_pdf()
|
||||
.title("Sustainable Development Goals")
|
||||
.description("UN SDG implementation guide")
|
||||
.url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf")
|
||||
.page_count(35));
|
||||
|
||||
let pdf3 = save_pdf(new_pdf()
|
||||
.title("Renewable Energy Handbook")
|
||||
.description("Technical guide to renewable energy systems")
|
||||
.url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf")
|
||||
.page_count(280));
|
||||
|
||||
let pdf4 = save_pdf(new_pdf()
|
||||
.title("Blockchain for Good")
|
||||
.description("How blockchain technology can solve global challenges")
|
||||
.url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype")
|
||||
.page_count(24));
|
||||
|
||||
let pdf5 = save_pdf(new_pdf()
|
||||
.title("Future of Work Report")
|
||||
.description("Analysis of changing work patterns and remote collaboration")
|
||||
.url("https://www.mckinsey.com/featured-insights/future-of-work")
|
||||
.page_count(156));
|
||||
|
||||
// === MARKDOWN DOCUMENTS ===
|
||||
print("Creating markdown documents...");
|
||||
|
||||
let md1 = save_markdown(new_markdown()
|
||||
.title("OurWorld Mission Statement")
|
||||
.description("Our vision for a better world")
|
||||
.content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities"));
|
||||
|
||||
let md2 = save_markdown(new_markdown()
|
||||
.title("Getting Started Guide")
|
||||
.description("How to join the OurWorld movement")
|
||||
.content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)"));
|
||||
|
||||
let md3 = save_markdown(new_markdown()
|
||||
.title("Technology Roadmap 2024")
|
||||
.description("Our technical development plans")
|
||||
.content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training"));
|
||||
|
||||
let md4 = save_markdown(new_markdown()
|
||||
.title("Community Guidelines")
|
||||
.description("How we work together")
|
||||
.content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone"));
|
||||
|
||||
|
||||
let investor = new_contact()
|
||||
.name("Example Investor")
|
||||
.save_contact();
|
||||
|
||||
let investors = new_group()
|
||||
.name("Investors")
|
||||
.description("A group for example inverstors of ourworld");
|
||||
|
||||
investors.add_contact(investor.id)
|
||||
.save_group();
|
||||
|
||||
// === BOOKS ===
|
||||
print("Creating books...");
|
||||
|
||||
let sustainability_book = save_book(new_book()
|
||||
.title("Sustainability Handbook")
|
||||
.description("Complete guide to sustainable living and practices")
|
||||
.add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.")
|
||||
.add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances")
|
||||
.add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need")
|
||||
.add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively")
|
||||
.add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Waste Reduction").page(2))
|
||||
.add_toc_entry(new_toc_entry().title("Sustainable Food").page(3)));
|
||||
|
||||
let tech_guide_book = save_book(new_book()
|
||||
.title("Green Technology Guide")
|
||||
.description("Understanding and implementing green technologies")
|
||||
.add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities")
|
||||
.add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates")
|
||||
.add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems")
|
||||
.add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Solar Technology").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2)));
|
||||
|
||||
let community_book = save_book(new_book()
|
||||
.title("Building Communities")
|
||||
.description("Guide to creating sustainable and inclusive communities")
|
||||
.add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship")
|
||||
.add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles")
|
||||
.add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1)));
|
||||
|
||||
// === SLIDES ===
|
||||
print("Creating slides...");
|
||||
|
||||
let climate_slides = save_slides(new_slides()
|
||||
.title("Climate Change Awareness")
|
||||
.description("Visual presentation on climate change impacts and solutions")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200").title("Global Temperature Rise"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200").title("Melting Ice Caps"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200").title("Extreme Weather Events"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200").title("Renewable Energy Solutions"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200").title("Sustainable Transportation")));
|
||||
|
||||
let innovation_slides = save_slides(new_slides()
|
||||
.title("Innovation Showcase")
|
||||
.description("Cutting-edge technologies for a sustainable future")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200").title("AI and Machine Learning"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200").title("Blockchain Technology"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200").title("IoT and Smart Cities"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200").title("Quantum Computing"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200").title("Biotechnology Advances")));
|
||||
|
||||
let nature_slides = save_slides(new_slides()
|
||||
.title("Biodiversity Gallery")
|
||||
.description("Celebrating Earth's incredible biodiversity")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200").title("Tropical Rainforest"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200").title("Coral Reef Ecosystem"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200").title("Arctic Wildlife"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200").title("Mountain Ecosystems")));
|
||||
|
||||
// === COLLECTIONS ===
|
||||
print("Creating collections...");
|
||||
|
||||
let nature_collection = save_collection(new_collection()
|
||||
.title("Nature & Environment")
|
||||
.description("Beautiful images and resources about our natural world")
|
||||
.add_image(nature1.id)
|
||||
.add_image(nature2.id)
|
||||
.add_image(nature3.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id)
|
||||
.add_book(sustainability_book.id)
|
||||
.add_slides(nature_slides.id));
|
||||
|
||||
let technology_collection = save_collection(new_collection()
|
||||
.title("Sustainable Technology")
|
||||
.description("Innovations driving positive change")
|
||||
.add_image(tech1.id)
|
||||
.add_image(tech2.id)
|
||||
.add_pdf(pdf3.id)
|
||||
.add_pdf(pdf4.id)
|
||||
.add_markdown(md3.id)
|
||||
.add_book(tech_guide_book.id)
|
||||
.add_slides(innovation_slides.id));
|
||||
|
||||
let space_collection = save_collection(new_collection()
|
||||
.title("Space & Cosmos")
|
||||
.description("Exploring the universe and our place in it")
|
||||
.add_image(space1.id)
|
||||
.add_image(space2.id)
|
||||
.add_pdf(pdf2.id)
|
||||
.add_markdown(md2.id));
|
||||
|
||||
let community_collection = save_collection(new_collection()
|
||||
.title("Community & Collaboration")
|
||||
.description("Building better communities together")
|
||||
.add_image(city1.id)
|
||||
.add_pdf(pdf5.id)
|
||||
.add_markdown(md4.id)
|
||||
.add_book(community_book.id));
|
||||
|
||||
let climate_collection = save_collection(new_collection()
|
||||
.title("Climate Action")
|
||||
.description("Understanding and addressing climate change")
|
||||
.add_slides(climate_slides.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id));
|
||||
|
||||
print("✅ OurWorld library created successfully!");
|
||||
print("📚 Collections: 5");
|
||||
print("🖼️ Images: 8");
|
||||
print("📄 PDFs: 5");
|
||||
print("📝 Markdown docs: 4");
|
||||
print("📖 Books: 3");
|
||||
print("🎞️ Slide shows: 3");
|
@ -1,249 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Sikana")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:9002/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
||||
|
||||
let circle = get_circle();
|
||||
|
||||
print("--- Creating OurWorld Library ---");
|
||||
|
||||
// === IMAGES ===
|
||||
print("Creating images...");
|
||||
|
||||
let nature1 = save_image(new_image()
|
||||
.title("Mountain Sunrise")
|
||||
.description("Breathtaking sunrise over mountain peaks")
|
||||
.url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature2 = save_image(new_image()
|
||||
.title("Ocean Waves")
|
||||
.description("Powerful ocean waves crashing on rocks")
|
||||
.url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature3 = save_image(new_image()
|
||||
.title("Forest Path")
|
||||
.description("Peaceful path through ancient forest")
|
||||
.url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech1 = save_image(new_image()
|
||||
.title("Solar Panels")
|
||||
.description("Modern solar panel installation")
|
||||
.url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech2 = save_image(new_image()
|
||||
.title("Wind Turbines")
|
||||
.description("Wind turbines generating clean energy")
|
||||
.url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space1 = save_image(new_image()
|
||||
.title("Earth from Space")
|
||||
.description("Our beautiful planet from orbit")
|
||||
.url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space2 = save_image(new_image()
|
||||
.title("Galaxy Spiral")
|
||||
.description("Stunning spiral galaxy in deep space")
|
||||
.url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let city1 = save_image(new_image()
|
||||
.title("Smart City")
|
||||
.description("Futuristic smart city at night")
|
||||
.url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
// === PDFs ===
|
||||
print("Creating PDFs...");
|
||||
|
||||
let pdf1 = save_pdf(new_pdf()
|
||||
.title("Climate Action Report 2024")
|
||||
.description("Comprehensive analysis of global climate initiatives")
|
||||
.url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf")
|
||||
.page_count(42));
|
||||
|
||||
let pdf2 = save_pdf(new_pdf()
|
||||
.title("Sustainable Development Goals")
|
||||
.description("UN SDG implementation guide")
|
||||
.url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf")
|
||||
.page_count(35));
|
||||
|
||||
let pdf3 = save_pdf(new_pdf()
|
||||
.title("Renewable Energy Handbook")
|
||||
.description("Technical guide to renewable energy systems")
|
||||
.url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf")
|
||||
.page_count(280));
|
||||
|
||||
let pdf4 = save_pdf(new_pdf()
|
||||
.title("Blockchain for Good")
|
||||
.description("How blockchain technology can solve global challenges")
|
||||
.url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype")
|
||||
.page_count(24));
|
||||
|
||||
let pdf5 = save_pdf(new_pdf()
|
||||
.title("Future of Work Report")
|
||||
.description("Analysis of changing work patterns and remote collaboration")
|
||||
.url("https://www.mckinsey.com/featured-insights/future-of-work")
|
||||
.page_count(156));
|
||||
|
||||
// === MARKDOWN DOCUMENTS ===
|
||||
print("Creating markdown documents...");
|
||||
|
||||
let md1 = save_markdown(new_markdown()
|
||||
.title("OurWorld Mission Statement")
|
||||
.description("Our vision for a better world")
|
||||
.content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities"));
|
||||
|
||||
let md2 = save_markdown(new_markdown()
|
||||
.title("Getting Started Guide")
|
||||
.description("How to join the OurWorld movement")
|
||||
.content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)"));
|
||||
|
||||
let md3 = save_markdown(new_markdown()
|
||||
.title("Technology Roadmap 2024")
|
||||
.description("Our technical development plans")
|
||||
.content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training"));
|
||||
|
||||
let md4 = save_markdown(new_markdown()
|
||||
.title("Community Guidelines")
|
||||
.description("How we work together")
|
||||
.content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone"));
|
||||
|
||||
|
||||
let investor = new_contact()
|
||||
.name("Example Investor")
|
||||
.save_contact();
|
||||
|
||||
let investors = new_group()
|
||||
.name("Investors")
|
||||
.description("A group for example inverstors of ourworld");
|
||||
|
||||
investors.add_contact(investor.id)
|
||||
.save_group();
|
||||
|
||||
// === BOOKS ===
|
||||
print("Creating books...");
|
||||
|
||||
let sustainability_book = save_book(new_book()
|
||||
.title("Sustainability Handbook")
|
||||
.description("Complete guide to sustainable living and practices")
|
||||
.add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.")
|
||||
.add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances")
|
||||
.add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need")
|
||||
.add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively")
|
||||
.add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Waste Reduction").page(2))
|
||||
.add_toc_entry(new_toc_entry().title("Sustainable Food").page(3)));
|
||||
|
||||
let tech_guide_book = save_book(new_book()
|
||||
.title("Green Technology Guide")
|
||||
.description("Understanding and implementing green technologies")
|
||||
.add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities")
|
||||
.add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates")
|
||||
.add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems")
|
||||
.add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Solar Technology").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2)));
|
||||
|
||||
let community_book = save_book(new_book()
|
||||
.title("Building Communities")
|
||||
.description("Guide to creating sustainable and inclusive communities")
|
||||
.add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship")
|
||||
.add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles")
|
||||
.add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1)));
|
||||
|
||||
// === SLIDES ===
|
||||
print("Creating slides...");
|
||||
|
||||
let climate_slides = save_slides(new_slides()
|
||||
.title("Climate Change Awareness")
|
||||
.description("Visual presentation on climate change impacts and solutions")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200").title("Global Temperature Rise"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200").title("Melting Ice Caps"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200").title("Extreme Weather Events"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200").title("Renewable Energy Solutions"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200").title("Sustainable Transportation")));
|
||||
|
||||
let innovation_slides = save_slides(new_slides()
|
||||
.title("Innovation Showcase")
|
||||
.description("Cutting-edge technologies for a sustainable future")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200").title("AI and Machine Learning"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200").title("Blockchain Technology"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200").title("IoT and Smart Cities"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200").title("Quantum Computing"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200").title("Biotechnology Advances")));
|
||||
|
||||
let nature_slides = save_slides(new_slides()
|
||||
.title("Biodiversity Gallery")
|
||||
.description("Celebrating Earth's incredible biodiversity")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200").title("Tropical Rainforest"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200").title("Coral Reef Ecosystem"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200").title("Arctic Wildlife"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200").title("Mountain Ecosystems")));
|
||||
|
||||
// === COLLECTIONS ===
|
||||
print("Creating collections...");
|
||||
|
||||
let nature_collection = save_collection(new_collection()
|
||||
.title("Nature & Environment")
|
||||
.description("Beautiful images and resources about our natural world")
|
||||
.add_image(nature1.id)
|
||||
.add_image(nature2.id)
|
||||
.add_image(nature3.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id)
|
||||
.add_book(sustainability_book.id)
|
||||
.add_slides(nature_slides.id));
|
||||
|
||||
let technology_collection = save_collection(new_collection()
|
||||
.title("Sustainable Technology")
|
||||
.description("Innovations driving positive change")
|
||||
.add_image(tech1.id)
|
||||
.add_image(tech2.id)
|
||||
.add_pdf(pdf3.id)
|
||||
.add_pdf(pdf4.id)
|
||||
.add_markdown(md3.id)
|
||||
.add_book(tech_guide_book.id)
|
||||
.add_slides(innovation_slides.id));
|
||||
|
||||
let space_collection = save_collection(new_collection()
|
||||
.title("Space & Cosmos")
|
||||
.description("Exploring the universe and our place in it")
|
||||
.add_image(space1.id)
|
||||
.add_image(space2.id)
|
||||
.add_pdf(pdf2.id)
|
||||
.add_markdown(md2.id));
|
||||
|
||||
let community_collection = save_collection(new_collection()
|
||||
.title("Community & Collaboration")
|
||||
.description("Building better communities together")
|
||||
.add_image(city1.id)
|
||||
.add_pdf(pdf5.id)
|
||||
.add_markdown(md4.id)
|
||||
.add_book(community_book.id));
|
||||
|
||||
let climate_collection = save_collection(new_collection()
|
||||
.title("Climate Action")
|
||||
.description("Understanding and addressing climate change")
|
||||
.add_slides(climate_slides.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id));
|
||||
|
||||
print("✅ OurWorld library created successfully!");
|
||||
print("📚 Collections: 5");
|
||||
print("🖼️ Images: 8");
|
||||
print("📄 PDFs: 5");
|
||||
print("📝 Markdown docs: 4");
|
||||
print("📖 Books: 3");
|
||||
print("🎞️ Slide shows: 3");
|
@ -1,249 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Threefold DMCC")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:8093/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
||||
|
||||
let circle = get_circle();
|
||||
|
||||
print("--- Creating OurWorld Library ---");
|
||||
|
||||
// === IMAGES ===
|
||||
print("Creating images...");
|
||||
|
||||
let nature1 = save_image(new_image()
|
||||
.title("Mountain Sunrise")
|
||||
.description("Breathtaking sunrise over mountain peaks")
|
||||
.url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature2 = save_image(new_image()
|
||||
.title("Ocean Waves")
|
||||
.description("Powerful ocean waves crashing on rocks")
|
||||
.url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let nature3 = save_image(new_image()
|
||||
.title("Forest Path")
|
||||
.description("Peaceful path through ancient forest")
|
||||
.url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech1 = save_image(new_image()
|
||||
.title("Solar Panels")
|
||||
.description("Modern solar panel installation")
|
||||
.url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let tech2 = save_image(new_image()
|
||||
.title("Wind Turbines")
|
||||
.description("Wind turbines generating clean energy")
|
||||
.url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space1 = save_image(new_image()
|
||||
.title("Earth from Space")
|
||||
.description("Our beautiful planet from orbit")
|
||||
.url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let space2 = save_image(new_image()
|
||||
.title("Galaxy Spiral")
|
||||
.description("Stunning spiral galaxy in deep space")
|
||||
.url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
let city1 = save_image(new_image()
|
||||
.title("Smart City")
|
||||
.description("Futuristic smart city at night")
|
||||
.url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800")
|
||||
.width(800).height(600));
|
||||
|
||||
// === PDFs ===
|
||||
print("Creating PDFs...");
|
||||
|
||||
let pdf1 = save_pdf(new_pdf()
|
||||
.title("Climate Action Report 2024")
|
||||
.description("Comprehensive analysis of global climate initiatives")
|
||||
.url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf")
|
||||
.page_count(42));
|
||||
|
||||
let pdf2 = save_pdf(new_pdf()
|
||||
.title("Sustainable Development Goals")
|
||||
.description("UN SDG implementation guide")
|
||||
.url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf")
|
||||
.page_count(35));
|
||||
|
||||
let pdf3 = save_pdf(new_pdf()
|
||||
.title("Renewable Energy Handbook")
|
||||
.description("Technical guide to renewable energy systems")
|
||||
.url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf")
|
||||
.page_count(280));
|
||||
|
||||
let pdf4 = save_pdf(new_pdf()
|
||||
.title("Blockchain for Good")
|
||||
.description("How blockchain technology can solve global challenges")
|
||||
.url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype")
|
||||
.page_count(24));
|
||||
|
||||
let pdf5 = save_pdf(new_pdf()
|
||||
.title("Future of Work Report")
|
||||
.description("Analysis of changing work patterns and remote collaboration")
|
||||
.url("https://www.mckinsey.com/featured-insights/future-of-work")
|
||||
.page_count(156));
|
||||
|
||||
// === MARKDOWN DOCUMENTS ===
|
||||
print("Creating markdown documents...");
|
||||
|
||||
let md1 = save_markdown(new_markdown()
|
||||
.title("OurWorld Mission Statement")
|
||||
.description("Our vision for a better world")
|
||||
.content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities"));
|
||||
|
||||
let md2 = save_markdown(new_markdown()
|
||||
.title("Getting Started Guide")
|
||||
.description("How to join the OurWorld movement")
|
||||
.content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)"));
|
||||
|
||||
let md3 = save_markdown(new_markdown()
|
||||
.title("Technology Roadmap 2024")
|
||||
.description("Our technical development plans")
|
||||
.content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training"));
|
||||
|
||||
let md4 = save_markdown(new_markdown()
|
||||
.title("Community Guidelines")
|
||||
.description("How we work together")
|
||||
.content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone"));
|
||||
|
||||
|
||||
let investor = new_contact()
|
||||
.name("Example Investor")
|
||||
.save_contact();
|
||||
|
||||
let investors = new_group()
|
||||
.name("Investors")
|
||||
.description("A group for example inverstors of ourworld");
|
||||
|
||||
investors.add_contact(investor.id)
|
||||
.save_group();
|
||||
|
||||
// === BOOKS ===
|
||||
print("Creating books...");
|
||||
|
||||
let sustainability_book = save_book(new_book()
|
||||
.title("Sustainability Handbook")
|
||||
.description("Complete guide to sustainable living and practices")
|
||||
.add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.")
|
||||
.add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances")
|
||||
.add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need")
|
||||
.add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively")
|
||||
.add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Waste Reduction").page(2))
|
||||
.add_toc_entry(new_toc_entry().title("Sustainable Food").page(3)));
|
||||
|
||||
let tech_guide_book = save_book(new_book()
|
||||
.title("Green Technology Guide")
|
||||
.description("Understanding and implementing green technologies")
|
||||
.add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities")
|
||||
.add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates")
|
||||
.add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems")
|
||||
.add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Solar Technology").page(1))
|
||||
.add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2)));
|
||||
|
||||
let community_book = save_book(new_book()
|
||||
.title("Building Communities")
|
||||
.description("Guide to creating sustainable and inclusive communities")
|
||||
.add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship")
|
||||
.add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles")
|
||||
.add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0))
|
||||
.add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1)));
|
||||
|
||||
// === SLIDES ===
|
||||
print("Creating slides...");
|
||||
|
||||
let climate_slides = save_slides(new_slides()
|
||||
.title("Climate Change Awareness")
|
||||
.description("Visual presentation on climate change impacts and solutions")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200").title("Global Temperature Rise"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200").title("Melting Ice Caps"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200").title("Extreme Weather Events"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200").title("Renewable Energy Solutions"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200").title("Sustainable Transportation")));
|
||||
|
||||
let innovation_slides = save_slides(new_slides()
|
||||
.title("Innovation Showcase")
|
||||
.description("Cutting-edge technologies for a sustainable future")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200").title("AI and Machine Learning"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200").title("Blockchain Technology"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200").title("IoT and Smart Cities"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200").title("Quantum Computing"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200").title("Biotechnology Advances")));
|
||||
|
||||
let nature_slides = save_slides(new_slides()
|
||||
.title("Biodiversity Gallery")
|
||||
.description("Celebrating Earth's incredible biodiversity")
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200").title("Tropical Rainforest"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200").title("Coral Reef Ecosystem"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200").title("Arctic Wildlife"))
|
||||
.add_slide(new_slide().url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200").title("Mountain Ecosystems")));
|
||||
|
||||
// === COLLECTIONS ===
|
||||
print("Creating collections...");
|
||||
|
||||
let nature_collection = save_collection(new_collection()
|
||||
.title("Nature & Environment")
|
||||
.description("Beautiful images and resources about our natural world")
|
||||
.add_image(nature1.id)
|
||||
.add_image(nature2.id)
|
||||
.add_image(nature3.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id)
|
||||
.add_book(sustainability_book.id)
|
||||
.add_slides(nature_slides.id));
|
||||
|
||||
let technology_collection = save_collection(new_collection()
|
||||
.title("Sustainable Technology")
|
||||
.description("Innovations driving positive change")
|
||||
.add_image(tech1.id)
|
||||
.add_image(tech2.id)
|
||||
.add_pdf(pdf3.id)
|
||||
.add_pdf(pdf4.id)
|
||||
.add_markdown(md3.id)
|
||||
.add_book(tech_guide_book.id)
|
||||
.add_slides(innovation_slides.id));
|
||||
|
||||
let space_collection = save_collection(new_collection()
|
||||
.title("Space & Cosmos")
|
||||
.description("Exploring the universe and our place in it")
|
||||
.add_image(space1.id)
|
||||
.add_image(space2.id)
|
||||
.add_pdf(pdf2.id)
|
||||
.add_markdown(md2.id));
|
||||
|
||||
let community_collection = save_collection(new_collection()
|
||||
.title("Community & Collaboration")
|
||||
.description("Building better communities together")
|
||||
.add_image(city1.id)
|
||||
.add_pdf(pdf5.id)
|
||||
.add_markdown(md4.id)
|
||||
.add_book(community_book.id));
|
||||
|
||||
let climate_collection = save_collection(new_collection()
|
||||
.title("Climate Action")
|
||||
.description("Understanding and addressing climate change")
|
||||
.add_slides(climate_slides.id)
|
||||
.add_pdf(pdf1.id)
|
||||
.add_markdown(md1.id));
|
||||
|
||||
print("✅ OurWorld library created successfully!");
|
||||
print("📚 Collections: 5");
|
||||
print("🖼️ Images: 8");
|
||||
print("📄 PDFs: 5");
|
||||
print("📝 Markdown docs: 4");
|
||||
print("📖 Books: 3");
|
||||
print("🎞️ Slide shows: 3");
|
@ -1,15 +0,0 @@
|
||||
// OurWorld Circle and Library Data
|
||||
|
||||
new_circle()
|
||||
.title("Timur Gordon")
|
||||
.description("Creating a better world.")
|
||||
.ws_url("ws://localhost:9100/ws")
|
||||
.add_circle("ws://localhost:9000/ws")
|
||||
.add_circle("ws://localhost:9001/ws")
|
||||
.add_circle("ws://localhost:9002/ws")
|
||||
.add_circle("ws://localhost:9003/ws")
|
||||
.add_circle("ws://localhost:9004/ws")
|
||||
.add_circle("ws://localhost:9005/ws")
|
||||
.add_circle("ws://localhost:8096/ws")
|
||||
.logo("🌍")
|
||||
.save_circle();
|
@ -16,7 +16,7 @@ use circle_client_ws::CircleWsClientBuilder;
|
||||
const TEST_CIRCLE_NAME: &str = "e2e_test_circle";
|
||||
const TEST_SERVER_PORT: u16 = 9876; // Choose a unique port for the test
|
||||
const RHAI_WORKER_BIN_NAME: &str = "worker";
|
||||
const CIRCLE_SERVER_WS_BIN_NAME: &str = "server_ws";
|
||||
const CIRCLE_SERVER_WS_BIN_NAME: &str = "server";
|
||||
|
||||
// RAII guard for cleaning up child processes
|
||||
struct ChildProcessGuard {
|
||||
|
@ -14,7 +14,7 @@ use tokio::time::{sleep, Duration};
|
||||
const EXAMPLE_SERVER_PORT: u16 = 8089; // Using a specific port for this example
|
||||
const WS_URL: &str = "ws://127.0.0.1:8089/ws";
|
||||
const CIRCLE_NAME_FOR_EXAMPLE: &str = "timeout_example_circle";
|
||||
const CIRCLE_SERVER_WS_BIN_NAME: &str = "server_ws";
|
||||
const CIRCLE_SERVER_WS_BIN_NAME: &str = "server";
|
||||
const SCRIPT_TIMEOUT_SECONDS: u64 = 30; // This is the server-side timeout we expect to hit
|
||||
|
||||
// RAII guard for cleaning up child processes
|
||||
|
83
examples/wss_auth_example.rs
Normal file
83
examples/wss_auth_example.rs
Normal file
@ -0,0 +1,83 @@
|
||||
//! WSS + Authentication Example
|
||||
//!
|
||||
//! This example demonstrates a secure WebSocket server with:
|
||||
//! - TLS/WSS encryption
|
||||
//! - secp256k1 authentication
|
||||
//! - Message handling with authentication verification
|
||||
//!
|
||||
//! Usage: cargo run --manifest-path src/server/Cargo.toml --example wss_auth_example
|
||||
|
||||
use circle_ws_lib::{ServerConfig, spawn_circle_server};
|
||||
use log::{info, warn};
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize logging
|
||||
env_logger::init();
|
||||
|
||||
info!("🔐 Starting WSS + Authentication Server Example");
|
||||
info!("🛡️ This example demonstrates secure WebSocket with authentication");
|
||||
|
||||
// Create server configuration with TLS and authentication enabled
|
||||
let mut config = ServerConfig::new(
|
||||
"127.0.0.1".to_string(),
|
||||
8080, // Regular WebSocket port
|
||||
"redis://127.0.0.1:6379".to_string(),
|
||||
);
|
||||
|
||||
// Configure TLS settings
|
||||
config.enable_tls = true;
|
||||
config.tls_port = Some(8443);
|
||||
config.cert_path = Some("cert.pem".to_string());
|
||||
config.key_path = Some("key.pem".to_string());
|
||||
config.enable_auth = true; // Enable secp256k1 authentication
|
||||
|
||||
info!("📋 Server Configuration:");
|
||||
info!(" Host: {}", config.host);
|
||||
info!(" Regular WS Port: {}", config.port);
|
||||
info!(" WSS Port: {}", config.tls_port.unwrap_or(0));
|
||||
info!(" TLS Enabled: {}", config.enable_tls);
|
||||
info!(" Auth Enabled: {}", config.enable_auth);
|
||||
info!(" Certificate: {:?}", config.cert_path);
|
||||
info!(" Private Key: {:?}", config.key_path);
|
||||
|
||||
// Start the server
|
||||
let (join_handle, _server_handle) = spawn_circle_server(config)
|
||||
.map_err(|e| -> Box<dyn std::error::Error> {
|
||||
warn!("❌ Failed to start WSS + Auth server: {}", e);
|
||||
Box::new(e)
|
||||
})?;
|
||||
|
||||
info!("✅ WSS + Auth Server started successfully!");
|
||||
info!("🔒 Secure WebSocket URL: wss://127.0.0.1:8443/ws");
|
||||
info!("🔓 Regular WebSocket URL: ws://127.0.0.1:8080/ws");
|
||||
info!("🛡️ Authentication: secp256k1 signatures required");
|
||||
info!("");
|
||||
info!("🧪 To test authenticated connections:");
|
||||
info!(" 1. Generate a secp256k1 key pair");
|
||||
info!(" 2. Sign your messages with the private key");
|
||||
info!(" 3. Include the signature in your WebSocket messages");
|
||||
info!(" 4. The server will verify signatures before processing");
|
||||
info!("");
|
||||
info!("📝 Message format for authenticated requests:");
|
||||
info!(" {{");
|
||||
info!(" \"type\": \"your_message_type\",");
|
||||
info!(" \"data\": {{...}},");
|
||||
info!(" \"signature\": \"hex_encoded_signature\",");
|
||||
info!(" \"public_key\": \"hex_encoded_public_key\"");
|
||||
info!(" }}");
|
||||
info!("");
|
||||
|
||||
// Keep server running for demonstration
|
||||
info!("⏰ Server will run for 60 seconds, then shutdown...");
|
||||
sleep(Duration::from_secs(60)).await;
|
||||
|
||||
info!("🛑 Shutting down WSS + Auth server example");
|
||||
|
||||
// Wait for the server to finish
|
||||
let _ = join_handle.await;
|
||||
|
||||
Ok(())
|
||||
}
|
69
examples/wss_basic_example.rs
Normal file
69
examples/wss_basic_example.rs
Normal file
@ -0,0 +1,69 @@
|
||||
//! Basic WSS (WebSocket Secure) server example
|
||||
//!
|
||||
//! This example demonstrates how to start a WSS server with TLS enabled.
|
||||
//! It uses the existing certificate and key files for testing.
|
||||
//!
|
||||
//! To run this example:
|
||||
//! ```bash
|
||||
//! cargo run --example wss_basic_example
|
||||
//! ```
|
||||
|
||||
use circle_ws_lib::{spawn_circle_server, ServerConfig};
|
||||
use log::info;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize logging
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
info!("🚀 Starting Basic WSS Server Example");
|
||||
|
||||
// Create server configuration with TLS enabled
|
||||
let config = ServerConfig::new(
|
||||
"127.0.0.1".to_string(),
|
||||
8443, // Use port 8443 for WSS
|
||||
"redis://127.0.0.1/".to_string(),
|
||||
)
|
||||
.with_tls("cert.pem".to_string(), "key.pem".to_string())
|
||||
.with_tls_port(8443);
|
||||
|
||||
info!("📋 Server Configuration:");
|
||||
info!(" Host: {}", config.host);
|
||||
info!(" WSS Port: {}", config.get_tls_port());
|
||||
info!(" TLS Enabled: {}", config.enable_tls);
|
||||
info!(" Auth Enabled: {}", config.enable_auth);
|
||||
info!(" Certificate: {:?}", config.cert_path);
|
||||
info!(" Private Key: {:?}", config.key_path);
|
||||
|
||||
// Start the WSS server
|
||||
let (server_task, server_handle) = spawn_circle_server(config)?;
|
||||
|
||||
info!("✅ WSS Server started successfully!");
|
||||
info!("🔒 You can now connect to: wss://127.0.0.1:8443/ws");
|
||||
info!("📝 Note: This uses a self-signed certificate for testing");
|
||||
info!("");
|
||||
info!("🧪 To test the connection, you can use:");
|
||||
info!(" - A WebSocket client that supports WSS");
|
||||
info!(" - The client_ws library with TLS support");
|
||||
info!(" - Browser developer tools (may show certificate warnings)");
|
||||
info!("");
|
||||
info!("⏰ Server will run for 30 seconds, then shutdown...");
|
||||
|
||||
// Let the server run for 30 seconds
|
||||
sleep(Duration::from_secs(30)).await;
|
||||
|
||||
info!("🛑 Shutting down WSS server...");
|
||||
server_handle.stop(true).await;
|
||||
|
||||
// Wait for the server task to complete
|
||||
match server_task.await {
|
||||
Ok(Ok(())) => info!("✅ WSS server shut down successfully"),
|
||||
Ok(Err(e)) => info!("⚠️ WSS server shut down with error: {}", e),
|
||||
Err(e) => info!("❌ Failed to wait for server shutdown: {}", e),
|
||||
}
|
||||
|
||||
info!("🏁 Basic WSS example completed");
|
||||
Ok(())
|
||||
}
|
7
examples/wss_demo/.gitignore
vendored
Normal file
7
examples/wss_demo/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# TLS Certificates (uncomment if you want to exclude them)
|
||||
# cert.pem
|
||||
# key.pem
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.log
|
2958
examples/wss_demo/Cargo.lock
generated
Normal file
2958
examples/wss_demo/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
examples/wss_demo/Cargo.toml
Normal file
35
examples/wss_demo/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "wss_demo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# Empty workspace table to exclude from parent workspace
|
||||
[workspace]
|
||||
|
||||
[[bin]]
|
||||
name = "wss_client"
|
||||
path = "wss_client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wss_server"
|
||||
path = "wss_server.rs"
|
||||
|
||||
[dependencies]
|
||||
# WebSocket client library
|
||||
circle_client_ws = { path = "../../src/client_ws", features = ["crypto"] }
|
||||
|
||||
# Server library for the WSS server demo
|
||||
circle_ws_lib = { path = "../../src/server", features = ["auth"] }
|
||||
|
||||
# Common dependencies
|
||||
tokio = { version = "1.45", features = ["full"] }
|
||||
env_logger = "0.10"
|
||||
log = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# Server-specific dependencies
|
||||
actix = "0.13"
|
||||
actix-web = "4.11"
|
||||
actix-web-actors = "4.3"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
204
examples/wss_demo/README.md
Normal file
204
examples/wss_demo/README.md
Normal file
@ -0,0 +1,204 @@
|
||||
# WSS (WebSocket Secure) Demo
|
||||
|
||||
This directory contains a complete demonstration of WSS (WebSocket Secure) functionality with TLS encryption and authentication.
|
||||
|
||||
## Contents
|
||||
|
||||
- `cert.pem` - Self-signed TLS certificate for testing
|
||||
- `key.pem` - Private key for the TLS certificate
|
||||
- `wss_server.rs` - WSS server example with authentication
|
||||
- `wss_client.rs` - WSS client example for testing
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Start the WSS Server
|
||||
|
||||
```bash
|
||||
# From the project root - specify the correct package and feature
|
||||
cargo run --manifest-path src/server/Cargo.toml --example wss_server --features circle_ws_lib/auth
|
||||
|
||||
# OR run from the wss_demo directory
|
||||
cd examples/wss_demo
|
||||
cargo run --bin wss_server
|
||||
```
|
||||
|
||||
The server will start on:
|
||||
- **WSS (Secure)**: `wss://127.0.0.1:8443/ws`
|
||||
- **WS (Regular)**: `ws://127.0.0.1:8080/ws`
|
||||
|
||||
### 2. Test with the WSS Client
|
||||
|
||||
Run the WSS client that uses the circle_client_ws library:
|
||||
|
||||
```bash
|
||||
# Navigate to the wss_demo directory
|
||||
cd examples/wss_demo
|
||||
|
||||
# Run with logging (recommended)
|
||||
RUST_LOG=info cargo run --bin wss_client
|
||||
|
||||
# Or without logging
|
||||
cargo run --bin wss_client
|
||||
```
|
||||
|
||||
This will run the `wss_client.rs` file which demonstrates:
|
||||
- Automatic credential generation using secp256k1
|
||||
- WSS connection with TLS encryption
|
||||
- Full authentication flow
|
||||
- Script execution over secure WebSocket
|
||||
|
||||
**Note**: The WSS client must be run from the `examples/wss_demo` directory as it's a standalone project with its own dependencies.
|
||||
|
||||
## Features Demonstrated
|
||||
|
||||
### 🔒 **TLS/WSS Encryption**
|
||||
- Self-signed certificate for development/testing
|
||||
- Secure WebSocket connections over TLS
|
||||
- Certificate validation and error handling
|
||||
|
||||
### 🛡️ **Authentication**
|
||||
- secp256k1 signature-based authentication
|
||||
- Nonce generation and verification
|
||||
- Authenticated vs unauthenticated request handling
|
||||
|
||||
### 📝 **JSON-RPC Protocol**
|
||||
- Standard JSON-RPC 2.0 over WebSocket
|
||||
- Method calls: `fetch_nonce`, `authenticate`, `play`
|
||||
- Error handling and response validation
|
||||
|
||||
## Certificate Information
|
||||
|
||||
The included certificate is a **self-signed certificate** for development and testing purposes only.
|
||||
|
||||
**Certificate Details:**
|
||||
- **Subject**: `/C=US/ST=Demo/L=Demo/O=WSS Demo/CN=localhost`
|
||||
- **Validity**: 365 days from generation
|
||||
- **Key Size**: RSA 4096-bit
|
||||
- **Usage**: Development/Testing only
|
||||
|
||||
⚠️ **Security Notice**: Do not use this certificate in production. Generate proper certificates from a trusted Certificate Authority for production use.
|
||||
|
||||
## Testing with Browser
|
||||
|
||||
You can test the WSS connection using browser developer tools:
|
||||
|
||||
```javascript
|
||||
// Open browser console and run:
|
||||
const ws = new WebSocket('wss://127.0.0.1:8443/ws');
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WSS Connected!');
|
||||
// Send a test message
|
||||
ws.send(JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
method: "fetch_nonce",
|
||||
params: { pubkey: "test_key" },
|
||||
id: 1
|
||||
}));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
console.log('Response:', JSON.parse(event.data));
|
||||
};
|
||||
```
|
||||
|
||||
**Note**: Your browser may show a security warning due to the self-signed certificate. This is expected for development.
|
||||
|
||||
## Testing with websocat
|
||||
|
||||
You can also test with websocat (WebSocket command-line client):
|
||||
|
||||
```bash
|
||||
# Install websocat if not already installed
|
||||
cargo install websocat
|
||||
|
||||
# Connect to WSS server (ignoring certificate validation for self-signed cert)
|
||||
websocat --insecure wss://127.0.0.1:8443/ws
|
||||
|
||||
# Or with more explicit options
|
||||
websocat -k wss://127.0.0.1:8443/ws
|
||||
|
||||
# Send a test message (after connecting)
|
||||
{"jsonrpc":"2.0","method":"fetch_nonce","params":{"pubkey":"test_key"},"id":1}
|
||||
```
|
||||
|
||||
**Note**: The `--insecure` or `-k` flag is needed because we're using a self-signed certificate.
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
### 1. Fetch Nonce
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "fetch_nonce",
|
||||
"params": { "pubkey": "your_public_key_hex" },
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Authenticate
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "authenticate",
|
||||
"params": {
|
||||
"pubkey": "your_public_key_hex",
|
||||
"signature": "signed_nonce_hex"
|
||||
},
|
||||
"id": 2
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Execute Commands
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "play",
|
||||
"params": { "script": "40 + 2" },
|
||||
"id": 3
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Certificate Issues
|
||||
If you encounter certificate-related errors:
|
||||
|
||||
1. **Regenerate certificates**:
|
||||
```bash
|
||||
cd examples/wss_demo
|
||||
rm cert.pem key.pem
|
||||
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/C=US/ST=Demo/L=Demo/O=WSS Demo/CN=localhost"
|
||||
```
|
||||
|
||||
2. **Check file permissions**:
|
||||
```bash
|
||||
ls -la cert.pem key.pem
|
||||
```
|
||||
|
||||
### Connection Issues
|
||||
- Ensure no other services are using ports 8080 or 8443
|
||||
- Check firewall settings
|
||||
- Verify the server started successfully (look for "✅ WSS Server started successfully!" message)
|
||||
|
||||
### Authentication Issues
|
||||
- Ensure you're using valid secp256k1 signatures
|
||||
- Check that the nonce hasn't expired (default: 5 minutes)
|
||||
- Verify the public key format is correct hex encoding
|
||||
|
||||
## Production Deployment
|
||||
|
||||
For production use:
|
||||
|
||||
1. **Use proper certificates** from a trusted CA
|
||||
2. **Configure proper hostnames** (not localhost)
|
||||
3. **Set up proper firewall rules**
|
||||
4. **Use environment variables** for sensitive configuration
|
||||
5. **Enable proper logging and monitoring**
|
||||
6. **Consider load balancing** for high availability
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [WSS Implementation Plan](../../WSS_IMPLEMENTATION_PLAN.md)
|
||||
- [Server WS README](../../src/server/README.md)
|
||||
- [Client WS README](../../src/client_ws/README.md)
|
94
examples/wss_demo/wss_client.rs
Normal file
94
examples/wss_demo/wss_client.rs
Normal file
@ -0,0 +1,94 @@
|
||||
//! WSS Client Demo using Circle WebSocket Client
|
||||
//!
|
||||
//! This example demonstrates connecting to a WSS (WebSocket Secure) server
|
||||
//! using the circle_client_ws library with proper authentication.
|
||||
|
||||
use circle_client_ws::{CircleWsClientBuilder, auth};
|
||||
use log::{info, error};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize logging - use RUST_LOG=info for detailed output
|
||||
env_logger::init();
|
||||
|
||||
println!("🧪 Starting WSS Client Demo");
|
||||
|
||||
// Generate authentication credentials
|
||||
let private_key = auth::generate_private_key()?;
|
||||
let public_key = auth::derive_public_key(&private_key)?;
|
||||
|
||||
info!("🔑 Generated credentials - Public key: {}...", &public_key[..20]);
|
||||
|
||||
// WSS server URL (secure WebSocket)
|
||||
let wss_url = "wss://127.0.0.1:8443/ws";
|
||||
|
||||
info!("🔗 Connecting to WSS server at {}", wss_url);
|
||||
println!("🔌 Connecting to WSS server...");
|
||||
|
||||
// Create WSS client with authentication
|
||||
let mut client = CircleWsClientBuilder::new(wss_url.to_string())
|
||||
.with_keypair(private_key)
|
||||
.build();
|
||||
|
||||
// Connect to the server
|
||||
match client.connect().await {
|
||||
Ok(()) => {
|
||||
println!("✅ Successfully connected to WSS server!");
|
||||
info!("📡 Response status: 101 Switching Protocols");
|
||||
|
||||
println!("\n🧪 Testing WSS connection with JSON-RPC messages...");
|
||||
|
||||
// Test 1: Authentication
|
||||
println!("📤 Test 1: Performing authentication");
|
||||
match client.authenticate().await {
|
||||
Ok(true) => {
|
||||
println!("✅ Authentication successful!");
|
||||
info!("🔐 Client authenticated with server");
|
||||
|
||||
// Test 2: Execute script (authenticated request)
|
||||
println!("\n📤 Test 2: Executing script (authenticated request)");
|
||||
let test_script = "40 + 2".to_string();
|
||||
|
||||
match client.play(test_script).await {
|
||||
Ok(result) => {
|
||||
println!("✅ Script execution successful!");
|
||||
println!("📊 Result: {}", result.output);
|
||||
info!("Script output: {}", result.output);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Script execution failed: {}", e);
|
||||
println!("❌ Script execution failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
println!("❌ Authentication failed - server rejected credentials");
|
||||
error!("Authentication failed");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Authentication error: {}", e);
|
||||
println!("❌ Authentication error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect
|
||||
client.disconnect().await;
|
||||
println!("\n🔌 Disconnected from WSS server");
|
||||
|
||||
println!("\n🎉 Summary:");
|
||||
println!(" ✅ WSS connection established");
|
||||
println!(" ✅ TLS encryption working");
|
||||
println!(" ✅ JSON-RPC protocol working");
|
||||
println!(" ✅ Authentication system working");
|
||||
println!(" ✅ Script execution working");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Failed to connect to WSS server: {}", e);
|
||||
println!("❌ Connection failed: {}", e);
|
||||
println!("\n💡 Make sure the WSS server is running:");
|
||||
println!(" cargo run --manifest-path src/server/Cargo.toml --example wss_server --features auth");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
91
examples/wss_demo/wss_server.rs
Normal file
91
examples/wss_demo/wss_server.rs
Normal file
@ -0,0 +1,91 @@
|
||||
//! WSS Server Demo
|
||||
//!
|
||||
//! This example demonstrates a complete WSS (WebSocket Secure) server with:
|
||||
//! - TLS encryption using self-signed certificates
|
||||
//! - secp256k1 authentication
|
||||
//! - JSON-RPC protocol support
|
||||
//! - Comprehensive logging and error handling
|
||||
//!
|
||||
//! Usage: cargo run --example wss_server --features auth
|
||||
|
||||
use circle_ws_lib::{ServerConfig, spawn_circle_server};
|
||||
use log::{info, warn, error};
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize logging
|
||||
env_logger::init();
|
||||
|
||||
info!("🚀 Starting WSS Server Demo");
|
||||
info!("🔐 This demo includes TLS encryption and secp256k1 authentication");
|
||||
info!("");
|
||||
|
||||
// Create server configuration with TLS and authentication enabled
|
||||
let config = ServerConfig::new(
|
||||
"127.0.0.1".to_string(),
|
||||
8080, // Regular WebSocket port
|
||||
"redis://127.0.0.1:6379".to_string(),
|
||||
)
|
||||
.with_tls(
|
||||
"../../src/server/examples/wss_demo/cert.pem".to_string(),
|
||||
"../../src/server/examples/wss_demo/key.pem".to_string()
|
||||
)
|
||||
.with_tls_port(8443) // Secure WebSocket port
|
||||
.with_auth(); // Enable secp256k1 authentication
|
||||
|
||||
info!("📋 Server Configuration:");
|
||||
info!(" Host: {}", config.host);
|
||||
info!(" Regular WS Port: {}", config.port);
|
||||
info!(" WSS Port: {}", config.get_tls_port());
|
||||
info!(" TLS Enabled: {}", config.enable_tls);
|
||||
info!(" Auth Enabled: {}", config.enable_auth);
|
||||
info!(" Certificate: {:?}", config.cert_path);
|
||||
info!(" Private Key: {:?}", config.key_path);
|
||||
info!("");
|
||||
|
||||
// Start the server
|
||||
let (join_handle, _server_handle) = spawn_circle_server(config)
|
||||
.map_err(|e| -> Box<dyn std::error::Error> {
|
||||
error!("❌ Failed to start WSS server: {}", e);
|
||||
Box::new(e)
|
||||
})?;
|
||||
|
||||
info!("✅ WSS Server started successfully!");
|
||||
info!("");
|
||||
info!("🔗 Connection URLs:");
|
||||
info!(" 🔒 Secure WebSocket: wss://127.0.0.1:8443/ws");
|
||||
info!(" 🔓 Regular WebSocket: ws://127.0.0.1:8080/ws");
|
||||
info!("");
|
||||
info!("🛡️ Authentication: secp256k1 signatures required for 'play' commands");
|
||||
info!("🔓 Public methods: 'fetch_nonce' (no auth required)");
|
||||
info!("");
|
||||
info!("📝 Example JSON-RPC requests:");
|
||||
info!(" 1. Fetch nonce (no auth):");
|
||||
info!(" {{\"jsonrpc\":\"2.0\",\"method\":\"fetch_nonce\",\"params\":{{\"pubkey\":\"your_pubkey\"}},\"id\":1}}");
|
||||
info!("");
|
||||
info!(" 2. Authenticate:");
|
||||
info!(" {{\"jsonrpc\":\"2.0\",\"method\":\"authenticate\",\"params\":{{\"pubkey\":\"your_pubkey\",\"signature\":\"signed_nonce\"}},\"id\":2}}");
|
||||
info!("");
|
||||
info!(" 3. Execute script (requires auth):");
|
||||
info!(" {{\"jsonrpc\":\"2.0\",\"method\":\"play\",\"params\":{{\"script\":\"40 + 2\"}},\"id\":3}}");
|
||||
info!("");
|
||||
info!("🧪 Test with the WSS client:");
|
||||
info!(" cargo run --example wss_client");
|
||||
info!("");
|
||||
info!("🌐 Test with browser (open console):");
|
||||
info!(" const ws = new WebSocket('wss://127.0.0.1:8443/ws');");
|
||||
info!(" ws.onopen = () => ws.send(JSON.stringify({{jsonrpc:'2.0',method:'fetch_nonce',params:{{pubkey:'test'}},id:1}}));");
|
||||
info!(" ws.onmessage = (e) => console.log(JSON.parse(e.data));");
|
||||
info!("");
|
||||
info!("⚠️ Note: Browser may show certificate warning (self-signed cert)");
|
||||
info!("");
|
||||
info!("🔄 Server running... Press Ctrl+C to stop");
|
||||
|
||||
// Keep server running until interrupted
|
||||
let _ = join_handle.await;
|
||||
|
||||
info!("🛑 WSS Server stopped");
|
||||
Ok(())
|
||||
}
|
136
examples/wss_test_client.rs
Normal file
136
examples/wss_test_client.rs
Normal file
@ -0,0 +1,136 @@
|
||||
//! WSS Test Client
|
||||
//!
|
||||
//! This example demonstrates connecting to a WSS server for testing purposes.
|
||||
//! It's a simple client that connects to the WSS server and sends a test message.
|
||||
//!
|
||||
//! Usage: cargo run --manifest-path src/server/Cargo.toml --example wss_test_client
|
||||
|
||||
use tokio_tungstenite::{connect_async_tls_with_config, Connector};
|
||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use log::{info, warn, error};
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize logging
|
||||
env_logger::init();
|
||||
|
||||
info!("🧪 Starting WSS Test Client");
|
||||
info!("🔗 Attempting to connect to wss://127.0.0.1:8443/ws");
|
||||
|
||||
// Create a TLS connector that accepts self-signed certificates for testing
|
||||
let connector = Connector::NativeTls(
|
||||
native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build()
|
||||
.map_err(|e| format!("TLS connector error: {}", e))?
|
||||
);
|
||||
|
||||
let config = Some(tokio_tungstenite::tungstenite::protocol::WebSocketConfig {
|
||||
max_send_queue: None,
|
||||
max_message_size: Some(64 << 20), // 64 MB
|
||||
max_frame_size: Some(16 << 20), // 16 MB
|
||||
accept_unmasked_frames: false,
|
||||
});
|
||||
|
||||
// Connect to the WSS server
|
||||
info!("🔌 Connecting to WSS server...");
|
||||
|
||||
let connect_result = timeout(
|
||||
Duration::from_secs(10),
|
||||
connect_async_tls_with_config(
|
||||
"wss://127.0.0.1:8443/ws",
|
||||
config,
|
||||
false,
|
||||
Some(connector)
|
||||
)
|
||||
).await;
|
||||
|
||||
let (ws_stream, response) = match connect_result {
|
||||
Ok(Ok((stream, response))) => {
|
||||
info!("✅ Successfully connected to WSS server!");
|
||||
info!("📡 Response status: {}", response.status());
|
||||
(stream, response)
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("❌ Failed to connect to WSS server: {}", e);
|
||||
return Err(format!("Connection failed: {}", e).into());
|
||||
}
|
||||
Err(_) => {
|
||||
error!("❌ Connection timeout after 10 seconds");
|
||||
return Err("Connection timeout".into());
|
||||
}
|
||||
};
|
||||
|
||||
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
|
||||
|
||||
// Send a test JSON-RPC message
|
||||
let test_message = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "fetch_nonce",
|
||||
"params": {
|
||||
"pubkey": "test_public_key_123"
|
||||
},
|
||||
"id": 1
|
||||
});
|
||||
|
||||
info!("📤 Sending test message: {}", test_message);
|
||||
|
||||
ws_sender.send(Message::Text(test_message.to_string())).await
|
||||
.map_err(|e| format!("Send error: {}", e))?;
|
||||
|
||||
// Wait for response
|
||||
info!("⏳ Waiting for server response...");
|
||||
|
||||
let response_result = timeout(Duration::from_secs(5), ws_receiver.next()).await;
|
||||
|
||||
match response_result {
|
||||
Ok(Some(Ok(Message::Text(response)))) => {
|
||||
info!("📥 Received response: {}", response);
|
||||
|
||||
// Parse the JSON response
|
||||
match serde_json::from_str::<serde_json::Value>(&response) {
|
||||
Ok(json_response) => {
|
||||
if json_response.get("result").is_some() {
|
||||
info!("✅ Server responded with valid JSON-RPC result");
|
||||
info!("🎉 WSS connection and communication test PASSED!");
|
||||
} else if json_response.get("error").is_some() {
|
||||
warn!("⚠️ Server responded with error (expected for unauthenticated request)");
|
||||
info!("✅ WSS connection test PASSED (server is responding correctly)");
|
||||
} else {
|
||||
warn!("⚠️ Unexpected response format");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("⚠️ Failed to parse JSON response: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(Ok(message))) => {
|
||||
info!("📥 Received non-text message: {:?}", message);
|
||||
}
|
||||
Ok(Some(Err(e))) => {
|
||||
error!("❌ WebSocket error: {}", e);
|
||||
return Err(format!("WebSocket error: {}", e).into());
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("⚠️ Connection closed by server");
|
||||
}
|
||||
Err(_) => {
|
||||
error!("❌ Response timeout after 5 seconds");
|
||||
return Err("Response timeout".into());
|
||||
}
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
info!("🔌 Closing WSS connection...");
|
||||
ws_sender.close().await
|
||||
.map_err(|e| format!("Close error: {}", e))?;
|
||||
|
||||
info!("✅ WSS Test Client completed successfully!");
|
||||
|
||||
Ok(())
|
||||
}
|
11
index.html
11
index.html
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Circles</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -6,7 +6,7 @@ This document provides a detailed look into the internal architecture of the `la
|
||||
|
||||
The `launcher` is a `tokio`-based asynchronous application. Its primary responsibility is to parse a configuration file and then spawn and manage the lifecycle of two key components for each configured Circle:
|
||||
|
||||
1. **A `server_ws` instance**: A WebSocket server running in its own `tokio` task.
|
||||
1. **A `server` instance**: A WebSocket server running in its own `tokio` task.
|
||||
2. **A Rhai worker**: A script execution engine running in a separate `tokio` task.
|
||||
|
||||
The launcher maintains a central registry of all running circles, allowing it to monitor their status and coordinate a graceful shutdown.
|
@ -1,22 +1,22 @@
|
||||
[package]
|
||||
name = "launcher"
|
||||
name = "circles-launcher"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "launcher"
|
||||
name = "circles_launcher"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "launcher"
|
||||
path = "src/cmd/main.rs"
|
||||
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
comfy-table = "7.0"
|
||||
actix-web = { workspace = true }
|
||||
secp256k1 = { version = "0.28.0", features = ["rand-std"] }
|
||||
@ -26,11 +26,11 @@ rhai = "1.18.0"
|
||||
|
||||
# Path dependencies to other local crates
|
||||
heromodels = { path = "../../../db/heromodels" }
|
||||
engine = { path = "../../../rhailib/src/engine" }
|
||||
rhailib_engine = { path = "../../../rhailib/src/engine" }
|
||||
rhailib_worker = { path = "../../../rhailib/src/worker" }
|
||||
rhai_client = { path = "../../../rhailib/src/client" }
|
||||
ourdb = { path = "../../../db/ourdb" } # Added for IdSequence
|
||||
circle_ws_lib = { path = "../server_ws" }
|
||||
sal-service-manager = { path = "../../../sal/service_manager" }
|
||||
tokio-tungstenite = "0.23"
|
||||
url = "2.5.2"
|
||||
|
||||
@ -43,3 +43,12 @@ futures-util = "0.3"
|
||||
redis = { version = "0.25.4", features = ["tokio-comp"] }
|
||||
rand = "0.8"
|
||||
url = "2.5.2"
|
||||
hex = "0.4"
|
||||
|
||||
[[example]]
|
||||
name = "circle_launcher_example"
|
||||
path = "examples/circle_launcher_example.rs"
|
||||
|
||||
[[example]]
|
||||
name = "cleanup_example"
|
||||
path = "examples/cleanup_example.rs"
|
254
research/launcher/README.md
Normal file
254
research/launcher/README.md
Normal file
@ -0,0 +1,254 @@
|
||||
# Circle Launcher
|
||||
|
||||
Crate for launching and managing [circle workers](../worker) and the [circles ws server](../server).
|
||||
|
||||
## Features
|
||||
|
||||
- **Single-server multi-circle architecture**: One WebSocket server handles all circles via path-based routing
|
||||
- **Dual operation modes**: Direct spawning or service manager integration
|
||||
- **Initialization scripts**: Send Rhai scripts to workers on startup
|
||||
- **Service management**: Automatic restart and background operation support
|
||||
- **Cross-platform**: macOS (launchctl) and Linux (systemd) support
|
||||
- **Service cleanup**: Automatic cleanup of background services on exit
|
||||
|
||||
## Installation
|
||||
|
||||
Build the launcher:
|
||||
|
||||
```bash
|
||||
cargo build --release --bin launcher
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```bash
|
||||
launcher [OPTIONS] -c <CIRCLE>...
|
||||
```
|
||||
|
||||
### Circle Configuration Format
|
||||
|
||||
Circles are specified using the `-c/--circle` option with the format:
|
||||
|
||||
```
|
||||
public_key[:init_script.rhai]
|
||||
```
|
||||
|
||||
- `public_key`: secp256k1 public key in hex format (required)
|
||||
- `init_script.rhai`: Optional initialization script to send to the worker
|
||||
|
||||
### Examples
|
||||
|
||||
#### Development Mode (Direct Spawning)
|
||||
|
||||
```bash
|
||||
# Single circle without initialization script
|
||||
launcher -c 02a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4
|
||||
|
||||
# Multiple circles with initialization scripts
|
||||
launcher -c 02a1b2c3d4e5f6a7...d4:setup.rhai -c 03b2c3d4e5f6a7...e5:config.rhai
|
||||
|
||||
# Mixed configuration (some with scripts, some without)
|
||||
launcher -c 02a1b2c3d4e5f6a7...d4:init.rhai -c 03b2c3d4e5f6a7...e5
|
||||
```
|
||||
|
||||
#### Production Mode (Service Manager)
|
||||
|
||||
```bash
|
||||
# Using service manager with worker binary
|
||||
launcher --use-service-manager --worker-binary ./target/release/worker \
|
||||
-c 02a1b2c3d4e5f6a7...d4:prod_init.rhai \
|
||||
-c 03b2c3d4e5f6a7...e5
|
||||
|
||||
# Service manager without initialization scripts
|
||||
launcher --use-service-manager --worker-binary /usr/local/bin/worker \
|
||||
-c 02a1b2c3d4e5f6a7...d4 \
|
||||
-c 03b2c3d4e5f6a7...e5
|
||||
```
|
||||
|
||||
## Command Line Options
|
||||
|
||||
| Option | Short | Description | Default |
|
||||
|--------|-------|-------------|---------|
|
||||
| `--circle` | `-c` | Circle configuration: `public_key[:init_script.rhai]` | Required |
|
||||
| `--port` | `-p` | WebSocket server port | 8080 |
|
||||
| `--redis-url` | | Redis connection URL | `redis://127.0.0.1:6379` |
|
||||
| `--enable-auth` | | Enable WebSocket authentication | false |
|
||||
| `--use-service-manager` | | Use service manager instead of direct spawning | false |
|
||||
| `--worker-binary` | | Path to worker binary (required with service manager) | None |
|
||||
| `--debug` | `-d` | Enable debug logging | false |
|
||||
| `--verbose` | `-v` | Increase verbosity (can be used multiple times) | 0 |
|
||||
|
||||
## Operation Modes
|
||||
|
||||
### Direct Spawn Mode (Default)
|
||||
|
||||
In direct spawn mode, workers run as Tokio tasks within the launcher process:
|
||||
|
||||
- **Pros**: Simple setup, immediate shutdown, ideal for development
|
||||
- **Cons**: Workers stop when launcher exits, no automatic restart
|
||||
|
||||
```bash
|
||||
launcher -c 02a1b2c3d4e5f6a7...d4:init.rhai
|
||||
```
|
||||
|
||||
### Service Manager Mode
|
||||
|
||||
In service manager mode, workers are managed by the system service manager:
|
||||
|
||||
- **Pros**: Background operation, automatic restart, production-ready
|
||||
- **Cons**: Requires worker binary, platform-specific setup
|
||||
|
||||
```bash
|
||||
launcher --use-service-manager --worker-binary ./target/release/worker \
|
||||
-c 02a1b2c3d4e5f6a7...d4:init.rhai
|
||||
```
|
||||
|
||||
#### Service Manager Support
|
||||
|
||||
- **macOS**: Uses `launchctl` with launch agents
|
||||
- **Linux**: Uses `systemd` (implementation in progress)
|
||||
|
||||
Services are named: `tf.ourworld.circles.circle-worker-{public_key}`
|
||||
|
||||
## Architecture
|
||||
|
||||
### Single-Server Multi-Circle
|
||||
|
||||
The launcher creates one WebSocket server that handles multiple circles through path-based routing:
|
||||
|
||||
- **Server URL**: `ws://127.0.0.1:8080`
|
||||
- **Circle URLs**: `ws://127.0.0.1:8080/{circle_public_key}`
|
||||
- **Worker Queues**: `rhai_tasks:{circle_public_key}`
|
||||
|
||||
### Initialization Scripts
|
||||
|
||||
When a circle configuration includes an initialization script:
|
||||
|
||||
1. Worker starts and connects to Redis
|
||||
2. Launcher waits 2 seconds for worker startup
|
||||
3. Launcher sends script content via RhaiClient to worker's queue
|
||||
4. Worker executes the initialization script
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `RUST_LOG`: Controls logging level (auto-configured based on verbosity)
|
||||
- `PRESERVE_TASKS`: Preserve Redis tasks on worker shutdown
|
||||
|
||||
### Data Directory
|
||||
|
||||
The launcher creates a `./launch_data` directory for:
|
||||
- Worker databases: `circle_db_{public_key}.db`
|
||||
- Service configuration files (service manager mode)
|
||||
|
||||
## Error Handling
|
||||
|
||||
Common error scenarios and solutions:
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| "Invalid public key" | Malformed secp256k1 key | Verify key format (64 hex chars) |
|
||||
| "Worker binary path required" | Missing `--worker-binary` in service mode | Provide path to worker executable |
|
||||
| "Failed to read init script" | Script file not found | Check script file path and permissions |
|
||||
| "Service already exists" | Service name conflict | Stop existing service or use different key |
|
||||
|
||||
## Development
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Debug build
|
||||
cargo build --bin launcher
|
||||
|
||||
# Release build
|
||||
cargo build --release --bin launcher
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run with debug logging
|
||||
RUST_LOG=debug cargo run --bin launcher -- -c 02a1b2c3d4e5f6a7...d4
|
||||
|
||||
# Test service manager mode
|
||||
cargo run --bin launcher -- --use-service-manager \
|
||||
--worker-binary ./target/debug/worker \
|
||||
-c 02a1b2c3d4e5f6a7...d4:test.rhai
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Worker Connection Issues
|
||||
|
||||
1. Verify Redis is running on the specified URL
|
||||
2. Check worker binary exists and is executable
|
||||
3. Ensure public keys are valid secp256k1 format
|
||||
|
||||
### Service Manager Issues
|
||||
|
||||
1. Check service manager logs: `launchctl log show --predicate 'subsystem == "tf.ourworld.circles"'`
|
||||
2. Verify worker binary permissions and dependencies
|
||||
3. Ensure working directory is accessible
|
||||
|
||||
### Script Execution Issues
|
||||
|
||||
1. Verify script file exists and is readable
|
||||
2. Check Redis connectivity for script transmission
|
||||
3. Monitor worker logs for script execution errors
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Public Key Validation**: All keys are validated as proper secp256k1 public keys
|
||||
- **Script Execution**: Initialization scripts run with worker privileges
|
||||
- **Service Isolation**: Each worker runs as a separate service in service manager mode
|
||||
- **Redis Security**: Ensure Redis instance is properly secured in production
|
||||
|
||||
## Performance
|
||||
|
||||
- **Concurrent Workers**: No hard limit on number of circles
|
||||
- **Resource Usage**: Each worker consumes memory for database and Rhai engine
|
||||
- **Network**: Single WebSocket server reduces port usage
|
||||
- **Startup Time**: ~2 second delay for initialization script transmission
|
||||
|
||||
## Service Cleanup
|
||||
|
||||
The launcher provides automatic cleanup functionality to stop and remove all circle-related services:
|
||||
|
||||
### Automatic Cleanup
|
||||
|
||||
Examples automatically clean up services on exit:
|
||||
|
||||
```bash
|
||||
# Run example - services are cleaned up automatically on exit or Ctrl+C
|
||||
cargo run --example circle_launcher_example
|
||||
```
|
||||
|
||||
### Manual Cleanup
|
||||
|
||||
Clean up all circle services manually:
|
||||
|
||||
```bash
|
||||
# Using the cleanup example
|
||||
cargo run --example cleanup_example
|
||||
|
||||
# Or using the library function
|
||||
use circles_launcher::cleanup_launcher;
|
||||
cleanup_launcher().await?;
|
||||
```
|
||||
|
||||
### What Gets Cleaned Up
|
||||
|
||||
The cleanup function removes:
|
||||
- All worker services (`circle-worker-{public_key}`)
|
||||
- WebSocket server service (`circle-ws-server`)
|
||||
- Associated service configuration files (plist files on macOS)
|
||||
|
||||
### Signal Handling
|
||||
|
||||
Examples include signal handling for graceful cleanup:
|
||||
- **Ctrl+C**: Triggers cleanup before exit
|
||||
- **Normal exit**: Always runs cleanup before termination
|
||||
- **Error exit**: Cleanup still runs to prevent orphaned services
|
144
research/launcher/examples/README.md
Normal file
144
research/launcher/examples/README.md
Normal file
@ -0,0 +1,144 @@
|
||||
# Launcher Examples
|
||||
|
||||
This directory contains examples demonstrating how to use the circles launcher.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running the examples, make sure you have:
|
||||
|
||||
1. Built the worker binary:
|
||||
```bash
|
||||
cd ../worker && cargo build --release
|
||||
```
|
||||
|
||||
2. Built the WebSocket server binary:
|
||||
```bash
|
||||
cd ../server && cargo build --release
|
||||
```
|
||||
|
||||
3. Redis server running on `redis://127.0.0.1:6379`
|
||||
|
||||
## Examples
|
||||
|
||||
### 1. Circle Launcher Example (`circle_launcher_example.rs`)
|
||||
|
||||
Demonstrates the builder pattern API for launching circles programmatically:
|
||||
|
||||
```bash
|
||||
cd src/launcher
|
||||
cargo run --example circle_launcher_example
|
||||
```
|
||||
|
||||
This example shows:
|
||||
- Creating circles with generated public keys
|
||||
- Using the builder pattern API
|
||||
- Launching single and multiple circles
|
||||
- Adding initialization scripts
|
||||
- Proper cleanup between examples
|
||||
|
||||
### 2. Cleanup Example (`cleanup_example.rs`)
|
||||
|
||||
Shows how to clean up all launcher services:
|
||||
|
||||
```bash
|
||||
cd src/launcher
|
||||
cargo run --example cleanup_example
|
||||
```
|
||||
|
||||
### 3. End-to-End Confirmation (`confirm_launch.rs`)
|
||||
|
||||
Tests the complete launcher workflow including service communication:
|
||||
|
||||
```bash
|
||||
cd src/launcher
|
||||
cargo run --example confirm_launch
|
||||
```
|
||||
|
||||
This example:
|
||||
- Launches the launcher binary with command line arguments
|
||||
- Tests worker communication via Redis
|
||||
- Verifies environment variables are set correctly
|
||||
- Performs end-to-end validation
|
||||
|
||||
### 4. OurWorld Example (`ourworld/main.rs`)
|
||||
|
||||
Real-world example using actual circle configurations:
|
||||
|
||||
```bash
|
||||
cd src/launcher
|
||||
cargo run --example ourworld
|
||||
```
|
||||
|
||||
## Command Line Usage
|
||||
|
||||
You can also use the launcher binary directly:
|
||||
|
||||
```bash
|
||||
# Single circle
|
||||
cargo run --bin launcher -- \
|
||||
--circle 02a1b2c3d4e5f6789abcdef... \
|
||||
--worker-binary ../target/release/worker \
|
||||
--port 8080
|
||||
|
||||
# Multiple circles with initialization scripts
|
||||
cargo run --bin launcher -- \
|
||||
--circle 02a1b2c3d4e5f6789abcdef...:test_script.rhai \
|
||||
--circle 03b2c3d4e5f6789abcdef012... \
|
||||
--worker-binary ../target/release/worker \
|
||||
--port 8080
|
||||
|
||||
# With custom Redis URL
|
||||
cargo run --bin launcher -- \
|
||||
--circle 02a1b2c3d4e5f6789abcdef... \
|
||||
--worker-binary ../target/release/worker \
|
||||
--redis-url redis://localhost:6379 \
|
||||
--port 8080
|
||||
```
|
||||
|
||||
## Circle Configuration Format
|
||||
|
||||
Circles can be specified in two formats:
|
||||
|
||||
1. **Public key only**: `02a1b2c3d4e5f6789abcdef...`
|
||||
2. **Public key with init script**: `02a1b2c3d4e5f6789abcdef...:init_script.rhai`
|
||||
|
||||
The public key must be a valid secp256k1 public key in hex format.
|
||||
|
||||
## Service Management
|
||||
|
||||
The launcher uses the system service manager (launchctl on macOS) to manage:
|
||||
|
||||
- **WebSocket server**: `circle-ws-server`
|
||||
- **Worker processes**: `circle-worker-<PUBLIC_KEY>`
|
||||
|
||||
Services are automatically started and can be managed independently after launch.
|
||||
|
||||
## Cleanup
|
||||
|
||||
To clean up all launcher services:
|
||||
|
||||
```bash
|
||||
cargo run --example cleanup_example
|
||||
```
|
||||
|
||||
Or use the library function:
|
||||
|
||||
```rust
|
||||
use circles_launcher::cleanup_launcher;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
cleanup_launcher().await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. **Port already in use**: The launcher checks if services are already running and reuses them when possible.
|
||||
|
||||
2. **Worker binary not found**: Make sure to build the worker binary first and specify the correct path.
|
||||
|
||||
3. **Redis connection failed**: Ensure Redis is running and accessible at the specified URL.
|
||||
|
||||
4. **Service manager errors**: Check system logs for service-specific errors. On macOS, use `launchctl list | grep circle` to see service status.
|
146
research/launcher/examples/circle_launcher_example.rs
Normal file
146
research/launcher/examples/circle_launcher_example.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use circles_launcher::{Circle, Launcher};
|
||||
use rand::rngs::OsRng;
|
||||
use sal_service_manager::create_service_manager;
|
||||
use secp256k1::{PublicKey, Secp256k1, SecretKey};
|
||||
|
||||
const WORKER_BINARY: &str = "../target/release/worker";
|
||||
const SERVER_BINARY: &str = "../target/release/server";
|
||||
const REDIS_URL: &str = "redis://127.0.0.1/";
|
||||
const PORT: u16 = 8080;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_examples().await
|
||||
}
|
||||
|
||||
async fn run_examples() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Generate valid secp256k1 keypairs for testing
|
||||
let secp = Secp256k1::new();
|
||||
let mut rng = OsRng;
|
||||
|
||||
let secret_key1 = SecretKey::new(&mut rng);
|
||||
let public_key1 = PublicKey::from_secret_key(&secp, &secret_key1);
|
||||
let pk1_hex = hex::encode(public_key1.serialize());
|
||||
|
||||
let secret_key2 = SecretKey::new(&mut rng);
|
||||
let public_key2 = PublicKey::from_secret_key(&secp, &secret_key2);
|
||||
let pk2_hex = hex::encode(public_key2.serialize());
|
||||
|
||||
let secret_key3 = SecretKey::new(&mut rng);
|
||||
let public_key3 = PublicKey::from_secret_key(&secp, &secret_key3);
|
||||
let pk3_hex = hex::encode(public_key3.serialize());
|
||||
|
||||
println!("Generated test public keys:");
|
||||
println!(" PK1: {}", pk1_hex);
|
||||
println!(" PK2: {}", pk2_hex);
|
||||
println!(" PK3: {}", pk3_hex);
|
||||
|
||||
// Example 1: Simple launcher with single circle
|
||||
println!("\n=== Example 1: Simple Launcher ===");
|
||||
let launcher1 = Launcher {
|
||||
service_manager: tokio::task::spawn_blocking(|| create_service_manager(None))
|
||||
.await??,
|
||||
circles: vec![Circle {
|
||||
public_key: pk1_hex.clone(),
|
||||
init_script: None,
|
||||
}],
|
||||
worker_binary: WORKER_BINARY.to_string(),
|
||||
server_binary: SERVER_BINARY.to_string(),
|
||||
redis_url: REDIS_URL.to_string(),
|
||||
port: PORT,
|
||||
};
|
||||
match launcher1.launch().await {
|
||||
Ok(_) => println!("Circle launched successfully!"),
|
||||
Err(e) => println!("Failed to launch: {}", e),
|
||||
}
|
||||
launcher1.clean().await?;
|
||||
|
||||
// Example 2: Launcher with multiple circles
|
||||
println!("\n=== Example 2: Multiple Circles ===");
|
||||
let launcher2 = Launcher {
|
||||
service_manager: tokio::task::spawn_blocking(|| create_service_manager(None))
|
||||
.await??,
|
||||
circles: vec![
|
||||
Circle {
|
||||
public_key: pk1_hex.clone(),
|
||||
init_script: None,
|
||||
},
|
||||
Circle {
|
||||
public_key: pk2_hex.clone(),
|
||||
init_script: None,
|
||||
},
|
||||
],
|
||||
worker_binary: WORKER_BINARY.to_string(),
|
||||
server_binary: SERVER_BINARY.to_string(),
|
||||
redis_url: REDIS_URL.to_string(),
|
||||
port: PORT,
|
||||
};
|
||||
match launcher2.launch().await {
|
||||
Ok(_) => println!("Multiple circles launched successfully!"),
|
||||
Err(e) => println!("Failed to launch multiple circles: {}", e),
|
||||
}
|
||||
launcher2.clean().await?;
|
||||
|
||||
// Example 3: Multiple circles with initialization scripts
|
||||
println!("\n=== Example 3: Multiple Circles with Init Scripts ===");
|
||||
let launcher3 = Launcher {
|
||||
service_manager: tokio::task::spawn_blocking(|| create_service_manager(None))
|
||||
.await??,
|
||||
circles: vec![
|
||||
Circle {
|
||||
public_key: pk1_hex.clone(),
|
||||
init_script: Some("test_script.rhai".to_string()),
|
||||
},
|
||||
Circle {
|
||||
public_key: pk2_hex.clone(),
|
||||
init_script: Some("test_script.rhai".to_string()),
|
||||
},
|
||||
Circle {
|
||||
public_key: pk3_hex.clone(),
|
||||
init_script: Some("test_script.rhai".to_string()),
|
||||
},
|
||||
],
|
||||
worker_binary: WORKER_BINARY.to_string(),
|
||||
server_binary: SERVER_BINARY.to_string(),
|
||||
redis_url: REDIS_URL.to_string(),
|
||||
port: PORT,
|
||||
};
|
||||
match launcher3.launch().await {
|
||||
Ok(_) => println!("Multiple circles with init scripts launched successfully!"),
|
||||
Err(e) => println!("Failed to launch multiple circles with init scripts: {}", e),
|
||||
}
|
||||
launcher3.clean().await?;
|
||||
|
||||
// Example 4: Mixed configuration (some with scripts, some without)
|
||||
println!("\n=== Example 4: Mixed Configuration ===");
|
||||
let launcher4 = Launcher {
|
||||
service_manager: tokio::task::spawn_blocking(|| create_service_manager(None))
|
||||
.await??,
|
||||
circles: vec![
|
||||
Circle {
|
||||
public_key: pk1_hex.clone(),
|
||||
init_script: Some("test_script.rhai".to_string()),
|
||||
},
|
||||
Circle {
|
||||
public_key: pk2_hex.clone(),
|
||||
init_script: None,
|
||||
},
|
||||
Circle {
|
||||
public_key: pk3_hex.clone(),
|
||||
init_script: Some("test_script.rhai".to_string()),
|
||||
},
|
||||
],
|
||||
worker_binary: WORKER_BINARY.to_string(),
|
||||
server_binary: SERVER_BINARY.to_string(),
|
||||
redis_url: REDIS_URL.to_string(),
|
||||
port: PORT,
|
||||
};
|
||||
match launcher4.launch().await {
|
||||
Ok(_) => println!("Mixed configuration launched successfully!"),
|
||||
Err(e) => println!("Failed to launch mixed configuration: {}", e),
|
||||
}
|
||||
launcher4.clean().await?;
|
||||
|
||||
println!("\nAll examples completed.");
|
||||
Ok(())
|
||||
}
|
13
research/launcher/examples/cleanup_example.rs
Normal file
13
research/launcher/examples/cleanup_example.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use circles_launcher::cleanup_launcher;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Cleaning up all launcher services...");
|
||||
|
||||
match cleanup_launcher().await {
|
||||
Ok(_) => println!("Cleanup completed successfully!"),
|
||||
Err(e) => println!("Cleanup failed: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
111
research/launcher/examples/confirm_launch.rs
Normal file
111
research/launcher/examples/confirm_launch.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use secp256k1::{Secp256k1, SecretKey, PublicKey};
|
||||
use rand::rngs::OsRng;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::Duration;
|
||||
|
||||
const REDIS_URL: &str = "redis://127.0.0.1:6379";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("--- Starting End-to-End Circle Launch Confirmation ---");
|
||||
|
||||
// Generate a test public key
|
||||
let secp = Secp256k1::new();
|
||||
let mut rng = OsRng;
|
||||
let secret_key = SecretKey::new(&mut rng);
|
||||
let public_key = PublicKey::from_secret_key(&secp, &secret_key);
|
||||
let test_public_key = hex::encode(public_key.serialize());
|
||||
|
||||
println!("Using test public key: {}", test_public_key);
|
||||
|
||||
// Start the launcher with the test public key
|
||||
let mut launcher_process: Child = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--bin")
|
||||
.arg("launcher")
|
||||
.arg("--")
|
||||
.arg("--circle")
|
||||
.arg(format!("{}:test_script.rhai", test_public_key))
|
||||
.arg("--worker-binary")
|
||||
.arg("../target/release/worker")
|
||||
.arg("--port")
|
||||
.arg("8080")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
println!(
|
||||
"Launcher process started with PID: {}",
|
||||
launcher_process.id()
|
||||
);
|
||||
|
||||
// Wait a moment for the launcher to start services
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
|
||||
let client = rhai_client::RhaiClientBuilder::new()
|
||||
.redis_url(REDIS_URL)
|
||||
.caller_id("test_launcher")
|
||||
.build()?;
|
||||
|
||||
// Test 1: Verify that CIRCLE_PUBLIC_KEY is set correctly.
|
||||
println!("--- Test 1: Verifying CIRCLE_PUBLIC_KEY ---");
|
||||
let script_circle_pk = r#"CIRCLE_PUBLIC_KEY"#;
|
||||
println!("Submitting script to verify CIRCLE_PUBLIC_KEY...");
|
||||
let task_details_circle_pk = client
|
||||
.new_play_request()
|
||||
.recipient_id(&format!("rhai_tasks:{}", test_public_key))
|
||||
.script(script_circle_pk)
|
||||
.request_id("task_id_circle_pk")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await?;
|
||||
println!("Received task details: {:?}", task_details_circle_pk);
|
||||
assert_eq!(task_details_circle_pk.status, "completed");
|
||||
assert_eq!(task_details_circle_pk.output, Some(test_public_key.to_string()));
|
||||
println!("✅ SUCCESS: Worker correctly reported its CIRCLE_PUBLIC_KEY.");
|
||||
|
||||
// Test 2: Verify that CALLER_PUBLIC_KEY is set correctly when the launcher calls.
|
||||
println!("\n--- Test 2: Verifying CALLER_PUBLIC_KEY for init scripts ---");
|
||||
let script_caller_pk = r#"CALLER_PUBLIC_KEY"#;
|
||||
println!("Submitting script to verify CALLER_PUBLIC_KEY...");
|
||||
let task_details_caller_pk = client
|
||||
.new_play_request()
|
||||
.recipient_id(&format!("rhai_tasks:{}", test_public_key))
|
||||
.script(script_caller_pk)
|
||||
.request_id("task_id_caller_pk")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await?;
|
||||
println!("Received task details: {:?}", task_details_caller_pk);
|
||||
assert_eq!(task_details_caller_pk.status, "completed");
|
||||
// The caller should be "launcher" as set in the RhaiClient
|
||||
println!("✅ SUCCESS: Worker correctly reported CALLER_PUBLIC_KEY for init script.");
|
||||
|
||||
// Test 3: Simple script execution
|
||||
println!("\n--- Test 3: Simple Script Execution ---");
|
||||
let simple_script = r#"print("Hello from worker!"); "test_result""#;
|
||||
println!("Submitting simple script...");
|
||||
let task_details_simple = client
|
||||
.new_play_request()
|
||||
.recipient_id(&format!("rhai_tasks:{}", test_public_key))
|
||||
.script(simple_script)
|
||||
.request_id("task_id_simple")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await?;
|
||||
println!("Received task details: {:?}", task_details_simple);
|
||||
assert_eq!(task_details_simple.status, "completed");
|
||||
assert_eq!(task_details_simple.output, Some("test_result".to_string()));
|
||||
println!("✅ SUCCESS: Worker executed simple script correctly.");
|
||||
|
||||
// Gracefully shut down the launcher
|
||||
println!("Shutting down launcher process...");
|
||||
launcher_process.kill()?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let _ = launcher_process.wait();
|
||||
})
|
||||
.await?;
|
||||
println!("--- End-to-End Test Finished Successfully ---");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -16,12 +16,19 @@
|
||||
//! 3. Create a `ourworld_output.json` file in the same directory with the details.
|
||||
//! 4. The launcher will run until you stop it with Ctrl+C.
|
||||
|
||||
use launcher::{run_launcher, Args, CircleConfig};
|
||||
use circles_launcher::{run_launcher, Args};
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error as StdError;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct OurWorldCircleConfig {
|
||||
pub public_key: String,
|
||||
pub init_script: Option<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
println!("--- Launching OurWorld Example Programmatically ---");
|
||||
@ -33,15 +40,6 @@ async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
println!("Using config file: {:?}", config_path);
|
||||
println!("Output will be written to: {:?}", output_path);
|
||||
|
||||
// Manually construct the arguments instead of parsing from command line.
|
||||
// This is useful when embedding the launcher logic in another application.
|
||||
let args = Args {
|
||||
config_path: config_path.clone(),
|
||||
output: Some(output_path),
|
||||
debug: true, // Enable debug logging for the example
|
||||
verbose: 2, // Set verbosity to max
|
||||
};
|
||||
|
||||
if !config_path.exists() {
|
||||
let msg = format!("Configuration file not found at {:?}", config_path);
|
||||
error!("{}", msg);
|
||||
@ -50,11 +48,11 @@ async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
|
||||
let config_content = fs::read_to_string(&config_path)?;
|
||||
|
||||
let circle_configs: Vec<CircleConfig> = match serde_json::from_str(&config_content) {
|
||||
let ourworld_configs: Vec<OurWorldCircleConfig> = match serde_json::from_str(&config_content) {
|
||||
Ok(configs) => configs,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to parse {}: {}. Ensure it's a valid JSON array of CircleConfig.",
|
||||
"Failed to parse {}: {}. Ensure it's a valid JSON array of circle configs.",
|
||||
config_path.display(),
|
||||
e
|
||||
);
|
||||
@ -62,7 +60,7 @@ async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
}
|
||||
};
|
||||
|
||||
if circle_configs.is_empty() {
|
||||
if ourworld_configs.is_empty() {
|
||||
info!(
|
||||
"No circle configurations found in {}. Exiting.",
|
||||
config_path.display()
|
||||
@ -70,11 +68,33 @@ async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Starting launcher... Press Ctrl+C to exit.");
|
||||
// Convert OurWorld configs to circle strings for the new API
|
||||
let mut circle_strings = Vec::new();
|
||||
for config in &ourworld_configs {
|
||||
let circle_str = if let Some(script) = &config.init_script {
|
||||
format!("{}:{}", config.public_key, script)
|
||||
} else {
|
||||
config.public_key.clone()
|
||||
};
|
||||
circle_strings.push(circle_str);
|
||||
}
|
||||
|
||||
// Manually construct the arguments for the new API
|
||||
let args = Args {
|
||||
port: 443, // Default port
|
||||
circles: circle_strings,
|
||||
redis_url: "redis://127.0.0.1:6379".to_string(),
|
||||
enable_auth: false,
|
||||
worker_binary: Some("../target/release/worker".to_string()),
|
||||
debug: true, // Enable debug logging for the example
|
||||
verbose: 2, // Set verbosity to max
|
||||
};
|
||||
|
||||
println!("Starting launcher with {} circles... Press Ctrl+C to exit.", ourworld_configs.len());
|
||||
|
||||
// The run_launcher function will setup logging, spawn circles, print the table,
|
||||
// and wait for a shutdown signal (Ctrl+C).
|
||||
run_launcher(args, circle_configs).await?;
|
||||
run_launcher(args).await?;
|
||||
|
||||
println!("--- OurWorld Example Finished ---");
|
||||
Ok(())
|
5
research/launcher/examples/test_circles.json
Normal file
5
research/launcher/examples/test_circles.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"note": "This file is no longer used by the launcher binary.",
|
||||
"new_usage": "Use command line arguments instead:",
|
||||
"example": "cargo run --bin launcher -- --circle 02a1b2c3d4e5f6...:test_script.rhai --worker-binary ../target/release/worker --port 8080"
|
||||
}
|
13
research/launcher/examples/test_script.rhai
Normal file
13
research/launcher/examples/test_script.rhai
Normal file
@ -0,0 +1,13 @@
|
||||
// Simple test script for circle initialization
|
||||
print("Initialization script running for circle: " + CIRCLE_PUBLIC_KEY);
|
||||
print("Called by: " + CALLER_PUBLIC_KEY);
|
||||
|
||||
// Set some test variables
|
||||
let test_value = 42;
|
||||
let test_message = "Hello from " + CIRCLE_PUBLIC_KEY;
|
||||
|
||||
print("Test value: " + test_value);
|
||||
print("Test message: " + test_message);
|
||||
|
||||
// Return a success message
|
||||
"Initialization completed successfully"
|
153
research/launcher/src/builder.rs
Normal file
153
research/launcher/src/builder.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use sal_service_manager::create_service_manager;
|
||||
use crate::Circle;
|
||||
use crate::Launcher;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
const DEFAULT_REDIS_URL: &str = "redis://127.0.0.1:6379";
|
||||
const DEFAULT_PORT: u16 = 8443;
|
||||
|
||||
pub struct LauncherBuilder {
|
||||
circles: Vec<CircleBuilder>, // circle pk's and their init scripts
|
||||
worker_binary: String, // path to worker binary
|
||||
server_binary: String, // path to server binary
|
||||
redis_url: String, // redis url
|
||||
port: u16, // port to bind to
|
||||
}
|
||||
|
||||
/// Creates a new launcher builder
|
||||
pub fn new_launcher() -> LauncherBuilder {
|
||||
LauncherBuilder::new()
|
||||
}
|
||||
|
||||
impl LauncherBuilder {
|
||||
/// Creates a new launcher builder
|
||||
pub fn new() -> Self {
|
||||
let server_binary = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent() // Go up one level from lib.rs
|
||||
.unwrap()
|
||||
.parent() // Go up one level from src
|
||||
.unwrap()
|
||||
.join("target/release/circles_server")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let worker_binary = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent() // Go up one level from lib.rs
|
||||
.unwrap()
|
||||
.parent() // Go up one level from src
|
||||
.unwrap()
|
||||
.join("target/release/worker")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
Self {
|
||||
circles: vec![],
|
||||
worker_binary: worker_binary,
|
||||
server_binary: server_binary,
|
||||
redis_url: DEFAULT_REDIS_URL.to_string(),
|
||||
port: DEFAULT_PORT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a circle by public key
|
||||
pub fn add_circle(mut self, public_key: impl ToString) -> Self {
|
||||
self.circles.push(CircleBuilder::new().public_key(public_key.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets initialization script for the last added circle
|
||||
pub fn add_init_script(mut self, script_path: impl ToString) -> Self {
|
||||
if let Some(last_circle) = self.circles.last_mut() {
|
||||
last_circle.init_script = Some(script_path.to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the worker binary path
|
||||
pub fn worker_binary(mut self, path: impl ToString) -> Self {
|
||||
// TODO: Validate path
|
||||
self.worker_binary = path.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the server binary path
|
||||
pub fn server_binary(mut self, path: impl ToString) -> Self {
|
||||
// TODO: Validate path
|
||||
self.server_binary = path.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the Redis URL
|
||||
pub fn redis_url(mut self, url: impl ToString) -> Self {
|
||||
// TODO: Validate URL
|
||||
self.redis_url = url.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the port
|
||||
pub fn port(mut self, port: u16) -> Self {
|
||||
// TODO: Validate port
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn build(self) -> Result<Launcher, Box<dyn std::error::Error>> {
|
||||
if self.circles.is_empty() {
|
||||
return Err("No circles configured. Use add_circle() to add circles.".into());
|
||||
}
|
||||
|
||||
let service_manager = tokio::task::spawn_blocking(|| create_service_manager(None))
|
||||
.await??;
|
||||
|
||||
Ok(Launcher {
|
||||
service_manager: Arc::new(Mutex::new(service_manager)),
|
||||
circles: self.circles.iter().map(|circle| circle.build()).collect(),
|
||||
worker_binary: self.worker_binary,
|
||||
server_binary: self.server_binary,
|
||||
redis_url: self.redis_url,
|
||||
port: self.port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a port is in use by any process.
|
||||
/// Note: This only indicates that *something* is using the port,
|
||||
/// not necessarily our WebSocket server. Should only be used as a fallback
|
||||
/// when service manager status is unavailable.
|
||||
async fn is_port_in_use(port: u16) -> bool {
|
||||
use std::net::{TcpListener, SocketAddr};
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
TcpListener::bind(addr).is_err()
|
||||
}
|
||||
|
||||
pub struct CircleBuilder {
|
||||
public_key: String,
|
||||
init_script: Option<String>,
|
||||
}
|
||||
|
||||
impl CircleBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
public_key: String::new(),
|
||||
init_script: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public_key(mut self, public_key: String) -> Self {
|
||||
self.public_key = public_key;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn init_script(mut self, init_script: String) -> Self {
|
||||
self.init_script = Some(init_script);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Circle {
|
||||
Circle {
|
||||
public_key: self.public_key.clone(),
|
||||
init_script: self.init_script.clone(),
|
||||
}
|
||||
}
|
||||
}
|
98
research/launcher/src/cmd/main.rs
Normal file
98
research/launcher/src/cmd/main.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use clap::Parser;
|
||||
use circles_launcher::{Circle, Launcher};
|
||||
use sal_service_manager::create_service_manager;
|
||||
use std::error::Error as StdError;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
const DEFAULT_REDIS_URL: &str = "redis://127.0.0.1/";
|
||||
|
||||
// Newtype wrapper to satisfy the orphan rule
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CircleArg(Circle);
|
||||
|
||||
impl FromStr for CircleArg {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split(':').collect();
|
||||
let circle = match parts.len() {
|
||||
1 => {
|
||||
// Validate public key
|
||||
secp256k1::PublicKey::from_str(parts[0])
|
||||
.map_err(|e| format!("Invalid public key '{}': {}", parts[0], e))?;
|
||||
Circle {
|
||||
public_key: parts[0].to_string(),
|
||||
init_script: None,
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// Validate public key
|
||||
secp256k1::PublicKey::from_str(parts[0])
|
||||
.map_err(|e| format!("Invalid public key '{}': {}", parts[0], e))?;
|
||||
Circle {
|
||||
public_key: parts[0].to_string(),
|
||||
init_script: Some(parts[1].to_string()),
|
||||
}
|
||||
}
|
||||
_ => return Err(format!("Invalid circle format '{}'. Expected 'public_key' or 'public_key:init_script.rhai'", s)),
|
||||
};
|
||||
Ok(CircleArg(circle))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// Port for the WebSocket server
|
||||
#[arg(short, long, default_value = "443")]
|
||||
pub port: u16,
|
||||
|
||||
/// Circle configurations: public_key[:init_script.rhai] (can be specified multiple times)
|
||||
#[arg(short = 'c', long = "circle", required = true)]
|
||||
pub circles: Vec<CircleArg>,
|
||||
|
||||
/// Redis URL
|
||||
#[arg(long, default_value = DEFAULT_REDIS_URL)]
|
||||
pub redis_url: String,
|
||||
|
||||
/// Worker binary path
|
||||
#[arg(long, default_value = "./target/release/worker")]
|
||||
pub worker_binary: PathBuf,
|
||||
|
||||
/// Server binary path
|
||||
#[arg(long, default_value = "./target/release/server")]
|
||||
pub server_binary: PathBuf,
|
||||
|
||||
/// Enable debug mode
|
||||
#[arg(short, long)]
|
||||
pub debug: bool,
|
||||
|
||||
/// Verbosity level
|
||||
#[arg(short, long, action = clap::ArgAction::Count)]
|
||||
pub verbose: u8,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
let args = Args::parse();
|
||||
|
||||
// To build the launcher, its fields must be public, or a public constructor
|
||||
// must be provided from the `circles_launcher` library.
|
||||
let service_manager = tokio::task::spawn_blocking(|| create_service_manager(None))
|
||||
.await??;
|
||||
|
||||
let launcher = Launcher {
|
||||
service_manager: Arc::new(Mutex::new(service_manager)),
|
||||
circles: args.circles.into_iter().map(|c| c.0).collect(),
|
||||
worker_binary: args.worker_binary.to_string_lossy().into_owned(),
|
||||
server_binary: args.server_binary.to_string_lossy().into_owned(),
|
||||
redis_url: args.redis_url,
|
||||
port: args.port,
|
||||
};
|
||||
|
||||
launcher.launch().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
236
research/launcher/src/lib.rs
Normal file
236
research/launcher/src/lib.rs
Normal file
@ -0,0 +1,236 @@
|
||||
use log::{info, debug};
|
||||
use rhai_client::RhaiClientBuilder;
|
||||
use sal_service_manager::{ServiceConfig as ServiceManagerConfig, ServiceStatus};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
mod builder;
|
||||
|
||||
pub use builder::*;
|
||||
|
||||
const SERVER_SERVICE_NAME: &str = "circle-ws-server";
|
||||
|
||||
pub struct Launcher {
|
||||
pub service_manager: Arc<Mutex<Box<dyn sal_service_manager::ServiceManager>>>,
|
||||
pub circles: Vec<Circle>,
|
||||
pub worker_binary: String,
|
||||
pub server_binary: String,
|
||||
pub redis_url: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Circle {
|
||||
pub public_key: String,
|
||||
pub init_script: Option<String>,
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn service_name(&self) -> String {
|
||||
format!("circle-worker-{}", self.public_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Launcher {
|
||||
/// Launches all configured circles
|
||||
pub async fn launch(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.launch_server().await?;
|
||||
for circle in &self.circles {
|
||||
println!("Launching circle {}", circle.public_key);
|
||||
self.launch_circle(circle).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Launches the circles WebSocket server
|
||||
async fn launch_server(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Check if service exists
|
||||
let exists = tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().exists(SERVER_SERVICE_NAME)
|
||||
}).await??;
|
||||
|
||||
if !exists {
|
||||
self.create_circle_server_service().await?;
|
||||
}
|
||||
|
||||
// Check if the WebSocket server service is already running via service manager
|
||||
let status = tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().status(SERVER_SERVICE_NAME)
|
||||
}).await??;
|
||||
|
||||
match status {
|
||||
ServiceStatus::Running => {
|
||||
println!("✓ WebSocket server service '{}' is already running", SERVER_SERVICE_NAME);
|
||||
return Ok(());
|
||||
}
|
||||
ServiceStatus::Failed => {
|
||||
println!("WebSocket server service '{}' exists but failed, removing it", SERVER_SERVICE_NAME);
|
||||
if let Err(e) = tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().remove(SERVER_SERVICE_NAME)
|
||||
}).await? {
|
||||
println!("Warning: Failed to remove failed service '{}': {}", SERVER_SERVICE_NAME, e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
ServiceStatus::Unknown => {
|
||||
println!("WebSocket server service '{}' exists but is in an unknown state, removing it", SERVER_SERVICE_NAME);
|
||||
if let Err(e) = tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().remove(SERVER_SERVICE_NAME)
|
||||
}).await? {
|
||||
println!("Warning: Failed to remove failed service '{}': {}", SERVER_SERVICE_NAME, e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
ServiceStatus::Stopped => {
|
||||
println!("WebSocket server service '{}' exists but is stopped, starting it", SERVER_SERVICE_NAME);
|
||||
match tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().start(SERVER_SERVICE_NAME)
|
||||
}).await? {
|
||||
Ok(_) => {
|
||||
println!("✓ WebSocket server service '{}' started", SERVER_SERVICE_NAME);
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to start existing service, removing and recreating: {}", e);
|
||||
if let Err(e) = tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().remove(SERVER_SERVICE_NAME)
|
||||
}).await? {
|
||||
println!("Warning: Failed to remove problematic service '{}': {}", SERVER_SERVICE_NAME, e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This part is reached if the service was Failed, Unknown or Stopped and then removed/failed to start.
|
||||
// We need to create and start it.
|
||||
println!("Creating and starting new WebSocket server service '{}'", SERVER_SERVICE_NAME);
|
||||
self.create_circle_server_service().await?;
|
||||
tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().start(SERVER_SERVICE_NAME)
|
||||
}).await??;
|
||||
println!("✓ WebSocket server service '{}' started successfully", SERVER_SERVICE_NAME);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Creates circles server service
|
||||
async fn create_circle_server_service(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ServiceManagerConfig {
|
||||
name: SERVER_SERVICE_NAME.to_string(),
|
||||
binary_path: self.server_binary.clone(),
|
||||
args: vec![
|
||||
"--port".to_string(),
|
||||
self.port.to_string(),
|
||||
"--redis-url".to_string(),
|
||||
self.redis_url.clone(),
|
||||
],
|
||||
working_directory: Some(std::env::current_dir()?.to_string_lossy().to_string()),
|
||||
environment: std::env::vars().collect(),
|
||||
auto_restart: true,
|
||||
};
|
||||
|
||||
// Use spawn_blocking to avoid runtime conflicts
|
||||
tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().create(&config)
|
||||
}).await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn launch_circle(&self, circle: &Circle) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Launching circle {}", circle.public_key);
|
||||
let config = ServiceManagerConfig {
|
||||
name: circle.service_name(),
|
||||
binary_path: self.worker_binary.clone(),
|
||||
args: vec![circle.public_key.clone()],
|
||||
auto_restart: true,
|
||||
environment: std::env::vars().collect(),
|
||||
working_directory: Some(std::env::current_dir()?.to_string_lossy().to_string()),
|
||||
};
|
||||
|
||||
// Use spawn_blocking for service manager operations
|
||||
let service_name = circle.service_name();
|
||||
tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
let config = config.clone();
|
||||
move || service_manager.lock().unwrap().create(&config)
|
||||
}).await?
|
||||
.map_err(|e| format!("Failed to create service manager: {}", e))?;
|
||||
|
||||
tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
let service_name = service_name.clone();
|
||||
move || service_manager.lock().unwrap().start(&service_name)
|
||||
}).await?
|
||||
.map_err(|e| format!("Failed to start service manager: {}", e))?;
|
||||
|
||||
if let Some(init_script) = &circle.init_script {
|
||||
send_init_script_to_worker(&circle.public_key, &init_script, &self.redis_url)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanup all services created by the launcher
|
||||
pub async fn clean(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().remove(SERVER_SERVICE_NAME)
|
||||
}).await??;
|
||||
|
||||
for circle in &self.circles {
|
||||
self.clean_circle(&circle.public_key).await?;
|
||||
}
|
||||
|
||||
println!("Cleanup completed.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanup all services created by the launcher
|
||||
pub async fn clean_circle(&self, circle_public_key: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let circle_key = circle_public_key.to_string();
|
||||
match tokio::task::spawn_blocking({
|
||||
let service_manager = Arc::clone(&self.service_manager);
|
||||
move || service_manager.lock().unwrap().remove(&circle_key)
|
||||
}).await? {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async fn send_init_script_to_worker(
|
||||
public_key: &str,
|
||||
init_script: &str,
|
||||
redis_url: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Sending initialization script '{}' to worker for circle: {}", init_script, public_key);
|
||||
|
||||
// Create RhaiClient and send script
|
||||
let client = RhaiClientBuilder::new()
|
||||
.redis_url(redis_url)
|
||||
.caller_id("launcher")
|
||||
.build()?;
|
||||
|
||||
client.new_play_request()
|
||||
.recipient_id(&format!("rhai_tasks:{}", public_key))
|
||||
.script(init_script)
|
||||
.submit()
|
||||
.await?;
|
||||
|
||||
println!("Successfully sent initialization script to worker for circle: {}", public_key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
173
research/launcher/tests/spawn_test.rs
Normal file
173
research/launcher/tests/spawn_test.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use circles_launcher::{new_launcher, setup_multi_circle_server, shutdown_circles, Args, CircleConfig};
|
||||
use secp256k1::Secp256k1;
|
||||
use tokio_tungstenite::connect_async;
|
||||
use url::Url;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_launcher_builder_pattern() {
|
||||
// Test the new builder pattern API
|
||||
let secp = Secp256k1::new();
|
||||
let (secret_key, public_key) = secp.generate_keypair(&mut secp256k1::rand::thread_rng());
|
||||
let public_key_str = public_key.to_string();
|
||||
|
||||
// Use the builder pattern to create a launcher
|
||||
let launcher = new_launcher()
|
||||
.add_circle(&public_key_str)
|
||||
.port(8088)
|
||||
.redis_url("redis://127.0.0.1:6379")
|
||||
.worker_binary("../target/debug/worker") // Use debug for tests
|
||||
.enable_auth(false);
|
||||
|
||||
// Note: We can't easily test the full launch in unit tests since it requires
|
||||
// actual binaries and Redis. This test verifies the builder pattern works.
|
||||
|
||||
// Verify the builder created the launcher correctly
|
||||
// (This is more of a compilation test than a runtime test)
|
||||
assert!(true, "Builder pattern works correctly");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_circle_config_parsing() {
|
||||
// Test parsing circle configurations from strings
|
||||
let public_key_only = "02a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890";
|
||||
let config = CircleConfig::from_str(public_key_only).expect("Failed to parse public key only");
|
||||
assert_eq!(config.public_key, public_key_only);
|
||||
assert!(config.init_script.is_none());
|
||||
|
||||
// Test with init script
|
||||
let with_script = "02a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890:init.rhai";
|
||||
let config = CircleConfig::from_str(with_script).expect("Failed to parse with script");
|
||||
assert_eq!(config.public_key, public_key_only);
|
||||
assert_eq!(config.init_script, Some("init.rhai".to_string()));
|
||||
|
||||
// Test invalid format
|
||||
let invalid = "invalid:too:many:colons";
|
||||
let result = CircleConfig::from_str(invalid);
|
||||
assert!(result.is_err(), "Should fail with invalid format");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_args_structure() {
|
||||
// Test that Args structure works correctly with the new API
|
||||
let secp = Secp256k1::new();
|
||||
let (_, public_key) = secp.generate_keypair(&mut secp256k1::rand::thread_rng());
|
||||
let public_key_str = public_key.to_string();
|
||||
|
||||
let args = Args {
|
||||
port: 8089,
|
||||
circles: vec![public_key_str.clone()],
|
||||
redis_url: "redis://127.0.0.1:6379".to_string(),
|
||||
enable_auth: false,
|
||||
worker_binary: Some("../target/debug/worker".to_string()),
|
||||
debug: true,
|
||||
verbose: 1,
|
||||
};
|
||||
|
||||
// Verify args structure
|
||||
assert_eq!(args.port, 8089);
|
||||
assert_eq!(args.circles.len(), 1);
|
||||
assert_eq!(args.circles[0], public_key_str);
|
||||
assert!(!args.enable_auth);
|
||||
assert!(args.worker_binary.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_setup_multi_circle_server_validation() {
|
||||
// Test validation in setup_multi_circle_server
|
||||
let args = Args {
|
||||
port: 8090,
|
||||
circles: vec![], // Empty circles should cause error
|
||||
redis_url: "redis://127.0.0.1:6379".to_string(),
|
||||
enable_auth: false,
|
||||
worker_binary: None, // Missing worker binary should cause error
|
||||
debug: true,
|
||||
verbose: 0,
|
||||
};
|
||||
|
||||
// This should fail due to missing worker binary
|
||||
let result = setup_multi_circle_server(&args).await;
|
||||
assert!(result.is_err(), "Should fail with missing worker binary");
|
||||
|
||||
if let Err(e) = result {
|
||||
let error_msg = e.to_string();
|
||||
assert!(
|
||||
error_msg.contains("Worker binary path is required"),
|
||||
"Error should mention missing worker binary, got: {}",
|
||||
error_msg
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_circle_config_validation() {
|
||||
// Test that invalid public keys are rejected
|
||||
let invalid_key = "not_a_valid_public_key";
|
||||
let result = CircleConfig::from_str(invalid_key);
|
||||
assert!(result.is_err(), "Should reject invalid public key");
|
||||
|
||||
// Test valid public key format
|
||||
let valid_key = "02a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890";
|
||||
let result = CircleConfig::from_str(valid_key);
|
||||
assert!(result.is_ok(), "Should accept valid public key");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_launcher_cleanup_functionality() {
|
||||
// Test that cleanup functionality exists and can be called
|
||||
// Note: This doesn't test actual cleanup since we don't have running services
|
||||
use circles_launcher::cleanup_launcher;
|
||||
|
||||
// This should not panic and should handle the case where no services exist
|
||||
let result = cleanup_launcher().await;
|
||||
// It's OK if this fails due to no services - we're just testing the API exists
|
||||
let _ = result;
|
||||
|
||||
assert!(true, "Cleanup function exists and can be called");
|
||||
}
|
||||
|
||||
// Integration test that would require actual binaries and Redis
|
||||
// Commented out since it requires external dependencies
|
||||
/*
|
||||
#[tokio::test]
|
||||
#[ignore] // Use `cargo test -- --ignored` to run this test
|
||||
async fn test_full_launcher_integration() {
|
||||
// This test requires:
|
||||
// 1. Redis server running on localhost:6379
|
||||
// 2. Worker binary built at ../target/debug/worker
|
||||
// 3. WebSocket server binary built at ../target/debug/circles_server
|
||||
|
||||
let secp = Secp256k1::new();
|
||||
let (_, public_key) = secp.generate_keypair(&mut secp256k1::rand::thread_rng());
|
||||
let public_key_str = public_key.to_string();
|
||||
|
||||
let args = Args {
|
||||
port: 8091,
|
||||
circles: vec![public_key_str.clone()],
|
||||
redis_url: "redis://127.0.0.1:6379".to_string(),
|
||||
enable_auth: false,
|
||||
worker_binary: Some("../target/debug/worker".to_string()),
|
||||
debug: true,
|
||||
verbose: 1,
|
||||
};
|
||||
|
||||
// Setup the multi-circle server
|
||||
let result = setup_multi_circle_server(&args).await;
|
||||
assert!(result.is_ok(), "Failed to setup multi-circle server: {:?}", result.err());
|
||||
|
||||
let (running_circles, outputs) = result.unwrap();
|
||||
|
||||
// Verify outputs
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].public_key, public_key_str);
|
||||
|
||||
// Test WebSocket connection
|
||||
let ws_url = &outputs[0].ws_url;
|
||||
let connection_result = connect_async(ws_url).await;
|
||||
assert!(connection_result.is_ok(), "Failed to connect to WebSocket");
|
||||
|
||||
// Cleanup
|
||||
shutdown_circles(running_circles).await;
|
||||
}
|
||||
*/
|
@ -28,15 +28,14 @@ gloo-utils = "0.2"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } # For StreamExt
|
||||
futures-channel = "0.3" # For MPSC channels
|
||||
rand = "0.8" # For random traffic simulation
|
||||
common_models = { path = "/Users/timurgordon/code/playground/yew/common_models" }
|
||||
engine = { path = "/Users/timurgordon/code/git.ourworld.tf/herocode/rhailib/src/engine" }
|
||||
rhailib_engine = { path = "../../../rhailib/src/engine" }
|
||||
rhai = "1.17"
|
||||
js-sys = "0.3"
|
||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
||||
urlencoding = "2.1"
|
||||
|
||||
# Authentication dependencies
|
||||
secp256k1 = { workspace = true, features = ["rand", "recovery", "hashes"] }
|
||||
secp256k1 = { version = "0.29", default-features = false, features = ["rand", "recovery", "hashes"] }
|
||||
hex = "0.4"
|
||||
sha3 = "0.10"
|
||||
gloo-storage = "0.3"
|
||||
|
@ -6,7 +6,7 @@ use yew::prelude::*;
|
||||
|
||||
// Imports from common_models
|
||||
use crate::ws_manager::CircleWsManager;
|
||||
use common_models::{AiConversation, AiMessageRole};
|
||||
use crate::common_models::{AiConversation, AiMessageRole};
|
||||
use heromodels::models::circle::Circle;
|
||||
|
||||
#[derive(Properties, PartialEq, Clone)]
|
@ -12,9 +12,7 @@ use crate::views::auth_view::AuthView;
|
||||
use crate::views::circles_view::CirclesView;
|
||||
use crate::views::customize_view::CustomizeView;
|
||||
use crate::views::inspector_view::InspectorView;
|
||||
use crate::views::intelligence_view::IntelligenceView;
|
||||
use crate::views::library_view::LibraryView;
|
||||
use crate::views::publishing_view::PublishingView;
|
||||
use crate::ws_manager::fetch_data_from_ws_urls;
|
||||
use heromodels::models::circle::{Circle, ThemeData};
|
||||
|
||||
@ -29,8 +27,6 @@ pub enum AppView {
|
||||
Login,
|
||||
Circles,
|
||||
Library,
|
||||
Intelligence,
|
||||
Publishing,
|
||||
Customize,
|
||||
Inspector, // Added Inspector
|
||||
}
|
||||
@ -41,8 +37,6 @@ impl AppView {
|
||||
AppView::Login => "/login".to_string(),
|
||||
AppView::Circles => "/".to_string(),
|
||||
AppView::Library => "/library".to_string(),
|
||||
AppView::Intelligence => "/intelligence".to_string(),
|
||||
AppView::Publishing => "/publishing".to_string(),
|
||||
AppView::Customize => "/customize".to_string(),
|
||||
AppView::Inspector => "/inspector".to_string(),
|
||||
}
|
||||
@ -52,8 +46,6 @@ impl AppView {
|
||||
let (base_view, _sub_route) = AppRouteParser::parse_app_route(path);
|
||||
match base_view.as_str() {
|
||||
"library" => AppView::Library,
|
||||
"intelligence" => AppView::Intelligence,
|
||||
"publishing" => AppView::Publishing,
|
||||
"customize" => AppView::Customize,
|
||||
"inspector" => AppView::Inspector,
|
||||
"login" => AppView::Login,
|
||||
@ -67,8 +59,6 @@ impl AppView {
|
||||
AppView::Login => "login",
|
||||
AppView::Circles => "",
|
||||
AppView::Library => "library",
|
||||
AppView::Intelligence => "intelligence",
|
||||
AppView::Publishing => "publishing",
|
||||
AppView::Customize => "customize",
|
||||
AppView::Inspector => "inspector",
|
||||
};
|
||||
@ -389,18 +379,6 @@ impl Component for App {
|
||||
/>
|
||||
}
|
||||
},
|
||||
AppView::Intelligence => html! {
|
||||
<IntelligenceView
|
||||
all_circles={Rc::new(all_circles_map.clone())}
|
||||
context_circle_ws_urls={Some(Rc::new(active_context_urls.clone()))}
|
||||
/>
|
||||
},
|
||||
AppView::Publishing => html! {
|
||||
<PublishingView
|
||||
all_circles={Rc::new(all_circles_map.clone())}
|
||||
context_circle_ws_urls={Some(Rc::new(active_context_urls.clone()))}
|
||||
/>
|
||||
},
|
||||
AppView::Inspector => {
|
||||
html! {
|
||||
<InspectorView
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::components::network_animation_view::NetworkAnimationView;
|
||||
use crate::components::world_map_svg::render_world_map_svg;
|
||||
use common_models::CircleData;
|
||||
use heromodels::models::circle::Circle;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
@ -25,33 +25,6 @@ pub fn inspector_network_tab(props: &InspectorNetworkTabProps) -> Html {
|
||||
// Create circle data for the map animation
|
||||
let circles_data = use_memo(props.circle_ws_addresses.clone(), |addresses| {
|
||||
let mut circles = HashMap::new();
|
||||
|
||||
for (index, ws_url) in addresses.iter().enumerate() {
|
||||
circles.insert(
|
||||
index as u32 + 1,
|
||||
CircleData {
|
||||
id: index as u32 + 1,
|
||||
name: format!("Circle {}", index + 1),
|
||||
description: format!("Circle at {}", ws_url),
|
||||
ws_url: ws_url.clone(),
|
||||
ws_urls: vec![],
|
||||
theme: HashMap::new(),
|
||||
tasks: None,
|
||||
epics: None,
|
||||
sprints: None,
|
||||
proposals: None,
|
||||
members: None,
|
||||
library: None,
|
||||
intelligence: None,
|
||||
timeline: None,
|
||||
calendar_events: None,
|
||||
treasury: None,
|
||||
publications: None,
|
||||
deployments: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Rc::new(circles)
|
||||
});
|
||||
|
||||
|
@ -22,16 +22,16 @@ pub fn nav_island(props: &NavIslandProps) -> yew::Html {
|
||||
),
|
||||
(AppView::Library, None::<()>, "fas fa-book", "Library"),
|
||||
(
|
||||
AppView::Intelligence,
|
||||
AppView::Customize,
|
||||
None::<()>,
|
||||
"fas fa-brain",
|
||||
"Intelligence",
|
||||
"Customize",
|
||||
),
|
||||
(
|
||||
AppView::Publishing,
|
||||
AppView::Inspector,
|
||||
None::<()>,
|
||||
"fas fa-rocket",
|
||||
"Publishing",
|
||||
"Inspector",
|
||||
),
|
||||
(
|
||||
AppView::Inspector,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use common_models::CircleData;
|
||||
use heromodels::models::circle::Circle;
|
||||
use gloo_timers::callback::{Interval, Timeout};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
@ -33,7 +33,7 @@ enum TransmissionType {
|
||||
|
||||
#[derive(Properties, Clone, PartialEq)]
|
||||
pub struct NetworkAnimationViewProps {
|
||||
pub all_circles: Rc<HashMap<u32, CircleData>>,
|
||||
pub all_circles: Rc<HashMap<u32, Circle>>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
@ -53,7 +53,7 @@ pub struct NetworkAnimationView {
|
||||
|
||||
impl NetworkAnimationView {
|
||||
fn calculate_server_positions(
|
||||
all_circles: &Rc<HashMap<u32, CircleData>>,
|
||||
all_circles: &Rc<HashMap<u32, Circle>>,
|
||||
) -> Rc<HashMap<u32, ServerNode>> {
|
||||
let mut nodes = HashMap::new();
|
||||
|
||||
@ -76,7 +76,7 @@ impl NetworkAnimationView {
|
||||
ServerNode {
|
||||
x: *x,
|
||||
y: *y,
|
||||
name: format!("{}", circle_data.name),
|
||||
name: format!("{}", circle_data.title),
|
||||
id: *id,
|
||||
is_active: true,
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
use circle_client_ws::CircleWsClientBuilder;
|
||||
use engine::{create_heromodels_engine, eval_script};
|
||||
use rhailib_engine::{create_heromodels_engine, eval_script};
|
||||
use heromodels::db::hero::OurDB;
|
||||
use rhai::Engine;
|
||||
use std::sync::Arc;
|
||||
@ -18,7 +18,7 @@ impl RhaiExecutor {
|
||||
let db = OurDB::new("app.db", true).expect("Failed to create database");
|
||||
|
||||
// Create the heromodels engine with all the registered functions
|
||||
let engine = create_heromodels_engine(Arc::new(db));
|
||||
let engine = create_heromodels_engine();
|
||||
Self { engine }
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,4 @@ pub mod auth_view;
|
||||
pub mod circles_view;
|
||||
pub mod customize_view;
|
||||
pub mod inspector_view;
|
||||
pub mod intelligence_view;
|
||||
pub mod library_view;
|
||||
pub mod publishing_view;
|
||||
|
@ -3,6 +3,10 @@ name = "circle_client_ws"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "circles_client"
|
||||
path = "cmd/main.rs"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
@ -23,7 +27,7 @@ secp256k1 = { workspace = true, optional = true }
|
||||
sha3 = { workspace = true, optional = true }
|
||||
|
||||
# Optional server dependency for end-to-end examples
|
||||
circle_ws_lib = { path = "../server_ws", optional = true }
|
||||
circle_ws_lib = { path = "../server", optional = true }
|
||||
|
||||
# WASM-specific dependencies
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
@ -36,10 +40,12 @@ web-sys = { version = "0.3", features = ["Request", "RequestInit", "RequestMode"
|
||||
|
||||
# Native-specific dependencies
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio-tungstenite = { version = "0.19.0", features = ["native-tls"] }
|
||||
native-tls = "0.2.11"
|
||||
tokio-native-tls = "0.3.0"
|
||||
tokio-tungstenite = { version = "0.23.1", features = ["native-tls"] }
|
||||
tokio = { workspace = true, features = ["rt", "macros", "time"] }
|
||||
native-tls = "0.2"
|
||||
clap = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
dotenv = "0.15"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
|
@ -1,89 +1,67 @@
|
||||
# `client_ws`: The Circles WebSocket Client
|
||||
# Circle WebSocket Client
|
||||
|
||||
The `client_ws` crate provides a high-level, cross-platform WebSocket client for interacting with the `server_ws`. It is designed to work seamlessly in both native Rust applications and WebAssembly (WASM) environments, making it suitable for a wide range of use cases, from command-line tools to web-based frontends.
|
||||
A Rust library for connecting to Circle WebSocket servers with authentication support.
|
||||
|
||||
## Features
|
||||
|
||||
- **Cross-Platform**: Works in both native and WASM environments using `tokio-tungstenite` and `gloo-net`, respectively.
|
||||
- **JSON-RPC 2.0**: Handles all the complexities of JSON-RPC 2.0 communication, including request serialization, response deserialization, and error handling.
|
||||
- **Asynchronous**: Built with `async/await` for non-blocking I/O.
|
||||
- **Script Execution**: Provides a simple `play` method to send Rhai scripts to the server for execution.
|
||||
- **Authentication**: Implements a `secp256k1` signature-based authentication flow, allowing the client to securely identify itself to the server.
|
||||
- **API**: The client's full API is formally defined in the root [openrpc.json](../../openrpc.json) file.
|
||||
- Cross-platform WebSocket client (native and WASM)
|
||||
- secp256k1 cryptographic authentication
|
||||
- JSON-RPC 2.0 protocol support
|
||||
- Async/await interface with Tokio
|
||||
- Built on tokio-tungstenite for reliable WebSocket connections
|
||||
|
||||
## Core Components
|
||||
## Usage
|
||||
|
||||
### `CircleWsClientBuilder`
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
A builder pattern is used to construct the client, providing a clear and flexible way to configure it.
|
||||
```toml
|
||||
[dependencies]
|
||||
circle_client_ws = { path = "../client_ws" }
|
||||
```
|
||||
|
||||
- `new(ws_url: String)`: Creates a new builder.
|
||||
- `with_keypair(private_key: String)`: Optionally provides a private key for authentication.
|
||||
- `build()`: Constructs the `CircleWsClient`.
|
||||
|
||||
### `CircleWsClient`
|
||||
|
||||
The main client struct.
|
||||
|
||||
- `connect()`: Establishes the WebSocket connection.
|
||||
- `authenticate()`: Performs the full, unified authentication flow over the WebSocket connection. Returns an error if no keypair was provided.
|
||||
- `play(script: String)`: Sends a Rhai script to the server for execution.
|
||||
- `disconnect()`: Closes the WebSocket connection.
|
||||
|
||||
## Usage Example
|
||||
|
||||
The following example demonstrates how to build a client with a keypair, connect, authenticate, and execute a script.
|
||||
### Basic Example
|
||||
|
||||
```rust
|
||||
use client_ws::CircleWsClientBuilder;
|
||||
use client_ws::auth; // For key generation
|
||||
use circle_client_ws::CircleWsClientBuilder;
|
||||
|
||||
async fn run_client() {
|
||||
// In a real application, the private key would be loaded securely.
|
||||
let private_key = auth::generate_private_key().unwrap();
|
||||
|
||||
let mut client = CircleWsClientBuilder::new("ws://127.0.0.1:9001/ws".to_string())
|
||||
.with_keypair(private_key)
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create client with private key
|
||||
let private_key = "your_private_key_hex";
|
||||
let client = CircleWsClientBuilder::new()
|
||||
.with_private_key(private_key)?
|
||||
.build();
|
||||
|
||||
if let Err(e) = client.connect().await {
|
||||
eprintln!("Failed to connect: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Authenticate with the server
|
||||
match client.authenticate().await {
|
||||
Ok(true) => println!("Successfully authenticated!"),
|
||||
Ok(false) => println!("Authentication failed."),
|
||||
Err(e) => eprintln!("Error during authentication: {}", e),
|
||||
}
|
||||
|
||||
// Execute a script
|
||||
let script = "40 + 2".to_string();
|
||||
match client.play(script).await {
|
||||
Ok(result) => {
|
||||
println!("Script output: {}", result.output);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error during play: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect().await;
|
||||
|
||||
// Connect and authenticate
|
||||
client.connect("ws://localhost:8080").await?;
|
||||
|
||||
// Use the authenticated client...
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
### Authentication Flow
|
||||
|
||||
The client includes a robust authentication mechanism to securely interact with protected server endpoints. For a detailed explanation of the authentication architecture and the cryptographic principles involved, see the [ARCHITECTURE.md](ARCHITECTURE.md) file.
|
||||
The client automatically handles the secp256k1 authentication flow:
|
||||
1. Connects to WebSocket server
|
||||
2. Receives authentication challenge
|
||||
3. Signs challenge with private key
|
||||
4. Sends signed response
|
||||
5. Receives authentication confirmation
|
||||
|
||||
## Building
|
||||
## Binary Tool
|
||||
|
||||
### Native
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
A command-line binary is also available for interactive use and script execution. See [`cmd/README.md`](cmd/README.md) for details.
|
||||
|
||||
### WASM
|
||||
```bash
|
||||
cargo build --target wasm32-unknown-unknown
|
||||
## Platform Support
|
||||
|
||||
- **Native**: Full support on all Rust-supported platforms
|
||||
- **WASM**: Browser support with web-sys bindings
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `tokio-tungstenite`: WebSocket implementation
|
||||
- `secp256k1`: Cryptographic operations
|
||||
- `serde`: JSON serialization
|
||||
- `uuid`: Request ID generation
|
||||
|
89
src/client_ws/cmd/README.md
Normal file
89
src/client_ws/cmd/README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Circles WebSocket Client Binary
|
||||
|
||||
A command-line WebSocket client for connecting to Circles servers with authentication support.
|
||||
|
||||
## Binary: `circles_client`
|
||||
|
||||
### Installation
|
||||
|
||||
Build the binary:
|
||||
```bash
|
||||
cargo build --bin circles_client --release
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a `.env` file in the `cmd/` directory:
|
||||
```bash
|
||||
# cmd/.env
|
||||
PRIVATE_KEY=your_actual_private_key_hex_here
|
||||
```
|
||||
|
||||
Or set the environment variable directly:
|
||||
```bash
|
||||
export PRIVATE_KEY=your_actual_private_key_hex_here
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Basic usage - connects and enters interactive mode
|
||||
circles_client ws://localhost:8080
|
||||
|
||||
# Execute a single Rhai script
|
||||
circles_client -s "print('Hello from Rhai!')" ws://localhost:8080
|
||||
|
||||
# Execute a script from file
|
||||
circles_client -f script.rhai ws://localhost:8080
|
||||
|
||||
# Increase verbosity (can be used multiple times)
|
||||
circles_client -v ws://localhost:8080
|
||||
circles_client -vv ws://localhost:8080
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- **Authentication**: Automatically loads private key and completes secp256k1 authentication flow
|
||||
- **Script Execution**: Supports both inline scripts (`-s`) and script files (`-f`)
|
||||
- **Interactive Mode**: When no script is provided, enters interactive REPL mode
|
||||
- **Verbosity Control**: Use `-v` flags to increase logging detail
|
||||
- **Cross-platform**: Works on all platforms supported by Rust and tokio-tungstenite
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
When run without `-s` or `-f` flags, the client enters interactive mode where you can:
|
||||
- Enter Rhai scripts line by line
|
||||
- Type `exit` or `quit` to close the connection
|
||||
- Use Ctrl+C to terminate
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Connect to local development server
|
||||
circles_client ws://localhost:8080
|
||||
|
||||
# Connect to secure WebSocket with verbose logging
|
||||
circles_client -v wss://circles.example.com/ws
|
||||
|
||||
# Execute a simple calculation
|
||||
circles_client -s "let result = 2 + 2; print(result);" ws://localhost:8080
|
||||
|
||||
# Load and execute a complex script
|
||||
circles_client -f examples/complex_script.rhai ws://localhost:8080
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
The client provides clear error messages for common issues:
|
||||
- Missing or invalid private key
|
||||
- Connection failures
|
||||
- Authentication errors
|
||||
- Script execution errors
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `tokio-tungstenite`: WebSocket client implementation
|
||||
- `secp256k1`: Cryptographic authentication
|
||||
- `clap`: Command-line argument parsing
|
||||
- `env_logger`: Logging infrastructure
|
||||
- `dotenv`: Environment variable loading
|
249
src/client_ws/cmd/main.rs
Normal file
249
src/client_ws/cmd/main.rs
Normal file
@ -0,0 +1,249 @@
|
||||
use circle_client_ws::CircleWsClientBuilder;
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use dotenv::dotenv;
|
||||
use env_logger;
|
||||
use log::{error, info};
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use tokio;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Args {
|
||||
ws_url: String,
|
||||
script: Option<String>,
|
||||
script_path: Option<String>,
|
||||
verbose: u8,
|
||||
no_timestamp: bool,
|
||||
}
|
||||
|
||||
fn parse_args() -> Args {
|
||||
let matches = Command::new("circles_client")
|
||||
.version("0.1.0")
|
||||
.about("WebSocket client for Circles server")
|
||||
.arg(
|
||||
Arg::new("url")
|
||||
.help("WebSocket server URL")
|
||||
.required(true)
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("script")
|
||||
.short('s')
|
||||
.long("script")
|
||||
.value_name("SCRIPT")
|
||||
.help("Rhai script to execute")
|
||||
.conflicts_with("script_path"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("script_path")
|
||||
.short('f')
|
||||
.long("file")
|
||||
.value_name("FILE")
|
||||
.help("Path to Rhai script file")
|
||||
.conflicts_with("script"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("Increase verbosity (can be used multiple times)")
|
||||
.action(ArgAction::Count),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no_timestamp")
|
||||
.long("no-timestamp")
|
||||
.help("Remove timestamps from log output")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
Args {
|
||||
ws_url: matches.get_one::<String>("url").unwrap().clone(),
|
||||
script: matches.get_one::<String>("script").cloned(),
|
||||
script_path: matches.get_one::<String>("script_path").cloned(),
|
||||
verbose: matches.get_count("verbose"),
|
||||
no_timestamp: matches.get_flag("no_timestamp"),
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_logging(verbose: u8, no_timestamp: bool) {
|
||||
let log_level = match verbose {
|
||||
0 => "warn,circle_client_ws=info",
|
||||
1 => "info,circle_client_ws=debug",
|
||||
2 => "debug",
|
||||
_ => "trace",
|
||||
};
|
||||
|
||||
std::env::set_var("RUST_LOG", log_level);
|
||||
|
||||
// Configure env_logger with or without timestamps
|
||||
if no_timestamp {
|
||||
env_logger::Builder::from_default_env()
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
} else {
|
||||
env_logger::init();
|
||||
}
|
||||
}
|
||||
|
||||
fn load_private_key() -> Result<String, Box<dyn std::error::Error>> {
|
||||
// Try to load from .env file first
|
||||
if let Ok(_) = dotenv() {
|
||||
if let Ok(key) = env::var("PRIVATE_KEY") {
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to load from cmd/.env file
|
||||
let cmd_env_path = Path::new("cmd/.env");
|
||||
if cmd_env_path.exists() {
|
||||
dotenv::from_path(cmd_env_path)?;
|
||||
if let Ok(key) = env::var("PRIVATE_KEY") {
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
|
||||
Err("PRIVATE_KEY not found in environment or .env files".into())
|
||||
}
|
||||
|
||||
async fn run_interactive_mode(client: circle_client_ws::CircleWsClient) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Entering interactive mode. Type 'exit' or 'quit' to leave.");
|
||||
println!("🔄 Interactive mode - Enter Rhai scripts (type 'exit' or 'quit' to leave):");
|
||||
|
||||
loop {
|
||||
print!("rhai> ");
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
let script = input.trim();
|
||||
|
||||
if script.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if script == "exit" || script == "quit" {
|
||||
println!("👋 Goodbye!");
|
||||
break;
|
||||
}
|
||||
|
||||
match client.play(script.to_string()).await {
|
||||
Ok(result) => {
|
||||
println!("📤 Result: {}", result.output);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Script execution failed: {}", e);
|
||||
println!("❌ Error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_script(client: circle_client_ws::CircleWsClient, script: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Executing script: {}", script);
|
||||
|
||||
match client.play(script).await {
|
||||
Ok(result) => {
|
||||
println!("{}", result.output);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Script execution failed: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_script_from_file(path: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let script = tokio::fs::read_to_string(path).await?;
|
||||
Ok(script)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = parse_args();
|
||||
setup_logging(args.verbose, args.no_timestamp);
|
||||
|
||||
info!("🚀 Starting Circles WebSocket client");
|
||||
info!("📡 Connecting to: {}", args.ws_url);
|
||||
|
||||
// Load private key from environment
|
||||
let private_key = match load_private_key() {
|
||||
Ok(key) => {
|
||||
info!("🔑 Private key loaded from environment");
|
||||
key
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Failed to load private key: {}", e);
|
||||
eprintln!("Error: {}", e);
|
||||
eprintln!("Please set PRIVATE_KEY in your environment or create a cmd/.env file with:");
|
||||
eprintln!("PRIVATE_KEY=your_private_key_here");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Build client with private key
|
||||
let mut client = CircleWsClientBuilder::new(args.ws_url.clone())
|
||||
.with_keypair(private_key)
|
||||
.build();
|
||||
|
||||
// Connect to WebSocket server
|
||||
info!("🔌 Connecting to WebSocket server...");
|
||||
if let Err(e) = client.connect().await {
|
||||
error!("❌ Failed to connect: {}", e);
|
||||
eprintln!("Connection failed: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
info!("✅ Connected successfully");
|
||||
|
||||
// Authenticate with server
|
||||
info!("🔐 Authenticating with server...");
|
||||
match client.authenticate().await {
|
||||
Ok(true) => {
|
||||
info!("✅ Authentication successful");
|
||||
println!("🔐 Authentication successful");
|
||||
}
|
||||
Ok(false) => {
|
||||
error!("❌ Authentication failed");
|
||||
eprintln!("Authentication failed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Authentication error: {}", e);
|
||||
eprintln!("Authentication error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine execution mode
|
||||
let result = if let Some(script) = args.script {
|
||||
// Execute provided script and exit
|
||||
execute_script(client, script).await
|
||||
} else if let Some(script_path) = args.script_path {
|
||||
// Load script from file and execute
|
||||
match load_script_from_file(&script_path).await {
|
||||
Ok(script) => execute_script(client, script).await,
|
||||
Err(e) => {
|
||||
error!("❌ Failed to load script from file '{}': {}", script_path, e);
|
||||
eprintln!("Failed to load script file: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Enter interactive mode
|
||||
run_interactive_mode(client).await
|
||||
};
|
||||
|
||||
// Handle any errors from execution
|
||||
if let Err(e) = result {
|
||||
error!("❌ Execution failed: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
info!("🏁 Client finished successfully");
|
||||
Ok(())
|
||||
}
|
@ -22,15 +22,13 @@ use {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use {
|
||||
native_tls::TlsConnector,
|
||||
tokio::net::TcpStream,
|
||||
tokio::spawn as spawn_local,
|
||||
tokio_tungstenite::{
|
||||
connect_async, connect_async_tls_with_config,
|
||||
tungstenite::{
|
||||
client::IntoClientRequest, handshake::client::Response,
|
||||
protocol::Message as TungsteniteWsMessage,
|
||||
},
|
||||
MaybeTlsStream, WebSocketStream,
|
||||
Connector,
|
||||
},
|
||||
};
|
||||
|
||||
@ -284,9 +282,10 @@ impl CircleWsClient {
|
||||
let (internal_tx, internal_rx) = mpsc::channel::<InternalWsMessage>(32);
|
||||
self.internal_tx = Some(internal_tx);
|
||||
|
||||
// Determine the final URL to connect to - always use the base ws_url now
|
||||
let connection_url = self.ws_url.replace("ws://", "ws://");
|
||||
info!("Connecting to WebSocket: {}", connection_url);
|
||||
// Use the URL as provided - support both ws:// and wss://
|
||||
let connection_url = self.ws_url.clone();
|
||||
let is_secure = connection_url.starts_with("wss://");
|
||||
info!("🔗 Connecting to WebSocket: {} ({})", connection_url, if is_secure { "WSS/TLS" } else { "WS/Plain" });
|
||||
|
||||
// Pending requests: map request_id to a oneshot sender for the response
|
||||
let pending_requests: Arc<
|
||||
@ -307,66 +306,37 @@ impl CircleWsClient {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let connect_attempt = async {
|
||||
let mut request = connection_url
|
||||
.into_client_request()
|
||||
.map_err(|e| CircleWsClientError::ConnectionError(e.to_string()))?;
|
||||
let _headers = request.headers_mut();
|
||||
// You can add custom headers here if needed, for example:
|
||||
// headers.insert("My-Header", "My-Value".try_into().unwrap());
|
||||
|
||||
let connector = TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
CircleWsClientError::ConnectionError(format!(
|
||||
"Failed to create TLS connector: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let authority = request
|
||||
.uri()
|
||||
.authority()
|
||||
.ok_or_else(|| {
|
||||
CircleWsClientError::ConnectionError(
|
||||
"Invalid URL: missing authority".to_string(),
|
||||
)
|
||||
})?
|
||||
.as_str();
|
||||
let host = request.uri().host().unwrap_or_default();
|
||||
|
||||
let stream = TcpStream::connect(authority).await.map_err(|e| {
|
||||
CircleWsClientError::ConnectionError(format!(
|
||||
"Failed to connect TCP stream: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let tls_stream = tokio_native_tls::TlsConnector::from(connector)
|
||||
.connect(host, stream)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
CircleWsClientError::ConnectionError(format!(
|
||||
"Failed to establish TLS connection: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let (ws_stream, response) = tokio_tungstenite::client_async_with_config(
|
||||
request,
|
||||
MaybeTlsStream::NativeTls(tls_stream),
|
||||
None, // WebSocketConfig
|
||||
)
|
||||
.await
|
||||
.map_err(|e| CircleWsClientError::ConnectionError(e.to_string()))?;
|
||||
|
||||
Ok((ws_stream, response))
|
||||
// Check if this is a secure WebSocket connection
|
||||
if connection_url.starts_with("wss://") {
|
||||
// For WSS connections, use a custom TLS connector that accepts self-signed certificates
|
||||
// This is for development/demo purposes only
|
||||
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
|
||||
|
||||
let request = connection_url.into_client_request()
|
||||
.map_err(|e| CircleWsClientError::ConnectionError(format!("Invalid URL: {}", e)))?;
|
||||
|
||||
// Create a native-tls connector that accepts invalid certificates (for development)
|
||||
let tls_connector = native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build()
|
||||
.map_err(|e| CircleWsClientError::ConnectionError(format!("TLS connector creation failed: {}", e)))?;
|
||||
|
||||
let connector = Connector::NativeTls(tls_connector);
|
||||
|
||||
warn!("⚠️ DEVELOPMENT MODE: Accepting self-signed certificates (NOT for production!)");
|
||||
connect_async_tls_with_config(request, None, false, Some(connector))
|
||||
.await
|
||||
.map_err(|e| CircleWsClientError::ConnectionError(format!("WSS connection failed: {}", e)))
|
||||
} else {
|
||||
// For regular WS connections, use the standard method
|
||||
connect_async(&connection_url)
|
||||
.await
|
||||
.map_err(|e| CircleWsClientError::ConnectionError(format!("WS connection failed: {}", e)))
|
||||
}
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let ws_result: Result<
|
||||
(WebSocketStream<MaybeTlsStream<TcpStream>>, Response),
|
||||
CircleWsClientError,
|
||||
> = connect_attempt.await;
|
||||
let ws_result = connect_attempt.await;
|
||||
|
||||
match ws_result {
|
||||
Ok(ws_conn_maybe_response) => {
|
||||
|
BIN
src/launcher/.DS_Store
vendored
BIN
src/launcher/.DS_Store
vendored
Binary file not shown.
@ -1,75 +0,0 @@
|
||||
# `launcher`: The Circles Orchestration Utility
|
||||
|
||||
The `launcher` is a command-line utility designed to spawn, manage, and monitor multiple, isolated "Circle" instances. It reads a simple JSON configuration file and, for each entry, launches a dedicated `worker` process and a corresponding WebSocket server.
|
||||
|
||||
This new architecture emphasizes isolation and robustness by running each Circle's worker as a separate OS process, identified by a unique public key.
|
||||
|
||||
## Core Architectural Concepts
|
||||
|
||||
- **Process-Based Isolation**: Instead of spawning workers as in-process tasks, the `launcher` spawns the `worker` binary as a separate OS process for each Circle. This ensures that a crash in one worker does not affect the launcher or other Circles.
|
||||
- **Public Key as Unique Identifier**: Each Circle is identified by a unique `secp256k1` public key, which is generated on startup. This key is used to name Redis queues and identify the Circle across the system, replacing the old numeric `id`.
|
||||
- **Task Submission via Redis**: The launcher uses the `rhai_client` library to submit initialization scripts as tasks to the worker's dedicated Redis queue. The worker listens on this queue, executes the script, and posts the result back to Redis.
|
||||
- **Dynamic Configuration**: All Circle instances are defined in a `circles.json` file, specifying their name, port, and an optional initialization script.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-Circle Management**: Run multiple, independent WebSocket servers and Rhai script workers from a single command.
|
||||
- **Dynamic Key Generation**: Automatically generates a unique `secp256k1` keypair for each Circle on startup.
|
||||
- **Configuration via JSON**: Define all your Circle instances in a `circles.json` file.
|
||||
- **Graceful Shutdown**: Listens for `Ctrl+C` to initiate a graceful shutdown of all spawned servers and worker processes.
|
||||
- **Status Display**: On startup, it displays a convenient table showing the name, unique public key, worker queue, and WebSocket URL for each running Circle.
|
||||
- **Verbose Logging**: Supports multiple levels of verbosity (`-v`, `-vv`, `-vvv`) and a debug flag (`-d`) for detailed logging.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. **Create `circles.json`**: Create a `circles.json` file. The launcher will look for it in the current directory by default. You can also provide a path as a command-line argument.
|
||||
|
||||
*The `id` field is now obsolete and has been removed.*
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "OurWorld",
|
||||
"port": 8090,
|
||||
"script_path": "scripts/ourworld.rhai"
|
||||
},
|
||||
{
|
||||
"name": "Dunia Cybercity",
|
||||
"port": 8091,
|
||||
"script_path": "scripts/dunia_cybercity.rhai"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
2. **Run the Launcher**: Execute the following command from the root of the `circles` project:
|
||||
|
||||
```bash
|
||||
# The launcher will find './circles.json' by default
|
||||
cargo run --package launcher
|
||||
|
||||
# Or specify a path to the config file
|
||||
cargo run --package launcher -- ./examples/test_circles.json
|
||||
```
|
||||
|
||||
3. **Add Verbosity (Optional)**: For more detailed logs, use the verbosity flags:
|
||||
|
||||
```bash
|
||||
# Info-level logging for Actix
|
||||
cargo run --package launcher -- -v
|
||||
|
||||
# Debug-level logging for project crates
|
||||
cargo run --package launcher -- -vv
|
||||
|
||||
# Full debug logging
|
||||
cargo run --package launcher -- -vvv
|
||||
```
|
||||
|
||||
## What It Does
|
||||
|
||||
For each entry in `circles.json`, the launcher will:
|
||||
1. Generate a new `secp256k1` keypair. The public key becomes the Circle's unique identifier.
|
||||
2. Spawn the `worker` binary as a child OS process, passing it the public key and Redis URL.
|
||||
3. Initialize a `server_ws` instance on the specified port.
|
||||
4. If a `script_path` is provided, it reads the script and submits it as a task to the worker's Redis queue. The `CALLER_PUBLIC_KEY` for this initial script is set to the Circle's own public key.
|
||||
|
||||
This makes it an ideal tool for setting up complex, multi-instance development environments or for deploying a full suite of Circle services with strong process isolation.
|
@ -1,114 +0,0 @@
|
||||
use rhai_client::RhaiClient;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
const REDIS_URL: &str = "redis://127.0.0.1:6379";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("--- Starting End-to-End Circle Launch Confirmation ---");
|
||||
|
||||
// Start the launcher
|
||||
let mut launcher_process: Child = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--bin")
|
||||
.arg("launcher")
|
||||
.arg("--")
|
||||
.arg("./examples/test_circles.json")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped()) // Pipe stderr to avoid interfering with the test output
|
||||
.spawn()?;
|
||||
|
||||
println!(
|
||||
"Launcher process started with PID: {}",
|
||||
launcher_process.id()
|
||||
);
|
||||
|
||||
let stdout = launcher_process
|
||||
.stdout
|
||||
.take()
|
||||
.expect("Failed to capture stdout");
|
||||
let mut reader = BufReader::new(stdout);
|
||||
let (tx, mut rx) = mpsc::channel::<String>(1);
|
||||
|
||||
// Spawn a task to read stdout and find the public key
|
||||
tokio::spawn(async move {
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
if reader.read_line(&mut line).unwrap_or(0) > 0 {
|
||||
if line.contains("Public Key") {
|
||||
if let Some(key) = line.split(": ").last() {
|
||||
tx.send(key.trim().to_string()).await.ok();
|
||||
break; // Found the key, exit the loop
|
||||
}
|
||||
}
|
||||
line.clear();
|
||||
} else {
|
||||
break; // EOF
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the public key
|
||||
let public_key = match tokio::time::timeout(Duration::from_secs(10), rx.recv()).await {
|
||||
Ok(Some(key)) => key,
|
||||
_ => {
|
||||
launcher_process.kill()?;
|
||||
return Err("Did not receive public key from launcher within 10 seconds".into());
|
||||
}
|
||||
};
|
||||
|
||||
println!("Found public key: {}", public_key);
|
||||
|
||||
let client = RhaiClient::new(REDIS_URL)?;
|
||||
|
||||
// Test 1: Verify that CIRCLE_PUBLIC_KEY is set correctly.
|
||||
println!("--- Test 1: Verifying CIRCLE_PUBLIC_KEY ---");
|
||||
let script_circle_pk = r#"CIRCLE_PUBLIC_KEY"#;
|
||||
println!("Submitting script to verify CIRCLE_PUBLIC_KEY...");
|
||||
let task_details_circle_pk = client
|
||||
.submit_script_and_await_result(
|
||||
&public_key,
|
||||
script_circle_pk.to_string(),
|
||||
"task_id".to_string(),
|
||||
Duration::from_secs(10),
|
||||
None, // Caller PK is not relevant for this constant.
|
||||
)
|
||||
.await?;
|
||||
println!("Received task details: {:?}", task_details_circle_pk);
|
||||
assert_eq!(task_details_circle_pk.status, "completed");
|
||||
assert_eq!(task_details_circle_pk.output, Some(public_key.to_string()));
|
||||
println!("✅ SUCCESS: Worker correctly reported its CIRCLE_PUBLIC_KEY.");
|
||||
|
||||
// Test 2: Verify that CALLER_PUBLIC_KEY is set correctly when the launcher calls.
|
||||
// We simulate the launcher by passing the circle's own PK as the caller.
|
||||
println!("\n--- Test 2: Verifying CALLER_PUBLIC_KEY for init scripts ---");
|
||||
let script_caller_pk = r#"CALLER_PUBLIC_KEY"#;
|
||||
println!("Submitting script to verify CALLER_PUBLIC_KEY...");
|
||||
let task_details_caller_pk = client
|
||||
.submit_script_and_await_result(
|
||||
&public_key,
|
||||
script_caller_pk.to_string(),
|
||||
"task_id".to_string(),
|
||||
Duration::from_secs(10),
|
||||
Some(public_key.clone()), // Simulate launcher by setting caller to the circle itself.
|
||||
)
|
||||
.await?;
|
||||
println!("Received task details: {:?}", task_details_caller_pk);
|
||||
assert_eq!(task_details_caller_pk.status, "completed");
|
||||
assert_eq!(task_details_caller_pk.output, Some(public_key.to_string()));
|
||||
println!("✅ SUCCESS: Worker correctly reported CALLER_PUBLIC_KEY for init script.");
|
||||
|
||||
// Gracefully shut down the launcher
|
||||
println!("Shutting down launcher process...");
|
||||
launcher_process.kill()?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let _ = launcher_process.wait();
|
||||
})
|
||||
.await?;
|
||||
println!("--- End-to-End Test Finished Successfully ---");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
[
|
||||
{ "name": "Test Circle", "port": 9000, "script_path": "test_script.rhai" }
|
||||
]
|
@ -1,2 +0,0 @@
|
||||
// test_script.rhai
|
||||
"Hello from the test circle!"
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,49 +0,0 @@
|
||||
use clap::Parser;
|
||||
use launcher::{run_launcher, Args};
|
||||
|
||||
use launcher::CircleConfig;
|
||||
use log::{error, info};
|
||||
use std::error::Error as StdError; // Import the trait
|
||||
use std::fs;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
// Use the alias for clarity
|
||||
let args = Args::parse();
|
||||
|
||||
let config_path = &args.config_path;
|
||||
if !config_path.exists() {
|
||||
error!(
|
||||
"Configuration file not found at {:?}. Please create circles.json.",
|
||||
config_path
|
||||
);
|
||||
// Create a simple string error that can be boxed into Box<dyn StdError>
|
||||
return Err(String::from("circles.json not found").into());
|
||||
}
|
||||
|
||||
let config_content =
|
||||
fs::read_to_string(&config_path).map_err(|e| Box::new(e) as Box<dyn StdError>)?;
|
||||
|
||||
let circle_configs: Vec<CircleConfig> = match serde_json::from_str(&config_content) {
|
||||
Ok(configs) => configs,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to parse circles.json: {}. Ensure it's a valid JSON array of CircleConfig.",
|
||||
e
|
||||
);
|
||||
// Explicitly cast serde_json::Error to Box<dyn StdError>
|
||||
return Err(Box::new(e) as Box<dyn StdError>);
|
||||
}
|
||||
};
|
||||
|
||||
if circle_configs.is_empty() {
|
||||
info!(
|
||||
"No circle configurations found in {}. Exiting.",
|
||||
config_path.display()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// run_launcher already returns Result<(), Box<dyn StdError>>, so this should be fine.
|
||||
run_launcher(args, circle_configs).await
|
||||
}
|
@ -1,358 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
// std::process::{Command, Child, Stdio}; // All parts of this line are no longer used directly here
|
||||
use actix_web::dev::ServerHandle;
|
||||
use circle_ws_lib::{spawn_circle_server, ServerConfig};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use log::{info, warn};
|
||||
use rhai_client::RhaiClient;
|
||||
use secp256k1::{rand, Secp256k1};
|
||||
use std::time::Duration;
|
||||
use tokio::signal;
|
||||
use tokio::task::JoinHandle;
|
||||
// use rhai::Engine; // No longer directly used, engine comes from create_heromodels_engine
|
||||
use engine::create_heromodels_engine;
|
||||
use heromodels::db::hero::OurDB;
|
||||
use rhailib_worker::spawn_rhai_worker; // Added
|
||||
use std::env; // Added
|
||||
use tokio::sync::mpsc; // Added
|
||||
|
||||
const DEFAULT_REDIS_URL: &str = "redis://127.0.0.1:6379";
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// Path to the circles.json configuration file
|
||||
#[arg(default_value = "./circles.json")]
|
||||
pub config_path: PathBuf,
|
||||
|
||||
/// Optional path to write the output JSON file with circle details
|
||||
#[arg(long)]
|
||||
pub output: Option<PathBuf>,
|
||||
|
||||
/// Enable debug mode
|
||||
#[arg(short, long)]
|
||||
pub debug: bool,
|
||||
|
||||
/// Verbosity level
|
||||
#[arg(short, long, action = clap::ArgAction::Count)]
|
||||
pub verbose: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CircleConfig {
|
||||
pub name: String,
|
||||
pub port: u16,
|
||||
pub script_path: Option<String>,
|
||||
pub public_key: Option<String>,
|
||||
pub secret_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct CircleOutput {
|
||||
pub name: String,
|
||||
pub public_key: String,
|
||||
pub secret_key: String,
|
||||
pub worker_queue: String,
|
||||
pub ws_url: String,
|
||||
}
|
||||
|
||||
pub struct RunningCircleInfo {
|
||||
pub config: CircleConfig,
|
||||
pub worker_queue: String,
|
||||
pub ws_url: String,
|
||||
pub public_key: String,
|
||||
// pub worker_process: Child, // Changed
|
||||
pub worker_task_join_handle: JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>>, // Added
|
||||
pub worker_shutdown_tx: mpsc::Sender<()>, // Added
|
||||
pub ws_server_instance_handle: Arc<Mutex<Option<ServerHandle>>>,
|
||||
pub _ws_server_task_join_handle: JoinHandle<std::io::Result<()>>,
|
||||
}
|
||||
|
||||
pub async fn setup_and_spawn_circles(
|
||||
circle_configs: Vec<CircleConfig>,
|
||||
) -> Result<(Vec<Arc<Mutex<RunningCircleInfo>>>, Vec<CircleOutput>), Box<dyn std::error::Error>> {
|
||||
if circle_configs.is_empty() {
|
||||
warn!("No circle configurations found. Exiting.");
|
||||
return Ok((Vec::new(), Vec::new()));
|
||||
}
|
||||
info!("Loaded {} circle configurations.", circle_configs.len());
|
||||
|
||||
let rhai_client = RhaiClient::new(DEFAULT_REDIS_URL)?;
|
||||
let mut running_circles_store: Vec<Arc<Mutex<RunningCircleInfo>>> = Vec::new();
|
||||
let mut circle_outputs: Vec<CircleOutput> = Vec::new();
|
||||
|
||||
let data_dir = PathBuf::from("./launch_data");
|
||||
if !data_dir.exists() {
|
||||
fs::create_dir_all(&data_dir).map_err(|e| {
|
||||
format!(
|
||||
"Failed to create data directory '{}': {}",
|
||||
data_dir.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
info!("Created data directory: {}", data_dir.display());
|
||||
}
|
||||
|
||||
for (idx, config) in circle_configs.into_iter().enumerate() {
|
||||
// Added enumerate for circle_id
|
||||
info!(
|
||||
"Initializing Circle Name: '{}', Port: {}",
|
||||
config.name, config.port
|
||||
);
|
||||
|
||||
let secp = Secp256k1::new();
|
||||
let (secret_key, public_key) = if let (Some(sk_str), Some(pk_str)) =
|
||||
(&config.secret_key, &config.public_key)
|
||||
{
|
||||
info!("Using provided keypair for circle '{}'", config.name);
|
||||
let secret_key = secp256k1::SecretKey::from_str(sk_str)
|
||||
.map_err(|e| format!("Invalid secret key for circle '{}': {}", config.name, e))?;
|
||||
let public_key = secp256k1::PublicKey::from_str(pk_str)
|
||||
.map_err(|e| format!("Invalid public key for circle '{}': {}", config.name, e))?;
|
||||
if public_key != secp256k1::PublicKey::from_secret_key(&secp, &secret_key) {
|
||||
return Err(format!(
|
||||
"Provided public key does not match secret key for circle '{}'",
|
||||
config.name
|
||||
)
|
||||
.into());
|
||||
}
|
||||
(secret_key, public_key)
|
||||
} else {
|
||||
info!("Generating new keypair for circle '{}'", config.name);
|
||||
secp.generate_keypair(&mut rand::thread_rng())
|
||||
};
|
||||
let public_key_hex = public_key.to_string();
|
||||
|
||||
// Spawn Rhai worker as a Tokio task
|
||||
let (worker_shutdown_tx, worker_shutdown_rx) = mpsc::channel(1);
|
||||
|
||||
// --- Initialize OurDB and the Rhai Engine for this circle ---
|
||||
let db_path_str = format!("./launch_data/circle_db_{}.db", config.name);
|
||||
let db = Arc::new(OurDB::new(db_path_str, true)?);
|
||||
let engine = create_heromodels_engine(db.clone());
|
||||
// --- End Engine Initialization ---
|
||||
|
||||
let redis_url = env::var("REDIS_URL").unwrap_or_else(|_| DEFAULT_REDIS_URL.to_string());
|
||||
|
||||
// Using idx as a placeholder for circle_id. Consider a more robust ID if needed.
|
||||
let circle_id_for_worker = idx as u32;
|
||||
// Defaulting preserve_tasks to false. Make configurable if needed.
|
||||
let preserve_tasks = env::var("PRESERVE_TASKS").is_ok();
|
||||
|
||||
let worker_task_join_handle = spawn_rhai_worker(
|
||||
circle_id_for_worker,
|
||||
public_key_hex.clone(),
|
||||
engine,
|
||||
redis_url.clone(),
|
||||
worker_shutdown_rx,
|
||||
preserve_tasks,
|
||||
);
|
||||
|
||||
let worker_queue = format!("rhai_tasks:{}", public_key_hex);
|
||||
let ws_url = format!("ws://127.0.0.1:{}", config.port);
|
||||
|
||||
// If a script is provided, read it and submit it to the worker
|
||||
if let Some(script_path_str) = &config.script_path {
|
||||
info!(
|
||||
"Found script for circle '{}' at path: {}",
|
||||
config.name, script_path_str
|
||||
);
|
||||
let script_path = PathBuf::from(script_path_str);
|
||||
if script_path.exists() {
|
||||
let script_content = fs::read_to_string(&script_path).map_err(|e| {
|
||||
format!(
|
||||
"Failed to read script file '{}': {}",
|
||||
script_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
info!("Submitting script to worker queue '{}'", worker_queue);
|
||||
let task_id = rhai_client
|
||||
.submit_script(
|
||||
&public_key_hex, // Use public key as the circle identifier
|
||||
script_content,
|
||||
Some(public_key_hex.clone()),
|
||||
)
|
||||
.await?;
|
||||
info!(
|
||||
"Script for circle '{}' submitted with task ID: {}",
|
||||
config.name, task_id
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"Script path '{}' for circle '{}' does not exist. Skipping.",
|
||||
script_path.display(),
|
||||
config.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn the WebSocket server for the circle
|
||||
let server_config = ServerConfig {
|
||||
port: config.port,
|
||||
circle_name: config.name.clone(),
|
||||
circle_public_key: public_key_hex.clone(),
|
||||
redis_url: env::var("REDIS_URL").unwrap_or_else(|_| DEFAULT_REDIS_URL.to_string()), // Ensure server also gets correct redis_url
|
||||
host: "127.0.0.1".to_string(),
|
||||
enable_auth: false,
|
||||
cert_path: None,
|
||||
key_path: None,
|
||||
};
|
||||
|
||||
let (ws_server_task_join_handle, ws_server_instance_handle) =
|
||||
spawn_circle_server(server_config.clone())?;
|
||||
|
||||
circle_outputs.push(CircleOutput {
|
||||
name: config.name.clone(),
|
||||
public_key: public_key_hex.clone(),
|
||||
secret_key: secret_key.display_secret().to_string(),
|
||||
worker_queue: worker_queue.clone(),
|
||||
ws_url: ws_url.clone(),
|
||||
});
|
||||
|
||||
running_circles_store.push(Arc::new(Mutex::new(RunningCircleInfo {
|
||||
config,
|
||||
worker_queue,
|
||||
ws_url,
|
||||
public_key: public_key_hex,
|
||||
worker_task_join_handle, // Changed
|
||||
worker_shutdown_tx, // Added
|
||||
ws_server_instance_handle: Arc::new(Mutex::new(Some(ws_server_instance_handle))),
|
||||
_ws_server_task_join_handle: ws_server_task_join_handle,
|
||||
})));
|
||||
}
|
||||
Ok((running_circles_store, circle_outputs))
|
||||
}
|
||||
|
||||
pub async fn shutdown_circles(running_circles_store: Vec<Arc<Mutex<RunningCircleInfo>>>) {
|
||||
for circle_arc in &running_circles_store {
|
||||
let (name, worker_shutdown_tx, mut worker_task_join_handle_opt, server_handle_opt);
|
||||
{
|
||||
let mut circle_info = circle_arc.lock().unwrap();
|
||||
name = circle_info.config.name.clone();
|
||||
// Take ownership of the JoinHandle and Sender for shutdown
|
||||
// We need to replace them with something to satisfy the struct,
|
||||
// but they won't be used again for this instance.
|
||||
let (dummy_tx, _dummy_rx) = mpsc::channel(1);
|
||||
worker_shutdown_tx = std::mem::replace(&mut circle_info.worker_shutdown_tx, dummy_tx);
|
||||
|
||||
// Create a dummy JoinHandle for replacement
|
||||
let dummy_join_handle = tokio::spawn(async {
|
||||
Ok(()) as Result<(), Box<dyn std::error::Error + Send + Sync>>
|
||||
});
|
||||
worker_task_join_handle_opt = Some(std::mem::replace(
|
||||
&mut circle_info.worker_task_join_handle,
|
||||
dummy_join_handle,
|
||||
));
|
||||
|
||||
server_handle_opt = circle_info.ws_server_instance_handle.lock().unwrap().take();
|
||||
}
|
||||
|
||||
info!("Shutting down Circle: '{}'", name);
|
||||
|
||||
// Shutdown worker task
|
||||
if let Err(e) = worker_shutdown_tx.send(()).await {
|
||||
warn!("Failed to send shutdown signal to worker for Circle '{}': {}. Worker might have already exited.", name, e);
|
||||
}
|
||||
|
||||
if let Some(worker_task_join_handle) = worker_task_join_handle_opt.take() {
|
||||
match worker_task_join_handle.await {
|
||||
Ok(Ok(_)) => info!("Worker task for Circle '{}' shut down gracefully.", name),
|
||||
Ok(Err(e)) => warn!(
|
||||
"Worker task for Circle '{}' returned an error: {:?}",
|
||||
name, e
|
||||
),
|
||||
Err(e) => warn!("Worker task for Circle '{}' panicked: {:?}", name, e),
|
||||
}
|
||||
} else {
|
||||
warn!("No worker task join handle found for Circle '{}'.", name);
|
||||
}
|
||||
|
||||
// Shutdown WebSocket server
|
||||
if let Some(handle) = server_handle_opt {
|
||||
info!("Stopping WebSocket server for Circle '{}' ...", name);
|
||||
handle.stop(true).await;
|
||||
info!("WebSocket server for Circle '{}' stop signal sent.", name);
|
||||
} else {
|
||||
warn!(
|
||||
"No server handle to stop WebSocket server for Circle '{}'.",
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_launcher(
|
||||
args: Args,
|
||||
circle_configs: Vec<CircleConfig>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
let log_level = if args.debug {
|
||||
"debug".to_string()
|
||||
} else {
|
||||
match args.verbose {
|
||||
0 => "info,actix_server=warn,actix_web=warn".to_string(),
|
||||
1 => "info,circle_ws_lib=info,actix_server=info,actix_web=info".to_string(),
|
||||
2 => "debug,launcher=debug,worker_lib=debug,circle_ws_lib=debug,actix_server=info,actix_web=info".to_string(),
|
||||
_ => "debug".to_string(),
|
||||
}
|
||||
};
|
||||
std::env::set_var("RUST_LOG", log_level);
|
||||
}
|
||||
env_logger::init();
|
||||
|
||||
info!("Starting Circles Orchestrator...");
|
||||
|
||||
let (running_circles_store, circle_outputs) = setup_and_spawn_circles(circle_configs).await?;
|
||||
|
||||
if running_circles_store.is_empty() {
|
||||
warn!("No circles were started. Exiting.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("All configured circles have been processed. Displaying circles table.");
|
||||
|
||||
{
|
||||
let circles = running_circles_store
|
||||
.iter()
|
||||
.map(|arc_info| arc_info.lock().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut table = Table::new();
|
||||
table.set_header(vec!["Name", "Public Key", "Worker Queue", "WS URL"]);
|
||||
|
||||
for info in circles.iter() {
|
||||
let mut row = Row::new();
|
||||
row.add_cell(Cell::new(&info.config.name));
|
||||
row.add_cell(Cell::new(&info.public_key));
|
||||
row.add_cell(Cell::new(&info.worker_queue));
|
||||
row.add_cell(Cell::new(&info.ws_url));
|
||||
table.add_row(row);
|
||||
}
|
||||
println!("{}", table);
|
||||
}
|
||||
|
||||
if let Some(output_path) = args.output {
|
||||
info!("Writing circle details to {:?}", output_path);
|
||||
let json_output = serde_json::to_string_pretty(&circle_outputs)?;
|
||||
fs::write(&output_path, json_output)?;
|
||||
info!("Successfully wrote circle details to {:?}", output_path);
|
||||
}
|
||||
|
||||
info!("Press Ctrl+C to shutdown all circles.");
|
||||
signal::ctrl_c().await?;
|
||||
info!("Ctrl-C received. Initiating graceful shutdown of all circles...");
|
||||
|
||||
shutdown_circles(running_circles_store).await;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
info!("Orchestrator shut down complete.");
|
||||
Ok(())
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use launcher::{setup_and_spawn_circles, shutdown_circles, CircleConfig};
|
||||
use secp256k1::Secp256k1;
|
||||
use tokio_tungstenite::connect_async;
|
||||
use url::Url;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_launcher_starts_and_stops_circle_with_generated_keys() {
|
||||
// 1. Setup: Define the test circle configuration directly, without keys
|
||||
let test_circle_config = vec![CircleConfig {
|
||||
name: "test_circle_generated".to_string(),
|
||||
port: 8088, // Use a distinct port for testing
|
||||
script_path: None,
|
||||
public_key: None,
|
||||
secret_key: None,
|
||||
}];
|
||||
|
||||
// 2. Action: Run the launcher setup with the direct config
|
||||
let (running_circles, outputs) = setup_and_spawn_circles(test_circle_config)
|
||||
.await
|
||||
.expect("Failed to setup and spawn circles");
|
||||
|
||||
// 3. Verification: Check if the circle was spawned
|
||||
assert_eq!(running_circles.len(), 1, "Expected one running circle");
|
||||
assert_eq!(outputs.len(), 1, "Expected one circle output");
|
||||
|
||||
let circle_output = &outputs[0];
|
||||
assert_eq!(circle_output.name, "test_circle_generated");
|
||||
assert!(!circle_output.public_key.is_empty()); // Key should be generated
|
||||
assert!(!circle_output.secret_key.is_empty());
|
||||
|
||||
// 4. Verification: Check if the WebSocket server is connectable
|
||||
let ws_url = Url::parse(&circle_output.ws_url).expect("Failed to parse WS URL");
|
||||
let connection_attempt = connect_async(ws_url.to_string()).await;
|
||||
assert!(
|
||||
connection_attempt.is_ok(),
|
||||
"Failed to connect to WebSocket server"
|
||||
);
|
||||
|
||||
if let Ok((ws_stream, _)) = connection_attempt {
|
||||
let (mut write, _read) = ws_stream.split();
|
||||
write
|
||||
.send(tokio_tungstenite::tungstenite::Message::Ping(vec![]))
|
||||
.await
|
||||
.expect("Failed to send ping");
|
||||
}
|
||||
|
||||
// 5. Cleanup: Shutdown the circles
|
||||
shutdown_circles(running_circles).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_launcher_uses_provided_keypair() {
|
||||
// 1. Setup: Generate a keypair to provide to the config
|
||||
let secp = Secp256k1::new();
|
||||
let (secret_key, public_key) = secp.generate_keypair(&mut secp256k1::rand::thread_rng());
|
||||
let secret_key_str = secret_key.display_secret().to_string();
|
||||
let public_key_str = public_key.to_string();
|
||||
|
||||
// 2. Setup: Define the test circle configuration with the generated keypair
|
||||
let test_circle_config = vec![CircleConfig {
|
||||
name: "test_circle_with_keys".to_string(),
|
||||
port: 8089, // Use another distinct port
|
||||
script_path: None,
|
||||
public_key: Some(public_key_str.clone()),
|
||||
secret_key: Some(secret_key_str.clone()),
|
||||
}];
|
||||
|
||||
// 3. Action: Run the launcher setup
|
||||
let (running_circles, outputs) = setup_and_spawn_circles(test_circle_config)
|
||||
.await
|
||||
.expect("Failed to setup and spawn circles with provided keys");
|
||||
|
||||
// 4. Verification: Check if the output public key matches the provided one
|
||||
assert_eq!(outputs.len(), 1, "Expected one circle output");
|
||||
let circle_output = &outputs[0];
|
||||
assert_eq!(circle_output.name, "test_circle_with_keys");
|
||||
assert_eq!(
|
||||
circle_output.public_key, public_key_str,
|
||||
"The public key in the output should match the one provided"
|
||||
);
|
||||
assert_eq!(
|
||||
circle_output.secret_key, secret_key_str,
|
||||
"The secret key in the output should match the one provided"
|
||||
);
|
||||
|
||||
// 5. Cleanup
|
||||
shutdown_circles(running_circles).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_launcher_fails_with_mismatched_keypair() {
|
||||
// 1. Setup: Generate two different keypairs
|
||||
let secp = Secp256k1::new();
|
||||
let (secret_key1, _public_key1) = secp.generate_keypair(&mut secp256k1::rand::thread_rng());
|
||||
let (_secret_key2, public_key2) = secp.generate_keypair(&mut secp256k1::rand::thread_rng());
|
||||
|
||||
let secret_key_str = secret_key1.display_secret().to_string();
|
||||
let public_key_str = public_key2.to_string(); // Mismatched public key
|
||||
|
||||
// 2. Setup: Define config with mismatched keys
|
||||
let test_circle_config = vec![CircleConfig {
|
||||
name: "test_circle_mismatched_keys".to_string(),
|
||||
port: 8090,
|
||||
script_path: None,
|
||||
public_key: Some(public_key_str),
|
||||
secret_key: Some(secret_key_str),
|
||||
}];
|
||||
|
||||
// 3. Action & Verification: Expect an error
|
||||
let result = setup_and_spawn_circles(test_circle_config).await;
|
||||
assert!(result.is_err(), "Expected an error due to mismatched keys");
|
||||
if let Err(e) = result {
|
||||
assert!(
|
||||
e.to_string()
|
||||
.contains("Provided public key does not match secret key"),
|
||||
"Error message did not contain expected text"
|
||||
);
|
||||
}
|
||||
}
|
10
src/server/.env.example
Normal file
10
src/server/.env.example
Normal file
@ -0,0 +1,10 @@
|
||||
# Webhook Configuration
|
||||
# Copy this file to .env and set your actual webhook secrets
|
||||
|
||||
# Stripe webhook endpoint secret
|
||||
# Get this from your Stripe dashboard under Webhooks
|
||||
STRIPE_WEBHOOK_SECRET=whsec_your_stripe_webhook_secret_here
|
||||
|
||||
# iDenfy webhook endpoint secret
|
||||
# Get this from your iDenfy dashboard under Webhooks
|
||||
IDENFY_WEBHOOK_SECRET=your_idenfy_webhook_secret_here
|
3
src/server/.gitignore
vendored
Normal file
3
src/server/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
file:memdb_test_server*
|
||||
*.pem
|
@ -8,9 +8,28 @@ name = "circle_ws_lib"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "server_ws_binary"
|
||||
name = "circles_server"
|
||||
path = "cmd/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "wss_basic_example"
|
||||
path = "../../examples/wss_basic_example.rs"
|
||||
|
||||
[[example]]
|
||||
name = "wss_auth_example"
|
||||
path = "../../examples/wss_auth_example.rs"
|
||||
required-features = ["auth"]
|
||||
|
||||
[[example]]
|
||||
name = "wss_test_client"
|
||||
path = "../../examples/wss_test_client.rs"
|
||||
|
||||
[[example]]
|
||||
name = "wss_server"
|
||||
path = "../../examples/wss_demo/wss_server.rs"
|
||||
required-features = ["auth"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
rustls = "0.23.5"
|
||||
rustls-pemfile = "2.1.2"
|
||||
@ -26,10 +45,18 @@ uuid = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
rhai_client = { path = "../../../rhailib/src/client" } # Corrected relative path
|
||||
thiserror = { workspace = true }
|
||||
heromodels = { path = "../../../db/heromodels" }
|
||||
|
||||
# Webhook dependencies
|
||||
hmac = "0.12"
|
||||
sha2 = "0.10"
|
||||
dotenv = "0.15"
|
||||
bytes = "1.0"
|
||||
hex = { workspace = true }
|
||||
|
||||
# Authentication dependencies (optional)
|
||||
secp256k1 = { workspace = true, optional = true }
|
||||
hex = { workspace = true, optional = true }
|
||||
sha3 = { workspace = true, optional = true }
|
||||
rand = { workspace = true, optional = true }
|
||||
once_cell = { workspace = true }
|
||||
@ -38,13 +65,16 @@ clap = { workspace = true }
|
||||
# Optional features for authentication
|
||||
[features]
|
||||
default = []
|
||||
auth = ["secp256k1", "hex", "sha3", "rand"]
|
||||
auth = ["secp256k1", "sha3", "rand"]
|
||||
|
||||
[dev-dependencies]
|
||||
redis = { version = "0.23.0", features = ["tokio-comp"] }
|
||||
uuid = { version = "1.2.2", features = ["v4"] }
|
||||
tokio-tungstenite = { version = "0.19.0", features = ["native-tls"] }
|
||||
futures-util = { workspace = true }
|
||||
url = { workspace = true }
|
||||
rhailib_worker = { path = "../../../rhailib/src/worker" }
|
||||
engine = { path = "../../../rhailib/src/engine" }
|
||||
rhailib_engine = { path = "../../../rhailib/src/engine" }
|
||||
heromodels = { path = "../../../db/heromodels" }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
native-tls = "0.2"
|
76
src/server/README.md
Normal file
76
src/server/README.md
Normal file
@ -0,0 +1,76 @@
|
||||
# `server`: The Circles WebSocket Server
|
||||
|
||||
The `server` crate provides a secure, high-performance WebSocket server built with `Actix`. It is the core backend component of the `circles` ecosystem, responsible for handling client connections, processing JSON-RPC requests, and executing Rhai scripts in a secure manner.
|
||||
|
||||
## Features
|
||||
|
||||
- **`Actix` Framework**: Built on `Actix`, a powerful and efficient actor-based web framework.
|
||||
- **WebSocket Management**: Uses `actix-web-actors` to manage each client connection in its own isolated actor (`CircleWs`), ensuring robust and concurrent session handling.
|
||||
- **JSON-RPC 2.0 API**: Implements a JSON-RPC 2.0 API for all client-server communication. The API is formally defined in the root [openrpc.json](../../openrpc.json) file.
|
||||
- **Secure Authentication**: Features a built-in `secp256k1` signature-based authentication system to protect sensitive endpoints.
|
||||
- **Stateful Session Management**: The `CircleWs` actor maintains the authentication state for each client, granting or denying access to protected methods like `play`.
|
||||
- **Webhook Integration**: Supports HTTP webhook endpoints for external services (Stripe, iDenfy) with signature verification and script execution capabilities.
|
||||
|
||||
## Core Components
|
||||
|
||||
### `spawn_circle_server`
|
||||
|
||||
This is the main entry point function for the server. It configures and starts the `Actix` HTTP server and sets up the WebSocket route with path-based routing (`/{circle_pk}`).
|
||||
|
||||
### `CircleWs` Actor
|
||||
|
||||
This `Actix` actor is the heart of the server's session management. A new instance of `CircleWs` is created for each client that connects. Its responsibilities include:
|
||||
- Handling the WebSocket connection lifecycle.
|
||||
- Parsing incoming JSON-RPC messages.
|
||||
- Managing the authentication state of the session (i.e., whether the client is authenticated or not).
|
||||
- Dispatching requests to the appropriate handlers (`fetch_nonce`, `authenticate`, and `play`).
|
||||
|
||||
## Authentication
|
||||
|
||||
The server provides a robust authentication mechanism to ensure that only authorized clients can execute scripts. The entire flow is handled over the WebSocket connection using two dedicated JSON-RPC methods:
|
||||
|
||||
1. **`fetch_nonce`**: The client requests a unique, single-use nonce (a challenge) from the server.
|
||||
2. **`authenticate`**: The client sends back the nonce signed with its private key. The `CircleWs` actor verifies the signature to confirm the client's identity.
|
||||
|
||||
For a more detailed breakdown of the authentication architecture, please see the [ARCHITECTURE.md](docs/ARCHITECTURE.md) file.
|
||||
|
||||
## Webhook Integration
|
||||
|
||||
The server also provides HTTP webhook endpoints for external services alongside the WebSocket functionality:
|
||||
|
||||
- **Stripe Webhooks**: `POST /webhooks/stripe/{circle_pk}` - Handles Stripe payment events
|
||||
- **iDenfy Webhooks**: `POST /webhooks/idenfy/{circle_pk}` - Handles iDenfy KYC verification events
|
||||
|
||||
### Webhook Features
|
||||
|
||||
- **Signature Verification**: All webhooks use HMAC signature verification for security
|
||||
- **Script Execution**: Webhook events trigger Rhai script execution via the same Redis-based system
|
||||
- **Type Safety**: Webhook payload types are defined in the `heromodels` library for reusability
|
||||
- **Modular Architecture**: Separate handlers for each webhook provider with common utilities
|
||||
|
||||
For detailed webhook architecture and configuration, see [WEBHOOK_ARCHITECTURE.md](WEBHOOK_ARCHITECTURE.md).
|
||||
|
||||
## How to Run
|
||||
|
||||
### As a Library
|
||||
|
||||
The `server` is designed to be used as a library by the `launcher`, which is responsible for spawning a single multi-circle server instance that can handle multiple circles via path-based routing.
|
||||
|
||||
To run the server via the launcher with circle public keys:
|
||||
```bash
|
||||
cargo run --package launcher -- -k <circle_public_key1> -k <circle_public_key2> [options]
|
||||
```
|
||||
|
||||
The launcher will start a single `server` instance that can handle multiple circles through path-based WebSocket connections at `/{circle_pk}`.
|
||||
|
||||
### Standalone Binary
|
||||
|
||||
A standalone binary is also available for development and testing purposes. See [`cmd/README.md`](cmd/README.md) for detailed usage instructions.
|
||||
|
||||
```bash
|
||||
# Basic standalone server
|
||||
cargo run
|
||||
|
||||
# With authentication and TLS
|
||||
cargo run -- --auth --tls --cert cert.pem --key key.pem
|
||||
```
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user