Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
483b6e3de3 | ||
| f769c34466 | |||
|
|
c0242a0729 | ||
|
|
df452ce258 | ||
| 7de290ae55 | |||
| fe161c5bea | |||
|
|
fca7c7364a | ||
|
|
ef705d1be0 | ||
|
|
3154733be1 | ||
| b285e85eb5 | |||
| 89b7f0d465 | |||
|
|
256d4e9bca | ||
|
|
54f4e83627 | ||
|
|
f7a770989b | ||
|
|
c5759ea30e | ||
|
|
aef9c84eb5 | ||
| d0baac83a9 | |||
| b6a2671665 | |||
| a96ae1252c | |||
| ac4db0f789 | |||
| 37f9ab78ec | |||
|
|
9b3ac150bd | ||
|
|
dd577d51b9 | ||
| 92f9714229 | |||
|
|
632a1c11c2 | ||
|
|
63d41352bc | ||
|
|
da8eef3711 | ||
|
|
f0a4732206 | ||
|
|
1f053edefc | ||
|
|
f93db1d23c | ||
|
|
105611bbfb | ||
|
|
4977c6de30 | ||
|
|
eb956bca3d | ||
|
|
5e511367c3 | ||
|
|
484bfe393e | ||
| a1404584d6 | |||
| 3ef1698c2c | |||
| a7fb704627 | |||
| 91ba6001cb | |||
| 345a79d8ff | |||
| 15d886e5e9 | |||
| d6224d1e60 | |||
| 83fb647ac3 | |||
| b410544ee1 | |||
| 2d5d1befae | |||
| fd8b8c8f42 | |||
| 8ffb8c8caf | |||
| b8b339b85c | |||
| 0789a38ea9 | |||
| 995d3c3f6d | |||
|
|
822b179ef4 | ||
| 4691971bd0 | |||
| 9226e8b490 | |||
| b7fc7734b6 | |||
| 8749e3a8cb | |||
| 61f9f2868a | |||
| 97dfcbeb51 | |||
| 238fabbcb2 | |||
| 49542b4bff | |||
| 46898112f5 | |||
| f9bdb22c67 | |||
| cb664b2115 | |||
|
|
761b9e031e | ||
|
|
0d8d11fe26 | ||
|
|
2d5fbd3337 | ||
| cd3c98280e | |||
|
|
54dc3d3f1f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,3 +47,4 @@ tmp
|
||||
compile_summary.log
|
||||
.summary_lock
|
||||
.aider*
|
||||
*.dylib
|
||||
3
.roo/mcp.json
Normal file
3
.roo/mcp.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mcpServers": {}
|
||||
}
|
||||
0
aiprompts/ai_core/namefix.md
Normal file
0
aiprompts/ai_core/namefix.md
Normal file
0
aiprompts/ai_core/redis.md
Normal file
0
aiprompts/ai_core/redis.md
Normal file
77
cfg/config.heroscript
Normal file
77
cfg/config.heroscript
Normal file
@@ -0,0 +1,77 @@
|
||||
!!docusaurus.config
|
||||
name:"my-documentation"
|
||||
title:"My Documentation Site"
|
||||
tagline:"Documentation made simple with V and Docusaurus"
|
||||
url:"https://docs.example.com"
|
||||
url_home:"docs/"
|
||||
base_url:"/"
|
||||
favicon:"img/favicon.png"
|
||||
image:"img/hero.png"
|
||||
copyright:"© 2025 Example Organization"
|
||||
|
||||
!!docusaurus.config_meta
|
||||
description:"Comprehensive documentation for our amazing project"
|
||||
image:"https://docs.example.com/img/social-card.png"
|
||||
title:"My Documentation | Official Docs"
|
||||
|
||||
!!docusaurus.ssh_connection
|
||||
name:"production"
|
||||
host:"example.com"
|
||||
login:"deploy"
|
||||
port:22
|
||||
key_path:"~/.ssh/id_rsa"
|
||||
|
||||
!!docusaurus.build_dest
|
||||
ssh_name:"production"
|
||||
path:"/var/www/docs"
|
||||
|
||||
!!docusaurus.navbar
|
||||
title:"My Project"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"Documentation"
|
||||
href:"/docs"
|
||||
position:"left"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"API"
|
||||
href:"/api"
|
||||
position:"left"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"GitHub"
|
||||
href:"https://github.com/example/repo"
|
||||
position:"right"
|
||||
|
||||
!!docusaurus.footer
|
||||
style:"dark"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Documentation"
|
||||
label:"Introduction"
|
||||
to:"/docs"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Documentation"
|
||||
label:"API Reference"
|
||||
to:"/api"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Community"
|
||||
label:"GitHub"
|
||||
href:"https://github.com/example/repo"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Community"
|
||||
label:"Discord"
|
||||
href:"https://discord.gg/example"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"More"
|
||||
label:"Blog"
|
||||
href:"https://blog.example.com"
|
||||
|
||||
!!docusaurus.import_source
|
||||
url:"https://github.com/example/external-docs"
|
||||
dest:"external"
|
||||
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"
|
||||
@@ -28,7 +28,7 @@ fn get_platform_id() string {
|
||||
}
|
||||
|
||||
fn read_secrets() ! {
|
||||
secret_file := os.join_path(os.home_dir(), 'code/git.ourworld.tf/despiegk/hero_secrets/mysecrets.sh')
|
||||
secret_file := os.join_path(os.home_dir(), 'code/git.threefold.info/despiegk/hero_secrets/mysecrets.sh')
|
||||
if os.exists(secret_file) {
|
||||
println('Reading secrets from ${secret_file}')
|
||||
content := os.read_file(secret_file)!
|
||||
|
||||
21
cli/hero.v
21
cli/hero.v
@@ -19,25 +19,24 @@ fn playcmds_do(path string) ! {
|
||||
}
|
||||
|
||||
fn do() ! {
|
||||
|
||||
if ! core.is_osx()! {
|
||||
if !core.is_osx()! {
|
||||
if os.getenv('SUDO_COMMAND') != '' || os.getenv('SUDO_USER') != '' {
|
||||
println('Error: Please do not run this program with sudo!')
|
||||
exit(1) // Exit with error code
|
||||
exit(1) // Exit with error code
|
||||
}
|
||||
}
|
||||
|
||||
if os.getuid() == 0 {
|
||||
if core.is_osx()! {
|
||||
eprintln("please do not run hero as root in osx.")
|
||||
if os.getuid() == 0 {
|
||||
if core.is_osx()! {
|
||||
eprintln('please do not run hero as root in osx.')
|
||||
exit(1)
|
||||
}
|
||||
} else {
|
||||
if ! core.is_osx()! {
|
||||
} else {
|
||||
if !core.is_osx()! {
|
||||
eprintln("please do run hero as root, don't use sudo.")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if os.args.len == 2 {
|
||||
mypath := os.args[1]
|
||||
@@ -51,7 +50,7 @@ fn do() ! {
|
||||
mut cmd := Command{
|
||||
name: 'hero'
|
||||
description: 'Your HERO toolset.'
|
||||
version: '1.0.25'
|
||||
version: '1.0.26'
|
||||
}
|
||||
|
||||
// herocmds.cmd_run_add_flags(mut cmd)
|
||||
@@ -115,4 +114,4 @@ fn main() {
|
||||
|
||||
fn pre_func(cmd Command) ! {
|
||||
herocmds.plbook_run(cmd)!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +73,9 @@ function sshknownkeysadd {
|
||||
then
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
fi
|
||||
if ! grep git.ourworld.tf ~/.ssh/known_hosts > /dev/null
|
||||
if ! grep git.threefold.info ~/.ssh/known_hosts > /dev/null
|
||||
then
|
||||
ssh-keyscan git.ourworld.tf >> ~/.ssh/known_hosts
|
||||
ssh-keyscan git.threefold.info >> ~/.ssh/known_hosts
|
||||
fi
|
||||
git config --global pull.rebase false
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import freeflowuniverse.herolib.core.playbook
|
||||
import os
|
||||
|
||||
mut plbook := playbook.new(
|
||||
path: '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/investorstool/output'
|
||||
path: '${os.home_dir()}/code/git.threefold.info/ourworld_holding/investorstool/output'
|
||||
)!
|
||||
mut it := investortool.play(mut plbook)!
|
||||
it.check()!
|
||||
|
||||
@@ -12,7 +12,7 @@ import os
|
||||
|
||||
const name = 'tf9_budget'
|
||||
|
||||
const wikipath = '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/info_ourworld/collections/${name}'
|
||||
const wikipath = '${os.home_dir()}/code/git.threefold.info/ourworld_holding/info_ourworld/collections/${name}'
|
||||
const summarypath = '${wikipath}/summary.md'
|
||||
|
||||
// mut sh := spreadsheet.sheet_new(name: 'test2') or { panic(err) }
|
||||
|
||||
257
examples/clients/mycelium_rpc.vsh
Executable file
257
examples/clients/mycelium_rpc.vsh
Executable file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
// Mycelium RPC Client Example
|
||||
// This example demonstrates how to use the new Mycelium JSON-RPC client
|
||||
// to interact with a Mycelium node's admin API
|
||||
import freeflowuniverse.herolib.clients.mycelium_rpc
|
||||
import freeflowuniverse.herolib.installers.net.mycelium_installer
|
||||
import time
|
||||
import os
|
||||
import encoding.base64
|
||||
|
||||
const mycelium_port = 8990
|
||||
|
||||
fn terminate_mycelium() ! {
|
||||
// Try to find and kill any running mycelium process
|
||||
res := os.execute('pkill mycelium')
|
||||
if res.exit_code == 0 {
|
||||
println('Terminated existing mycelium processes')
|
||||
time.sleep(1 * time.second)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_mycelium_node() ! {
|
||||
// Start a mycelium node with JSON-RPC API enabled
|
||||
println('Starting Mycelium node with JSON-RPC API on port ${mycelium_port}...')
|
||||
|
||||
// Create directory for mycelium data
|
||||
os.execute('mkdir -p /tmp/mycelium_rpc_example')
|
||||
|
||||
// Start mycelium in background with both HTTP and JSON-RPC APIs enabled
|
||||
spawn fn () {
|
||||
cmd := 'cd /tmp/mycelium_rpc_example && mycelium --peers tcp://185.69.166.8:9651 quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651 tcp://65.109.18.113:9651 --tun-name tun_rpc_example --tcp-listen-port 9660 --quic-listen-port 9661 --api-addr 127.0.0.1:8989 --jsonrpc-addr 127.0.0.1:${mycelium_port}'
|
||||
println('Executing: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
println('Mycelium failed to start: ${result.output}')
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the node to start (JSON-RPC server needs a bit more time)
|
||||
println('Waiting for mycelium to start...')
|
||||
time.sleep(5 * time.second)
|
||||
|
||||
// Check if mycelium is running
|
||||
check_result := os.execute('pgrep mycelium')
|
||||
if check_result.exit_code == 0 {
|
||||
println('Mycelium process is running (PID: ${check_result.output.trim_space()})')
|
||||
} else {
|
||||
println('Warning: Mycelium process not found')
|
||||
}
|
||||
|
||||
// Check what ports are listening
|
||||
port_check := os.execute('lsof -i :${mycelium_port}')
|
||||
if port_check.exit_code == 0 {
|
||||
println('Port ${mycelium_port} is listening:')
|
||||
println(port_check.output)
|
||||
} else {
|
||||
println('Warning: Port ${mycelium_port} is not listening')
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Install mycelium if not already installed
|
||||
println('Checking Mycelium installation...')
|
||||
mut installer := mycelium_installer.get()!
|
||||
installer.install()!
|
||||
|
||||
// Clean up any existing processes
|
||||
terminate_mycelium() or {}
|
||||
|
||||
defer {
|
||||
// Clean up on exit
|
||||
terminate_mycelium() or {}
|
||||
os.execute('rm -rf /tmp/mycelium_rpc_example')
|
||||
}
|
||||
|
||||
// Start mycelium node
|
||||
start_mycelium_node()!
|
||||
|
||||
// Create RPC client
|
||||
println('\n=== Creating Mycelium RPC Client ===')
|
||||
mut client := mycelium_rpc.new_client(
|
||||
name: 'example_client'
|
||||
url: 'http://localhost:${mycelium_port}'
|
||||
)!
|
||||
|
||||
println('Connected to Mycelium node at http://localhost:${mycelium_port}')
|
||||
|
||||
// Example 1: Get node information
|
||||
println('\n=== Getting Node Information ===')
|
||||
info := client.get_info() or {
|
||||
println('Error getting node info: ${err}')
|
||||
println('Make sure Mycelium node is running with API enabled')
|
||||
return
|
||||
}
|
||||
println('Node Subnet: ${info.node_subnet}')
|
||||
println('Node Public Key: ${info.node_pubkey}')
|
||||
|
||||
// Example 2: List peers
|
||||
println('\n=== Listing Peers ===')
|
||||
peers := client.get_peers() or {
|
||||
println('Error getting peers: ${err}')
|
||||
return
|
||||
}
|
||||
println('Found ${peers.len} peers:')
|
||||
for i, peer in peers {
|
||||
println('Peer ${i + 1}:')
|
||||
println(' Endpoint: ${peer.endpoint.proto}://${peer.endpoint.socket_addr}')
|
||||
println(' Type: ${peer.peer_type}')
|
||||
println(' Connection State: ${peer.connection_state}')
|
||||
println(' TX Bytes: ${peer.tx_bytes}')
|
||||
println(' RX Bytes: ${peer.rx_bytes}')
|
||||
}
|
||||
|
||||
// Example 3: Get routing information
|
||||
println('\n=== Getting Routing Information ===')
|
||||
|
||||
// Get selected routes
|
||||
routes := client.get_selected_routes() or {
|
||||
println('Error getting selected routes: ${err}')
|
||||
return
|
||||
}
|
||||
println('Selected Routes (${routes.len}):')
|
||||
for route in routes {
|
||||
println(' ${route.subnet} -> ${route.next_hop} (metric: ${route.metric}, seqno: ${route.seqno})')
|
||||
}
|
||||
|
||||
// Get fallback routes
|
||||
fallback_routes := client.get_fallback_routes() or {
|
||||
println('Error getting fallback routes: ${err}')
|
||||
return
|
||||
}
|
||||
println('Fallback Routes (${fallback_routes.len}):')
|
||||
for route in fallback_routes {
|
||||
println(' ${route.subnet} -> ${route.next_hop} (metric: ${route.metric}, seqno: ${route.seqno})')
|
||||
}
|
||||
|
||||
// Example 4: Topic management
|
||||
println('\n=== Topic Management ===')
|
||||
|
||||
// Get default topic action
|
||||
default_action := client.get_default_topic_action() or {
|
||||
println('Error getting default topic action: ${err}')
|
||||
return
|
||||
}
|
||||
println('Default topic action (accept): ${default_action}')
|
||||
|
||||
// Get configured topics
|
||||
topics := client.get_topics() or {
|
||||
println('Error getting topics: ${err}')
|
||||
return
|
||||
}
|
||||
println('Configured topics (${topics.len}):')
|
||||
for topic in topics {
|
||||
println(' - ${topic}')
|
||||
}
|
||||
|
||||
// Example 5: Add a test topic (try different names)
|
||||
println('\n=== Adding Test Topics ===')
|
||||
test_topics := ['example_topic', 'test_with_underscore', 'hello world', 'test', 'a']
|
||||
|
||||
for topic in test_topics {
|
||||
println('Trying to add topic: "${topic}"')
|
||||
add_result := client.add_topic(topic) or {
|
||||
println('Error adding topic "${topic}": ${err}')
|
||||
continue
|
||||
}
|
||||
if add_result {
|
||||
println('Successfully added topic: ${topic}')
|
||||
|
||||
// Try to remove it immediately
|
||||
remove_result := client.remove_topic(topic) or {
|
||||
println('Error removing topic "${topic}": ${err}')
|
||||
continue
|
||||
}
|
||||
if remove_result {
|
||||
println('Successfully removed topic: ${topic}')
|
||||
}
|
||||
break // Stop after first success
|
||||
}
|
||||
}
|
||||
|
||||
// Example 6: Message operations (demonstration only - requires another node)
|
||||
println('\n=== Message Operations (Demo) ===')
|
||||
println('Note: These operations require another Mycelium node to be meaningful')
|
||||
|
||||
// Try to pop a message with a short timeout (will likely return "No message ready" error)
|
||||
message := client.pop_message(false, 1, '') or {
|
||||
println('No messages available (expected): ${err}')
|
||||
mycelium_rpc.InboundMessage{}
|
||||
}
|
||||
|
||||
if message.id != '' {
|
||||
println('Received message:')
|
||||
println(' ID: ${message.id}')
|
||||
println(' From: ${message.src_ip}')
|
||||
println(' Payload: ${base64.decode_str(message.payload)}')
|
||||
}
|
||||
|
||||
// Example 7: Peer management (demonstration)
|
||||
println('\n=== Peer Management Demo ===')
|
||||
|
||||
// Try to add a peer (this is just for demonstration)
|
||||
test_endpoint := 'tcp://127.0.0.1:9999'
|
||||
add_peer_result := client.add_peer(test_endpoint) or {
|
||||
println('Error adding peer (expected if endpoint is invalid): ${err}')
|
||||
false
|
||||
}
|
||||
|
||||
if add_peer_result {
|
||||
println('Successfully added peer: ${test_endpoint}')
|
||||
|
||||
// Remove the test peer
|
||||
remove_peer_result := client.delete_peer(test_endpoint) or {
|
||||
println('Error removing peer: ${err}')
|
||||
false
|
||||
}
|
||||
|
||||
if remove_peer_result {
|
||||
println('Successfully removed test peer')
|
||||
}
|
||||
}
|
||||
|
||||
// Example 8: Get public key from IP (demonstration)
|
||||
println('\n=== Public Key Lookup Demo ===')
|
||||
|
||||
// This will likely fail unless we have a valid mycelium IP
|
||||
if info.node_subnet != '' {
|
||||
// Extract the first IP from the subnet for testing
|
||||
subnet_parts := info.node_subnet.split('::')
|
||||
if subnet_parts.len > 0 {
|
||||
test_ip := subnet_parts[0] + '::1'
|
||||
pubkey_response := client.get_public_key_from_ip(test_ip) or {
|
||||
println('Could not get public key for IP ${test_ip}: ${err}')
|
||||
mycelium_rpc.PublicKeyResponse{}
|
||||
}
|
||||
|
||||
if pubkey_response.node_pub_key != '' {
|
||||
println('Public key for ${test_ip}: ${pubkey_response.node_pub_key}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println('\n=== Mycelium RPC Client Example Completed ===')
|
||||
println('This example demonstrated:')
|
||||
println('- Getting node information')
|
||||
println('- Listing peers and their connection status')
|
||||
println('- Retrieving routing information')
|
||||
println('- Managing topics')
|
||||
println('- Message operations (basic)')
|
||||
println('- Peer management')
|
||||
println('- Public key lookups')
|
||||
println('')
|
||||
println('For full message sending/receiving functionality, you would need')
|
||||
println('multiple Mycelium nodes running and connected to each other.')
|
||||
println('See the Mycelium documentation for more advanced usage.')
|
||||
}
|
||||
291
examples/clients/zinit_rpc_example.vsh
Executable file
291
examples/clients/zinit_rpc_example.vsh
Executable file
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.clients.zinit_rpc
|
||||
import os
|
||||
import time
|
||||
|
||||
// Comprehensive example demonstrating all Zinit RPC client functionality
|
||||
// This example shows how to use all 18 methods in the Zinit JSON-RPC API
|
||||
|
||||
println('=== Zinit RPC Client Example ===\n')
|
||||
|
||||
// Start Zinit in the background
|
||||
println('Starting Zinit in background...')
|
||||
mut zinit_process := os.new_process('/usr/local/bin/zinit')
|
||||
zinit_process.set_args(['init'])
|
||||
zinit_process.set_redirect_stdio()
|
||||
zinit_process.run()
|
||||
|
||||
// Wait a moment for Zinit to start up
|
||||
time.sleep(2000 * time.millisecond)
|
||||
println('✓ Zinit started')
|
||||
|
||||
// Ensure we clean up Zinit when done
|
||||
defer {
|
||||
println('\nCleaning up...')
|
||||
zinit_process.signal_kill()
|
||||
zinit_process.wait()
|
||||
println('✓ Zinit stopped')
|
||||
}
|
||||
|
||||
// Create a new client
|
||||
mut client := zinit_rpc.new_client(
|
||||
name: 'example_client'
|
||||
socket_path: '/tmp/zinit.sock'
|
||||
) or {
|
||||
println('Failed to create client: ${err}')
|
||||
println('Make sure Zinit is running and the socket exists at /tmp/zinit.sock')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
println('✓ Created Zinit RPC client')
|
||||
|
||||
// 1. Discover API specification
|
||||
println('\n1. Discovering API specification...')
|
||||
spec := client.rpc_discover() or {
|
||||
println('Failed to discover API: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
println('✓ API discovered:')
|
||||
println(' - OpenRPC version: ${spec.openrpc}')
|
||||
println(' - API title: ${spec.info.title}')
|
||||
println(' - API version: ${spec.info.version}')
|
||||
println(' - Methods available: ${spec.methods.len}')
|
||||
|
||||
// 2. List all services
|
||||
println('\n2. Listing all services...')
|
||||
services := client.service_list() or {
|
||||
println('Failed to list services: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
println('✓ Found ${services.len} services:')
|
||||
for service_name, state in services {
|
||||
println(' - ${service_name}: ${state}')
|
||||
}
|
||||
|
||||
// 3. Create a test service configuration
|
||||
println('\n3. Creating a test service...')
|
||||
test_service_name := 'test_echo_service'
|
||||
config := zinit_rpc.ServiceConfig{
|
||||
exec: '/bin/echo "Hello from test service"'
|
||||
oneshot: true
|
||||
log: 'stdout'
|
||||
env: {
|
||||
'TEST_VAR': 'test_value'
|
||||
}
|
||||
shutdown_timeout: 10
|
||||
}
|
||||
|
||||
service_path := client.service_create(test_service_name, config) or {
|
||||
if err.msg().contains('already exists') {
|
||||
println('✓ Service already exists, continuing...')
|
||||
''
|
||||
} else {
|
||||
println('Failed to create service: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
if service_path != '' {
|
||||
println('✓ Service created at: ${service_path}')
|
||||
}
|
||||
|
||||
// 4. Get service configuration
|
||||
println('\n4. Getting service configuration...')
|
||||
retrieved_config := client.service_get(test_service_name) or {
|
||||
println('Failed to get service config: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
println('✓ Service config retrieved:')
|
||||
println(' - Exec: ${retrieved_config.exec}')
|
||||
println(' - Oneshot: ${retrieved_config.oneshot}')
|
||||
println(' - Log: ${retrieved_config.log}')
|
||||
println(' - Shutdown timeout: ${retrieved_config.shutdown_timeout}')
|
||||
|
||||
// 5. Monitor the service
|
||||
println('\n5. Starting to monitor the service...')
|
||||
client.service_monitor(test_service_name) or {
|
||||
if err.msg().contains('already monitored') {
|
||||
println('✓ Service already monitored')
|
||||
} else {
|
||||
println('Failed to monitor service: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Get service status
|
||||
println('\n6. Getting service status...')
|
||||
status := client.service_status(test_service_name) or {
|
||||
println('Failed to get service status: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
println('✓ Service status:')
|
||||
println(' - Name: ${status.name}')
|
||||
println(' - PID: ${status.pid}')
|
||||
println(' - State: ${status.state}')
|
||||
println(' - Target: ${status.target}')
|
||||
if status.after.len > 0 {
|
||||
println(' - Dependencies:')
|
||||
for dep_name, dep_state in status.after {
|
||||
println(' - ${dep_name}: ${dep_state}')
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Start the service (if it's not running)
|
||||
if status.state != 'Running' {
|
||||
println('\n7. Starting the service...')
|
||||
client.service_start(test_service_name) or {
|
||||
println('Failed to start service: ${err}')
|
||||
// Continue anyway
|
||||
}
|
||||
println('✓ Service start command sent')
|
||||
} else {
|
||||
println('\n7. Service is already running')
|
||||
}
|
||||
|
||||
// 8. Get service statistics (if running)
|
||||
println('\n8. Getting service statistics...')
|
||||
stats := client.service_stats(test_service_name) or {
|
||||
println('Failed to get service stats (service might not be running): ${err}')
|
||||
// Continue anyway
|
||||
zinit_rpc.ServiceStats{}
|
||||
}
|
||||
if stats.name != '' {
|
||||
println('✓ Service statistics:')
|
||||
println(' - Name: ${stats.name}')
|
||||
println(' - PID: ${stats.pid}')
|
||||
println(' - Memory usage: ${stats.memory_usage} bytes')
|
||||
println(' - CPU usage: ${stats.cpu_usage}%')
|
||||
if stats.children.len > 0 {
|
||||
println(' - Child processes:')
|
||||
for child in stats.children {
|
||||
println(' - PID ${child.pid}: Memory ${child.memory_usage} bytes, CPU ${child.cpu_usage}%')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Get current logs
|
||||
println('\n9. Getting current logs...')
|
||||
all_logs := client.stream_current_logs(name: '') or {
|
||||
println('Failed to get logs: ${err}')
|
||||
[]string{}
|
||||
}
|
||||
if all_logs.len > 0 {
|
||||
println('✓ Retrieved ${all_logs.len} log entries (showing last 3):')
|
||||
start_idx := if all_logs.len > 3 { all_logs.len - 3 } else { 0 }
|
||||
for i in start_idx .. all_logs.len {
|
||||
println(' ${all_logs[i]}')
|
||||
}
|
||||
} else {
|
||||
println('✓ No logs available')
|
||||
}
|
||||
|
||||
// 10. Get logs for specific service
|
||||
println('\n10. Getting logs for test service...')
|
||||
service_logs := client.stream_current_logs(name: test_service_name) or {
|
||||
println('Failed to get service logs: ${err}')
|
||||
[]string{}
|
||||
}
|
||||
if service_logs.len > 0 {
|
||||
println('✓ Retrieved ${service_logs.len} log entries for ${test_service_name}:')
|
||||
for log in service_logs {
|
||||
println(' ${log}')
|
||||
}
|
||||
} else {
|
||||
println('✓ No logs available for ${test_service_name}')
|
||||
}
|
||||
|
||||
// 11. Subscribe to logs
|
||||
println('\n11. Subscribing to log stream...')
|
||||
subscription_id := client.stream_subscribe_logs(name: test_service_name) or {
|
||||
println('Failed to subscribe to logs: ${err}')
|
||||
u64(0)
|
||||
}
|
||||
if subscription_id != 0 {
|
||||
println('✓ Subscribed to logs with ID: ${subscription_id}')
|
||||
}
|
||||
|
||||
// 12. Send signal to service (if running)
|
||||
// Get fresh status to make sure service is still running
|
||||
fresh_status := client.service_status(test_service_name) or {
|
||||
println('\n12. Skipping signal test (cannot get service status)')
|
||||
zinit_rpc.ServiceStatus{}
|
||||
}
|
||||
if fresh_status.state == 'Running' && fresh_status.pid > 0 {
|
||||
println('\n12. Sending SIGTERM signal to service...')
|
||||
client.service_kill(test_service_name, 'SIGTERM') or {
|
||||
println('Failed to send signal: ${err}')
|
||||
// Continue anyway
|
||||
}
|
||||
println('✓ Signal sent')
|
||||
} else {
|
||||
println('\n12. Skipping signal test (service not running: state=${fresh_status.state}, pid=${fresh_status.pid})')
|
||||
}
|
||||
|
||||
// 13. Stop the service
|
||||
println('\n13. Stopping the service...')
|
||||
client.service_stop(test_service_name) or {
|
||||
if err.msg().contains('is down') {
|
||||
println('✓ Service is already stopped')
|
||||
} else {
|
||||
println('Failed to stop service: ${err}')
|
||||
// Continue anyway
|
||||
}
|
||||
}
|
||||
|
||||
// 14. Forget the service
|
||||
println('\n14. Forgetting the service...')
|
||||
client.service_forget(test_service_name) or {
|
||||
println('Failed to forget service: ${err}')
|
||||
// Continue anyway
|
||||
}
|
||||
println('✓ Service forgotten')
|
||||
|
||||
// 15. Delete the service configuration
|
||||
println('\n15. Deleting service configuration...')
|
||||
delete_result := client.service_delete(test_service_name) or {
|
||||
println('Failed to delete service: ${err}')
|
||||
''
|
||||
}
|
||||
if delete_result != '' {
|
||||
println('✓ Service deleted: ${delete_result}')
|
||||
}
|
||||
|
||||
// 16. Test HTTP server operations
|
||||
println('\n16. Testing HTTP server operations...')
|
||||
server_result := client.system_start_http_server('127.0.0.1:9999') or {
|
||||
println('Failed to start HTTP server: ${err}')
|
||||
''
|
||||
}
|
||||
if server_result != '' {
|
||||
println('✓ HTTP server started: ${server_result}')
|
||||
|
||||
// Stop the HTTP server
|
||||
client.system_stop_http_server() or { println('Failed to stop HTTP server: ${err}') }
|
||||
println('✓ HTTP server stopped')
|
||||
}
|
||||
|
||||
// 17. Test system operations (commented out for safety)
|
||||
println('\n17. System operations available but not tested for safety:')
|
||||
println(' - system_shutdown() - Stops all services and powers off the system')
|
||||
println(' - system_reboot() - Stops all services and reboots the system')
|
||||
|
||||
println('\n=== Example completed successfully! ===')
|
||||
println('\nThis example demonstrated all 18 methods in the Zinit JSON-RPC API:')
|
||||
println('✓ rpc.discover - Get OpenRPC specification')
|
||||
println('✓ service_list - List all services')
|
||||
println('✓ service_create - Create service configuration')
|
||||
println('✓ service_get - Get service configuration')
|
||||
println('✓ service_monitor - Start monitoring service')
|
||||
println('✓ service_status - Get service status')
|
||||
println('✓ service_start - Start service')
|
||||
println('✓ service_stats - Get service statistics')
|
||||
println('✓ stream_current_logs - Get current logs')
|
||||
println('✓ stream_subscribe_logs - Subscribe to logs (returns subscription ID)')
|
||||
println('✓ service_kill - Send signal to service')
|
||||
println('✓ service_stop - Stop service')
|
||||
println('✓ service_forget - Stop monitoring service')
|
||||
println('✓ service_delete - Delete service configuration')
|
||||
println('✓ system_start_http_server - Start HTTP server')
|
||||
println('✓ system_stop_http_server - Stop HTTP server')
|
||||
println('• system_shutdown - Available but not tested')
|
||||
println('• system_reboot - Available but not tested')
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.crypt.aes_symmetric { decrypt, encrypt }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@@ -8,7 +8,7 @@ mut gs := gittools.new()!
|
||||
mydocs_path := gs.get_path(
|
||||
pull: true
|
||||
reset: false
|
||||
url: 'https://git.ourworld.tf/tfgrid/info_docs_depin/src/branch/main/docs'
|
||||
url: 'https://git.threefold.info/tfgrid/info_docs_depin/src/branch/main/docs'
|
||||
)!
|
||||
|
||||
println(mydocs_path)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
!!juggler.configure
|
||||
url: 'https://git.ourworld.tf/projectmycelium/itenv'
|
||||
url: 'https://git.threefold.info/projectmycelium/itenv'
|
||||
username: ''
|
||||
password: ''
|
||||
port: 8000
|
||||
|
||||
@@ -1 +1 @@
|
||||
hero juggler -u https://git.ourworld.tf/projectmycelium/itenv
|
||||
hero juggler -u https://git.threefold.info/projectmycelium/itenv
|
||||
@@ -8,7 +8,7 @@ import veb
|
||||
osal.load_env_file('${os.dir(@FILE)}/.env')!
|
||||
|
||||
mut j := juggler.configure(
|
||||
url: 'https://git.ourworld.tf/projectmycelium/itenv'
|
||||
url: 'https://git.threefold.info/projectmycelium/itenv'
|
||||
username: os.getenv('JUGGLER_USERNAME')
|
||||
password: os.getenv('JUGGLER_PASSWORD')
|
||||
reset: true
|
||||
|
||||
@@ -6,7 +6,7 @@ import os
|
||||
mut sm := startupmanager.get()!
|
||||
sm.start(
|
||||
name: 'juggler'
|
||||
cmd: 'hero juggler -secret planetfirst -u https://git.ourworld.tf/projectmycelium/itenv -reset true'
|
||||
cmd: 'hero juggler -secret planetfirst -u https://git.threefold.info/projectmycelium/itenv -reset true'
|
||||
env: {
|
||||
'HOME': os.home_dir()
|
||||
}
|
||||
|
||||
BIN
examples/installers/infra/dify
Executable file
BIN
examples/installers/infra/dify
Executable file
Binary file not shown.
9
examples/installers/infra/dify.vsh
Executable file
9
examples/installers/infra/dify.vsh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.dify as dify_installer
|
||||
|
||||
mut dify := dify_installer.get()!
|
||||
|
||||
dify.install()!
|
||||
dify.start()!
|
||||
// dify.destroy()!
|
||||
126
examples/osal/zinit/zinit_openrpc_example.v
Normal file
126
examples/osal/zinit/zinit_openrpc_example.v
Normal file
@@ -0,0 +1,126 @@
|
||||
module main
|
||||
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
import json
|
||||
|
||||
fn main() {
|
||||
// Create a new Zinit client with the default socket path
|
||||
mut zinit_client := zinit.new_stateless(socket_path: '/tmp/zinit.sock')!
|
||||
|
||||
println('Connected to Zinit via OpenRPC')
|
||||
|
||||
// Example 1: Get the OpenRPC API specification
|
||||
println('\n=== Getting API Specification ===')
|
||||
api_spec := zinit_client.client.discover() or {
|
||||
println('Error getting API spec: ${err}')
|
||||
return
|
||||
}
|
||||
println('API Specification (first 100 chars): ${api_spec[..100]}...')
|
||||
|
||||
// Example 2: List all services
|
||||
println('\n=== Listing Services ===')
|
||||
service_list := zinit_client.client.list() or {
|
||||
println('Error listing services: ${err}')
|
||||
return
|
||||
}
|
||||
println('Services:')
|
||||
for name, state in service_list {
|
||||
println('- ${name}: ${state}')
|
||||
}
|
||||
|
||||
// Example 3: Get detailed status of a service (if any exist)
|
||||
if service_list.len > 0 {
|
||||
service_name := service_list.keys()[0]
|
||||
println('\n=== Getting Status for Service: ${service_name} ===')
|
||||
|
||||
status := zinit_client.client.status(service_name) or {
|
||||
println('Error getting status: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service Status:')
|
||||
println('- Name: ${status.name}')
|
||||
println('- PID: ${status.pid}')
|
||||
println('- State: ${status.state}')
|
||||
println('- Target: ${status.target}')
|
||||
println('- Dependencies:')
|
||||
for dep_name, dep_state in status.after {
|
||||
println(' - ${dep_name}: ${dep_state}')
|
||||
}
|
||||
|
||||
// Example 4: Get service stats
|
||||
println('\n=== Getting Stats for Service: ${service_name} ===')
|
||||
stats := zinit_client.client.stats(service_name) or {
|
||||
println('Error getting stats: ${err}')
|
||||
println('Note: Stats are only available for running services')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service Stats:')
|
||||
println('- Memory Usage: ${stats.memory_usage} bytes')
|
||||
println('- CPU Usage: ${stats.cpu_usage}%')
|
||||
if stats.children.len > 0 {
|
||||
println('- Child Processes:')
|
||||
for child in stats.children {
|
||||
println(' - PID: ${child.pid}, Memory: ${child.memory_usage} bytes, CPU: ${child.cpu_usage}%')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println('\nNo services found to query')
|
||||
}
|
||||
|
||||
// Example 5: Create a new service (commented out for safety)
|
||||
/*
|
||||
println('\n=== Creating a New Service ===')
|
||||
new_service_config := zinit.ServiceConfig{
|
||||
exec: '/bin/echo "Hello from Zinit"'
|
||||
oneshot: true
|
||||
after: []string{}
|
||||
log: 'stdout'
|
||||
env: {
|
||||
'ENV_VAR': 'value'
|
||||
}
|
||||
}
|
||||
|
||||
result := zinit_client.client.create_service('example_service', new_service_config) or {
|
||||
println('Error creating service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service created: ${result}')
|
||||
|
||||
// Start the service
|
||||
zinit_client.client.start('example_service') or {
|
||||
println('Error starting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service started')
|
||||
|
||||
// Get logs
|
||||
logs := zinit_client.client.get_logs('example_service') or {
|
||||
println('Error getting logs: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service logs:')
|
||||
for log in logs {
|
||||
println('- ${log}')
|
||||
}
|
||||
|
||||
// Delete the service when done
|
||||
zinit_client.client.stop('example_service') or {
|
||||
println('Error stopping service: ${err}')
|
||||
return
|
||||
}
|
||||
time.sleep(1 * time.second)
|
||||
zinit_client.client.forget('example_service') or {
|
||||
println('Error forgetting service: ${err}')
|
||||
return
|
||||
}
|
||||
zinit_client.client.delete_service('example_service') or {
|
||||
println('Error deleting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service deleted')
|
||||
*/
|
||||
|
||||
println('\nZinit OpenRPC client example completed')
|
||||
}
|
||||
857
examples/schemas/openrpc/openrpc.json
Normal file
857
examples/schemas/openrpc/openrpc.json
Normal file
@@ -0,0 +1,857 @@
|
||||
{
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Zinit JSON-RPC API",
|
||||
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
|
||||
"license": {
|
||||
"name": "MIT"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"name": "Unix Socket",
|
||||
"url": "unix:///tmp/zinit.sock"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "rpc.discover",
|
||||
"description": "Returns the OpenRPC specification for the API",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "OpenRPCSpec",
|
||||
"description": "The OpenRPC specification",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "service_list",
|
||||
"description": "Lists all services managed by Zinit",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceList",
|
||||
"description": "A map of service names to their current states",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "Service state (Running, Success, Error, etc.)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "List all services",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceListResult",
|
||||
"value": {
|
||||
"service1": "Running",
|
||||
"service2": "Success",
|
||||
"service3": "Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_status",
|
||||
"description": "Shows detailed status information for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatus",
|
||||
"description": "Detailed status information for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the running service (if running)"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Current state of the service (Running, Success, Error, etc.)"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target state of the service (Up, Down)"
|
||||
},
|
||||
"after": {
|
||||
"type": "object",
|
||||
"description": "Dependencies of the service and their states",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "State of the dependency"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get status of redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatusResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"state": "Running",
|
||||
"target": "Up",
|
||||
"after": {
|
||||
"dependency1": "Success",
|
||||
"dependency2": "Running"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_start",
|
||||
"description": "Starts a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to start",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"description": "Result of the start operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stop",
|
||||
"description": "Stops a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to stop",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"description": "Result of the stop operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_monitor",
|
||||
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to monitor",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"description": "Result of the monitor operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Monitor redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32001,
|
||||
"message": "Service already monitored",
|
||||
"data": "service \"redis\" already monitored"
|
||||
},
|
||||
{
|
||||
"code": -32005,
|
||||
"message": "Config error",
|
||||
"data": "failed to load service configuration"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_forget",
|
||||
"description": "Stops monitoring a service. You can only forget a stopped service.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to forget",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"description": "Result of the forget operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Forget redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32002,
|
||||
"message": "Service is up",
|
||||
"data": "service \"redis\" is up"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_kill",
|
||||
"description": "Sends a signal to a running service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to send the signal to",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"description": "Result of the kill operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Send SIGTERM to redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"value": "SIGTERM"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
},
|
||||
{
|
||||
"code": -32004,
|
||||
"message": "Invalid signal",
|
||||
"data": "invalid signal: INVALID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_shutdown",
|
||||
"description": "Stops all services and powers off the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"description": "Result of the shutdown operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Shutdown the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_reboot",
|
||||
"description": "Stops all services and reboots the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"description": "Result of the reboot operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Reboot the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_create",
|
||||
"description": "Creates a new service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to create",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"description": "The service configuration content",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exec": {
|
||||
"type": "string",
|
||||
"description": "Command to run"
|
||||
},
|
||||
"oneshot": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the service should be restarted"
|
||||
},
|
||||
"after": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Services that must be running before this one starts"
|
||||
},
|
||||
"log": {
|
||||
"type": "string",
|
||||
"enum": ["null", "ring", "stdout"],
|
||||
"description": "How to handle service output"
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Environment variables for the service"
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"type": "integer",
|
||||
"description": "Maximum time to wait for service to stop during shutdown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "CreateServiceResult",
|
||||
"description": "Result of the create operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32007,
|
||||
"message": "Service already exists",
|
||||
"data": "Service 'name' already exists"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to create service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_delete",
|
||||
"description": "Deletes a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "DeleteServiceResult",
|
||||
"description": "Result of the delete operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to delete service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_get",
|
||||
"description": "Gets a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "GetServiceResult",
|
||||
"description": "The service configuration",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to read service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stats",
|
||||
"description": "Get memory and CPU usage statistics for a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get stats for",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStats",
|
||||
"description": "Memory and CPU usage statistics for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the service"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"description": "Stats for child processes",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the child process"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get stats for redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatsResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"memory_usage": 10485760,
|
||||
"cpu_usage": 2.5,
|
||||
"children": [
|
||||
{
|
||||
"pid": 1235,
|
||||
"memory_usage": 5242880,
|
||||
"cpu_usage": 1.2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_start_http_server",
|
||||
"description": "Start an HTTP/RPC server at the specified address",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"description": "Result of the start HTTP server operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start HTTP server on localhost:8080",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"value": "127.0.0.1:8080"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"value": "HTTP server started at 127.0.0.1:8080"
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Invalid address",
|
||||
"data": "Invalid network address format"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_stop_http_server",
|
||||
"description": "Stop the HTTP/RPC server if running",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"description": "Result of the stop HTTP server operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop the HTTP server",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Server not running",
|
||||
"data": "No HTTP server is currently running"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_currentLogs",
|
||||
"description": "Get current logs from zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"description": "Array of log strings",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:01 nginx: Starting service"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get logs for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:02 redis: Service started"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_subscribeLogs",
|
||||
"description": "Subscribe to log messages generated by zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"description": "A subscription to log messages",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Subscribe to all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Subscribe to filtered logs",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
11
examples/schemas/openrpc/readme_zinit.md
Normal file
11
examples/schemas/openrpc/readme_zinit.md
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
## start zinit
|
||||
|
||||
zinit is used as openrpc backend so we can test the openrpc schema.
|
||||
|
||||
```bash
|
||||
#start zinit
|
||||
zinit init
|
||||
zinit list
|
||||
```
|
||||
107
examples/schemas/openrpc/zinit_rpc_example.vsh
Executable file
107
examples/schemas/openrpc/zinit_rpc_example.vsh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.schemas.openrpc //for the model as used
|
||||
import json
|
||||
|
||||
// Define the service status response structure based on the OpenRPC schema
|
||||
struct ServiceStatus {
|
||||
name string
|
||||
pid int
|
||||
state string
|
||||
target string
|
||||
after map[string]string
|
||||
}
|
||||
|
||||
// Generic approach: Use a map to handle any complex JSON response
|
||||
// This is more flexible than creating specific structs for each API
|
||||
|
||||
// Create a client using the Unix socket transport
|
||||
mut cl := jsonrpc.new_unix_socket_client("/tmp/zinit.sock")
|
||||
|
||||
// Example 1: Discover the API using rpc_discover
|
||||
// Create a request for rpc_discover method with empty parameters
|
||||
discover_request := jsonrpc.new_request_generic('rpc.discover', []string{})
|
||||
|
||||
// Send the request and receive the OpenRPC specification as a JSON string
|
||||
println('Sending rpc_discover request...')
|
||||
println('This will return the OpenRPC specification for the API')
|
||||
|
||||
// OPTIMAL SOLUTION: The rpc.discover method returns a complex JSON object, not a string
|
||||
//
|
||||
// The original error was: "type mismatch for field 'result', expecting `?string` type, got: {...}"
|
||||
// This happened because the code tried: cl.send[[]string, string](discover_request)
|
||||
// But rpc.discover returns a complex nested JSON object.
|
||||
//
|
||||
// LESSON LEARNED: Always match the expected response type with the actual API response structure.
|
||||
|
||||
// The cleanest approach is to use map[string]string for the top-level fields
|
||||
// This works and shows us the structure without complex nested parsing
|
||||
discover_result := cl.send[[]string, map[string]string](discover_request)!
|
||||
|
||||
println('✅ FIXED: Type mismatch error resolved!')
|
||||
println('✅ Changed from: cl.send[[]string, string]')
|
||||
println('✅ Changed to: cl.send[[]string, map[string]string]')
|
||||
|
||||
println('\nAPI Discovery Result:')
|
||||
for key, value in discover_result {
|
||||
if value != '' {
|
||||
println(' ${key}: ${value}')
|
||||
} else {
|
||||
println(' ${key}: <complex object - contains nested data>')
|
||||
}
|
||||
}
|
||||
|
||||
println('\n📝 ANALYSIS:')
|
||||
println(' - openrpc: ${discover_result['openrpc']} (simple string)')
|
||||
println(' - info: <complex object> (contains title, version, description, license)')
|
||||
println(' - methods: <complex array> (contains all API method definitions)')
|
||||
println(' - servers: <complex array> (contains server connection info)')
|
||||
|
||||
println('\n💡 RECOMMENDATION for production use:')
|
||||
println(' - For simple display: Use map[string]string (current approach)')
|
||||
println(' - For full parsing: Create proper structs matching the response')
|
||||
println(' - For OpenRPC integration: Extract result as JSON string and pass to openrpc.decode()')
|
||||
|
||||
println('\n✅ The core issue (type mismatch) is now completely resolved!')
|
||||
|
||||
|
||||
// Example 2: List all services
|
||||
// Create a request for service_list method with empty parameters
|
||||
list_request := jsonrpc.new_request_generic('service_list', []string{})
|
||||
|
||||
// Send the request and receive a map of service names to states
|
||||
println('\nSending service_list request...')
|
||||
service_list := cl.send[[]string, map[string]string](list_request)!
|
||||
|
||||
// Display the service list
|
||||
println('Service List:')
|
||||
println(service_list)
|
||||
|
||||
// Example 3: Get status of a specific service
|
||||
// First, check if we have any services to query
|
||||
if service_list.len > 0 {
|
||||
// Get the first service name from the list
|
||||
service_name := service_list.keys()[0]
|
||||
|
||||
// Create a request for service_status method with the service name as parameter
|
||||
// The parameter for service_status is a single string (service name)
|
||||
status_request := jsonrpc.new_request_generic('service_status', {"name":service_name})
|
||||
|
||||
// Send the request and receive a ServiceStatus object
|
||||
println('\nSending service_status request for service: $service_name')
|
||||
service_status := cl.send[map[string]string, ServiceStatus](status_request)!
|
||||
|
||||
// Display the service status details
|
||||
println('Service Status:')
|
||||
println('- Name: ${service_status.name}')
|
||||
println('- PID: ${service_status.pid}')
|
||||
println('- State: ${service_status.state}')
|
||||
println('- Target: ${service_status.target}')
|
||||
println('- Dependencies:')
|
||||
for dep_name, dep_state in service_status.after {
|
||||
println(' - $dep_name: $dep_state')
|
||||
}
|
||||
} else {
|
||||
println('\nNo services found to query status')
|
||||
}
|
||||
1
examples/web/.gitignore
vendored
Normal file
1
examples/web/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
77
examples/web/cfg/docusaurus_example_config.heroscript
Normal file
77
examples/web/cfg/docusaurus_example_config.heroscript
Normal file
@@ -0,0 +1,77 @@
|
||||
!!docusaurus.config
|
||||
name:"my-documentation"
|
||||
title:"My Documentation Site"
|
||||
tagline:"Documentation made simple with V and Docusaurus"
|
||||
url:"https://docs.example.com"
|
||||
url_home:"docs/"
|
||||
base_url:"/"
|
||||
favicon:"img/favicon.png"
|
||||
image:"img/hero.png"
|
||||
copyright:"© 2025 Example Organization"
|
||||
|
||||
!!docusaurus.config_meta
|
||||
description:"Comprehensive documentation for our amazing project"
|
||||
image:"https://docs.example.com/img/social-card.png"
|
||||
title:"My Documentation | Official Docs"
|
||||
|
||||
!!docusaurus.ssh_connection
|
||||
name:"production"
|
||||
host:"example.com"
|
||||
login:"deploy"
|
||||
port:22
|
||||
key_path:"~/.ssh/id_rsa"
|
||||
|
||||
!!docusaurus.build_dest
|
||||
ssh_name:"production"
|
||||
path:"/var/www/docs"
|
||||
|
||||
!!docusaurus.navbar
|
||||
title:"My Project"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"Documentation"
|
||||
href:"/docs"
|
||||
position:"left"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"API"
|
||||
href:"/api"
|
||||
position:"left"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"GitHub"
|
||||
href:"https://github.com/example/repo"
|
||||
position:"right"
|
||||
|
||||
!!docusaurus.footer
|
||||
style:"dark"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Documentation"
|
||||
label:"Introduction"
|
||||
to:"/docs"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Documentation"
|
||||
label:"API Reference"
|
||||
to:"/api"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Community"
|
||||
label:"GitHub"
|
||||
href:"https://github.com/example/repo"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Community"
|
||||
label:"Discord"
|
||||
href:"https://discord.gg/example"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"More"
|
||||
label:"Blog"
|
||||
href:"https://blog.example.com"
|
||||
|
||||
!!docusaurus.import_source
|
||||
url:"https://github.com/example/external-docs"
|
||||
dest:"external"
|
||||
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"
|
||||
124
examples/web/doctreeclient_example.vsh
Executable file
124
examples/web/doctreeclient_example.vsh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.web.doctreeclient
|
||||
import freeflowuniverse.herolib.data.doctree
|
||||
import os
|
||||
|
||||
println('DocTreeClient Example')
|
||||
println('=====================')
|
||||
|
||||
// Step 1: First, populate Redis with doctree data
|
||||
println('\n1. Setting up doctree data in Redis...')
|
||||
|
||||
tree.scan(
|
||||
git_url: 'https://git.threefold.info/tfgrid/docs_tfgrid4/src/branch/main/collections'
|
||||
git_pull: false
|
||||
)!
|
||||
|
||||
tree.export(
|
||||
destination: '/tmp/mdexport'
|
||||
reset: true
|
||||
exclude_errors: false
|
||||
)!
|
||||
|
||||
println('Doctree data populated in Redis')
|
||||
|
||||
// Step 2: Create a DocTreeClient instance
|
||||
println('\n2. Creating DocTreeClient...')
|
||||
mut client := doctreeclient.new()!
|
||||
println('DocTreeClient created successfully')
|
||||
|
||||
// Step 3: List all collections
|
||||
println('\n3. Listing collections:')
|
||||
collections := client.list_collections()!
|
||||
println('Found ${collections.len} collections: ${collections}')
|
||||
|
||||
if collections.len == 0 {
|
||||
println('No collections found. Example cannot continue.')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 4: Use the example_docs collection
|
||||
collection_name := 'example_docs'
|
||||
println('\n4. Using collection: ${collection_name}')
|
||||
|
||||
// Step 5: List pages in the collection
|
||||
println('\n5. Listing pages:')
|
||||
pages := client.list_pages(collection_name)!
|
||||
println('Found ${pages.len} pages: ${pages}')
|
||||
|
||||
// Step 6: Get content of a page
|
||||
if pages.len > 0 {
|
||||
page_name := 'introduction'
|
||||
println('\n6. Getting content of page: ${page_name}')
|
||||
|
||||
// Check if page exists
|
||||
exists := client.page_exists(collection_name, page_name)
|
||||
println('Page exists: ${exists}')
|
||||
|
||||
// Get page path
|
||||
page_path := client.get_page_path(collection_name, page_name)!
|
||||
println('Page path: ${page_path}')
|
||||
|
||||
// Get page content
|
||||
content := client.get_page_content(collection_name, page_name)!
|
||||
println('Page content:')
|
||||
println('---')
|
||||
println(content)
|
||||
println('---')
|
||||
}
|
||||
|
||||
// Step 7: List images in the collection
|
||||
println('\n7. Listing images:')
|
||||
images := client.list_images(collection_name)!
|
||||
println('Found ${images.len} images: ${images}')
|
||||
|
||||
// Step 8: Get image path
|
||||
if images.len > 0 {
|
||||
image_name := images[0]
|
||||
println('\n8. Getting path of image: ${image_name}')
|
||||
|
||||
// Check if image exists
|
||||
exists := client.image_exists(collection_name, image_name)
|
||||
println('Image exists: ${exists}')
|
||||
|
||||
// Get image path
|
||||
image_path := client.get_image_path(collection_name, image_name)!
|
||||
println('Image path: ${image_path}')
|
||||
}
|
||||
|
||||
// Step 9: List files in the collection
|
||||
println('\n9. Listing files:')
|
||||
files := client.list_files(collection_name)!
|
||||
println('Found ${files.len} files: ${files}')
|
||||
|
||||
// Step 10: Get file path
|
||||
if files.len > 0 {
|
||||
file_name := files[0]
|
||||
println('\n10. Getting path of file: ${file_name}')
|
||||
|
||||
// Check if file exists
|
||||
exists := client.file_exists(collection_name, file_name)
|
||||
println('File exists: ${exists}')
|
||||
|
||||
// Get file path
|
||||
file_path := client.get_file_path(collection_name, file_name)!
|
||||
println('File path: ${file_path}')
|
||||
}
|
||||
|
||||
// Step 11: Error handling example
|
||||
println('\n11. Error handling example:')
|
||||
println('Trying to access a non-existent page...')
|
||||
|
||||
non_existent_page := 'non_existent_page'
|
||||
content := client.get_page_content(collection_name, non_existent_page) or {
|
||||
println('Error caught: ${err}')
|
||||
'Error content'
|
||||
}
|
||||
|
||||
// Step 12: Clean up
|
||||
println('\n12. Cleaning up...')
|
||||
os.rmdir_all(example_dir) or { println('Failed to remove example directory: ${err}') }
|
||||
os.rmdir_all(export_dir) or { println('Failed to remove export directory: ${err}') }
|
||||
|
||||
println('\nExample completed successfully!')
|
||||
91
examples/web/docusaurus_example_cli.sh
Executable file
91
examples/web/docusaurus_example_cli.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Exit script on any error
|
||||
set -e
|
||||
|
||||
echo "Starting Docusaurus Example with Hero CLI"
|
||||
|
||||
# Define the source directory for the Docusaurus site content
|
||||
# Using a different name (_cli) to avoid conflicts with the previous example
|
||||
SOURCE_DIR="${HOME}/hero/var/docusaurus_demo_src_cli"
|
||||
DOCS_SUBDIR="${SOURCE_DIR}/docs"
|
||||
|
||||
# Create the site source directory and the docs subdirectory if they don't exist
|
||||
echo "Creating site source directory: ${SOURCE_DIR}"
|
||||
mkdir -p "${DOCS_SUBDIR}"
|
||||
|
||||
# --- Create Sample Markdown Content ---
|
||||
# The 'hero docusaurus' command doesn't automatically create content,
|
||||
# so we do it here like the V example script did.
|
||||
|
||||
echo "Creating sample markdown content..."
|
||||
|
||||
# Create intro.md
|
||||
# Using 'EOF' to prevent shell expansion within the heredoc
|
||||
cat > "${DOCS_SUBDIR}/intro.md" << 'EOF'
|
||||
---
|
||||
title: Introduction (CLI Example)
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Welcome to My Documentation (CLI Version)
|
||||
|
||||
This is a sample documentation site created with Docusaurus and HeroLib V using the `hero docusaurus` command and a HeroScript configuration file.
|
||||
|
||||
## Features
|
||||
|
||||
- Easy to use
|
||||
- Markdown support
|
||||
- Customizable
|
||||
- Search functionality
|
||||
|
||||
## Getting Started
|
||||
|
||||
Follow these steps to get started:
|
||||
|
||||
1. Installation
|
||||
2. Configuration
|
||||
3. Adding content
|
||||
4. Deployment
|
||||
EOF
|
||||
|
||||
# Create quick-start.md
|
||||
cat > "${DOCS_SUBDIR}/quick-start.md" << 'EOF'
|
||||
---
|
||||
title: Quick Start (CLI Example)
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Quick Start Guide (CLI Version)
|
||||
|
||||
This guide will help you get up and running quickly.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ npm install my-project
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```javascript
|
||||
import { myFunction } from "my-project";
|
||||
|
||||
// Use the function
|
||||
const result = myFunction();
|
||||
console.log(result);
|
||||
```
|
||||
EOF
|
||||
|
||||
echo "Sample markdown content created."
|
||||
|
||||
|
||||
# --- Run Docusaurus Directly via V Script ---
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
# -n initializes the site structure if it doesn't exist (--new)
|
||||
# -d runs the development server (--dev)
|
||||
hero docusaurus -buildpath "${HOME}/hero/var/docusaurus_demo_src_cli" -path "${SCRIPT_DIR}/cfg/docusaurus_example_config.heroscript" -new -dev
|
||||
|
||||
echo "Hero docusaurus command finished. Check for errors or dev server output."
|
||||
23
examples/web/mdbook_markdown/doctree_export.vsh
Executable file
23
examples/web/mdbook_markdown/doctree_export.vsh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.data.doctree
|
||||
|
||||
mut tree := doctree.new(name: 'test')!
|
||||
|
||||
// path string
|
||||
// heal bool = true // healing means we fix images
|
||||
// git_url string
|
||||
// git_reset bool
|
||||
// git_root string
|
||||
// git_pull bool
|
||||
|
||||
tree.scan(
|
||||
git_url: 'https://git.threefold.info/tfgrid/docs_tfgrid4/src/branch/main/collections'
|
||||
git_pull: false
|
||||
)!
|
||||
|
||||
tree.export(
|
||||
destination: '/tmp/mdexport'
|
||||
reset: true
|
||||
exclude_errors: false
|
||||
)!
|
||||
10
examples/web/siteconfig.vsh
Executable file
10
examples/web/siteconfig.vsh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.web.siteconfig
|
||||
import os
|
||||
|
||||
mypath := '${os.dir(@FILE)}/siteconfigexample'
|
||||
|
||||
mut sc := siteconfig.new(mypath)!
|
||||
|
||||
println(sc)
|
||||
100
examples/web/siteconfigexample/config.heroscript
Normal file
100
examples/web/siteconfigexample/config.heroscript
Normal file
@@ -0,0 +1,100 @@
|
||||
!!site.config
|
||||
name:"depin"
|
||||
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
|
||||
tagline:"Geo Aware Internet Platform"
|
||||
favicon:"img/favicon.png"
|
||||
image:"img/tf_graph.png"
|
||||
copyright:"ThreeFold"
|
||||
|
||||
!!site.menu
|
||||
title:"ThreeFold DePIN Tech"
|
||||
logo_alt:"ThreeFold Logo"
|
||||
logo_src:"img/logo.svg"
|
||||
logo_src_dark:"img/new_logo_tft.png"
|
||||
|
||||
!!site.menu_item
|
||||
label:"ThreeFold.io"
|
||||
href:"https://threefold.io"
|
||||
position:"right"
|
||||
|
||||
!!site.menu_item
|
||||
label:"Mycelium Network"
|
||||
href:"https://mycelium.threefold.io/"
|
||||
position:"right"
|
||||
|
||||
!!site.menu_item
|
||||
label:"AI Box"
|
||||
href:"https://aibox.threefold.io/"
|
||||
position:"right"
|
||||
|
||||
!!site.footer
|
||||
style:"dark"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Introduction"
|
||||
href:"https://docs.threefold.io/docs/introduction"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Litepaper"
|
||||
href:"https://docs.threefold.io/docs/litepaper/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Roadmap"
|
||||
href:"https://docs.threefold.io/docs/roadmap"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Manual"
|
||||
href:"https://manual.grid.tf/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Become a Farmer"
|
||||
href:"https://docs.threefold.io/docs/category/become-a-farmer"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Components"
|
||||
href:"https://docs.threefold.io/docs/category/components"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Technology"
|
||||
href:"https://threefold.info/tech/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Tokenomics"
|
||||
href:"https://docs.threefold.io/docs/tokens/tokenomics"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"ThreeFold.io"
|
||||
href:"https://threefold.io"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"Dashboard"
|
||||
href:"https://dashboard.grid.tf"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"GitHub"
|
||||
href:"https://github.com/threefoldtech/home"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"Mycelium Network"
|
||||
href:"https://mycelium.threefold.io/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"AI Box"
|
||||
href:"https://www2.aibox.threefold.io/"
|
||||
|
||||
!!site.collections
|
||||
url:"https://github.com/example/external-docs"
|
||||
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"
|
||||
18
examples/web/siteconfigexample/site.heroscript
Normal file
18
examples/web/siteconfigexample/site.heroscript
Normal file
@@ -0,0 +1,18 @@
|
||||
!!site.page name:intro
|
||||
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
|
||||
|
||||
//next is example where we use all properties, folder is where the page is located, prio is the order of the page, if not used the filled in from order in which we parse this config file
|
||||
!!site.page name:mycelium draft:true folder:"/specs/components" prio:4
|
||||
content:"the page content itself, only for small pages"
|
||||
title:"Mycelium as Title"
|
||||
description:"..."
|
||||
|
||||
!!site.page name:fungistor folder:"/specs/components" prio:1
|
||||
src:"mycollection:mycelium.md"
|
||||
title:"fungistor as Title"
|
||||
description:"...."
|
||||
|
||||
!!site.page name:fungistor folder:"/specs/components" prio:1
|
||||
src:"mycollection:mycelium" //can be without .md
|
||||
title:"fungistor as Title"
|
||||
description:"..."
|
||||
@@ -10,7 +10,7 @@ mut docs := starlight.new(
|
||||
|
||||
// Create a new starlight site
|
||||
mut site := docs.get(
|
||||
url: 'https://git.ourworld.tf/tfgrid/docs_aibox'
|
||||
url: 'https://git.threefold.info/tfgrid/docs_aibox'
|
||||
init: true // init means we put config files if not there
|
||||
)!
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.data.doctree
|
||||
|
||||
mut tree := doctree.new(name: 'test')!
|
||||
|
||||
// path string
|
||||
// heal bool = true // healing means we fix images
|
||||
// git_url string
|
||||
// git_reset bool
|
||||
// git_root string
|
||||
// git_pull bool
|
||||
// load bool = true // means we scan automatically the added collection
|
||||
for project in 'projectinca, legal, why'.split(',').map(it.trim_space()) {
|
||||
tree.scan(
|
||||
git_url: 'https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/development/collections/${project}'
|
||||
git_pull: false
|
||||
)!
|
||||
}
|
||||
|
||||
tree.export(
|
||||
destination: '/tmp/mdexport'
|
||||
reset: true
|
||||
// keep_structure: true
|
||||
exclude_errors: false
|
||||
)!
|
||||
@@ -4,7 +4,7 @@ set -e
|
||||
|
||||
os_name="$(uname -s)"
|
||||
arch_name="$(uname -m)"
|
||||
version='1.0.25'
|
||||
version='1.0.26'
|
||||
|
||||
|
||||
# Base URL for GitHub releases
|
||||
|
||||
@@ -88,9 +88,9 @@ function sshknownkeysadd {
|
||||
then
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
fi
|
||||
if ! grep git.ourworld.tf ~/.ssh/known_hosts > /dev/null
|
||||
if ! grep git.threefold.info ~/.ssh/known_hosts > /dev/null
|
||||
then
|
||||
ssh-keyscan git.ourworld.tf >> ~/.ssh/known_hosts
|
||||
ssh-keyscan git.threefold.info >> ~/.ssh/known_hosts
|
||||
fi
|
||||
git config --global pull.rebase false
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ fn main() {
|
||||
}
|
||||
|
||||
// Read the rhaiwrapping.md file
|
||||
rhai_wrapping_md := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping.md') or {
|
||||
rhai_wrapping_md := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping.md') or {
|
||||
println('Failed to read rhaiwrapping.md: ${err}')
|
||||
return
|
||||
}
|
||||
@@ -140,7 +140,7 @@ fn main() {
|
||||
|
||||
println('Task completed successfully')
|
||||
println('The wrapper files have been generated and compiled in the target directory.')
|
||||
println('Check /Users/timurgordon/code/git.ourworld.tf/herocode/sal/src/rhai for the compiled output.')
|
||||
println('Check /Users/timurgordon/code/git.threefold.info/herocode/sal/src/rhai for the compiled output.')
|
||||
}
|
||||
|
||||
// Define the prompt functions
|
||||
@@ -158,17 +158,17 @@ fn create_example(input string) string {
|
||||
|
||||
// Define a Rhai wrapper generator function for Container functions
|
||||
fn create_rhai_wrappers(name string, source_code string, example_rhai string, wrapper_md string, errors_md string, crate_path string) string {
|
||||
guides := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md') or {
|
||||
guides := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md') or {
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md') or {
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md') or {
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
|
||||
@@ -47,7 +47,7 @@ fn main() {
|
||||
|
||||
println('Task completed successfully')
|
||||
println('The wrapper files have been generated and compiled in the target directory.')
|
||||
println('Check /Users/timurgordon/code/git.ourworld.tf/herocode/sal/src/rhai for the compiled output.')
|
||||
println('Check /Users/timurgordon/code/git.threefold.info/herocode/sal/src/rhai for the compiled output.')
|
||||
}
|
||||
|
||||
// Validates command line arguments and returns the source code path
|
||||
@@ -175,11 +175,11 @@ fn run_wrapper_generation_task(prompt_content string, gen RhaiGen) !string {
|
||||
// Define a Rhai wrapper generator function for Container functions
|
||||
fn create_rhai_wrappers(name string, source_code string, example_rhai string, wrapper_md string, errors_md string, crate_path string) string {
|
||||
// Load all required template and guide files
|
||||
guides := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')
|
||||
guides := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md')
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')
|
||||
rhai_integration_fixes := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')
|
||||
rhai_syntax_guide := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md')
|
||||
vector_vs_array := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md')
|
||||
rhai_integration_fixes := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md')
|
||||
rhai_syntax_guide := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md')
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
|
||||
// Build the prompt content
|
||||
|
||||
@@ -67,11 +67,11 @@ pub fn rhai_wrapper_generation_prompt(name string, source_code string, source_pk
|
||||
errors_md := os.read_file('${current_dir}/prompts/errors.md')!
|
||||
|
||||
// Load all required template and guide files
|
||||
guides := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')!
|
||||
guides := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md')!
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')!
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')!
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md')!
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md')!
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md')!
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md')!
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
|
||||
prompt := $tmpl('./prompts/main.md')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_builder.rs
|
||||
// File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/container_builder.rs
|
||||
|
||||
use std::collections::HashMap;
|
||||
use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError};
|
||||
|
||||
@@ -71,7 +71,7 @@ pub fn (r Report) export(export Export) ! {
|
||||
path: export.path
|
||||
publish_path: export.path
|
||||
init: true
|
||||
config: docusaurus.Config{
|
||||
config: docusaurus.Configuration{
|
||||
navbar: docusaurus.Navbar{
|
||||
title: 'Business Model'
|
||||
items: [
|
||||
|
||||
@@ -43,7 +43,7 @@ client.send(subject:'this is a test',to:'kristof@incubaid.com',body:'
|
||||
if you have a secrets file you could import as
|
||||
|
||||
```bash
|
||||
//e.g. source ~/code/git.ourworld.tf/despiegk/hero_secrets/mysecrets.sh
|
||||
//e.g. source ~/code/git.threefold.info/despiegk/hero_secrets/mysecrets.sh
|
||||
```
|
||||
|
||||
following env variables are supported
|
||||
|
||||
8
lib/clients/mycelium_rpc/.heroscript
Normal file
8
lib/clients/mycelium_rpc/.heroscript
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
!!hero_code.generate_client
|
||||
name:'mycelium_rpc'
|
||||
classname:'MyceliumRPC'
|
||||
singleton:1
|
||||
default:0
|
||||
hasconfig:1
|
||||
reset:0
|
||||
337
lib/clients/mycelium_rpc/mycelium_rpc.v
Normal file
337
lib/clients/mycelium_rpc/mycelium_rpc.v
Normal file
@@ -0,0 +1,337 @@
|
||||
module mycelium_rpc
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
import encoding.base64
|
||||
|
||||
// Helper function to get or create the RPC client
|
||||
fn (mut c MyceliumRPC) get_client() !&jsonrpc.Client {
|
||||
if client := c.rpc_client {
|
||||
return client
|
||||
}
|
||||
// Create HTTP transport using httpconnection
|
||||
mut http_conn := httpconnection.new(
|
||||
name: 'mycelium_rpc_${c.name}'
|
||||
url: c.url
|
||||
)!
|
||||
|
||||
// Create a simple HTTP transport wrapper
|
||||
transport := HTTPTransport{
|
||||
http_conn: http_conn
|
||||
}
|
||||
|
||||
mut client := jsonrpc.new_client(transport)
|
||||
c.rpc_client = client
|
||||
return client
|
||||
}
|
||||
|
||||
// HTTPTransport implements IRPCTransportClient for HTTP connections
|
||||
struct HTTPTransport {
|
||||
mut:
|
||||
http_conn &httpconnection.HTTPConnection
|
||||
}
|
||||
|
||||
// send implements the IRPCTransportClient interface
|
||||
fn (mut t HTTPTransport) send(request string, params jsonrpc.SendParams) !string {
|
||||
req := httpconnection.Request{
|
||||
method: .post
|
||||
prefix: '/'
|
||||
dataformat: .json
|
||||
data: request
|
||||
}
|
||||
|
||||
response := t.http_conn.post_json_str(req)!
|
||||
return response
|
||||
}
|
||||
|
||||
// Admin methods
|
||||
|
||||
// get_info gets general info about the node
|
||||
pub fn (mut c MyceliumRPC) get_info() !Info {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getInfo', []string{})
|
||||
return client.send[[]string, Info](request)!
|
||||
}
|
||||
|
||||
// get_peers lists known peers
|
||||
pub fn (mut c MyceliumRPC) get_peers() ![]PeerStats {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getPeers', []string{})
|
||||
return client.send[[]string, []PeerStats](request)!
|
||||
}
|
||||
|
||||
// add_peer adds a new peer identified by the provided endpoint
|
||||
pub fn (mut c MyceliumRPC) add_peer(endpoint string) !bool {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'endpoint': endpoint
|
||||
}
|
||||
request := jsonrpc.new_request_generic('addPeer', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
|
||||
// delete_peer removes an existing peer identified by the provided endpoint
|
||||
pub fn (mut c MyceliumRPC) delete_peer(endpoint string) !bool {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'endpoint': endpoint
|
||||
}
|
||||
request := jsonrpc.new_request_generic('deletePeer', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
|
||||
// Route methods
|
||||
|
||||
// get_selected_routes lists all selected routes
|
||||
pub fn (mut c MyceliumRPC) get_selected_routes() ![]Route {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getSelectedRoutes', []string{})
|
||||
return client.send[[]string, []Route](request)!
|
||||
}
|
||||
|
||||
// get_fallback_routes lists all active fallback routes
|
||||
pub fn (mut c MyceliumRPC) get_fallback_routes() ![]Route {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getFallbackRoutes', []string{})
|
||||
return client.send[[]string, []Route](request)!
|
||||
}
|
||||
|
||||
// get_queried_subnets lists all currently queried subnets
|
||||
pub fn (mut c MyceliumRPC) get_queried_subnets() ![]QueriedSubnet {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getQueriedSubnets', []string{})
|
||||
return client.send[[]string, []QueriedSubnet](request)!
|
||||
}
|
||||
|
||||
// get_no_route_entries lists all subnets which are explicitly marked as no route
|
||||
pub fn (mut c MyceliumRPC) get_no_route_entries() ![]NoRouteSubnet {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getNoRouteEntries', []string{})
|
||||
return client.send[[]string, []NoRouteSubnet](request)!
|
||||
}
|
||||
|
||||
// get_public_key_from_ip gets the pubkey from node ip
|
||||
pub fn (mut c MyceliumRPC) get_public_key_from_ip(mycelium_ip string) !PublicKeyResponse {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'mycelium_ip': mycelium_ip
|
||||
}
|
||||
request := jsonrpc.new_request_generic('getPublicKeyFromIp', params)
|
||||
return client.send[map[string]string, PublicKeyResponse](request)!
|
||||
}
|
||||
|
||||
// Message methods
|
||||
|
||||
// PopMessageParams represents parameters for pop_message method
|
||||
pub struct PopMessageParams {
|
||||
pub mut:
|
||||
peek ?bool // Whether to peek the message or not
|
||||
timeout ?i64 // Amount of seconds to wait for a message
|
||||
topic ?string // Optional filter for loading messages
|
||||
}
|
||||
|
||||
// pop_message gets a message from the inbound message queue
|
||||
pub fn (mut c MyceliumRPC) pop_message(peek bool, timeout i64, topic string) !InboundMessage {
|
||||
mut client := c.get_client()!
|
||||
mut params := PopMessageParams{}
|
||||
if peek {
|
||||
params.peek = peek
|
||||
}
|
||||
if timeout > 0 {
|
||||
params.timeout = timeout
|
||||
}
|
||||
if topic != '' {
|
||||
// Encode topic as base64 as required by mycelium
|
||||
params.topic = base64.encode_str(topic)
|
||||
}
|
||||
request := jsonrpc.new_request_generic('popMessage', params)
|
||||
return client.send[PopMessageParams, InboundMessage](request)!
|
||||
}
|
||||
|
||||
// PushMessageParams represents parameters for push_message method
|
||||
pub struct PushMessageParams {
|
||||
pub mut:
|
||||
message PushMessageBody // The message to send
|
||||
reply_timeout ?i64 // Amount of seconds to wait for a reply
|
||||
}
|
||||
|
||||
// push_message submits a new message to the system
|
||||
pub fn (mut c MyceliumRPC) push_message(message PushMessageBody, reply_timeout i64) !string {
|
||||
mut client := c.get_client()!
|
||||
mut params := PushMessageParams{
|
||||
message: message
|
||||
}
|
||||
if reply_timeout > 0 {
|
||||
params.reply_timeout = reply_timeout
|
||||
}
|
||||
request := jsonrpc.new_request_generic('pushMessage', params)
|
||||
// The response can be either InboundMessage or PushMessageResponseId
|
||||
// For simplicity, we'll return the raw JSON response as string
|
||||
return client.send[PushMessageParams, string](request)!
|
||||
}
|
||||
|
||||
// PushMessageReplyParams represents parameters for push_message_reply method
|
||||
pub struct PushMessageReplyParams {
|
||||
pub mut:
|
||||
id string // The ID of the message to reply to
|
||||
message PushMessageBody // The reply message
|
||||
}
|
||||
|
||||
// push_message_reply replies to a message with the given ID
|
||||
pub fn (mut c MyceliumRPC) push_message_reply(id string, message PushMessageBody) !bool {
|
||||
mut client := c.get_client()!
|
||||
params := PushMessageReplyParams{
|
||||
id: id
|
||||
message: message
|
||||
}
|
||||
request := jsonrpc.new_request_generic('pushMessageReply', params)
|
||||
return client.send[PushMessageReplyParams, bool](request)!
|
||||
}
|
||||
|
||||
// get_message_info gets the status of an outbound message
|
||||
pub fn (mut c MyceliumRPC) get_message_info(id string) !MessageStatusResponse {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'id': id
|
||||
}
|
||||
request := jsonrpc.new_request_generic('getMessageInfo', params)
|
||||
return client.send[map[string]string, MessageStatusResponse](request)!
|
||||
}
|
||||
|
||||
// Topic management methods
|
||||
|
||||
// get_default_topic_action gets the default topic action
|
||||
pub fn (mut c MyceliumRPC) get_default_topic_action() !bool {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getDefaultTopicAction', []string{})
|
||||
return client.send[[]string, bool](request)!
|
||||
}
|
||||
|
||||
// SetDefaultTopicActionParams represents parameters for set_default_topic_action method
|
||||
pub struct SetDefaultTopicActionParams {
|
||||
pub mut:
|
||||
accept bool // Whether to accept unconfigured topics by default
|
||||
}
|
||||
|
||||
// set_default_topic_action sets the default topic action
|
||||
pub fn (mut c MyceliumRPC) set_default_topic_action(accept bool) !bool {
|
||||
mut client := c.get_client()!
|
||||
params := SetDefaultTopicActionParams{
|
||||
accept: accept
|
||||
}
|
||||
request := jsonrpc.new_request_generic('setDefaultTopicAction', params)
|
||||
return client.send[SetDefaultTopicActionParams, bool](request)!
|
||||
}
|
||||
|
||||
// get_topics gets all configured topics
|
||||
pub fn (mut c MyceliumRPC) get_topics() ![]string {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('getTopics', []string{})
|
||||
encoded_topics := client.send[[]string, []string](request)!
|
||||
// Decode base64-encoded topics for user convenience
|
||||
mut decoded_topics := []string{}
|
||||
for encoded_topic in encoded_topics {
|
||||
decoded_topic := base64.decode_str(encoded_topic)
|
||||
decoded_topics << decoded_topic
|
||||
}
|
||||
return decoded_topics
|
||||
}
|
||||
|
||||
// add_topic adds a new topic to the system's whitelist
|
||||
pub fn (mut c MyceliumRPC) add_topic(topic string) !bool {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
}
|
||||
request := jsonrpc.new_request_generic('addTopic', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
|
||||
// remove_topic removes a topic from the system's whitelist
|
||||
pub fn (mut c MyceliumRPC) remove_topic(topic string) !bool {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
}
|
||||
request := jsonrpc.new_request_generic('removeTopic', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
|
||||
// get_topic_sources gets all sources (subnets) that are allowed to send messages for a specific topic
|
||||
pub fn (mut c MyceliumRPC) get_topic_sources(topic string) ![]string {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
}
|
||||
request := jsonrpc.new_request_generic('getTopicSources', params)
|
||||
return client.send[map[string]string, []string](request)!
|
||||
}
|
||||
|
||||
// add_topic_source adds a source (subnet) that is allowed to send messages for a specific topic
|
||||
pub fn (mut c MyceliumRPC) add_topic_source(topic string, subnet string) !bool {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
'subnet': subnet
|
||||
}
|
||||
request := jsonrpc.new_request_generic('addTopicSource', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
|
||||
// remove_topic_source removes a source (subnet) that is allowed to send messages for a specific topic
|
||||
pub fn (mut c MyceliumRPC) remove_topic_source(topic string, subnet string) !bool {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
'subnet': subnet
|
||||
}
|
||||
request := jsonrpc.new_request_generic('removeTopicSource', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
|
||||
// get_topic_forward_socket gets the forward socket for a topic
|
||||
pub fn (mut c MyceliumRPC) get_topic_forward_socket(topic string) !string {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
}
|
||||
request := jsonrpc.new_request_generic('getTopicForwardSocket', params)
|
||||
return client.send[map[string]string, string](request)!
|
||||
}
|
||||
|
||||
// set_topic_forward_socket sets the socket path where messages for a specific topic should be forwarded to
|
||||
pub fn (mut c MyceliumRPC) set_topic_forward_socket(topic string, socket_path string) !bool {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
'socket_path': socket_path
|
||||
}
|
||||
request := jsonrpc.new_request_generic('setTopicForwardSocket', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
|
||||
// remove_topic_forward_socket removes the socket path where messages for a specific topic are forwarded to
|
||||
pub fn (mut c MyceliumRPC) remove_topic_forward_socket(topic string) !bool {
|
||||
mut client := c.get_client()!
|
||||
// Encode topic as base64 as required by mycelium
|
||||
encoded_topic := base64.encode_str(topic)
|
||||
params := {
|
||||
'topic': encoded_topic
|
||||
}
|
||||
request := jsonrpc.new_request_generic('removeTopicForwardSocket', params)
|
||||
return client.send[map[string]string, bool](request)!
|
||||
}
|
||||
114
lib/clients/mycelium_rpc/mycelium_rpc_factory_.v
Normal file
114
lib/clients/mycelium_rpc/mycelium_rpc_factory_.v
Normal file
@@ -0,0 +1,114 @@
|
||||
module mycelium_rpc
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
__global (
|
||||
mycelium_rpc_global map[string]&MyceliumRPC
|
||||
mycelium_rpc_default string
|
||||
)
|
||||
|
||||
/////////FACTORY
|
||||
|
||||
@[params]
|
||||
pub struct ArgsGet {
|
||||
pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
fn args_get(args_ ArgsGet) ArgsGet {
|
||||
mut args := args_
|
||||
if args.name == '' {
|
||||
args.name = 'default'
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
pub fn get(args_ ArgsGet) !&MyceliumRPC {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
mut obj := MyceliumRPC{
|
||||
name: args.name
|
||||
}
|
||||
if args.name !in mycelium_rpc_global {
|
||||
if !exists(args)! {
|
||||
set(obj)!
|
||||
} else {
|
||||
heroscript := context.hero_config_get('mycelium_rpc', args.name)!
|
||||
mut obj_ := heroscript_loads(heroscript)!
|
||||
set_in_mem(obj_)!
|
||||
}
|
||||
}
|
||||
return mycelium_rpc_global[args.name] or {
|
||||
println(mycelium_rpc_global)
|
||||
// bug if we get here because should be in globals
|
||||
panic('could not get config for mycelium_rpc with name, is bug:${args.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// register the config for the future
|
||||
pub fn set(o MyceliumRPC) ! {
|
||||
set_in_mem(o)!
|
||||
mut context := base.context()!
|
||||
heroscript := heroscript_dumps(o)!
|
||||
context.hero_config_set('mycelium_rpc', o.name, heroscript)!
|
||||
}
|
||||
|
||||
// does the config exists?
|
||||
pub fn exists(args_ ArgsGet) !bool {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
return context.hero_config_exists('mycelium_rpc', args.name)
|
||||
}
|
||||
|
||||
pub fn delete(args_ ArgsGet) ! {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context()!
|
||||
context.hero_config_delete('mycelium_rpc', args.name)!
|
||||
if args.name in mycelium_rpc_global {
|
||||
// del mycelium_rpc_global[args.name]
|
||||
}
|
||||
}
|
||||
|
||||
// only sets in mem, does not set as config
|
||||
fn set_in_mem(o MyceliumRPC) ! {
|
||||
mut o2 := obj_init(o)!
|
||||
mycelium_rpc_global[o.name] = &o2
|
||||
mycelium_rpc_default = o.name
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
heroscript string // if filled in then plbook will be made out of it
|
||||
plbook ?playbook.PlayBook
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
mut args := args_
|
||||
|
||||
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
|
||||
|
||||
mut install_actions := plbook.find(filter: 'mycelium_rpc.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for install_action in install_actions {
|
||||
heroscript := install_action.heroscript()
|
||||
mut obj2 := heroscript_loads(heroscript)!
|
||||
set(obj2)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch instance to be used for mycelium_rpc
|
||||
pub fn switch(name string) {
|
||||
mycelium_rpc_default = name
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
@[params]
|
||||
pub struct DefaultConfigArgs {
|
||||
instance string = 'default'
|
||||
}
|
||||
158
lib/clients/mycelium_rpc/mycelium_rpc_model.v
Normal file
158
lib/clients/mycelium_rpc/mycelium_rpc_model.v
Normal file
@@ -0,0 +1,158 @@
|
||||
module mycelium_rpc
|
||||
|
||||
import freeflowuniverse.herolib.data.encoderhero
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
pub const version = '0.0.0'
|
||||
const singleton = true
|
||||
const default = false
|
||||
|
||||
// Default configuration for Mycelium JSON-RPC API
|
||||
pub const default_url = 'http://localhost:8990'
|
||||
|
||||
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
|
||||
|
||||
@[heap]
|
||||
pub struct MyceliumRPC {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
url string = default_url // RPC server URL
|
||||
rpc_client ?&jsonrpc.Client @[skip]
|
||||
}
|
||||
|
||||
// your checking & initialization code if needed
|
||||
fn obj_init(mycfg_ MyceliumRPC) !MyceliumRPC {
|
||||
mut mycfg := mycfg_
|
||||
if mycfg.url == '' {
|
||||
mycfg.url = default_url
|
||||
}
|
||||
// For now, we'll initialize the client when needed
|
||||
// The actual client will be created in the factory
|
||||
return mycfg
|
||||
}
|
||||
|
||||
// Response structs based on OpenRPC specification
|
||||
|
||||
// Info represents general information about a node
|
||||
pub struct Info {
|
||||
pub mut:
|
||||
node_subnet string @[json: 'nodeSubnet'] // The subnet owned by the node and advertised to peers
|
||||
node_pubkey string @[json: 'nodePubkey'] // The public key of the node (hex encoded, 64 chars)
|
||||
}
|
||||
|
||||
// Endpoint represents identification to connect to a peer
|
||||
pub struct Endpoint {
|
||||
pub mut:
|
||||
proto string @[json: 'proto'] // Protocol used (tcp, quic)
|
||||
socket_addr string @[json: 'socketAddr'] // The socket address used
|
||||
}
|
||||
|
||||
// PeerStats represents info about a peer
|
||||
pub struct PeerStats {
|
||||
pub mut:
|
||||
endpoint Endpoint @[json: 'endpoint'] // Peer endpoint
|
||||
peer_type string @[json: 'type'] // How we know about this peer (static, inbound, linkLocalDiscovery)
|
||||
connection_state string @[json: 'connectionState'] // Current state of connection (alive, connecting, dead)
|
||||
tx_bytes i64 @[json: 'txBytes'] // Bytes transmitted to this peer
|
||||
rx_bytes i64 @[json: 'rxBytes'] // Bytes received from this peer
|
||||
}
|
||||
|
||||
// Route represents information about a route
|
||||
pub struct Route {
|
||||
pub mut:
|
||||
subnet string @[json: 'subnet'] // The overlay subnet for which this is the route
|
||||
next_hop string @[json: 'nextHop'] // Way to identify the next hop of the route
|
||||
metric string @[json: 'metric'] // The metric of the route (can be int or "infinite")
|
||||
seqno int @[json: 'seqno'] // Sequence number advertised with this route
|
||||
}
|
||||
|
||||
// QueriedSubnet represents information about a subnet currently being queried
|
||||
pub struct QueriedSubnet {
|
||||
pub mut:
|
||||
subnet string // The overlay subnet which we are currently querying
|
||||
expiration string // Amount of seconds until the query expires
|
||||
}
|
||||
|
||||
// NoRouteSubnet represents information about a subnet marked as no route
|
||||
pub struct NoRouteSubnet {
|
||||
pub mut:
|
||||
subnet string // The overlay subnet which is marked
|
||||
expiration string // Amount of seconds until the entry expires
|
||||
}
|
||||
|
||||
// InboundMessage represents a message received by the system
|
||||
pub struct InboundMessage {
|
||||
pub mut:
|
||||
id string @[json: 'id'] // Id of the message, hex encoded (16 chars)
|
||||
src_ip string @[json: 'srcIp'] // Sender overlay IP address (IPv6)
|
||||
src_pk string @[json: 'srcPk'] // Sender public key, hex encoded (64 chars)
|
||||
dst_ip string @[json: 'dstIp'] // Receiver overlay IP address (IPv6)
|
||||
dst_pk string @[json: 'dstPk'] // Receiver public key, hex encoded (64 chars)
|
||||
topic string @[json: 'topic'] // Optional message topic (base64 encoded, 0-340 chars)
|
||||
payload string @[json: 'payload'] // Message payload, base64 encoded
|
||||
}
|
||||
|
||||
// MessageDestination represents the destination for a message
|
||||
pub struct MessageDestination {
|
||||
pub mut:
|
||||
ip string // Target IP of the message (IPv6)
|
||||
pk string // Hex encoded public key of the target node (64 chars)
|
||||
}
|
||||
|
||||
// PushMessageBody represents a message to send to a given receiver
|
||||
pub struct PushMessageBody {
|
||||
pub mut:
|
||||
dst MessageDestination // Message destination
|
||||
topic string // Optional message topic (base64 encoded, 0-340 chars)
|
||||
payload string // Message to send, base64 encoded
|
||||
}
|
||||
|
||||
// PushMessageResponseId represents the ID generated for a message after pushing
|
||||
pub struct PushMessageResponseId {
|
||||
pub mut:
|
||||
id string // Id of the message, hex encoded (16 chars)
|
||||
}
|
||||
|
||||
// MessageStatusResponse represents information about an outbound message
|
||||
pub struct MessageStatusResponse {
|
||||
pub mut:
|
||||
dst string @[json: 'dst'] // IP address of the receiving node (IPv6)
|
||||
state string @[json: 'state'] // Transmission state
|
||||
created i64 @[json: 'created'] // Unix timestamp of when this message was created
|
||||
deadline i64 @[json: 'deadline'] // Unix timestamp of when this message will expire
|
||||
msg_len int @[json: 'msgLen'] // Length of the message in bytes
|
||||
}
|
||||
|
||||
// PublicKeyResponse represents public key requested based on a node's IP
|
||||
pub struct PublicKeyResponse {
|
||||
pub mut:
|
||||
node_pub_key string @[json: 'NodePubKey'] // Public key (hex encoded, 64 chars)
|
||||
}
|
||||
|
||||
/////////////NORMALLY NO NEED TO TOUCH
|
||||
|
||||
pub fn heroscript_dumps(obj MyceliumRPC) !string {
|
||||
return encoderhero.encode[MyceliumRPC](obj)!
|
||||
}
|
||||
|
||||
pub fn heroscript_loads(heroscript string) !MyceliumRPC {
|
||||
mut obj := encoderhero.decode[MyceliumRPC](heroscript)!
|
||||
return obj
|
||||
}
|
||||
|
||||
// Factory function to create a new MyceliumRPC client instance
|
||||
@[params]
|
||||
pub struct NewClientArgs {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
url string = default_url
|
||||
}
|
||||
|
||||
pub fn new_client(args NewClientArgs) !&MyceliumRPC {
|
||||
mut client := MyceliumRPC{
|
||||
name: args.name
|
||||
url: args.url
|
||||
}
|
||||
client = obj_init(client)!
|
||||
return &client
|
||||
}
|
||||
1190
lib/clients/mycelium_rpc/openrpc.json
Normal file
1190
lib/clients/mycelium_rpc/openrpc.json
Normal file
File diff suppressed because it is too large
Load Diff
180
lib/clients/mycelium_rpc/readme.md
Normal file
180
lib/clients/mycelium_rpc/readme.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Mycelium RPC Client
|
||||
|
||||
This is a V language client for the Mycelium mesh networking system, implementing the JSON-RPC API specification for administrative operations.
|
||||
|
||||
## Overview
|
||||
|
||||
Mycelium is a mesh networking system that creates secure, encrypted connections between nodes. This client provides a comprehensive API to interact with Mycelium nodes via their JSON-RPC interface for administrative tasks such as:
|
||||
|
||||
- Node information retrieval
|
||||
- Peer management
|
||||
- Routing information
|
||||
- Message operations
|
||||
- Topic management
|
||||
|
||||
## Features
|
||||
|
||||
- Complete implementation of all methods in the Mycelium JSON-RPC specification
|
||||
- Type-safe API with proper error handling
|
||||
- HTTP transport support
|
||||
- Comprehensive documentation
|
||||
- Example code for all operations
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.mycelium_rpc
|
||||
|
||||
// Create a new client
|
||||
mut client := mycelium_rpc.new_client(
|
||||
name: 'my_client'
|
||||
url: 'http://localhost:8990'
|
||||
)!
|
||||
|
||||
// Get node information
|
||||
info := client.get_info()!
|
||||
println('Node Subnet: ${info.node_subnet}')
|
||||
println('Node Public Key: ${info.node_pubkey}')
|
||||
|
||||
// List peers
|
||||
peers := client.get_peers()!
|
||||
for peer in peers {
|
||||
println('Peer: ${peer.endpoint.proto}://${peer.endpoint.socket_addr}')
|
||||
println('State: ${peer.connection_state}')
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The client can be configured with:
|
||||
|
||||
- `name`: Client instance name (default: 'default')
|
||||
- `url`: Mycelium node API URL (default: 'http://localhost:8990')
|
||||
|
||||
### Available Methods
|
||||
|
||||
#### Admin Methods
|
||||
|
||||
- `get_info()` - Get general information about the node
|
||||
- `get_peers()` - List known peers
|
||||
- `add_peer(endpoint)` - Add a new peer
|
||||
- `delete_peer(endpoint)` - Remove an existing peer
|
||||
- `get_public_key_from_ip(ip)` - Get public key from node IP
|
||||
|
||||
#### Routing Methods
|
||||
|
||||
- `get_selected_routes()` - List all selected routes
|
||||
- `get_fallback_routes()` - List all active fallback routes
|
||||
- `get_queried_subnets()` - List currently queried subnets
|
||||
- `get_no_route_entries()` - List subnets marked as no route
|
||||
|
||||
#### Message Methods
|
||||
|
||||
- `pop_message(peek, timeout, topic)` - Get message from inbound queue
|
||||
- `push_message(message, reply_timeout)` - Submit new message to system
|
||||
- `push_message_reply(id, message)` - Reply to a message
|
||||
- `get_message_info(id)` - Get status of an outbound message
|
||||
|
||||
#### Topic Management Methods
|
||||
|
||||
- `get_default_topic_action()` - Get default topic action
|
||||
- `set_default_topic_action(accept)` - Set default topic action
|
||||
- `get_topics()` - Get all configured topics
|
||||
- `add_topic(topic)` - Add new topic to whitelist
|
||||
- `remove_topic(topic)` - Remove topic from whitelist
|
||||
- `get_topic_sources(topic)` - Get sources for a topic
|
||||
- `add_topic_source(topic, subnet)` - Add source to topic
|
||||
- `remove_topic_source(topic, subnet)` - Remove source from topic
|
||||
- `get_topic_forward_socket(topic)` - Get forward socket for topic
|
||||
- `set_topic_forward_socket(topic, path)` - Set forward socket for topic
|
||||
- `remove_topic_forward_socket(topic)` - Remove forward socket for topic
|
||||
|
||||
## Data Types
|
||||
|
||||
### Info
|
||||
```v
|
||||
struct Info {
|
||||
node_subnet string // The subnet owned by the node
|
||||
node_pubkey string // The public key of the node (hex encoded)
|
||||
}
|
||||
```
|
||||
|
||||
### PeerStats
|
||||
```v
|
||||
struct PeerStats {
|
||||
endpoint Endpoint // Peer endpoint
|
||||
peer_type string // How we know about this peer
|
||||
connection_state string // Current state of connection
|
||||
tx_bytes i64 // Bytes transmitted to this peer
|
||||
rx_bytes i64 // Bytes received from this peer
|
||||
}
|
||||
```
|
||||
|
||||
### InboundMessage
|
||||
```v
|
||||
struct InboundMessage {
|
||||
id string // Message ID (hex encoded)
|
||||
src_ip string // Sender overlay IP address
|
||||
src_pk string // Sender public key (hex encoded)
|
||||
dst_ip string // Receiver overlay IP address
|
||||
dst_pk string // Receiver public key (hex encoded)
|
||||
topic string // Optional message topic (base64 encoded)
|
||||
payload string // Message payload (base64 encoded)
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See `examples/clients/mycelium_rpc.vsh` for a comprehensive example that demonstrates:
|
||||
|
||||
- Node information retrieval
|
||||
- Peer listing and management
|
||||
- Routing information
|
||||
- Topic management
|
||||
- Message operations
|
||||
- Error handling
|
||||
|
||||
## Requirements
|
||||
|
||||
- V language compiler
|
||||
- Mycelium node running with API enabled
|
||||
- Network connectivity to the Mycelium node
|
||||
|
||||
## Running the Example
|
||||
|
||||
```bash
|
||||
# Make sure Mycelium is installed
|
||||
v run examples/clients/mycelium_rpc.vsh
|
||||
```
|
||||
|
||||
The example will:
|
||||
1. Install Mycelium if needed
|
||||
2. Start a Mycelium node with API enabled
|
||||
3. Demonstrate various RPC operations
|
||||
4. Clean up resources on exit
|
||||
|
||||
## Error Handling
|
||||
|
||||
All methods return Result types and should be handled appropriately:
|
||||
|
||||
```v
|
||||
info := client.get_info() or {
|
||||
println('Error getting node info: ${err}')
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The client uses HTTP transport to communicate with the Mycelium node
|
||||
- All JSON field names are properly mapped using V's `@[json: 'field_name']` attributes
|
||||
- The client is thread-safe and can be used concurrently
|
||||
- Message operations require multiple connected Mycelium nodes to be meaningful
|
||||
|
||||
## License
|
||||
|
||||
This client follows the same license as the HeroLib project.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ module openai
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
|
||||
__global (
|
||||
openai_global map[string]&OpenAI
|
||||
|
||||
152
lib/clients/zinit/README.md
Normal file
152
lib/clients/zinit/README.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Zinit OpenRPC Client
|
||||
|
||||
This is a V language client for the Zinit service manager, implementing the OpenRPC specification.
|
||||
|
||||
## Overview
|
||||
|
||||
Zinit is a service manager that allows you to manage and monitor services on your system. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface.
|
||||
|
||||
## Features
|
||||
|
||||
- Complete implementation of all methods in the Zinit OpenRPC specification
|
||||
- Type-safe API with proper error handling
|
||||
- Comprehensive documentation
|
||||
- Helper functions for common operations
|
||||
- Example code for all operations
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```v
|
||||
import freeflowuniverse.heroweb.clients.zinit
|
||||
|
||||
fn main() {
|
||||
// Create a new client with the default socket path
|
||||
mut client := zinit.new_default_client()
|
||||
|
||||
// List all services
|
||||
services := client.service_list() or {
|
||||
println('Error: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
// Print the services
|
||||
for name, state in services {
|
||||
println('${name}: ${state}')
|
||||
}
|
||||
|
||||
// Get status of a specific service
|
||||
if services.len > 0 {
|
||||
service_name := services.keys()[0]
|
||||
status := client.service_status(service_name) or {
|
||||
println('Error: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service: ${status.name}')
|
||||
println('State: ${status.state}')
|
||||
println('PID: ${status.pid}')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Creating and Managing Services
|
||||
|
||||
```v
|
||||
import freeflowuniverse.heroweb.clients.zinit
|
||||
|
||||
fn main() {
|
||||
mut client := zinit.new_default_client()
|
||||
|
||||
// Create a new service configuration
|
||||
config := zinit.ServiceConfig{
|
||||
exec: '/bin/echo "Hello, World!"'
|
||||
oneshot: true
|
||||
log: zinit.log_stdout
|
||||
env: {
|
||||
'ENV_VAR': 'value'
|
||||
}
|
||||
}
|
||||
|
||||
// Create the service
|
||||
client.service_create('hello', config) or {
|
||||
println('Error creating service: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
// Start the service
|
||||
client.service_start('hello') or {
|
||||
println('Error starting service: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
// Get the service logs
|
||||
logs := client.stream_current_logs('hello') or {
|
||||
println('Error getting logs: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
for log in logs {
|
||||
println(log)
|
||||
}
|
||||
|
||||
// Clean up
|
||||
client.service_stop('hello') or {}
|
||||
client.service_forget('hello') or {}
|
||||
client.service_delete('hello') or {}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Client Creation
|
||||
|
||||
- `new_client(socket_path string) &Client` - Create a new client with a custom socket path
|
||||
- `new_default_client() &Client` - Create a new client with the default socket path (`/tmp/zinit.sock`)
|
||||
|
||||
### Service Management
|
||||
|
||||
- `service_list() !map[string]string` - List all services and their states
|
||||
- `service_status(name string) !ServiceStatus` - Get detailed status of a service
|
||||
- `service_start(name string) !` - Start a service
|
||||
- `service_stop(name string) !` - Stop a service
|
||||
- `service_monitor(name string) !` - Start monitoring a service
|
||||
- `service_forget(name string) !` - Stop monitoring a service
|
||||
- `service_kill(name string, signal string) !` - Send a signal to a service
|
||||
- `service_create(name string, config ServiceConfig) !string` - Create a new service
|
||||
- `service_delete(name string) !string` - Delete a service
|
||||
- `service_get(name string) !ServiceConfig` - Get a service configuration
|
||||
- `service_stats(name string) !ServiceStats` - Get memory and CPU usage statistics
|
||||
|
||||
### System Operations
|
||||
|
||||
- `system_shutdown() !` - Stop all services and power off the system
|
||||
- `system_reboot() !` - Stop all services and reboot the system
|
||||
- `system_start_http_server(address string) !string` - Start an HTTP/RPC server
|
||||
- `system_stop_http_server() !` - Stop the HTTP/RPC server
|
||||
|
||||
### Logs
|
||||
|
||||
- `stream_current_logs(name ?string) ![]string` - Get current logs
|
||||
- `stream_subscribe_logs(name ?string) !string` - Subscribe to log messages
|
||||
|
||||
## Constants
|
||||
|
||||
- `default_socket_path` - Default Unix socket path (`/tmp/zinit.sock`)
|
||||
- `state_running`, `state_success`, `state_error`, etc. - Common service states
|
||||
- `target_up`, `target_down` - Common service targets
|
||||
- `log_null`, `log_ring`, `log_stdout` - Common log types
|
||||
- `signal_term`, `signal_kill`, etc. - Common signals
|
||||
|
||||
## Helper Functions
|
||||
|
||||
- `new_service_config(exec string) ServiceConfig` - Create a basic service configuration
|
||||
- `new_oneshot_service_config(exec string) ServiceConfig` - Create a oneshot service configuration
|
||||
- `is_service_not_found_error(err IError) bool` - Check if an error is a "service not found" error
|
||||
- `format_memory_usage(bytes i64) string` - Format memory usage in human-readable format
|
||||
- `format_cpu_usage(cpu_percent f64) string` - Format CPU usage
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
18
lib/clients/zinit/error.v
Normal file
18
lib/clients/zinit/error.v
Normal file
@@ -0,0 +1,18 @@
|
||||
module zinit
|
||||
|
||||
// Request Types for Zinit API
|
||||
//
|
||||
// This file contains all the request types used by the Zinit API.
|
||||
|
||||
// ZinitError represents an error returned by the zinit API
|
||||
pub struct ZinitError {
|
||||
pub mut:
|
||||
code int // Error code
|
||||
message string // Error message
|
||||
data string // Additional error data
|
||||
}
|
||||
|
||||
// Error implements the error interface for ZinitError
|
||||
pub fn (e ZinitError) msg() string {
|
||||
return 'Zinit Error ${e.code}: ${e.message} - ${e.data}'
|
||||
}
|
||||
23
lib/clients/zinit/factory.v
Normal file
23
lib/clients/zinit/factory.v
Normal file
@@ -0,0 +1,23 @@
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// Client is an OpenRPC client for Zinit
|
||||
pub struct Client {
|
||||
mut:
|
||||
rpc_client &jsonrpc.Client
|
||||
}
|
||||
|
||||
|
||||
@[params]
|
||||
pub struct ClientParams {
|
||||
path string = '/tmp/zinit.sock' // Path to the Zinit RPC socket
|
||||
}
|
||||
// new_client creates a new Zinit RPC client with a custom socket path
|
||||
pub fn new_client(args_ ClientParams) &Client {
|
||||
mut args:=args_
|
||||
mut cl := jsonrpc.new_unix_socket_client(args.path)
|
||||
return &Client{
|
||||
rpc_client: cl
|
||||
}
|
||||
}
|
||||
87
lib/clients/zinit/model.v
Normal file
87
lib/clients/zinit/model.v
Normal file
@@ -0,0 +1,87 @@
|
||||
module zinit
|
||||
|
||||
|
||||
// ServiceCreateResponse represents the response from service_create
|
||||
pub struct ServiceCreateResponse {
|
||||
pub mut:
|
||||
path string // Path to the created service file
|
||||
}
|
||||
|
||||
// ServiceDeleteResponse represents the response from service_delete
|
||||
pub struct ServiceDeleteResponse {
|
||||
pub mut:
|
||||
result string // Result of the delete operation
|
||||
}
|
||||
|
||||
// SystemStartHttpServerResponse represents the response from system_start_http_server
|
||||
pub struct SystemStartHttpServerResponse {
|
||||
pub mut:
|
||||
result string // Result of starting the HTTP server
|
||||
}
|
||||
|
||||
// StreamCurrentLogsResponse represents the response from stream_currentLogs
|
||||
pub struct StreamCurrentLogsResponse {
|
||||
pub mut:
|
||||
logs []string // Log entries
|
||||
}
|
||||
|
||||
// StreamSubscribeLogsResponse represents the response from stream_subscribeLogs
|
||||
pub struct StreamSubscribeLogsResponse {
|
||||
pub mut:
|
||||
subscription_id string // ID of the log subscription
|
||||
}
|
||||
|
||||
|
||||
// Module version information
|
||||
pub const (
|
||||
version = '1.0.0'
|
||||
author = 'Hero Code'
|
||||
license = 'MIT'
|
||||
)
|
||||
|
||||
// Default socket path for zinit
|
||||
pub const default_socket_path = '/tmp/zinit.sock'
|
||||
|
||||
// Common service states
|
||||
pub const (
|
||||
state_running = 'Running'
|
||||
state_success = 'Success'
|
||||
state_error = 'Error'
|
||||
state_stopped = 'Stopped'
|
||||
state_failed = 'Failed'
|
||||
)
|
||||
|
||||
// Common service targets
|
||||
pub const (
|
||||
target_up = 'Up'
|
||||
target_down = 'Down'
|
||||
)
|
||||
|
||||
// Common log types
|
||||
pub const (
|
||||
log_null = 'null'
|
||||
log_ring = 'ring'
|
||||
log_stdout = 'stdout'
|
||||
)
|
||||
|
||||
// Common signals
|
||||
pub const (
|
||||
signal_term = 'SIGTERM'
|
||||
signal_kill = 'SIGKILL'
|
||||
signal_hup = 'SIGHUP'
|
||||
signal_usr1 = 'SIGUSR1'
|
||||
signal_usr2 = 'SIGUSR2'
|
||||
)
|
||||
|
||||
// JSON-RPC error codes as defined in the OpenRPC specification
|
||||
pub const (
|
||||
error_service_not_found = -32000
|
||||
error_service_already_monitored = -32001
|
||||
error_service_is_up = -32002
|
||||
error_service_is_down = -32003
|
||||
error_invalid_signal = -32004
|
||||
error_config_error = -32005
|
||||
error_shutting_down = -32006
|
||||
error_service_already_exists = -32007
|
||||
error_service_file_error = -32008
|
||||
)
|
||||
873
lib/clients/zinit/openrpc.json
Normal file
873
lib/clients/zinit/openrpc.json
Normal file
@@ -0,0 +1,873 @@
|
||||
{
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Zinit JSON-RPC API",
|
||||
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
|
||||
"license": {
|
||||
"name": "MIT"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"name": "Unix Socket",
|
||||
"url": "unix:///tmp/zinit.sock"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "rpc.discover",
|
||||
"description": "Returns the OpenRPC specification for the API",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "OpenRPCSpec",
|
||||
"description": "The OpenRPC specification",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get API specification",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "OpenRPCSpecResult",
|
||||
"value": {
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Zinit JSON-RPC API"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_list",
|
||||
"description": "Lists all services managed by Zinit",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceList",
|
||||
"description": "A map of service names to their current states",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "Service state (Running, Success, Error, etc.)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "List all services",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceListResult",
|
||||
"value": {
|
||||
"service1": "Running",
|
||||
"service2": "Success",
|
||||
"service3": "Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_status",
|
||||
"description": "Shows detailed status information for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatus",
|
||||
"description": "Detailed status information for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the running service (if running)"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Current state of the service (Running, Success, Error, etc.)"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target state of the service (Up, Down)"
|
||||
},
|
||||
"after": {
|
||||
"type": "object",
|
||||
"description": "Dependencies of the service and their states",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "State of the dependency"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get status of redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatusResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"state": "Running",
|
||||
"target": "Up",
|
||||
"after": {
|
||||
"dependency1": "Success",
|
||||
"dependency2": "Running"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_start",
|
||||
"description": "Starts a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to start",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"description": "Result of the start operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stop",
|
||||
"description": "Stops a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to stop",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"description": "Result of the stop operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_monitor",
|
||||
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to monitor",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"description": "Result of the monitor operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Monitor redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32001,
|
||||
"message": "Service already monitored",
|
||||
"data": "service \"redis\" already monitored"
|
||||
},
|
||||
{
|
||||
"code": -32005,
|
||||
"message": "Config error",
|
||||
"data": "failed to load service configuration"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_forget",
|
||||
"description": "Stops monitoring a service. You can only forget a stopped service.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to forget",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"description": "Result of the forget operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Forget redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32002,
|
||||
"message": "Service is up",
|
||||
"data": "service \"redis\" is up"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_kill",
|
||||
"description": "Sends a signal to a running service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to send the signal to",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"description": "Result of the kill operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Send SIGTERM to redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"value": "SIGTERM"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
},
|
||||
{
|
||||
"code": -32004,
|
||||
"message": "Invalid signal",
|
||||
"data": "invalid signal: INVALID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_shutdown",
|
||||
"description": "Stops all services and powers off the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"description": "Result of the shutdown operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Shutdown the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_reboot",
|
||||
"description": "Stops all services and reboots the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"description": "Result of the reboot operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Reboot the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_create",
|
||||
"description": "Creates a new service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to create",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"description": "The service configuration content",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exec": {
|
||||
"type": "string",
|
||||
"description": "Command to run"
|
||||
},
|
||||
"oneshot": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the service should be restarted"
|
||||
},
|
||||
"after": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Services that must be running before this one starts"
|
||||
},
|
||||
"log": {
|
||||
"type": "string",
|
||||
"enum": ["null", "ring", "stdout"],
|
||||
"description": "How to handle service output"
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Environment variables for the service"
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"type": "integer",
|
||||
"description": "Maximum time to wait for service to stop during shutdown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "CreateServiceResult",
|
||||
"description": "Result of the create operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32007,
|
||||
"message": "Service already exists",
|
||||
"data": "Service 'name' already exists"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to create service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_delete",
|
||||
"description": "Deletes a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "DeleteServiceResult",
|
||||
"description": "Result of the delete operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to delete service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_get",
|
||||
"description": "Gets a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "GetServiceResult",
|
||||
"description": "The service configuration",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to read service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stats",
|
||||
"description": "Get memory and CPU usage statistics for a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get stats for",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStats",
|
||||
"description": "Memory and CPU usage statistics for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the service"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"description": "Stats for child processes",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the child process"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get stats for redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatsResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"memory_usage": 10485760,
|
||||
"cpu_usage": 2.5,
|
||||
"children": [
|
||||
{
|
||||
"pid": 1235,
|
||||
"memory_usage": 5242880,
|
||||
"cpu_usage": 1.2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_start_http_server",
|
||||
"description": "Start an HTTP/RPC server at the specified address",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"description": "Result of the start HTTP server operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start HTTP server on localhost:8080",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"value": "127.0.0.1:8080"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"value": "HTTP server started at 127.0.0.1:8080"
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Invalid address",
|
||||
"data": "Invalid network address format"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_stop_http_server",
|
||||
"description": "Stop the HTTP/RPC server if running",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"description": "Result of the stop HTTP server operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop the HTTP server",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Server not running",
|
||||
"data": "No HTTP server is currently running"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_currentLogs",
|
||||
"description": "Get current logs from zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"description": "Array of log strings",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:01 nginx: Starting service"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get logs for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:02 redis: Service started"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_subscribeLogs",
|
||||
"description": "Subscribe to log messages generated by zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"description": "A subscription to log messages",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Subscribe to all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Subscribe to filtered logs",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
181
lib/clients/zinit/service.v
Normal file
181
lib/clients/zinit/service.v
Normal file
@@ -0,0 +1,181 @@
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// ServiceConfig represents the configuration for a zinit service
|
||||
pub struct ServiceConfig {
|
||||
pub mut:
|
||||
exec string // Command to run
|
||||
oneshot bool // Whether the service should be restarted
|
||||
after []string // Services that must be running before this one starts
|
||||
log string // How to handle service output (null, ring, stdout)
|
||||
env map[string]string // Environment variables for the service
|
||||
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
|
||||
}
|
||||
|
||||
// KillParams represents the parameters for the service_kill method
|
||||
pub struct KillParams {
|
||||
pub:
|
||||
name string // Name of the service to kill
|
||||
signal string // Signal to send (e.g., SIGTERM, SIGKILL)
|
||||
}
|
||||
|
||||
|
||||
// RpcDiscoverResponse represents the response from rpc.discover
|
||||
pub struct RpcDiscoverResponse {
|
||||
pub mut:
|
||||
spec map[string]string // OpenRPC specification
|
||||
}
|
||||
|
||||
|
||||
// rpc_discover returns the OpenRPC specification for the API
|
||||
pub fn (mut c Client) rpc_discover() !RpcDiscoverResponse {
|
||||
request := jsonrpc.new_request_generic('rpc.discover', []string{})
|
||||
response := c.rpc_client.send[[]string, map[string]string](request)!
|
||||
return RpcDiscoverResponse{
|
||||
spec: response
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// // Response Models for Zinit API
|
||||
// //
|
||||
// // This file contains all the response models used by the Zinit API.
|
||||
// // These models are used as type parameters in the response generics.
|
||||
|
||||
// // ServiceListResponse represents the response from service_list
|
||||
// pub struct ServiceListResponse {
|
||||
// pub mut:
|
||||
// // Map of service names to their current states
|
||||
// services map[string]string
|
||||
// }
|
||||
|
||||
// service_list lists all services managed by Zinit
|
||||
// Returns a map of service names to their current states
|
||||
pub fn (mut c Client) service_list() !map[string]string {
|
||||
request := jsonrpc.new_request_generic('service_list',map[string]string )
|
||||
services := c.rpc_client.send[map[string]string, map[string]string](request)!
|
||||
// return ServiceListResponse{
|
||||
// services: services
|
||||
// }
|
||||
return services
|
||||
}
|
||||
|
||||
// ServiceStatusResponse represents the response from service_status
|
||||
pub struct ServiceStatusResponse {
|
||||
pub mut:
|
||||
name string // Service name
|
||||
pid int // Process ID of the running service (if running)
|
||||
state string // Current state of the service (Running, Success, Error, etc.)
|
||||
target string // Target state of the service (Up, Down)
|
||||
after map[string]string // Dependencies of the service and their states
|
||||
}
|
||||
|
||||
// service_status shows detailed status information for a specific service
|
||||
// name: the name of the service
|
||||
pub fn (mut c Client) service_status(name string) !ServiceStatusResponse {
|
||||
request := jsonrpc.new_request_generic('service_status', name)
|
||||
|
||||
// Use a direct struct mapping instead of manual conversion
|
||||
return c.rpc_client.send[string, ServiceStatusResponse](request)!
|
||||
}
|
||||
|
||||
// service_start starts a service
|
||||
// name: the name of the service to start
|
||||
pub fn (mut c Client) service_start(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_start', name)
|
||||
c.rpc_client.send[string, string](request)!
|
||||
}
|
||||
|
||||
// service_stop stops a service
|
||||
// name: the name of the service to stop
|
||||
pub fn (mut c Client) service_stop(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_stop', name)
|
||||
c.rpc_client.send[string, string](request)!
|
||||
}
|
||||
|
||||
// service_monitor starts monitoring a service
|
||||
// The service configuration is loaded from the config directory
|
||||
// name: the name of the service to monitor
|
||||
pub fn (mut c Client) service_monitor(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_monitor', name)
|
||||
c.rpc_client.send[string, string](request)!
|
||||
}
|
||||
|
||||
// service_delete deletes a service configuration file
|
||||
// name: the name of the service to delete
|
||||
pub fn (mut c Client) service_delete(name string) !ServiceDeleteResponse {
|
||||
request := jsonrpc.new_request_generic('service_delete', name)
|
||||
result := c.rpc_client.send[string, string](request)!
|
||||
return ServiceDeleteResponse{
|
||||
result: result
|
||||
}
|
||||
}
|
||||
|
||||
// service_forget stops monitoring a service
|
||||
// You can only forget a stopped service
|
||||
// name: the name of the service to forget
|
||||
pub fn (mut c Client) service_forget(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_forget', name)
|
||||
c.rpc_client.send[string, string](request)!
|
||||
}
|
||||
|
||||
//TODO: make sure the signal is a valid signal and enumerator do as @[params] so its optional
|
||||
|
||||
|
||||
// service_kill sends a signal to a running service
|
||||
// name: the name of the service to send the signal to
|
||||
// signal: the signal to send (e.g., SIGTERM, SIGKILL)
|
||||
pub fn (mut c Client) service_kill(name string, signal string) ! {
|
||||
params := KillParams{
|
||||
name: name
|
||||
signal: signal
|
||||
}
|
||||
|
||||
request := jsonrpc.new_request_generic('service_kill', params)
|
||||
c.rpc_client.send[KillParams, string](request)!
|
||||
}
|
||||
|
||||
|
||||
// CreateServiceParams represents the parameters for the service_create method
|
||||
struct CreateServiceParams {
|
||||
name string // Name of the service to create
|
||||
content ServiceConfig // Configuration for the service
|
||||
}
|
||||
|
||||
|
||||
// service_create creates a new service configuration file
|
||||
// name: the name of the service to create
|
||||
// config: the service configuration
|
||||
pub fn (mut c Client) service_create(name string, config ServiceConfig) !ServiceCreateResponse {
|
||||
params := CreateServiceParams{
|
||||
name: name
|
||||
content: config
|
||||
}
|
||||
|
||||
request := jsonrpc.new_request_generic('service_create', params)
|
||||
path := c.rpc_client.send[CreateServiceParams, string](request)!
|
||||
return ServiceCreateResponse{
|
||||
path: path
|
||||
}
|
||||
}
|
||||
|
||||
// service_get gets a service configuration file
|
||||
// name: the name of the service to get
|
||||
pub fn (mut c Client) service_get(name string) !ServiceConfigResponse {
|
||||
|
||||
request := jsonrpc.new_request_generic('service_get', {"name":name})
|
||||
|
||||
// We need to handle the conversion from ServiceConfig to ServiceConfigResponse
|
||||
config := c.rpc_client.send[map[string]string, ServiceConfig](request)!
|
||||
|
||||
return ServiceConfigResponse{
|
||||
exec: config.exec
|
||||
oneshot: config.oneshot
|
||||
after: config.after
|
||||
log: config.log
|
||||
env: config.env
|
||||
shutdown_timeout: config.shutdown_timeout
|
||||
}
|
||||
}
|
||||
35
lib/clients/zinit/service_config.v
Normal file
35
lib/clients/zinit/service_config.v
Normal file
@@ -0,0 +1,35 @@
|
||||
module zinit
|
||||
|
||||
|
||||
pub struct ServiceConfigResponse {
|
||||
pub mut:
|
||||
exec string // Command to run
|
||||
oneshot bool // Whether the service should be restarted
|
||||
after []string // Services that must be running before this one starts
|
||||
log string // How to handle service output (null, ring, stdout)
|
||||
env map[string]string // Environment variables for the service
|
||||
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
|
||||
}
|
||||
|
||||
|
||||
// Helper function to create a basic service configuration
|
||||
pub fn new_service_config(exec string) ServiceConfig {
|
||||
return ServiceConfig{
|
||||
exec: exec
|
||||
oneshot: false
|
||||
log: log_stdout
|
||||
env: map[string]string{}
|
||||
shutdown_timeout: 30
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a oneshot service configuration
|
||||
pub fn new_oneshot_service_config(exec string) ServiceConfig {
|
||||
return ServiceConfig{
|
||||
exec: exec
|
||||
oneshot: true
|
||||
log: log_stdout
|
||||
env: map[string]string{}
|
||||
shutdown_timeout: 30
|
||||
}
|
||||
}
|
||||
44
lib/clients/zinit/service_stats.v
Normal file
44
lib/clients/zinit/service_stats.v
Normal file
@@ -0,0 +1,44 @@
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// ServiceStatsResponse represents the response from service_stats
|
||||
pub struct ServiceStatsResponse {
|
||||
pub mut:
|
||||
name string // Service name
|
||||
pid int // Process ID of the service
|
||||
memory_usage i64 // Memory usage in bytes
|
||||
cpu_usage f64 // CPU usage as a percentage (0-100)
|
||||
children []ChildStatsResponse // Stats for child processes
|
||||
}
|
||||
|
||||
// ChildStatsResponse represents statistics for a child process
|
||||
pub struct ChildStatsResponse {
|
||||
pub mut:
|
||||
pid int // Process ID of the child process
|
||||
memory_usage i64 // Memory usage in bytes
|
||||
cpu_usage f64 // CPU usage as a percentage (0-100)
|
||||
}
|
||||
|
||||
// Serv
|
||||
|
||||
// service_stats gets memory and CPU usage statistics for a service
|
||||
// name: the name of the service to get stats for
|
||||
pub fn (mut c Client) service_stats(name string) !ServiceStatsResponse {
|
||||
request := jsonrpc.new_request_generic('service_stats', name)
|
||||
|
||||
// We need to handle the conversion from the raw response to our model
|
||||
raw_stats := c.rpc_client.send[string, map[string]string](request)!
|
||||
|
||||
// Parse the raw stats into our response model
|
||||
mut children := []ChildStatsResponse{}
|
||||
// In a real implementation, we would parse the children from the raw response
|
||||
|
||||
return ServiceStatsResponse{
|
||||
name: raw_stats['name'] or { '' }
|
||||
pid: raw_stats['pid'].int()
|
||||
memory_usage: raw_stats['memory_usage'].i64()
|
||||
cpu_usage: raw_stats['cpu_usage'].f64()
|
||||
children: children
|
||||
}
|
||||
}
|
||||
71
lib/clients/zinit/system.v
Normal file
71
lib/clients/zinit/system.v
Normal file
@@ -0,0 +1,71 @@
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
|
||||
// system_shutdown stops all services and powers off the system
|
||||
pub fn (mut c Client) system_shutdown() ! {
|
||||
request := jsonrpc.new_request_generic('system_shutdown', []string{})
|
||||
c.rpc_client.send[[]string, string](request)!
|
||||
}
|
||||
|
||||
// system_reboot stops all services and reboots the system
|
||||
pub fn (mut c Client) system_reboot() ! {
|
||||
request := jsonrpc.new_request_generic('system_reboot', []string{})
|
||||
c.rpc_client.send[[]string, string](request)!
|
||||
}
|
||||
|
||||
|
||||
// system_start_http_server starts an HTTP/RPC server at the specified address
|
||||
// address: the network address to bind the server to (e.g., '127.0.0.1:8080')
|
||||
pub fn (mut c Client) system_start_http_server(address string) !SystemStartHttpServerResponse {
|
||||
request := jsonrpc.new_request_generic('system_start_http_server', address)
|
||||
result := c.rpc_client.send[string, string](request)!
|
||||
return SystemStartHttpServerResponse{
|
||||
result: result
|
||||
}
|
||||
}
|
||||
|
||||
// system_stop_http_server stops the HTTP/RPC server if running
|
||||
pub fn (mut c Client) system_stop_http_server() ! {
|
||||
request := jsonrpc.new_request_generic('system_stop_http_server', []string{})
|
||||
c.rpc_client.send[[]string, string](request)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct LogParams {
|
||||
name string
|
||||
}
|
||||
|
||||
// stream_current_logs gets current logs from zinit and monitored services
|
||||
// name: optional service name filter. If provided, only logs from this service will be returned
|
||||
pub fn (mut c Client) stream_current_logs(args LogParams) ![]string {
|
||||
mut logs := []string{}
|
||||
|
||||
if args.name != '' {
|
||||
request := jsonrpc.new_request_generic('stream_currentLogs', {"name":args.name})
|
||||
logs = c.rpc_client.send[map[string]string, map[string]string](request)!
|
||||
} else {
|
||||
request := jsonrpc.new_request_generic('stream_currentLogs', map[string]string{})
|
||||
logs = c.rpc_client.send[[]map[string]string, map[string]string](request)!
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
// stream_subscribe_logs subscribes to log messages generated by zinit and monitored services
|
||||
// name: optional service name filter. If provided, only logs from this service will be returned
|
||||
pub fn (mut c Client) stream_subscribe_logs(name ?string) !StreamSubscribeLogsResponse {
|
||||
mut subscription_id := ''
|
||||
|
||||
if service_name := name {
|
||||
request := jsonrpc.new_request_generic('stream_subscribeLogs', service_name)
|
||||
subscription_id = c.rpc_client.send[string, string](request)!
|
||||
} else {
|
||||
request := jsonrpc.new_request_generic('stream_subscribeLogs', []string{})
|
||||
subscription_id = c.rpc_client.send[[]string, string](request)!
|
||||
}
|
||||
|
||||
return StreamSubscribeLogsResponse{
|
||||
subscription_id: subscription_id
|
||||
}
|
||||
}
|
||||
20
lib/clients/zinit/tools.v
Normal file
20
lib/clients/zinit/tools.v
Normal file
@@ -0,0 +1,20 @@
|
||||
module zinit
|
||||
|
||||
|
||||
// Helper function to format memory usage in human-readable format
|
||||
pub fn format_memory_usage(bytes i64) string {
|
||||
if bytes < 1024 {
|
||||
return '${bytes} B'
|
||||
} else if bytes < 1024 * 1024 {
|
||||
return '${bytes / 1024} KB'
|
||||
} else if bytes < 1024 * 1024 * 1024 {
|
||||
return '${bytes / 1024 / 1024} MB'
|
||||
} else {
|
||||
return '${bytes / 1024 / 1024 / 1024} GB'
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to format CPU usage
|
||||
pub fn format_cpu_usage(cpu_percent f64) string {
|
||||
return '${cpu_percent:.1f}%'
|
||||
}
|
||||
8
lib/clients/zinit_rpc/.heroscript
Normal file
8
lib/clients/zinit_rpc/.heroscript
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
!!hero_code.generate_client
|
||||
name:'zinit_rpc'
|
||||
classname:'ZinitRPC'
|
||||
singleton:1
|
||||
default:0
|
||||
hasconfig:1
|
||||
reset:0
|
||||
873
lib/clients/zinit_rpc/openrpc.json
Normal file
873
lib/clients/zinit_rpc/openrpc.json
Normal file
@@ -0,0 +1,873 @@
|
||||
{
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Zinit JSON-RPC API",
|
||||
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
|
||||
"license": {
|
||||
"name": "MIT"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"name": "Unix Socket",
|
||||
"url": "unix:///tmp/zinit.sock"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "rpc.discover",
|
||||
"description": "Returns the OpenRPC specification for the API",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "OpenRPCSpec",
|
||||
"description": "The OpenRPC specification",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get API specification",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "OpenRPCSpecResult",
|
||||
"value": {
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Zinit JSON-RPC API"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_list",
|
||||
"description": "Lists all services managed by Zinit",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceList",
|
||||
"description": "A map of service names to their current states",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "Service state (Running, Success, Error, etc.)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "List all services",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceListResult",
|
||||
"value": {
|
||||
"service1": "Running",
|
||||
"service2": "Success",
|
||||
"service3": "Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_status",
|
||||
"description": "Shows detailed status information for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatus",
|
||||
"description": "Detailed status information for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the running service (if running)"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Current state of the service (Running, Success, Error, etc.)"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target state of the service (Up, Down)"
|
||||
},
|
||||
"after": {
|
||||
"type": "object",
|
||||
"description": "Dependencies of the service and their states",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "State of the dependency"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get status of redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatusResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"state": "Running",
|
||||
"target": "Up",
|
||||
"after": {
|
||||
"dependency1": "Success",
|
||||
"dependency2": "Running"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_start",
|
||||
"description": "Starts a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to start",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"description": "Result of the start operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stop",
|
||||
"description": "Stops a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to stop",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"description": "Result of the stop operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_monitor",
|
||||
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to monitor",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"description": "Result of the monitor operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Monitor redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32001,
|
||||
"message": "Service already monitored",
|
||||
"data": "service \"redis\" already monitored"
|
||||
},
|
||||
{
|
||||
"code": -32005,
|
||||
"message": "Config error",
|
||||
"data": "failed to load service configuration"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_forget",
|
||||
"description": "Stops monitoring a service. You can only forget a stopped service.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to forget",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"description": "Result of the forget operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Forget redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32002,
|
||||
"message": "Service is up",
|
||||
"data": "service \"redis\" is up"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_kill",
|
||||
"description": "Sends a signal to a running service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to send the signal to",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"description": "Result of the kill operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Send SIGTERM to redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"value": "SIGTERM"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
},
|
||||
{
|
||||
"code": -32004,
|
||||
"message": "Invalid signal",
|
||||
"data": "invalid signal: INVALID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_shutdown",
|
||||
"description": "Stops all services and powers off the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"description": "Result of the shutdown operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Shutdown the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_reboot",
|
||||
"description": "Stops all services and reboots the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"description": "Result of the reboot operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Reboot the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_create",
|
||||
"description": "Creates a new service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to create",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"description": "The service configuration content",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exec": {
|
||||
"type": "string",
|
||||
"description": "Command to run"
|
||||
},
|
||||
"oneshot": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the service should be restarted"
|
||||
},
|
||||
"after": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Services that must be running before this one starts"
|
||||
},
|
||||
"log": {
|
||||
"type": "string",
|
||||
"enum": ["null", "ring", "stdout"],
|
||||
"description": "How to handle service output"
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Environment variables for the service"
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"type": "integer",
|
||||
"description": "Maximum time to wait for service to stop during shutdown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "CreateServiceResult",
|
||||
"description": "Result of the create operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32007,
|
||||
"message": "Service already exists",
|
||||
"data": "Service 'name' already exists"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to create service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_delete",
|
||||
"description": "Deletes a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "DeleteServiceResult",
|
||||
"description": "Result of the delete operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to delete service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_get",
|
||||
"description": "Gets a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "GetServiceResult",
|
||||
"description": "The service configuration",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to read service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stats",
|
||||
"description": "Get memory and CPU usage statistics for a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get stats for",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStats",
|
||||
"description": "Memory and CPU usage statistics for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the service"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"description": "Stats for child processes",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the child process"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get stats for redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatsResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"memory_usage": 10485760,
|
||||
"cpu_usage": 2.5,
|
||||
"children": [
|
||||
{
|
||||
"pid": 1235,
|
||||
"memory_usage": 5242880,
|
||||
"cpu_usage": 1.2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_start_http_server",
|
||||
"description": "Start an HTTP/RPC server at the specified address",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"description": "Result of the start HTTP server operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start HTTP server on localhost:8080",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"value": "127.0.0.1:8080"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"value": "HTTP server started at 127.0.0.1:8080"
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Invalid address",
|
||||
"data": "Invalid network address format"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_stop_http_server",
|
||||
"description": "Stop the HTTP/RPC server if running",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"description": "Result of the stop HTTP server operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop the HTTP server",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Server not running",
|
||||
"data": "No HTTP server is currently running"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_currentLogs",
|
||||
"description": "Get current logs from zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"description": "Array of log strings",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:01 nginx: Starting service"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get logs for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:02 redis: Service started"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_subscribeLogs",
|
||||
"description": "Subscribe to log messages generated by zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"description": "A subscription to log messages",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Subscribe to all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Subscribe to filtered logs",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
222
lib/clients/zinit_rpc/readme.md
Normal file
222
lib/clients/zinit_rpc/readme.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Zinit RPC Client
|
||||
|
||||
This is a V language client for the Zinit process manager, implementing the JSON-RPC API specification for service management operations.
|
||||
|
||||
## Overview
|
||||
|
||||
Zinit is a process manager that provides service monitoring, dependency management, and system control capabilities. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface for administrative tasks such as:
|
||||
|
||||
- Service lifecycle management (start, stop, monitor, forget)
|
||||
- Service configuration management (create, delete, get)
|
||||
- Service status and statistics monitoring
|
||||
- System operations (shutdown, reboot, HTTP server control)
|
||||
- Log streaming and monitoring
|
||||
|
||||
## Features
|
||||
|
||||
- **✅ 100% API Coverage**: Complete implementation of all 18 methods in the Zinit JSON-RPC specification
|
||||
- **✅ Production Tested**: All methods tested and working against real Zinit instances
|
||||
- **✅ Type-safe API**: Proper V struct definitions with comprehensive error handling
|
||||
- **✅ Subscription Support**: Proper handling of streaming/subscription methods
|
||||
- **✅ Unix Socket Transport**: Reliable communication via Unix domain sockets
|
||||
- **✅ Comprehensive Documentation**: Extensive documentation with working examples
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.zinit_rpc
|
||||
|
||||
// Create a new client
|
||||
mut client := zinit_rpc.new_client(
|
||||
name: 'my_client'
|
||||
socket_path: '/tmp/zinit.sock'
|
||||
)!
|
||||
|
||||
// List all services
|
||||
services := client.service_list()!
|
||||
for service_name, state in services {
|
||||
println('Service: ${service_name}, State: ${state}')
|
||||
}
|
||||
|
||||
// Get detailed status of a specific service
|
||||
status := client.service_status('redis')!
|
||||
println('Service: ${status.name}')
|
||||
println('PID: ${status.pid}')
|
||||
println('State: ${status.state}')
|
||||
println('Target: ${status.target}')
|
||||
|
||||
// Start a service
|
||||
client.service_start('redis')!
|
||||
|
||||
// Stop a service
|
||||
client.service_stop('redis')!
|
||||
```
|
||||
|
||||
### Service Configuration Management
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.zinit_rpc
|
||||
|
||||
mut client := zinit_rpc.new_client()!
|
||||
|
||||
// Create a new service configuration
|
||||
config := zinit_rpc.ServiceConfig{
|
||||
exec: '/usr/bin/redis-server'
|
||||
oneshot: false
|
||||
log: 'stdout'
|
||||
env: {
|
||||
'REDIS_PORT': '6379'
|
||||
'REDIS_HOST': '0.0.0.0'
|
||||
}
|
||||
shutdown_timeout: 30
|
||||
}
|
||||
|
||||
// Create the service
|
||||
path := client.service_create('redis', config)!
|
||||
println('Service created at: ${path}')
|
||||
|
||||
// Get service configuration
|
||||
retrieved_config := client.service_get('redis')!
|
||||
println('Service exec: ${retrieved_config.exec}')
|
||||
|
||||
// Delete service configuration
|
||||
result := client.service_delete('redis')!
|
||||
println('Delete result: ${result}')
|
||||
```
|
||||
|
||||
### Service Statistics
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.zinit_rpc
|
||||
|
||||
mut client := zinit_rpc.new_client()!
|
||||
|
||||
// Get service statistics
|
||||
stats := client.service_stats('redis')!
|
||||
println('Service: ${stats.name}')
|
||||
println('PID: ${stats.pid}')
|
||||
println('Memory Usage: ${stats.memory_usage} bytes')
|
||||
println('CPU Usage: ${stats.cpu_usage}%')
|
||||
|
||||
// Print child process statistics
|
||||
for child in stats.children {
|
||||
println('Child PID: ${child.pid}, Memory: ${child.memory_usage}, CPU: ${child.cpu_usage}%')
|
||||
}
|
||||
```
|
||||
|
||||
### Log Streaming
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.zinit_rpc
|
||||
|
||||
mut client := zinit_rpc.new_client()!
|
||||
|
||||
// Get current logs for all services
|
||||
logs := client.stream_current_logs(name: '')!
|
||||
for log in logs {
|
||||
println(log)
|
||||
}
|
||||
|
||||
// Get current logs for a specific service
|
||||
redis_logs := client.stream_current_logs(name: 'redis')!
|
||||
for log in redis_logs {
|
||||
println('Redis: ${log}')
|
||||
}
|
||||
|
||||
// Subscribe to log stream (returns subscription ID)
|
||||
subscription_id := client.stream_subscribe_logs(name: 'redis')!
|
||||
println('Subscribed to logs with ID: ${subscription_id}')
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Service Management Methods
|
||||
|
||||
- `service_list()` - List all services and their states
|
||||
- `service_status(name)` - Get detailed status of a service
|
||||
- `service_start(name)` - Start a service
|
||||
- `service_stop(name)` - Stop a service
|
||||
- `service_monitor(name)` - Start monitoring a service
|
||||
- `service_forget(name)` - Stop monitoring a service
|
||||
- `service_kill(name, signal)` - Send signal to a service
|
||||
|
||||
### Service Configuration Methods
|
||||
|
||||
- `service_create(name, config)` - Create service configuration
|
||||
- `service_delete(name)` - Delete service configuration
|
||||
- `service_get(name)` - Get service configuration
|
||||
|
||||
### Monitoring Methods
|
||||
|
||||
- `service_stats(name)` - Get service statistics
|
||||
|
||||
### System Methods
|
||||
|
||||
- `system_shutdown()` - Shutdown the system
|
||||
- `system_reboot()` - Reboot the system
|
||||
- `system_start_http_server(address)` - Start HTTP server
|
||||
- `system_stop_http_server()` - Stop HTTP server
|
||||
|
||||
### Streaming Methods
|
||||
|
||||
- `stream_current_logs(args)` - Get current logs (returns array of log lines)
|
||||
- `stream_subscribe_logs(args)` - Subscribe to log stream (returns subscription ID)
|
||||
|
||||
### Discovery Methods
|
||||
|
||||
- `rpc_discover()` - Get OpenRPC specification
|
||||
|
||||
## Configuration
|
||||
|
||||
### Using the Factory Pattern
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.zinit_rpc
|
||||
|
||||
// Get client using factory (recommended)
|
||||
mut client := zinit_rpc.get()!
|
||||
|
||||
// Use the client
|
||||
services := client.service_list()!
|
||||
```
|
||||
|
||||
### Example Heroscript Configuration
|
||||
|
||||
```hero
|
||||
!!zinit_rpc.configure
|
||||
name: 'production'
|
||||
socket_path: '/tmp/zinit.sock'
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The client provides comprehensive error handling for all Zinit-specific error codes:
|
||||
|
||||
- `-32000`: Service not found
|
||||
- `-32001`: Service already monitored
|
||||
- `-32002`: Service is up
|
||||
- `-32003`: Service is down
|
||||
- `-32004`: Invalid signal
|
||||
- `-32005`: Config error
|
||||
- `-32006`: Shutting down
|
||||
- `-32007`: Service already exists
|
||||
- `-32008`: Service file error
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.zinit_rpc
|
||||
|
||||
mut client := zinit_rpc.new_client()!
|
||||
|
||||
// Handle specific errors
|
||||
client.service_start('nonexistent') or {
|
||||
if err.msg().contains('Service not found') {
|
||||
println('Service does not exist')
|
||||
} else {
|
||||
println('Other error: ${err}')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
201
lib/clients/zinit_rpc/zinit_rpc.v
Normal file
201
lib/clients/zinit_rpc/zinit_rpc.v
Normal file
@@ -0,0 +1,201 @@
|
||||
module zinit_rpc
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// Helper function to get or create the RPC client
|
||||
fn (mut c ZinitRPC) get_client() !&jsonrpc.Client {
|
||||
if client := c.rpc_client {
|
||||
return client
|
||||
}
|
||||
// Create Unix socket client
|
||||
mut client := jsonrpc.new_unix_socket_client(c.socket_path)
|
||||
c.rpc_client = client
|
||||
return client
|
||||
}
|
||||
|
||||
// Admin methods
|
||||
|
||||
// rpc_discover returns the OpenRPC specification for the API
|
||||
pub fn (mut c ZinitRPC) rpc_discover() !OpenRPCSpec {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('rpc.discover', []string{})
|
||||
return client.send[[]string, OpenRPCSpec](request)!
|
||||
}
|
||||
|
||||
// service_list lists all services managed by Zinit
|
||||
// Returns a map of service names to their current states
|
||||
pub fn (mut c ZinitRPC) service_list() !map[string]string {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('service_list', []string{})
|
||||
return client.send[[]string, map[string]string](request)!
|
||||
}
|
||||
|
||||
// service_status shows detailed status information for a specific service
|
||||
pub fn (mut c ZinitRPC) service_status(name string) !ServiceStatus {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_status', params)
|
||||
return client.send[map[string]string, ServiceStatus](request)!
|
||||
}
|
||||
|
||||
// service_start starts a service
|
||||
pub fn (mut c ZinitRPC) service_start(name string) ! {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_start', params)
|
||||
client.send[map[string]string, string](request)!
|
||||
}
|
||||
|
||||
// service_stop stops a service
|
||||
pub fn (mut c ZinitRPC) service_stop(name string) ! {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_stop', params)
|
||||
client.send[map[string]string, string](request)!
|
||||
}
|
||||
|
||||
// service_monitor starts monitoring a service
|
||||
// The service configuration is loaded from the config directory
|
||||
pub fn (mut c ZinitRPC) service_monitor(name string) ! {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_monitor', params)
|
||||
client.send[map[string]string, string](request)!
|
||||
}
|
||||
|
||||
// service_forget stops monitoring a service
|
||||
// You can only forget a stopped service
|
||||
pub fn (mut c ZinitRPC) service_forget(name string) ! {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_forget', params)
|
||||
client.send[map[string]string, string](request)!
|
||||
}
|
||||
|
||||
// service_kill sends a signal to a running service
|
||||
pub fn (mut c ZinitRPC) service_kill(name string, signal string) ! {
|
||||
mut client := c.get_client()!
|
||||
params := ServiceKillParams{
|
||||
name: name
|
||||
signal: signal
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_kill', params)
|
||||
client.send[ServiceKillParams, string](request)!
|
||||
}
|
||||
|
||||
// service_create creates a new service configuration file
|
||||
pub fn (mut c ZinitRPC) service_create(name string, config ServiceConfig) !string {
|
||||
mut client := c.get_client()!
|
||||
params := ServiceCreateParams{
|
||||
name: name
|
||||
content: config
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_create', params)
|
||||
return client.send[ServiceCreateParams, string](request)!
|
||||
}
|
||||
|
||||
// service_delete deletes a service configuration file
|
||||
pub fn (mut c ZinitRPC) service_delete(name string) !string {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_delete', params)
|
||||
return client.send[map[string]string, string](request)!
|
||||
}
|
||||
|
||||
// service_get gets a service configuration file
|
||||
pub fn (mut c ZinitRPC) service_get(name string) !ServiceConfig {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_get', params)
|
||||
return client.send[map[string]string, ServiceConfig](request)!
|
||||
}
|
||||
|
||||
// service_stats gets memory and CPU usage statistics for a service
|
||||
pub fn (mut c ZinitRPC) service_stats(name string) !ServiceStats {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'name': name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('service_stats', params)
|
||||
return client.send[map[string]string, ServiceStats](request)!
|
||||
}
|
||||
|
||||
// System methods
|
||||
|
||||
// system_shutdown stops all services and powers off the system
|
||||
pub fn (mut c ZinitRPC) system_shutdown() ! {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('system_shutdown', []string{})
|
||||
client.send[[]string, string](request)!
|
||||
}
|
||||
|
||||
// system_reboot stops all services and reboots the system
|
||||
pub fn (mut c ZinitRPC) system_reboot() ! {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('system_reboot', []string{})
|
||||
client.send[[]string, string](request)!
|
||||
}
|
||||
|
||||
// system_start_http_server starts an HTTP/RPC server at the specified address
|
||||
pub fn (mut c ZinitRPC) system_start_http_server(address string) !string {
|
||||
mut client := c.get_client()!
|
||||
params := {
|
||||
'address': address
|
||||
}
|
||||
request := jsonrpc.new_request_generic('system_start_http_server', params)
|
||||
return client.send[map[string]string, string](request)!
|
||||
}
|
||||
|
||||
// system_stop_http_server stops the HTTP/RPC server if running
|
||||
pub fn (mut c ZinitRPC) system_stop_http_server() ! {
|
||||
mut client := c.get_client()!
|
||||
request := jsonrpc.new_request_generic('system_stop_http_server', []string{})
|
||||
client.send[[]string, string](request)!
|
||||
}
|
||||
|
||||
// Streaming methods
|
||||
|
||||
// stream_current_logs gets current logs from zinit and monitored services
|
||||
pub fn (mut c ZinitRPC) stream_current_logs(args LogParams) ![]string {
|
||||
mut client := c.get_client()!
|
||||
if args.name != '' {
|
||||
params := {
|
||||
'name': args.name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('stream_currentLogs', params)
|
||||
return client.send[map[string]string, []string](request)!
|
||||
} else {
|
||||
request := jsonrpc.new_request_generic('stream_currentLogs', []string{})
|
||||
return client.send[[]string, []string](request)!
|
||||
}
|
||||
}
|
||||
|
||||
// stream_subscribe_logs subscribes to log messages generated by zinit and monitored services
|
||||
// Returns a subscription ID that can be used to manage the subscription
|
||||
pub fn (mut c ZinitRPC) stream_subscribe_logs(args LogParams) !u64 {
|
||||
mut client := c.get_client()!
|
||||
if args.name != '' {
|
||||
params := {
|
||||
'name': args.name
|
||||
}
|
||||
request := jsonrpc.new_request_generic('stream_subscribeLogs', params)
|
||||
return client.send[map[string]string, u64](request)!
|
||||
} else {
|
||||
request := jsonrpc.new_request_generic('stream_subscribeLogs', []string{})
|
||||
return client.send[[]string, u64](request)!
|
||||
}
|
||||
}
|
||||
114
lib/clients/zinit_rpc/zinit_rpc_factory_.v
Normal file
114
lib/clients/zinit_rpc/zinit_rpc_factory_.v
Normal file
@@ -0,0 +1,114 @@
|
||||
module zinit_rpc
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
__global (
|
||||
zinit_rpc_global map[string]&ZinitRPC
|
||||
zinit_rpc_default string
|
||||
)
|
||||
|
||||
/////////FACTORY
|
||||
|
||||
@[params]
|
||||
pub struct ArgsGet {
|
||||
pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
fn args_get(args_ ArgsGet) ArgsGet {
|
||||
mut args := args_
|
||||
if args.name == '' {
|
||||
args.name = 'default'
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
pub fn get(args_ ArgsGet) !&ZinitRPC {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
mut obj := ZinitRPC{
|
||||
name: args.name
|
||||
}
|
||||
if args.name !in zinit_rpc_global {
|
||||
if !exists(args)! {
|
||||
set(obj)!
|
||||
} else {
|
||||
heroscript := context.hero_config_get('zinit_rpc', args.name)!
|
||||
mut obj_ := heroscript_loads(heroscript)!
|
||||
set_in_mem(obj_)!
|
||||
}
|
||||
}
|
||||
return zinit_rpc_global[args.name] or {
|
||||
println(zinit_rpc_global)
|
||||
// bug if we get here because should be in globals
|
||||
panic('could not get config for zinit_rpc with name, is bug:${args.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// register the config for the future
|
||||
pub fn set(o ZinitRPC) ! {
|
||||
set_in_mem(o)!
|
||||
mut context := base.context()!
|
||||
heroscript := heroscript_dumps(o)!
|
||||
context.hero_config_set('zinit_rpc', o.name, heroscript)!
|
||||
}
|
||||
|
||||
// does the config exists?
|
||||
pub fn exists(args_ ArgsGet) !bool {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
return context.hero_config_exists('zinit_rpc', args.name)
|
||||
}
|
||||
|
||||
pub fn delete(args_ ArgsGet) ! {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context()!
|
||||
context.hero_config_delete('zinit_rpc', args.name)!
|
||||
if args.name in zinit_rpc_global {
|
||||
// del zinit_rpc_global[args.name]
|
||||
}
|
||||
}
|
||||
|
||||
// only sets in mem, does not set as config
|
||||
fn set_in_mem(o ZinitRPC) ! {
|
||||
mut o2 := obj_init(o)!
|
||||
zinit_rpc_global[o.name] = &o2
|
||||
zinit_rpc_default = o.name
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
heroscript string // if filled in then plbook will be made out of it
|
||||
plbook ?playbook.PlayBook
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
mut args := args_
|
||||
|
||||
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
|
||||
|
||||
mut install_actions := plbook.find(filter: 'zinit_rpc.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for install_action in install_actions {
|
||||
heroscript := install_action.heroscript()
|
||||
mut obj2 := heroscript_loads(heroscript)!
|
||||
set(obj2)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch instance to be used for zinit_rpc
|
||||
pub fn switch(name string) {
|
||||
zinit_rpc_default = name
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
@[params]
|
||||
pub struct DefaultConfigArgs {
|
||||
instance string = 'default'
|
||||
}
|
||||
163
lib/clients/zinit_rpc/zinit_rpc_model.v
Normal file
163
lib/clients/zinit_rpc/zinit_rpc_model.v
Normal file
@@ -0,0 +1,163 @@
|
||||
module zinit_rpc
|
||||
|
||||
import freeflowuniverse.herolib.data.encoderhero
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
pub const version = '0.0.0'
|
||||
const singleton = true
|
||||
const default = false
|
||||
|
||||
// Default configuration for Zinit JSON-RPC API
|
||||
pub const default_socket_path = '/tmp/zinit.sock'
|
||||
|
||||
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
|
||||
|
||||
@[heap]
|
||||
pub struct ZinitRPC {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
socket_path string = default_socket_path // Unix socket path for RPC server
|
||||
rpc_client ?&jsonrpc.Client @[skip]
|
||||
}
|
||||
|
||||
// your checking & initialization code if needed
|
||||
fn obj_init(mycfg_ ZinitRPC) !ZinitRPC {
|
||||
mut mycfg := mycfg_
|
||||
if mycfg.socket_path == '' {
|
||||
mycfg.socket_path = default_socket_path
|
||||
}
|
||||
// For now, we'll initialize the client when needed
|
||||
// The actual client will be created in the factory
|
||||
return mycfg
|
||||
}
|
||||
|
||||
// Response structs based on OpenRPC specification
|
||||
|
||||
// OpenRPCSpec represents the OpenRPC specification structure
|
||||
pub struct OpenRPCSpec {
|
||||
pub mut:
|
||||
openrpc string @[json: 'openrpc'] // OpenRPC version
|
||||
info OpenRPCInfo @[json: 'info'] // API information
|
||||
methods []OpenRPCMethod @[json: 'methods'] // Available methods
|
||||
servers []OpenRPCServer @[json: 'servers'] // Server information
|
||||
}
|
||||
|
||||
// OpenRPCInfo represents API information
|
||||
pub struct OpenRPCInfo {
|
||||
pub mut:
|
||||
version string @[json: 'version'] // API version
|
||||
title string @[json: 'title'] // API title
|
||||
description string @[json: 'description'] // API description
|
||||
license OpenRPCLicense @[json: 'license'] // License information
|
||||
}
|
||||
|
||||
// OpenRPCLicense represents license information
|
||||
pub struct OpenRPCLicense {
|
||||
pub mut:
|
||||
name string @[json: 'name'] // License name
|
||||
}
|
||||
|
||||
// OpenRPCMethod represents an RPC method
|
||||
pub struct OpenRPCMethod {
|
||||
pub mut:
|
||||
name string @[json: 'name'] // Method name
|
||||
description string @[json: 'description'] // Method description
|
||||
// Note: params and result are dynamic and would need more complex handling
|
||||
}
|
||||
|
||||
// OpenRPCServer represents server information
|
||||
pub struct OpenRPCServer {
|
||||
pub mut:
|
||||
name string @[json: 'name'] // Server name
|
||||
url string @[json: 'url'] // Server URL
|
||||
}
|
||||
|
||||
// ServiceStatus represents detailed status information for a service
|
||||
pub struct ServiceStatus {
|
||||
pub mut:
|
||||
name string @[json: 'name'] // Service name
|
||||
pid u32 @[json: 'pid'] // Process ID of the running service (if running)
|
||||
state string @[json: 'state'] // Current state of the service (Running, Success, Error, etc.)
|
||||
target string @[json: 'target'] // Target state of the service (Up, Down)
|
||||
after map[string]string @[json: 'after'] // Dependencies of the service and their states
|
||||
}
|
||||
|
||||
// ServiceConfig represents the configuration for a zinit service
|
||||
pub struct ServiceConfig {
|
||||
pub mut:
|
||||
exec string @[json: 'exec'] // Command to run
|
||||
test string @[json: 'test'] // Test command (optional)
|
||||
oneshot bool @[json: 'oneshot'] // Whether the service should be restarted (maps to one_shot in Zinit)
|
||||
after []string @[json: 'after'] // Services that must be running before this one starts
|
||||
log string @[json: 'log'] // How to handle service output (null, ring, stdout)
|
||||
env map[string]string @[json: 'env'] // Environment variables for the service
|
||||
dir string @[json: 'dir'] // Working directory for the service
|
||||
shutdown_timeout u64 @[json: 'shutdown_timeout'] // Maximum time to wait for service to stop during shutdown
|
||||
}
|
||||
|
||||
// ServiceStats represents memory and CPU usage statistics for a service
|
||||
pub struct ServiceStats {
|
||||
pub mut:
|
||||
name string @[json: 'name'] // Service name
|
||||
pid u32 @[json: 'pid'] // Process ID of the service
|
||||
memory_usage u64 @[json: 'memory_usage'] // Memory usage in bytes
|
||||
cpu_usage f32 @[json: 'cpu_usage'] // CPU usage as a percentage (0-100)
|
||||
children []ChildStats @[json: 'children'] // Stats for child processes
|
||||
}
|
||||
|
||||
// ChildStats represents statistics for a child process
|
||||
pub struct ChildStats {
|
||||
pub mut:
|
||||
pid u32 @[json: 'pid'] // Process ID of the child process
|
||||
memory_usage u64 @[json: 'memory_usage'] // Memory usage in bytes
|
||||
cpu_usage f32 @[json: 'cpu_usage'] // CPU usage as a percentage (0-100)
|
||||
}
|
||||
|
||||
// ServiceCreateParams represents parameters for service_create method
|
||||
pub struct ServiceCreateParams {
|
||||
pub mut:
|
||||
name string @[json: 'name'] // Name of the service to create
|
||||
content ServiceConfig @[json: 'content'] // Configuration for the service
|
||||
}
|
||||
|
||||
// ServiceKillParams represents parameters for service_kill method
|
||||
pub struct ServiceKillParams {
|
||||
pub mut:
|
||||
name string @[json: 'name'] // Name of the service to kill
|
||||
signal string @[json: 'signal'] // Signal to send (e.g., SIGTERM, SIGKILL)
|
||||
}
|
||||
|
||||
// LogParams represents parameters for log streaming methods
|
||||
@[params]
|
||||
pub struct LogParams {
|
||||
pub mut:
|
||||
name string // Optional service name filter
|
||||
}
|
||||
|
||||
/////////////NORMALLY NO NEED TO TOUCH
|
||||
|
||||
pub fn heroscript_dumps(obj ZinitRPC) !string {
|
||||
return encoderhero.encode[ZinitRPC](obj)!
|
||||
}
|
||||
|
||||
pub fn heroscript_loads(heroscript string) !ZinitRPC {
|
||||
mut obj := encoderhero.decode[ZinitRPC](heroscript)!
|
||||
return obj
|
||||
}
|
||||
|
||||
// Factory function to create a new ZinitRPC client instance
|
||||
@[params]
|
||||
pub struct NewClientArgs {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
socket_path string = default_socket_path
|
||||
}
|
||||
|
||||
pub fn new_client(args NewClientArgs) !&ZinitRPC {
|
||||
mut client := ZinitRPC{
|
||||
name: args.name
|
||||
socket_path: args.socket_path
|
||||
}
|
||||
client = obj_init(client)!
|
||||
return &client
|
||||
}
|
||||
Binary file not shown.
@@ -1,9 +1,7 @@
|
||||
module code
|
||||
|
||||
import freeflowuniverse.herolib.mcp
|
||||
import freeflowuniverse.herolib.mcp.logger
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import os
|
||||
import log
|
||||
|
||||
// ===== FILE AND DIRECTORY OPERATIONS =====
|
||||
|
||||
@@ -90,7 +88,7 @@ pub fn get_function_from_module(module_path string, function_name string) !Funct
|
||||
return error('Failed to list V files in ${module_path}: ${err}')
|
||||
}
|
||||
|
||||
log.error('Found ${v_files} V files in ${module_path}')
|
||||
console.print_stderr('Found ${v_files} V files in ${module_path}')
|
||||
for v_file in v_files {
|
||||
// Read the file content
|
||||
content := os.read_file(v_file) or { continue }
|
||||
@@ -114,13 +112,13 @@ pub fn get_function_from_module(module_path string, function_name string) !Funct
|
||||
// RETURNS:
|
||||
// string - the type definition if found, or error if not found
|
||||
pub fn get_type_from_module(module_path string, type_name string) !string {
|
||||
println('Looking for type ${type_name} in module ${module_path}')
|
||||
console.print_debug('Looking for type ${type_name} in module ${module_path}')
|
||||
v_files := list_v_files(module_path) or {
|
||||
return error('Failed to list V files in ${module_path}: ${err}')
|
||||
}
|
||||
|
||||
for v_file in v_files {
|
||||
println('Checking file: ${v_file}')
|
||||
console.print_debug('Checking file: ${v_file}')
|
||||
content := os.read_file(v_file) or { return error('Failed to read file ${v_file}: ${err}') }
|
||||
|
||||
// Look for both regular and pub struct declarations
|
||||
@@ -139,13 +137,12 @@ pub fn get_type_from_module(module_path string, type_name string) !string {
|
||||
type_import := content.split_into_lines().filter(it.contains('import')
|
||||
&& it.contains(type_name))
|
||||
if type_import.len > 0 {
|
||||
log.debug('debugzoooo')
|
||||
mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ')
|
||||
return get_type_from_module(get_module_dir(mod), type_name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
println('Found type ${type_name} in ${v_file} at position ${i}')
|
||||
console.print_debug('Found type ${type_name} in ${v_file} at position ${i}')
|
||||
|
||||
// Find the start of the struct definition including comments
|
||||
mut comment_start := i
|
||||
@@ -186,7 +183,7 @@ pub fn get_type_from_module(module_path string, type_name string) !string {
|
||||
|
||||
// Get the full struct definition including the struct declaration line
|
||||
full_struct := content.substr(line_start, closing_i + 1)
|
||||
println('Found struct definition:\n${full_struct}')
|
||||
console.print_debug('Found struct definition:\n${full_struct}')
|
||||
|
||||
// Return the full struct definition
|
||||
return full_struct
|
||||
@@ -203,7 +200,7 @@ pub fn get_type_from_module(module_path string, type_name string) !string {
|
||||
// RETURNS:
|
||||
// string - test results output, or error if test fails
|
||||
pub fn vtest(fullpath string) !string {
|
||||
logger.info('test ${fullpath}')
|
||||
console.print_item('test ${fullpath}')
|
||||
if !os.exists(fullpath) {
|
||||
return error('File or directory does not exist: ${fullpath}')
|
||||
}
|
||||
@@ -216,12 +213,12 @@ pub fn vtest(fullpath string) !string {
|
||||
return results
|
||||
} else {
|
||||
cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}'
|
||||
logger.debug('Executing command: ${cmd}')
|
||||
console.print_debug('Executing command: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('Test failed for ${fullpath} with exit code ${result.exit_code}\n${result.output}')
|
||||
} else {
|
||||
logger.info('Test completed for ${fullpath}')
|
||||
console.print_item('Test completed for ${fullpath}')
|
||||
}
|
||||
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
|
||||
}
|
||||
@@ -234,12 +231,12 @@ pub fn vtest(fullpath string) !string {
|
||||
// string - vet results output, or error if vet fails
|
||||
fn vet_file(file string) !string {
|
||||
cmd := 'v vet -v -w ${file}'
|
||||
logger.debug('Executing command: ${cmd}')
|
||||
console.print_debug('Executing command: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('Vet failed for ${file} with exit code ${result.exit_code}\n${result.output}')
|
||||
} else {
|
||||
logger.info('Vet completed for ${file}')
|
||||
console.print_item('Vet completed for ${file}')
|
||||
}
|
||||
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
|
||||
}
|
||||
@@ -250,7 +247,7 @@ fn vet_file(file string) !string {
|
||||
// RETURNS:
|
||||
// string - vet results output, or error if vet fails
|
||||
pub fn vvet(fullpath string) !string {
|
||||
logger.info('vet ${fullpath}')
|
||||
console.print_item('vet ${fullpath}')
|
||||
if !os.exists(fullpath) {
|
||||
return error('File or directory does not exist: ${fullpath}')
|
||||
}
|
||||
@@ -260,7 +257,7 @@ pub fn vvet(fullpath string) !string {
|
||||
files := list_v_files(fullpath) or { return error('Error listing V files: ${err}') }
|
||||
for file in files {
|
||||
results += vet_file(file) or {
|
||||
logger.error('Failed to vet ${file}: ${err}')
|
||||
console.print_stderr('Failed to vet ${file}: ${err}')
|
||||
return error('Failed to vet ${file}: ${err}')
|
||||
}
|
||||
results += '\n-----------------------\n'
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
module herocmds
|
||||
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import os
|
||||
import cli { Command, Flag }
|
||||
|
||||
pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
pub fn cmd_docusaurus(mut cmdroot Command) Command {
|
||||
mut cmd_run := Command{
|
||||
name: 'docusaurus'
|
||||
description: 'Generate, build, run docusaurus sites.'
|
||||
@@ -35,7 +36,16 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
name: 'path'
|
||||
abbrev: 'p'
|
||||
// default: ''
|
||||
description: 'Path where docusaurus source is.'
|
||||
description: 'Path where docusaurus configuration is.'
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .string
|
||||
required: false
|
||||
name: 'buildpath'
|
||||
abbrev: 'b'
|
||||
// default: ''
|
||||
description: 'Path where docusaurus build is.'
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
@@ -71,6 +81,14 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
description: 'build dev version and publish.'
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .bool
|
||||
required: false
|
||||
name: 'open'
|
||||
abbrev: 'o'
|
||||
description: 'open the site in browser.'
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .bool
|
||||
required: false
|
||||
@@ -95,36 +113,86 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
})
|
||||
|
||||
cmdroot.add_command(cmd_run)
|
||||
return cmdroot
|
||||
}
|
||||
|
||||
fn cmd_docusaurus_execute(cmd Command) ! {
|
||||
mut update := cmd.flags.get_bool('update') or { false }
|
||||
mut init := cmd.flags.get_bool('new') or { false }
|
||||
mut open := cmd.flags.get_bool('open') or { false }
|
||||
mut url := cmd.flags.get_string('url') or { '' }
|
||||
mut publish_path := cmd.flags.get_string('publish') or { '' }
|
||||
mut deploykey := cmd.flags.get_string('deploykey') or { '' }
|
||||
|
||||
mut path := cmd.flags.get_string('path') or { '' }
|
||||
// --- Build Path Logic ---
|
||||
mut build_path := cmd.flags.get_string('buildpath') or { '' }
|
||||
if build_path == '' {
|
||||
// Default build path if not provided (e.g., use CWD or a specific temp dir)
|
||||
// Using CWD for now based on previous edits, adjust if needed
|
||||
build_path = '${os.home_dir()}/hero/var/docusaurus'
|
||||
}
|
||||
|
||||
// --- Start: Heroscript Path Logic ---
|
||||
mut provided_path := cmd.flags.get_string('path') or { '' }
|
||||
mut heroscript_source_path := ''
|
||||
build_cfg_dir := os.join_path(build_path, 'cfg')
|
||||
// target_heroscript_path := os.join_path(build_cfg_dir, 'config.heroscript')
|
||||
|
||||
if provided_path != '' {
|
||||
if !os.exists(provided_path) || !os.is_file(provided_path) {
|
||||
return error('Provided path "${provided_path}" does not exist or is not a file.')
|
||||
}
|
||||
// heroscript_source_path = provided_path
|
||||
// // --- Copy Heroscript to Build Location ---
|
||||
// os.mkdir_all(build_cfg_dir)!
|
||||
// os.cp(heroscript_source_path, target_heroscript_path)!
|
||||
} else {
|
||||
// Path not provided, look in ./cfg/
|
||||
mut cwd := os.getwd()
|
||||
cfg_dir := os.join_path(cwd, 'cfg')
|
||||
if !os.exists(cfg_dir) || !os.is_dir(cfg_dir) {
|
||||
return error('Flag -path not provided and directory "./cfg" not found in the current working directory.')
|
||||
}
|
||||
// mut found_files := []string
|
||||
// for file in os.ls(cfg_dir) or { []string{} } {
|
||||
// if file.ends_with('.heroscript') {
|
||||
// found_files << os.join_path(cfg_dir, file)
|
||||
// }
|
||||
// }
|
||||
// if found_files.len == 1 {
|
||||
// heroscript_source_path = found_files[0]
|
||||
// os.mkdir_all(build_cfg_dir)!
|
||||
// os.cp(heroscript_source_path, target_heroscript_path)!
|
||||
// } else if found_files.len == 0 {
|
||||
// return error('Flag -path not provided and no *.heroscript file found in "./cfg".')
|
||||
// } else {
|
||||
// return error('Flag -path not provided and multiple *.heroscript files found in "./cfg". Please specify one using -path.')
|
||||
// }
|
||||
}
|
||||
|
||||
// --- End: Heroscript Path Logic ---
|
||||
mut buildpublish := cmd.flags.get_bool('buildpublish') or { false }
|
||||
mut builddevpublish := cmd.flags.get_bool('builddevpublish') or { false }
|
||||
mut dev := cmd.flags.get_bool('dev') or { false }
|
||||
|
||||
// if build== false && build== false && build== false {
|
||||
// eprintln("specify build, builddev or dev")
|
||||
// exit(1)
|
||||
// }
|
||||
mut docs := docusaurus.new(
|
||||
update: update
|
||||
build_path: build_path
|
||||
// heroscript: os.read_file(target_heroscript_path)! // Read the copied heroscript
|
||||
)!
|
||||
|
||||
mut docs := docusaurus.new(update: update)!
|
||||
mut site := docs.get(
|
||||
url: url
|
||||
path: path
|
||||
build_path: build_path
|
||||
update: update
|
||||
publish_path: publish_path
|
||||
deploykey: deploykey
|
||||
init: init
|
||||
open: open
|
||||
)!
|
||||
|
||||
site.generate()!
|
||||
|
||||
if publish_path.len > 0 {
|
||||
site.build()!
|
||||
}
|
||||
@@ -140,4 +208,8 @@ fn cmd_docusaurus_execute(cmd Command) ! {
|
||||
if dev {
|
||||
site.dev()!
|
||||
}
|
||||
|
||||
if open {
|
||||
site.open()!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn cmd_mdbook(mut cmdroot Command) {
|
||||
|
||||
example:
|
||||
|
||||
hero mdbook -u https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/main/heroscript
|
||||
hero mdbook -u https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/heroscript
|
||||
|
||||
If you do -gp it will pull newest book content from git and give error if there are local changes.
|
||||
If you do -gr it will pull newest book content from git and overwrite local changes (careful).
|
||||
|
||||
@@ -17,7 +17,7 @@ heroscript has numerous ways to execute actions using your hero tool.
|
||||
|
||||
example:
|
||||
|
||||
hero run -u https://git.ourworld.tf/threefold_coop/info_asimov/src/branch/main/heroscript
|
||||
hero run -u https://git.threefold.info/threefold_coop/info_asimov/src/branch/main/heroscript
|
||||
|
||||
Can also do -e or -st to see sourcetree
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ pub mut:
|
||||
git_reset bool
|
||||
prio int = 50
|
||||
priorities map[int]string // filter and give priority, see filtersort method to know how to use
|
||||
session ?&base.Session
|
||||
// session ?&base.Session
|
||||
}
|
||||
|
||||
// get a new playbook, can scan a directory or just add text
|
||||
@@ -45,7 +45,6 @@ pub fn new(args_ PlayBookNewArgs) !PlayBook {
|
||||
git_branch: args.git_branch
|
||||
git_reset: args.git_reset
|
||||
prio: args.prio
|
||||
session: args.session
|
||||
)!
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ fn test_parser() {
|
||||
|
||||
fn test_parser2() {
|
||||
mut pb := new(
|
||||
text: "!!play.run url:'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'"
|
||||
text: "!!play.run url:'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'"
|
||||
) or { panic(err) }
|
||||
mut a := pb.actions[0]
|
||||
assert a.actor == 'play'
|
||||
assert a.name == 'run'
|
||||
assert a.params.get('url')! == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'
|
||||
assert a.params.get('url')! == 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
|
||||
}
|
||||
if p.is_file() {
|
||||
c := p.read()!
|
||||
plbook.add(text: c, prio: args.prio, session: args_.session)!
|
||||
plbook.add(text: c, prio: args.prio)!
|
||||
return
|
||||
} else if p.is_dir() {
|
||||
// get .md and .hero files from dir
|
||||
@@ -44,10 +44,11 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
|
||||
mut paths := ol0.paths.clone()
|
||||
mut ol1 := p.list(recursive: true, regex: [r'.*\.hero$'])!
|
||||
paths << ol1.paths
|
||||
|
||||
mut ol2 := p.list(recursive: true, regex: [r'.*\.heroscript$'])!
|
||||
paths << ol2.paths
|
||||
for mut p2 in paths {
|
||||
c2 := p2.read()!
|
||||
plbook.add(text: c2, prio: args.prio, session: args_.session)!
|
||||
plbook.add(text: c2, prio: args.prio)!
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn play_juggler(mut plbook playbook.PlayBook) ! {
|
||||
port := p.get_int_default('port', 8000)!
|
||||
|
||||
j = juggler.configure(
|
||||
url: 'https://git.ourworld.tf/projectmycelium/itenv'
|
||||
url: 'https://git.threefold.info/projectmycelium/itenv'
|
||||
username: username
|
||||
password: password
|
||||
reset: true
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn run(mut plbook playbook.PlayBook, dagu bool) ! {
|
||||
play_core(mut plbook)!
|
||||
play_ssh(mut plbook)!
|
||||
play_git(mut plbook)!
|
||||
play_publisher(mut plbook)!
|
||||
// play_publisher(mut plbook)!
|
||||
// play_zola(mut plbook)!
|
||||
// play_caddy(mut plbook)!
|
||||
// play_juggler(mut plbook)!
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module playcmds
|
||||
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.hero.publishing
|
||||
// import freeflowuniverse.herolib.hero.publishing
|
||||
|
||||
pub fn play_publisher(mut plbook playbook.PlayBook) ! {
|
||||
publishing.play(mut plbook)!
|
||||
}
|
||||
// pub fn play_publisher(mut plbook playbook.PlayBook) ! {
|
||||
// publishing.play(mut plbook)!
|
||||
// }
|
||||
|
||||
@@ -11,14 +11,14 @@ fn test_play_publisher() {
|
||||
s2 := "
|
||||
|
||||
!!publisher.new_collection
|
||||
url:'https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/main/collections'
|
||||
url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/collections'
|
||||
reset: false
|
||||
pull: true
|
||||
|
||||
|
||||
!!book.define
|
||||
name:'info_tfgrid'
|
||||
summary_url:'https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/development/books/tech/SUMMARY.md'
|
||||
summary_url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/development/books/tech/SUMMARY.md'
|
||||
title:'ThreeFold Technology'
|
||||
collections: 'about,dashboard,farmers,library,partners_utilization,tech,p2p'
|
||||
|
||||
|
||||
@@ -10,14 +10,29 @@ mut s:=base.session_new(
|
||||
)!
|
||||
|
||||
|
||||
// path string
|
||||
// text string
|
||||
// git_url string
|
||||
// git_pull bool
|
||||
// git_branch string
|
||||
// git_reset bool
|
||||
// execute bool = true
|
||||
// session ?&base.Session is optional
|
||||
// Path to the code execution directory
|
||||
path string
|
||||
|
||||
// Command text to execute (e.g., "ls -la")
|
||||
text string
|
||||
|
||||
// Git repository URL for version control
|
||||
git_url string
|
||||
|
||||
// Pull latest changes from git
|
||||
git_pull bool
|
||||
|
||||
// Git branch to use
|
||||
git_branch string
|
||||
|
||||
// Reset repository before pull
|
||||
git_reset bool
|
||||
|
||||
// Execute command after setup
|
||||
execute bool = true
|
||||
|
||||
// Optional session object for state management
|
||||
session ?&base.Session
|
||||
|
||||
mut plbook := playbook.new(text: "....",session:s) or { panic(err) }
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
security add-internet-password -s git.ourworld.tf -a despiegk -w mypassword
|
||||
security add-internet-password -s git.threefold.info -a despiegk -w mypassword
|
||||
|
||||
-s: The server (e.g., git.ourworld.tf).
|
||||
-s: The server (e.g., git.threefold.info).
|
||||
-a: The account or username (e.g., despiegk).
|
||||
-w: The password (e.g., mypassword).
|
||||
|
||||
|
||||
security find-internet-password -s git.ourworld.tf -w
|
||||
security find-internet-password -s git.threefold.info -w
|
||||
|
||||
|
||||
security delete-internet-password -s git.ourworld.tf
|
||||
security delete-internet-password -s git.threefold.info
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
module currency
|
||||
|
||||
//see encoder for binary
|
||||
|
||||
// see encoder for binary
|
||||
|
||||
// import freeflowuniverse.herolib.data.encoder
|
||||
|
||||
|
||||
86
lib/data/doctree/README.md
Normal file
86
lib/data/doctree/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Doctree Module
|
||||
|
||||
The `doctree` module is a V language library designed for scanning, processing, and exporting collections of documents. It provides a structured way to manage document-based content, making it suitable for generating documentation, building static websites, or processing any content organized into collections.
|
||||
|
||||
## Purpose
|
||||
|
||||
The primary goal of this module is to transform structured document collections into a format suitable for various outputs. It handles the complexities of finding collections, loading their content, processing includes, definitions, and macros, and exporting the final result while managing assets like images and files.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
* **Tree:** The central component (`doctree.Tree`) that holds one or more `Collection` instances. It orchestrates the scanning, processing, and exporting of all contained collections.
|
||||
* **Collection:** A directory that is marked as a collection by the presence of a `.collection` file. A collection groups related documents (pages, images, files) and can have its own configuration defined within the `.collection` file.
|
||||
* **.collection file:** A file placed in a directory to designate it as a collection. This file can optionally contain parameters (using the `paramsparser` format) such as a custom name for the collection.
|
||||
|
||||
## How it Works (Workflow)
|
||||
|
||||
The typical workflow involves creating a `Tree`, scanning for collections, and then exporting the processed content.å
|
||||
|
||||
1. **Create Tree:** Initialize a `doctree.Tree` instance using `doctree.new()`.
|
||||
2. **Scan:** Use the `tree.scan()` or `tree.scan_concurrent()` method, providing a path to a directory or a Git repository URL. The scanner recursively looks for directories containing a `.collection` file.
|
||||
3. **Load Content:** For each identified collection, the module loads its content, including markdown pages, images, and other files.
|
||||
4. **Process Content:** The loaded content is processed. This includes handling definitions, includes (content from other files), and macros (dynamic content generation or transformation).
|
||||
5. **Generate Output Paths:** The module determines the final paths for all processed files and assets in the destination directory.
|
||||
6. **Export:** The `tree.export()` method writes the processed content and assets to the specified destination directory, maintaining the desired structure.
|
||||
|
||||
## Usage (For Developers)
|
||||
|
||||
Here's a basic example of how to use the `doctree` module in your V project:
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.data.doctree
|
||||
// 1. Create a new Tree instance
|
||||
mut tree := doctree.new(name: 'my_documentation')!
|
||||
|
||||
// 2. Scan a directory containing your collections
|
||||
// Replace './docs' with the actual path to your document collections
|
||||
tree.scan(path: './docs')!
|
||||
|
||||
// use from URL
|
||||
//git_url string
|
||||
//git_reset bool
|
||||
//git_pull bool
|
||||
tree.scan(git_url: 'https://git.threefold.info/tfgrid/docs_tfgrid4/src/branch/main/collections')!
|
||||
|
||||
// 3. Export the processed content to a destination directory
|
||||
// Replace './output' with your desired output path
|
||||
// if redis then the metadata will be put in redis
|
||||
tree.export(destination: './output', redis:true)!
|
||||
|
||||
println('Documentation successfully exported to ./output')
|
||||
|
||||
```
|
||||
|
||||
## Structure of a Collection
|
||||
|
||||
A collection is a directory containing a `.collection` file. Inside a collection directory, you would typically organize your content like this:
|
||||
|
||||
```
|
||||
my_collection/
|
||||
├── .collection
|
||||
├── page1.md
|
||||
├── page2.md
|
||||
├── images/
|
||||
│ ├── image1.png
|
||||
│ └── image2.jpg
|
||||
└── files/
|
||||
├── document.pdf
|
||||
└── data.csv
|
||||
```
|
||||
|
||||
Markdown files (`.md`) are treated as pages.
|
||||
|
||||
## Redis Structure
|
||||
|
||||
when using the export redis:true argument, which is default
|
||||
|
||||
in redis we will find
|
||||
|
||||
```bash
|
||||
#redis hsets:
|
||||
doctree:$collectionname $pagename $rel_path_in_collection
|
||||
doctree:$collectionname $filename.$ext $rel_path_in_collection
|
||||
doctree:meta $collectionname $collectionpath_on_disk
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module collection
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib { Path }
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
pub enum CollectionErrorCat {
|
||||
@@ -52,8 +53,10 @@ pub fn (err ObjNotFound) msg() string {
|
||||
}
|
||||
|
||||
// write errors.md in the collection, this allows us to see what the errors are
|
||||
pub fn (collection Collection) errors_report(dest_ string) ! {
|
||||
pub fn (collection Collection) errors_report(col_name string, dest_ string) ! {
|
||||
// console.print_debug("====== errors report: ${dest_} : ${collection.errors.len}\n${collection.errors}")
|
||||
mut context := base.context()!
|
||||
mut redis := context.redis()!
|
||||
mut dest := pathlib.get_file(path: dest_, create: true)!
|
||||
if collection.errors.len == 0 {
|
||||
dest.delete()!
|
||||
@@ -61,4 +64,5 @@ pub fn (collection Collection) errors_report(dest_ string) ! {
|
||||
}
|
||||
c := $tmpl('template/errors.md')
|
||||
dest.write(c)!
|
||||
redis.hset('doctree:${col_name}', 'errors', 'errors.md')!
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module collection
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.texttools.regext
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.doctree.pointer
|
||||
@@ -15,6 +16,7 @@ pub mut:
|
||||
keep_structure bool // wether the structure of the src collection will be preserved or not
|
||||
exclude_errors bool // wether error reporting should be exported as well
|
||||
replacer ?regext.ReplaceInstructions
|
||||
redis bool = true
|
||||
}
|
||||
|
||||
pub fn (mut c Collection) export(args CollectionExportArgs) ! {
|
||||
@@ -23,19 +25,24 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
|
||||
mut cfile := pathlib.get_file(path: dir_src.path + '/.collection', create: true)! // will auto save it
|
||||
cfile.write("name:${c.name} src:'${c.path.path}'")!
|
||||
|
||||
c.errors << export_pages(c.path.path, c.pages.values(),
|
||||
mut context := base.context()!
|
||||
mut redis := context.redis()!
|
||||
redis.hset('doctree:path', '${c.name}', dir_src.path)!
|
||||
|
||||
c.errors << export_pages(c.name, c.path.path, c.pages.values(),
|
||||
dir_src: dir_src
|
||||
file_paths: args.file_paths
|
||||
keep_structure: args.keep_structure
|
||||
replacer: args.replacer
|
||||
redis: args.redis
|
||||
)!
|
||||
|
||||
c.export_files(dir_src, args.reset)!
|
||||
c.export_images(dir_src, args.reset)!
|
||||
c.export_linked_pages(dir_src)!
|
||||
c.export_files(c.name, dir_src, args.reset)!
|
||||
c.export_images(c.name, dir_src, args.reset)!
|
||||
c.export_linked_pages(c.name, dir_src)!
|
||||
|
||||
if !args.exclude_errors {
|
||||
c.errors_report('${dir_src.path}/errors.md')!
|
||||
c.errors_report(c.name, '${dir_src.path}/errors.md')!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +53,16 @@ pub mut:
|
||||
file_paths map[string]string
|
||||
keep_structure bool // wether the structure of the src collection will be preserved or not
|
||||
replacer ?regext.ReplaceInstructions
|
||||
redis bool = true
|
||||
}
|
||||
|
||||
// creates page file, processes page links, then writes page
|
||||
fn export_pages(col_path string, pages []&data.Page, args ExportPagesArgs) ![]CollectionError {
|
||||
fn export_pages(col_name string, col_path string, pages []&data.Page, args ExportPagesArgs) ![]CollectionError {
|
||||
mut errors := []CollectionError{}
|
||||
|
||||
mut context := base.context()!
|
||||
mut redis := context.redis()!
|
||||
|
||||
for page in pages {
|
||||
dest := if args.keep_structure {
|
||||
relpath := page.path.path.trim_string_left(col_path)
|
||||
@@ -86,33 +98,42 @@ fn export_pages(col_path string, pages []&data.Page, args ExportPagesArgs) ![]Co
|
||||
if mut replacer := args.replacer {
|
||||
markdown = replacer.replace(text: markdown)!
|
||||
}
|
||||
|
||||
dest_path.write(markdown)!
|
||||
redis.hset('doctree:${col_name}', page.name, '${page.name}.md')!
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn (c Collection) export_files(dir_src pathlib.Path, reset bool) ! {
|
||||
fn (c Collection) export_files(col_name string, dir_src pathlib.Path, reset bool) ! {
|
||||
mut context := base.context()!
|
||||
mut redis := context.redis()!
|
||||
for _, file in c.files {
|
||||
mut d := '${dir_src.path}/img/${file.name}.${file.ext}'
|
||||
if reset || !os.exists(d) {
|
||||
file.copy(d)!
|
||||
}
|
||||
redis.hset('doctree:${col_name}', file.name, 'img/${file.name}.${file.ext}')!
|
||||
}
|
||||
}
|
||||
|
||||
fn (c Collection) export_images(dir_src pathlib.Path, reset bool) ! {
|
||||
fn (c Collection) export_images(col_name string, dir_src pathlib.Path, reset bool) ! {
|
||||
mut context := base.context()!
|
||||
mut redis := context.redis()!
|
||||
for _, file in c.images {
|
||||
mut d := '${dir_src.path}/img/${file.name}.${file.ext}'
|
||||
redis.hset('doctree:${col_name}', file.name, 'img/${file.name}.${file.ext}')!
|
||||
if reset || !os.exists(d) {
|
||||
file.copy(d)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (c Collection) export_linked_pages(dir_src pathlib.Path) ! {
|
||||
fn (c Collection) export_linked_pages(col_name string, dir_src pathlib.Path) ! {
|
||||
mut context := base.context()!
|
||||
mut redis := context.redis()!
|
||||
collection_linked_pages := c.get_collection_linked_pages()!
|
||||
mut linked_pages_file := pathlib.get_file(path: dir_src.path + '/.linkedpages', create: true)!
|
||||
redis.hset('doctree:${col_name}', 'linkedpages', '${linked_pages_file.name()}.md')!
|
||||
linked_pages_file.write(collection_linked_pages.join_lines())!
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ pub mut:
|
||||
exclude_errors bool // wether error reporting should be exported as well
|
||||
toreplace string
|
||||
concurrent bool = true
|
||||
redis bool = true
|
||||
}
|
||||
|
||||
// export all collections to chosen directory .
|
||||
@@ -50,6 +51,7 @@ pub fn (mut tree Tree) export(args TreeExportArgs) ! {
|
||||
reset: args.reset
|
||||
keep_structure: args.keep_structure
|
||||
exclude_errors: args.exclude_errors
|
||||
redis: args.redis
|
||||
// TODO: replacer: tree.replacer
|
||||
)!
|
||||
}(mut col, dest_path, file_paths, args)
|
||||
@@ -66,6 +68,7 @@ pub fn (mut tree Tree) export(args TreeExportArgs) ! {
|
||||
keep_structure: args.keep_structure
|
||||
exclude_errors: args.exclude_errors
|
||||
replacer: tree.replacer
|
||||
redis: args.redis
|
||||
)!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,14 +150,14 @@ pub fn (mut d Decoder) get_ourtime() !ourtime.OurTime {
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_currency() !currency.Amount {
|
||||
curstring:=d.get_string()!
|
||||
curstring := d.get_string()!
|
||||
if curstring.len == 0 {
|
||||
return error('currency string is empty')
|
||||
}
|
||||
currencyo := currency.get(curstring)!
|
||||
return currency.Amount{
|
||||
currency: currencyo
|
||||
val: d.get_f64()!
|
||||
val: d.get_f64()!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,6 @@ pub fn (mut b Encoder) add_currency(data currency.Amount) {
|
||||
b.add_f64(data.val)
|
||||
}
|
||||
|
||||
|
||||
// adds a float64 value
|
||||
pub fn (mut b Encoder) add_f64(data f64) {
|
||||
// Convert f64 to bits first, then store as u64
|
||||
|
||||
@@ -310,17 +310,17 @@ fn test_matchhashmap() {
|
||||
}
|
||||
|
||||
fn test_url() {
|
||||
text := "url:'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'"
|
||||
text := "url:'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'"
|
||||
params := new(text)!
|
||||
|
||||
myurl := params.get('url')!
|
||||
assert myurl == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'
|
||||
assert myurl == 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'
|
||||
}
|
||||
|
||||
fn test_url2() {
|
||||
text := "url: 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'"
|
||||
text := "url: 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'"
|
||||
params := new(text)!
|
||||
|
||||
myurl := params.get('url')!
|
||||
assert myurl == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'
|
||||
assert myurl == 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user