add other horus installers, create examples, test startup

This commit is contained in:
Timur Gordon
2025-11-17 15:57:40 +01:00
parent 06fcfa5b50
commit bf6dec48f1
43 changed files with 3515 additions and 132 deletions

47
compare_dirs.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Usage: ./compare_dirs.sh <branch1> <branch2> <dir_path>
# Example: ./compare_dirs.sh main feature-branch src
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <branch1> <branch2> <dir_path>"
exit 1
fi
BRANCH1=$1
BRANCH2=$2
DIR_PATH=$3
TMP_DIR1=$(mktemp -d)
TMP_DIR2=$(mktemp -d)
# Ensure we're in a Git repo
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo "Error: Not inside a Git repository"
exit 1
fi
# Fetch branch contents without switching branches
git worktree add "$TMP_DIR1" "$BRANCH1" > /dev/null 2>&1
git worktree add "$TMP_DIR2" "$BRANCH2" > /dev/null 2>&1
# Check if the directory exists in both branches
if [ ! -d "$TMP_DIR1/$DIR_PATH" ]; then
echo "Error: $DIR_PATH does not exist in $BRANCH1"
exit 1
fi
if [ ! -d "$TMP_DIR2/$DIR_PATH" ]; then
echo "Error: $DIR_PATH does not exist in $BRANCH2"
exit 1
fi
# Compare directories
echo "Comparing $DIR_PATH between $BRANCH1 and $BRANCH2..."
diff -qr "$TMP_DIR1/$DIR_PATH" "$TMP_DIR2/$DIR_PATH"
# Detailed differences
diff -u -r "$TMP_DIR1/$DIR_PATH" "$TMP_DIR2/$DIR_PATH"
# Clean up temporary worktrees
git worktree remove "$TMP_DIR1" --force
git worktree remove "$TMP_DIR2" --force

View File

@@ -0,0 +1,209 @@
# Horus Installation Examples
This directory contains example scripts for installing and managing all Horus components using the herolib installer framework.
## Components
The Horus ecosystem consists of the following components:
1. **Coordinator** - Central coordination service (HTTP: 8081, WS: 9653)
2. **Supervisor** - Supervision and monitoring service (HTTP: 8082, WS: 9654)
3. **Hero Runner** - Command execution runner for Hero jobs
4. **Osiris Runner** - Database-backed runner
5. **SAL Runner** - System Abstraction Layer runner
## Quick Start
### Full Installation and Start
To install and start all Horus components:
```bash
# 1. Install all components (this will take several minutes)
./horus_full_install.vsh
# 2. Start all services
./horus_start_all.vsh
# 3. Check status
./horus_status.vsh
```
### Stop All Services
```bash
./horus_stop_all.vsh
```
## Available Scripts
### `horus_full_install.vsh`
Installs all Horus components:
- Checks and installs Redis if needed
- Checks and installs Rust if needed
- Clones the horus repository
- Builds all binaries from source
**Note:** This script can take 10-30 minutes depending on your system, as it compiles Rust code.
### `horus_start_all.vsh`
Starts all Horus services in the correct order:
1. Coordinator
2. Supervisor
3. Hero Runner
4. Osiris Runner
5. SAL Runner
### `horus_stop_all.vsh`
Stops all running Horus services in reverse order.
### `horus_status.vsh`
Checks and displays the status of all Horus services.
## Prerequisites
- **Operating System**: Linux or macOS
- **Dependencies** (automatically installed):
- Redis (required for all components)
- Rust toolchain (for building from source)
- Git (for cloning repositories)
## Configuration
All components use default configurations:
### Coordinator
- Binary: `/hero/var/bin/coordinator`
- HTTP Port: `8081`
- WebSocket Port: `9653`
- Redis: `127.0.0.1:6379`
### Supervisor
- Binary: `/hero/var/bin/supervisor`
- HTTP Port: `8082`
- WebSocket Port: `9654`
- Redis: `127.0.0.1:6379`
### Runners
- Hero Runner: `/hero/var/bin/herorunner`
- Osiris Runner: `/hero/var/bin/runner_osiris`
- SAL Runner: `/hero/var/bin/runner_sal`
## Custom Configuration
To customize the configuration, you can use heroscript:
```v
import incubaid.herolib.installers.horus.coordinator
mut coordinator := herocoordinator.get(create: true)!
coordinator.http_port = 9000
coordinator.ws_port = 9001
coordinator.log_level = 'debug'
herocoordinator.set(coordinator)!
coordinator.install()!
coordinator.start()!
```
## Testing
After starting the services, you can test them:
```bash
# Test Coordinator HTTP endpoint
curl http://127.0.0.1:8081
# Test Supervisor HTTP endpoint
curl http://127.0.0.1:8082
# Check running processes
pgrep -f coordinator
pgrep -f supervisor
pgrep -f herorunner
pgrep -f runner_osiris
pgrep -f runner_sal
```
## Troubleshooting
### Redis Not Running
If you get Redis connection errors:
```bash
# Check if Redis is running
redis-cli ping
# Start Redis (Ubuntu/Debian)
sudo systemctl start redis-server
# Start Redis (macOS with Homebrew)
brew services start redis
```
### Build Failures
If the build fails:
1. Ensure you have enough disk space (at least 5GB free)
2. Check that Rust is properly installed: `rustc --version`
3. Try cleaning the build: `cd /root/code/git.ourworld.tf/herocode/horus && cargo clean`
### Port Conflicts
If ports 8081 or 8082 are already in use, you can customize the ports in the configuration.
## Advanced Usage
### Individual Component Installation
You can install components individually:
```bash
# Install only coordinator
v run coordinator_only.vsh
# Install only supervisor
v run supervisor_only.vsh
```
### Using with Heroscript
You can also use heroscript files for configuration:
```heroscript
!!herocoordinator.configure
name:'production'
http_port:8081
ws_port:9653
log_level:'info'
!!herocoordinator.install
!!herocoordinator.start
```
## Service Management
Services are managed using the system's startup manager (zinit or systemd):
```bash
# Check service status with systemd
systemctl status coordinator
# View logs
journalctl -u coordinator -f
```
## Cleanup
To completely remove all Horus components:
```bash
# Stop all services
./horus_stop_all.vsh
# Destroy all components (removes binaries)
v run horus_destroy_all.vsh
```
## Support
For issues or questions:
- Check the main Horus repository: https://git.ourworld.tf/herocode/horus
- Review the installer code in `lib/installers/horus/`

View File

@@ -0,0 +1,60 @@
// Horus Configuration Heroscript
// This file demonstrates how to configure all Horus components using heroscript
// Configure Coordinator
!!herocoordinator.configure
name:'default'
binary_path:'/hero/var/bin/coordinator'
redis_addr:'127.0.0.1:6379'
http_port:8081
ws_port:9653
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
// Configure Supervisor
!!supervisor.configure
name:'default'
binary_path:'/hero/var/bin/supervisor'
redis_addr:'127.0.0.1:6379'
http_port:8082
ws_port:9654
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
// Configure Hero Runner
!!herorunner.configure
name:'default'
binary_path:'/hero/var/bin/herorunner'
redis_addr:'127.0.0.1:6379'
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
// Configure Osiris Runner
!!osirisrunner.configure
name:'default'
binary_path:'/hero/var/bin/runner_osiris'
redis_addr:'127.0.0.1:6379'
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
// Configure SAL Runner
!!salrunner.configure
name:'default'
binary_path:'/hero/var/bin/runner_sal'
redis_addr:'127.0.0.1:6379'
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
// Install all components
!!herocoordinator.install
!!supervisor.install
!!herorunner.install
!!osirisrunner.install
!!salrunner.install
// Start all services
!!herocoordinator.start name:'default'
!!supervisor.start name:'default'
!!herorunner.start name:'default'
!!osirisrunner.start name:'default'
!!salrunner.start name:'default'

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.horus.coordinator
import incubaid.herolib.installers.horus.supervisor
import incubaid.herolib.installers.horus.herorunner
import incubaid.herolib.installers.horus.osirisrunner
import incubaid.herolib.installers.horus.salrunner
// Full Horus Installation Example
// This script installs and configures all Horus components:
// - Coordinator (port 8081)
// - Supervisor (port 8082)
// - Hero Runner
// - Osiris Runner
// - SAL Runner
println('🚀 Starting Full Horus Installation')
// Step 1: Install Coordinator
println('\n📦 Step 1/5: Installing Coordinator...')
mut coordinator_installer := coordinator.get(create: true)!
coordinator_installer.install()!
println(' Coordinator installed at ${coordinator_installer.binary_path}')
// Step 2: Install Supervisor
println('\n📦 Step 2/5: Installing Supervisor...')
mut supervisor_inst := supervisor.get(create: true)!
supervisor_inst.install()!
println(' Supervisor installed at ${supervisor_inst.binary_path}')
// Step 3: Install Hero Runner
println('\n📦 Step 3/5: Installing Hero Runner...')
mut hero_runner := herorunner.get(create: true)!
hero_runner.install()!
println(' Hero Runner installed at ${hero_runner.binary_path}')
// Step 4: Install Osiris Runner
println('\n📦 Step 4/5: Installing Osiris Runner...')
mut osiris_runner := osirisrunner.get(create: true)!
osiris_runner.install()!
println(' Osiris Runner installed at ${osiris_runner.binary_path}')
// Step 5: Install SAL Runner
println('\n📦 Step 5/5: Installing SAL Runner...')
mut sal_runner := salrunner.get(create: true)!
sal_runner.install()!
println(' SAL Runner installed at ${sal_runner.binary_path}')
println('🎉 All Horus components installed successfully!')
println('\n📋 Installation Summary:')
println(' Coordinator: ${coordinator_installer.binary_path} (HTTP: ${coordinator_installer.http_port}, WS: ${coordinator_installer.ws_port})')
println(' Supervisor: ${supervisor_inst.binary_path} (HTTP: ${supervisor_inst.http_port}, WS: ${supervisor_inst.ws_port})')
println(' Hero Runner: ${hero_runner.binary_path}')
println(' Osiris Runner: ${osiris_runner.binary_path}')
println(' SAL Runner: ${sal_runner.binary_path}')
println('\n💡 Next Steps:')
println(' To start services, run: ./horus_start_all.vsh')
println(' To test individual components, see the other example scripts')

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.horus.coordinator
import incubaid.herolib.installers.horus.supervisor
import incubaid.herolib.installers.horus.herorunner
import incubaid.herolib.installers.horus.osirisrunner
import incubaid.herolib.installers.horus.salrunner
import time
// Start All Horus Services
// This script starts all Horus components in the correct order
println('🚀 Starting All Horus Services')
// Step 1: Start Coordinator
println('\n Step 1/5: Starting Coordinator...')
mut coordinator_installer := coordinator.get()!
coordinator_installer.start()!
if coordinator_installer.running()! {
println(' Coordinator is running on HTTP:${coordinator_installer.http_port} WS:${coordinator_installer.ws_port}')
} else {
println(' Coordinator failed to start')
}
// Step 2: Start Supervisor
println('\n Step 2/5: Starting Supervisor...')
mut supervisor_inst := supervisor.get()!
supervisor_inst.start()!
if supervisor_inst.running()! {
println(' Supervisor is running on HTTP:${supervisor_inst.http_port} WS:${supervisor_inst.ws_port}')
} else {
println(' Supervisor failed to start')
}
// Step 3: Start Hero Runner
println('\n Step 3/5: Starting Hero Runner...')
mut hero_runner := herorunner.get()!
hero_runner.start()!
if hero_runner.running()! {
println(' Hero Runner is running')
} else {
println(' Hero Runner failed to start')
}
// Step 4: Start Osiris Runner
println('\n Step 4/5: Starting Osiris Runner...')
mut osiris_runner := osirisrunner.get()!
osiris_runner.start()!
if osiris_runner.running()! {
println(' Osiris Runner is running')
} else {
println(' Osiris Runner failed to start')
}
// Step 5: Start SAL Runner
println('\n Step 5/5: Starting SAL Runner...')
mut sal_runner := salrunner.get()!
sal_runner.start()!
if sal_runner.running()! {
println(' SAL Runner is running')
} else {
println(' SAL Runner failed to start')
}
println('🎉 All Horus services started!')
println('\n📊 Service Status:')
println(' Coordinator: ${if coordinator_installer.running()! { " Running" } else { " Stopped" }} (http://127.0.0.1:${coordinator_installer.http_port})')
println(' Supervisor: ${if supervisor_inst.running()! { " Running" } else { " Stopped" }} (http://127.0.0.1:${supervisor_inst.http_port})')
println(' Hero Runner: ${if hero_runner.running()! { " Running" } else { " Stopped" }}')
println(' Osiris Runner: ${if osiris_runner.running()! { " Running" } else { " Stopped" }}')
println(' SAL Runner: ${if sal_runner.running()! { " Running" } else { " Stopped" }}')
println('\n💡 Next Steps:')
println(' To stop services, run: ./horus_stop_all.vsh')
println(' To check status, run: ./horus_status.vsh')

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.horus.coordinator
import incubaid.herolib.installers.horus.supervisor
import incubaid.herolib.installers.horus.herorunner
import incubaid.herolib.installers.horus.osirisrunner
import incubaid.herolib.installers.horus.salrunner
// Check Status of All Horus Services
println('📊 Horus Services Status')
println('=' * 60)
// Get all services
mut coordinator := herocoordinator.get()!
mut supervisor_inst := supervisor.get()!
mut hero_runner := herorunner.get()!
mut osiris_runner := osirisrunner.get()!
mut sal_runner := salrunner.get()!
// Check status
println('\n🔍 Checking service status...\n')
coord_running := coordinator.running()!
super_running := supervisor_inst.running()!
hero_running := hero_runner.running()!
osiris_running := osiris_runner.running()!
sal_running := sal_runner.running()!
println('Service Status Details')
println('-' * 60)
println('Coordinator ${if coord_running { " Running" } else { " Stopped" }} http://127.0.0.1:${coordinator.http_port}')
println('Supervisor ${if super_running { " Running" } else { " Stopped" }} http://127.0.0.1:${supervisor_inst.http_port}')
println('Hero Runner ${if hero_running { " Running" } else { " Stopped" }}')
println('Osiris Runner ${if osiris_running { " Running" } else { " Stopped" }}')
println('SAL Runner ${if sal_running { " Running" } else { " Stopped" }}')
println('\n' + '=' * 60)
// Count running services
mut running_count := 0
if coord_running { running_count++ }
if super_running { running_count++ }
if hero_running { running_count++ }
if osiris_running { running_count++ }
if sal_running { running_count++ }
println('Summary: ${running_count}/5 services running')
if running_count == 5 {
println('🎉 All services are running!')
} else if running_count == 0 {
println('💤 All services are stopped')
} else {
println(' Some services are not running')
}

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.horus.coordinator
import incubaid.herolib.installers.horus.supervisor
import incubaid.herolib.installers.horus.herorunner
import incubaid.herolib.installers.horus.osirisrunner
import incubaid.herolib.installers.horus.salrunner
// Stop All Horus Services
// This script stops all running Horus components
println('🛑 Stopping All Horus Services')
println('=' * 60)
// Stop in reverse order
println('\n Stopping SAL Runner...')
mut sal_runner := salrunner.get()!
sal_runner.stop()!
println(' SAL Runner stopped')
println('\n Stopping Osiris Runner...')
mut osiris_runner := osirisrunner.get()!
osiris_runner.stop()!
println(' Osiris Runner stopped')
println('\n Stopping Hero Runner...')
mut hero_runner := herorunner.get()!
hero_runner.stop()!
println(' Hero Runner stopped')
println('\n Stopping Supervisor...')
mut supervisor_inst := supervisor.get()!
supervisor_inst.stop()!
println(' Supervisor stopped')
println('\n Stopping Coordinator...')
mut coordinator := herocoordinator.get()!
coordinator.stop()!
println(' Coordinator stopped')
println('\n' + '=' * 60)
println(' All Horus services stopped!')
println('=' * 60)

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.horus.coordinator
import incubaid.herolib.installers.horus.supervisor
// Quick Start Example - Install and Start Coordinator and Supervisor
// This is a minimal example to get started with Horus
println('🚀 Horus Quick Start')
println('=' * 60)
println('This will install and start Coordinator and Supervisor')
println('(Runners can be added later using the full install script)')
println('=' * 60)
// Install Coordinator
println('\n📦 Installing Coordinator...')
mut coordinator := herocoordinator.get(create: true)!
coordinator.install()!
println(' Coordinator installed')
// Install Supervisor
println('\n📦 Installing Supervisor...')
mut supervisor_inst := supervisor.get(create: true)!
supervisor_inst.install()!
println(' Supervisor installed')
// Start services
println('\n Starting Coordinator...')
coordinator.start()!
if coordinator.running()! {
println(' Coordinator is running on http://127.0.0.1:${coordinator.http_port}')
}
println('\n Starting Supervisor...')
supervisor_inst.start()!
if supervisor_inst.running()! {
println(' Supervisor is running on http://127.0.0.1:${supervisor_inst.http_port}')
}
println('\n' + '=' * 60)
println('🎉 Quick Start Complete!')
println('=' * 60)
println('\n📊 Services Running:')
println(' Coordinator: http://127.0.0.1:${coordinator.http_port}')
println(' Supervisor: http://127.0.0.1:${supervisor_inst.http_port}')
println('\n💡 Next Steps:')
println(' Test coordinator: curl http://127.0.0.1:${coordinator.http_port}')
println(' Test supervisor: curl http://127.0.0.1:${supervisor_inst.http_port}')
println(' Install runners: ./horus_full_install.vsh')
println(' Check status: ./horus_status.vsh')
println(' Stop services: ./horus_stop_all.vsh')

View File

@@ -17,7 +17,7 @@ pub mut:
pub fn (b BizModel) export(args ExportArgs) ! { pub fn (b BizModel) export(args ExportArgs) ! {
name := if args.name != '' { args.name } else { texttools.snake_case(args.title) } name := if args.name != '' { args.name } else { texttools.snake_case(args.title) }
path := pathlib.get_dir( path := pathlib.get_dir(
path: os.join_path(os.home_dir(), '/hero/var/bizmodel/exports/${name}') path: os.join_path(os.home_dir(), 'hero/var/bizmodel/exports/${name}')
create: true create: true
empty: true empty: true
)! )!

View File

@@ -1,7 +1,7 @@
!!hero_code.generate_installer !!hero_code.generate_installer
name:'' name:''
classname:'HerocoordinatorServer' classname:'CoordinatorServer'
singleton:0 singleton:0
templates:1 templates:1
default:1 default:1

View File

@@ -2,11 +2,11 @@ module coordinator
import incubaid.herolib.osal.core as osal import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console import incubaid.herolib.ui.console
import incubaid.herolib.core.texttools
import incubaid.herolib.core.pathlib import incubaid.herolib.core.pathlib
import incubaid.herolib.osal.startupmanager import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist import incubaid.herolib.installers.ulist
import incubaid.herolib.installers.lang.rust import incubaid.herolib.installers.lang.rust
import incubaid.herolib.installers.base.redis
import incubaid.herolib.develop.gittools import incubaid.herolib.develop.gittools
import os import os
@@ -34,21 +34,16 @@ fn running() !bool {
return res.exit_code == 0 return res.exit_code == 0
} }
// Lifecycle hooks - can be implemented for custom pre/post actions
fn start_pre() ! { fn start_pre() ! {
// Add any pre-start actions here if needed
} }
fn start_post() ! { fn start_post() ! {
// Add any post-start actions here if needed
} }
fn stop_pre() ! { fn stop_pre() ! {
// Add any pre-stop actions here if needed
} }
fn stop_post() ! { fn stop_post() ! {
// Add any post-stop actions here if needed
} }
//////////////////// following actions are not specific to instance of the object //////////////////// following actions are not specific to instance of the object
@@ -80,80 +75,66 @@ fn upload() ! {
// )! // )!
} }
// Helper function to ensure Redis is installed and running
@[params]
struct EnsureRedisArgs {
pub mut:
redis_port int = 6379
redis_addr string = 'localhost'
datadir string = '/var/lib/redis'
}
fn ensure_redis_running(args EnsureRedisArgs) ! {
redis_config := redis.RedisInstall{
port: args.redis_port
datadir: args.datadir
ipaddr: args.redis_addr
}
if !redis.check(redis_config) {
println('Installing and starting Redis on ${args.redis_addr}:${args.redis_port}...')
redis.redis_install(redis_config)!
} else {
println('Redis is already running on ${args.redis_addr}:${args.redis_port}')
}
}
fn install() ! { fn install() ! {
console.print_header('install coordinator') console.print_header('install coordinator')
// For coordinator, we build from source instead of downloading // For coordinator, we build from source instead of downloading
build()! build()!
} }
// Public build function that works with or without Redis/factory available // Public function to build coordinator without requiring factory/redis
pub fn build() ! { pub fn build_coordinator() ! {
console.print_header('build coordinator') console.print_header('build coordinator')
println('Starting coordinator build process...\n') println('📦 Starting coordinator build process...\n')
// Try to get config from factory, fallback to default if Redis not available // Use default config instead of getting from factory
println('Initializing configuration...') println(' Initializing configuration...')
mut cfg_ref := get() or { mut cfg := CoordinatorServer{}
console.print_debug('Factory not available, using default config') println(' Configuration initialized')
mut default_cfg := CoordinatorServer{}
_ := set_in_mem(default_cfg)!
coordinator_global[default_cfg.name]
}
mut cfg := *cfg_ref
println('Configuration initialized')
println(' - Binary path: ${cfg.binary_path}') println(' - Binary path: ${cfg.binary_path}')
println(' - Redis address: ${cfg.redis_addr}') println(' - Redis address: ${cfg.redis_addr}')
println(' - HTTP port: ${cfg.http_port}') println(' - HTTP port: ${cfg.http_port}')
println(' - WS port: ${cfg.ws_port}\n') println(' - WS port: ${cfg.ws_port}\n')
// Ensure Redis is installed and running (required for coordinator) // Ensure Redis is installed and running (required for coordinator)
println('Step 1/4: Checking Redis dependency...') println('🔍 Step 1/4: Checking Redis dependency...')
// Parse redis_addr to extract host
parts := cfg.redis_addr.split(':') // First check if redis-server is installed
redis_host := if parts.len > 0 { parts[0] } else { 'localhost' } if !osal.cmd_exists_profile('redis-server') {
ensure_redis_running(redis_port: cfg.redis_port, redis_addr: redis_host)! println(' Redis is not installed')
println('Redis is ready\n') println('📥 Installing Redis...')
osal.package_install('redis-server')!
println(' Redis installed')
} else {
println(' Redis is already installed')
}
// Now check if it's running
println('🔍 Checking if Redis is running...')
redis_check := osal.exec(cmd: 'redis-cli -c -p 6379 ping', stdout: false, raise_error: false)!
if redis_check.exit_code != 0 {
println(' Redis is not running')
println('🚀 Starting Redis...')
osal.exec(cmd: 'systemctl start redis-server')!
println(' Redis started successfully\n')
} else {
println(' Redis is already running\n')
}
// Ensure rust is installed // Ensure rust is installed
println('Step 2/4: Checking Rust dependency...') println('🔍 Step 2/4: Checking Rust dependency...')
mut rust_installer := rust.get()! mut rust_installer := rust.get()!
res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)! res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)!
if res.exit_code != 0 { if res.exit_code != 0 {
println('Installing Rust...') println('📥 Installing Rust...')
rust_installer.install()! rust_installer.install()!
println('Rust installed\n') println(' Rust installed\n')
} else { } else {
println('Rust is already installed: ${res.output.trim_space()}\n') println(' Rust is already installed: ${res.output.trim_space()}\n')
} }
// Clone or get the repository // Clone or get the repository
println('Step 3/4: Cloning/updating horus repository...') println('🔍 Step 3/4: Cloning/updating horus repository...')
// Use the configured repo_path or default coderoot mut gs := gittools.new()!
mut gs := gittools.new(coderoot: '/root/code')!
mut repo := gs.get_repo( mut repo := gs.get_repo(
url: 'https://git.ourworld.tf/herocode/horus.git' url: 'https://git.ourworld.tf/herocode/horus.git'
pull: true pull: true
@@ -162,40 +143,103 @@ pub fn build() ! {
// Update the path to the actual cloned repo // Update the path to the actual cloned repo
cfg.repo_path = repo.path() cfg.repo_path = repo.path()
println('Repository ready at: ${cfg.repo_path}\n') println(' Repository ready at: ${cfg.repo_path}\n')
// Build the coordinator binary from the horus workspace // Build the coordinator binary from the horus workspace
println('Step 4/4: Building coordinator binary...') println('🔍 Step 4/4: Building coordinator binary...')
println('WARNING: This may take several minutes (compiling Rust code)...') println(' This may take several minutes (compiling Rust code)...')
println('Running: cargo build -p hero-coordinator --release\n') println('📝 Running: cargo build -p hero-coordinator --release\n')
cmd := 'cd ${cfg.repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p hero-coordinator --release' cmd := 'cd ${cfg.repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p hero-coordinator --release'
osal.execute_stdout(cmd)! osal.execute_stdout(cmd)!
println('\nBuild completed successfully') println('\n Build completed successfully')
// Ensure binary directory exists and copy the binary // Ensure binary directory exists and copy the binary
println('Preparing binary directory: ${cfg.binary_path}') println('📁 Preparing binary directory: ${cfg.binary_path}')
mut binary_path_obj := pathlib.get(cfg.binary_path) mut binary_path_obj := pathlib.get(cfg.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())! osal.dir_ensure(binary_path_obj.path_dir())!
// Copy the built binary to the configured location // Copy the built binary to the configured location
source_binary := '${cfg.repo_path}/target/release/coordinator' source_binary := '${cfg.repo_path}/target/release/coordinator'
println('Copying binary from: ${source_binary}') println('📋 Copying binary from: ${source_binary}')
println('Copying binary to: ${cfg.binary_path}') println('📋 Copying binary to: ${cfg.binary_path}')
mut source_file := pathlib.get_file(path: source_binary)!
// Verify source binary exists before copying
mut source_file := pathlib.get_file(path: source_binary) or {
return error('Built binary not found at ${source_binary}. Build may have failed.')
}
if !source_file.exists() {
return error('Built binary not found at ${source_binary}. Build may have failed.')
}
source_file.copy(dest: cfg.binary_path, rsync: false)! source_file.copy(dest: cfg.binary_path, rsync: false)!
println('\nCoordinator built successfully!') println('\n🎉 Coordinator built successfully!')
println('Binary location: ${cfg.binary_path}') println('📍 Binary location: ${cfg.binary_path}')
}
fn build() ! {
console.print_header('build coordinator')
mut cfg := get()!
// Ensure Redis is installed and running (required for coordinator)
console.print_debug('Checking if Redis is installed and running...')
redis_check := osal.exec(cmd: 'redis-cli -c -p 6379 ping', stdout: false, raise_error: false)!
if redis_check.exit_code != 0 {
console.print_header('Redis is not running, checking if installed...')
if !osal.cmd_exists_profile('redis-server') {
console.print_header('Installing Redis...')
osal.package_install('redis-server')!
}
console.print_header('Starting Redis...')
osal.exec(cmd: 'systemctl start redis-server')!
console.print_debug('Redis started successfully')
} else {
console.print_debug('Redis is already running')
}
// Ensure rust is installed
console.print_debug('Checking if Rust is installed...')
mut rust_installer := rust.get()!
res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)!
if res.exit_code != 0 {
console.print_header('Installing Rust first...')
rust_installer.install()!
} else {
console.print_debug('Rust is already installed: ${res.output.trim_space()}')
}
// Clone or get the repository
console.print_debug('Cloning/updating horus repository...')
mut gs := gittools.new()!
mut repo := gs.get_repo(
url: 'https://git.ourworld.tf/herocode/horus.git'
pull: true
reset: false
)!
// Update the path to the actual cloned repo
cfg.repo_path = repo.path()
set(cfg)!
console.print_debug('Repository path: ${cfg.repo_path}')
// Build the coordinator binary from the horus workspace
console.print_header('Building coordinator binary (this may take several minutes ${cfg.repo_path})...')
console.print_debug('Running: cargo build -p hero-coordinator --release')
console.print_debug('Build output:')
cmd := 'cd ${cfg.repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p hero-coordinator --release'
osal.execute_stdout(cmd)!
console.print_debug('Build completed successfully')
// Ensure binary directory exists and copy the binary
console.print_header('Preparing binary directory: ${cfg.binary_path}')
mut binary_path_obj := pathlib.get(cfg.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
// Copy the built binary to the configured location
source_binary := '${cfg.repo_path}/target/release/coordinator'
console.print_debug('Copying binary from: ${source_binary}')
console.print_debug('Copying binary to: ${cfg.binary_path}')
mut source_file := pathlib.get_file(path: source_binary)!
source_file.copy(dest: cfg.binary_path, rsync: false)!
console.print_header('coordinator built successfully at ${cfg.binary_path}')
} }
fn destroy() ! { fn destroy() ! {

View File

@@ -38,15 +38,7 @@ pub fn new(args ArgsGet) !&CoordinatorServer {
log_level: args.log_level log_level: args.log_level
repo_path: args.repo_path repo_path: args.repo_path
} }
// Try to set in Redis, if it fails (Redis not available), build first
set(obj) or {
console.print_header('Redis not available, installing dependencies first...')
build()! // build() now handles both factory and non-factory cases
// Now try again with Redis available
set(obj)! set(obj)!
}
return get(name: args.name)! return get(name: args.name)!
} }
@@ -215,8 +207,9 @@ fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.Sta
return startupmanager.get(.systemd)! return startupmanager.get(.systemd)!
} }
else { else {
// default to zinit
console.print_debug("installer: coordinator' startupmanager get auto") console.print_debug("installer: coordinator' startupmanager get auto")
return startupmanager.get(.auto)! return startupmanager.get(.zinit)!
} }
} }
} }
@@ -229,6 +222,7 @@ pub fn (mut self CoordinatorServer) reload() ! {
pub fn (mut self CoordinatorServer) start() ! { pub fn (mut self CoordinatorServer) start() ! {
switch(self.name) switch(self.name)
if self.running()! { if self.running()! {
return return
} }
@@ -242,7 +236,6 @@ pub fn (mut self CoordinatorServer) start() ! {
configure()! configure()!
start_pre()! start_pre()!
for zprocess in startupcmd()! { for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)! mut sm := startupmanager_get(zprocess.startuptype)!

View File

@@ -1,5 +1,7 @@
module coordinator module coordinator
import os
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero import incubaid.herolib.data.encoderhero
import incubaid.herolib.osal.core as osal import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.pathlib import incubaid.herolib.core.pathlib
@@ -13,33 +15,26 @@ const default = true
pub struct CoordinatorServer { pub struct CoordinatorServer {
pub mut: pub mut:
name string = 'default' name string = 'default'
binary_path string = '/hero/var/bin/coordinator' binary_path string = os.join_path(os.home_dir(), 'hero/bin/coordinator')
redis_addr string = '127.0.0.1:6379' redis_addr string = '127.0.0.1:6379'
redis_port int = 6379
http_port int = 8081 http_port int = 8081
ws_port int = 9653 ws_port int = 9653
log_level string = 'info' log_level string = 'info'
repo_path string = '/root/code/git.ourworld.tf/herocode/horus' repo_path string = '/root/code/git.ourworld.tf/herocode/horus'
} }
// Initialize configuration with defaults if not provided // your checking & initialization code if needed
// Note: Struct defaults are already set, this ensures runtime overrides work correctly
fn obj_init(mycfg_ CoordinatorServer) !CoordinatorServer { fn obj_init(mycfg_ CoordinatorServer) !CoordinatorServer {
mut mycfg := mycfg_ mut mycfg := mycfg_
// Only override if explicitly empty (struct defaults should handle most cases)
if mycfg.name == '' { if mycfg.name == '' {
mycfg.name = 'default' mycfg.name = 'default'
} }
if mycfg.binary_path == '' { if mycfg.binary_path == '' {
mycfg.binary_path = '/hero/var/bin/coordinator' mycfg.binary_path = os.join_path(os.home_dir(),'hero/bin/coordinator')
} }
if mycfg.redis_addr == '' { if mycfg.redis_addr == '' {
mycfg.redis_addr = '127.0.0.1:6379' mycfg.redis_addr = '127.0.0.1:6379'
} }
if mycfg.redis_port == 0 {
mycfg.redis_port = 6379
}
if mycfg.http_port == 0 { if mycfg.http_port == 0 {
mycfg.http_port = 8081 mycfg.http_port = 8081
} }
@@ -52,7 +47,6 @@ fn obj_init(mycfg_ CoordinatorServer) !CoordinatorServer {
if mycfg.repo_path == '' { if mycfg.repo_path == '' {
mycfg.repo_path = '/root/code/git.ourworld.tf/herocode/horus' mycfg.repo_path = '/root/code/git.ourworld.tf/herocode/horus'
} }
return mycfg return mycfg
} }

View File

@@ -1,6 +1,6 @@
# Herocoordinator Installer # Coordinator Installer
A V language installer module for building and managing the Herocoordinator service. This installer handles the complete lifecycle of the Herocoordinator binary from the Horus workspace. A V language installer module for building and managing the Coordinator service. This installer handles the complete lifecycle of the Coordinator binary from the Horus workspace.
## Features ## Features
@@ -16,23 +16,23 @@ A V language installer module for building and managing the Herocoordinator serv
```bash ```bash
cd /root/code/github/incubaid/herolib/examples/installers/infra cd /root/code/github/incubaid/herolib/examples/installers/infra
./herocoordinator.vsh ./coordinator.vsh
``` ```
### Manual Usage ### Manual Usage
```v ```v
import incubaid.herolib.installers.infra.herocoordinator as herocoordinator_installer import incubaid.herolib.installers.infra.coordinator as coordinator_installer
mut herocoordinator := herocoordinator_installer.get()! mut coordinator := coordinator_installer.get()!
herocoordinator.install()! coordinator.install()!
herocoordinator.start()! coordinator.start()!
``` ```
## Configuration ## Configuration
```bash ```bash
!!herocoordinator.configure !!coordinator.configure
name:'default' name:'default'
binary_path:'/hero/var/bin/coordinator' binary_path:'/hero/var/bin/coordinator'
redis_addr:'127.0.0.1:6379' redis_addr:'127.0.0.1:6379'
@@ -61,35 +61,35 @@ Builds the coordinator binary from the horus workspace. This will:
3. Build the coordinator binary with `cargo build -p hero-coordinator --release` 3. Build the coordinator binary with `cargo build -p hero-coordinator --release`
```bash ```bash
hero herocoordinator.install hero coordinator.install
``` ```
### Start ### Start
Starts the herocoordinator service using zinit: Starts the coordinator service using zinit:
```bash ```bash
hero herocoordinator.start hero coordinator.start
``` ```
### Stop ### Stop
Stops the running service: Stops the running service:
```bash ```bash
hero herocoordinator.stop hero coordinator.stop
``` ```
### Restart ### Restart
Restarts the service: Restarts the service:
```bash ```bash
hero herocoordinator.restart hero coordinator.restart
``` ```
### Destroy ### Destroy
Stops the service and removes all files: Stops the service and removes all files:
```bash ```bash
hero herocoordinator.destroy hero coordinator.destroy
``` ```
## Requirements ## Requirements
@@ -104,9 +104,9 @@ hero herocoordinator.destroy
The installer follows the standard herolib installer pattern: The installer follows the standard herolib installer pattern:
- **herocoordinator_model.v**: Configuration structure and initialization - **coordinator_model.v**: Configuration structure and initialization
- **herocoordinator_actions.v**: Build, install, start, stop, destroy logic - **coordinator_actions.v**: Build, install, start, stop, destroy logic
- **herocoordinator_factory_.v**: Factory pattern for instance management - **coordinator_factory_.v**: Factory pattern for instance management
## Notes ## Notes
@@ -119,7 +119,7 @@ The installer follows the standard herolib installer pattern:
## Example Workflow ## Example Workflow
```v ```v
import incubaid.herolib.installers.infra.herocoordinator as hc import incubaid.herolib.installers.infra.coordinator as hc
// Get installer instance // Get installer instance
mut coordinator := hc.get()! mut coordinator := hc.get()!

View File

@@ -0,0 +1,12 @@
!!hero_code.generate_installer
name:''
classname:'HerorunnerServer'
singleton:0
templates:1
default:1
title:''
supported_platforms:''
startupmanager:1
hasconfig:1
build:1

View File

@@ -0,0 +1,139 @@
module herorunner
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
import incubaid.herolib.core.pathlib
import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist
import incubaid.herolib.installers.lang.rust
import incubaid.herolib.develop.gittools
import os
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
mut cfg := get()!
mut res := []startupmanager.ZProcessNewArgs{}
res << startupmanager.ZProcessNewArgs{
name: 'herorunner'
cmd: '${cfg.binary_path} --redis-addr ${cfg.redis_addr}'
env: {
'HOME': os.home_dir()
'RUST_LOG': cfg.log_level
'RUST_LOG_STYLE': 'never'
}
}
return res
}
fn running() !bool {
// Check if the process is running
res := osal.exec(cmd: 'pgrep -f herorunner', stdout: false, raise_error: false)!
return res.exit_code == 0
}
fn start_pre() ! {
}
fn start_post() ! {
}
fn stop_pre() ! {
}
fn stop_post() ! {
}
//////////////////// following actions are not specific to instance of the object
// checks if a certain version or above is installed
fn installed() !bool {
mut cfg := get()!
// Check if the binary exists
mut binary := pathlib.get(cfg.binary_path)
if !binary.exists() {
return false
}
return true
}
// get the Upload List of the files
fn ulist_get() !ulist.UList {
// optionally build a UList which is all paths which are result of building, is then used e.g. in upload
return ulist.UList{}
}
// uploads to S3 server if configured
fn upload() ! {
}
fn install() ! {
console.print_header('install herorunner')
// For herorunner, we build from source instead of downloading
build()!
}
fn build() ! {
console.print_header('build herorunner')
mut cfg := get()!
// Ensure rust is installed
console.print_debug('Checking if Rust is installed...')
mut rust_installer := rust.get()!
res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)!
if res.exit_code != 0 {
console.print_header('Installing Rust first...')
rust_installer.install()!
} else {
console.print_debug('Rust is already installed: ${res.output.trim_space()}')
}
// Clone or get the repository
console.print_debug('Cloning/updating horus repository...')
mut gs := gittools.new()!
mut repo := gs.get_repo(
url: 'https://git.ourworld.tf/herocode/horus.git'
pull: true
reset: false
)!
repo_path := repo.path()
console.print_debug('Repository path: ${repo_path}')
// Build the herorunner binary from the horus workspace
console.print_header('Building herorunner binary (this may take several minutes)...')
console.print_debug('Running: cargo build -p runner-hero --release')
console.print_debug('Build output:')
cmd := 'cd ${repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p runner-hero --release'
osal.execute_stdout(cmd)!
console.print_debug('Build completed successfully')
// Ensure binary directory exists and copy the binary
console.print_debug('Preparing binary directory: ${cfg.binary_path}')
mut binary_path_obj := pathlib.get(cfg.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
// Copy the built binary to the configured location
source_binary := '${repo_path}/target/release/herorunner'
console.print_debug('Copying binary from: ${source_binary}')
console.print_debug('Copying binary to: ${cfg.binary_path}')
mut source_file := pathlib.get_file(path: source_binary)!
source_file.copy(dest: cfg.binary_path, rsync: false)!
console.print_header('herorunner built successfully at ${cfg.binary_path}')
}
fn destroy() ! {
mut server := get()!
server.stop()!
osal.process_kill_recursive(name: 'herorunner')!
// Remove the built binary
osal.rm(server.binary_path)!
}

View File

@@ -0,0 +1,318 @@
module herorunner
import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console
import json
import incubaid.herolib.osal.startupmanager
import time
__global (
herorunner_global map[string]&HerorunnerServer
herorunner_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
binary_path string
redis_addr string
log_level string
fromdb bool // will load from filesystem
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&HerorunnerServer {
mut obj := HerorunnerServer{
name: args.name
binary_path: args.binary_path
redis_addr: args.redis_addr
log_level: args.log_level
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&HerorunnerServer {
mut context := base.context()!
herorunner_default = args.name
if args.fromdb || args.name !in herorunner_global {
mut r := context.redis()!
if r.hexists('context:herorunner', args.name)! {
data := r.hget('context:herorunner', args.name)!
if data.len == 0 {
print_backtrace()
return error('HerorunnerServer with name: ${args.name} does not exist, prob bug.')
}
mut obj := json.decode(HerorunnerServer, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
print_backtrace()
return error("HerorunnerServer with name '${args.name}' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return herorunner_global[args.name] or {
print_backtrace()
return error('could not get config for herorunner with name:${args.name}')
}
}
// register the config for the future
pub fn set(o HerorunnerServer) ! {
mut o2 := set_in_mem(o)!
herorunner_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:herorunner', o2.name, json.encode(o2))!
}
// does the config exists?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:herorunner', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:herorunner', args.name)!
}
@[params]
pub struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&HerorunnerServer {
mut res := []&HerorunnerServer{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
herorunner_global = map[string]&HerorunnerServer{}
herorunner_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:herorunner')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in herorunner_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o HerorunnerServer) !HerorunnerServer {
mut o2 := obj_init(o)!
herorunner_global[o2.name] = &o2
herorunner_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'herorunner.') {
return
}
mut install_actions := plbook.find(filter: 'herorunner.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
install_action.done = true
}
}
mut other_actions := plbook.find(filter: 'herorunner.')!
for mut other_action in other_actions {
if other_action.name in ['destroy', 'install', 'build'] {
mut p := other_action.params
reset := p.get_default_false('reset')
if other_action.name == 'destroy' || reset {
console.print_debug('install action herorunner.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action herorunner.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut herorunner_obj := get(name: name)!
console.print_debug('action object:\n${herorunner_obj}')
if other_action.name == 'start' {
console.print_debug('install action herorunner.${other_action.name}')
herorunner_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action herorunner.${other_action.name}')
herorunner_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action herorunner.${other_action.name}')
herorunner_obj.restart()!
}
}
other_action.done = true
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.screen {
console.print_debug("installer: herorunner' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: herorunner' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: herorunner' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: herorunner' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self HerorunnerServer) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self HerorunnerServer) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('installer: herorunner start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: herorunner starting with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.start(zprocess.name)!
}
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('herorunner did not install properly.')
}
pub fn (mut self HerorunnerServer) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self HerorunnerServer) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
}
pub fn (mut self HerorunnerServer) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self HerorunnerServer) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
if zprocess.startuptype != .screen {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
}
return running()!
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self HerorunnerServer) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self HerorunnerServer) build() ! {
switch(self.name)
build()!
}
pub fn (mut self HerorunnerServer) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for herorunner
pub fn switch(name string) {
herorunner_default = name
}

View File

@@ -0,0 +1,58 @@
module herorunner
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.pathlib
import os
const version = '0.1.0'
const singleton = true
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct HerorunnerServer {
pub mut:
name string = 'default'
binary_path string = os.join_path(os.home_dir(), 'hero/bin/herorunner')
redis_addr string = '127.0.0.1:6379'
log_level string = 'info'
}
// your checking & initialization code if needed
fn obj_init(mycfg_ HerorunnerServer) !HerorunnerServer {
mut mycfg := mycfg_
if mycfg.name == '' {
mycfg.name = 'default'
}
if mycfg.binary_path == '' {
mycfg.binary_path = os.join_path(os.home_dir(), 'hero/bin/herorunner')
}
if mycfg.redis_addr == '' {
mycfg.redis_addr = '127.0.0.1:6379'
}
if mycfg.log_level == '' {
mycfg.log_level = 'info'
}
return mycfg
}
// called before start if done
fn configure() ! {
mut server := get()!
// Ensure the binary directory exists
mut binary_path_obj := pathlib.get(server.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj HerorunnerServer) !string {
return encoderhero.encode[HerorunnerServer](obj)!
}
pub fn heroscript_loads(heroscript string) !HerorunnerServer {
mut obj := encoderhero.decode[HerorunnerServer](heroscript)!
return obj
}

View File

@@ -0,0 +1,104 @@
# Herorunner Installer
A V language installer module for building and managing the Hero Runner service. This installer handles the complete lifecycle of the Herorunner binary from the Horus workspace.
## Features
- **Automatic Rust Installation**: Installs Rust toolchain if not present
- **Git Repository Management**: Clones and manages the horus repository
- **Binary Building**: Compiles the herorunner binary from the horus workspace
- **Service Management**: Start/stop/restart via zinit
- **Configuration**: Customizable Redis connection
## Quick Start
### Manual Usage
```v
import freeflowuniverse.herolib.installers.horus.herorunner as herorunner_installer
mut herorunner := herorunner_installer.get()!
herorunner.install()!
herorunner.start()!
```
## Configuration
```bash
!!herorunner.configure
name:'default'
binary_path:'/hero/var/bin/herorunner'
redis_addr:'127.0.0.1:6379'
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
```
### Configuration Fields
- **name**: Instance name (default: 'default')
- **binary_path**: Path where the herorunner binary will be installed (default: '/hero/var/bin/herorunner')
- **redis_addr**: Redis server address (default: '127.0.0.1:6379')
- **log_level**: Rust log level - trace, debug, info, warn, error (default: 'info')
- **repo_path**: Path to clone the horus repository (default: '/root/code/git.ourworld.tf/herocode/horus')
## Commands
### Install
Builds the herorunner binary from the horus workspace. This will:
1. Install Rust if not present
2. Clone the horus repository from git.ourworld.tf
3. Build the herorunner binary with `cargo build -p runner-hero --release`
```bash
hero herorunner.install
```
### Start
Starts the herorunner service using zinit:
```bash
hero herorunner.start
```
### Stop
Stops the running service:
```bash
hero herorunner.stop
```
### Restart
Restarts the service:
```bash
hero herorunner.restart
```
### Destroy
Stops the service and removes all files:
```bash
hero herorunner.destroy
```
## Requirements
- **Dependencies**:
- Rust toolchain (automatically installed)
- Git (for cloning repository)
- Redis (must be running separately)
## Architecture
The installer follows the standard herolib installer pattern:
- **herorunner_model.v**: Configuration structure and initialization
- **herorunner_actions.v**: Build, install, start, stop, destroy logic
- **herorunner_factory_.v**: Factory pattern for instance management
## Notes
- The installer builds from source rather than downloading pre-built binaries
- Redis must be running and accessible at the configured address
- The binary is built with `RUSTFLAGS="-A warnings"` to suppress warnings
- Service management uses zinit by default

View File

@@ -0,0 +1,5 @@
name: ${cfg.configpath}

View File

@@ -0,0 +1,12 @@
!!hero_code.generate_installer
name:''
classname:'OsirisrunnerServer'
singleton:0
templates:1
default:1
title:''
supported_platforms:''
startupmanager:1
hasconfig:1
build:1

View File

@@ -0,0 +1,142 @@
module osirisrunner
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
import incubaid.herolib.core.pathlib
import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist
import incubaid.herolib.installers.lang.rust
import incubaid.herolib.develop.gittools
import os
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
mut cfg := get()!
mut res := []startupmanager.ZProcessNewArgs{}
res << startupmanager.ZProcessNewArgs{
name: 'runner_osiris'
cmd: '${cfg.binary_path} --redis-addr ${cfg.redis_addr}'
env: {
'HOME': os.home_dir()
'RUST_LOG': cfg.log_level
'RUST_LOG_STYLE': 'never'
}
}
return res
}
fn running() !bool {
mut cfg := get()!
// Check if the process is running
res := osal.exec(cmd: 'pgrep -f runner_osiris', stdout: false, raise_error: false)!
return res.exit_code == 0
}
fn start_pre() ! {
}
fn start_post() ! {
}
fn stop_pre() ! {
}
fn stop_post() ! {
}
//////////////////// following actions are not specific to instance of the object
// checks if a certain version or above is installed
fn installed() !bool {
mut cfg := get()!
// Check if the binary exists
mut binary := pathlib.get(cfg.binary_path)
if !binary.exists() {
return false
}
return true
}
// get the Upload List of the files
fn ulist_get() !ulist.UList {
// optionally build a UList which is all paths which are result of building, is then used e.g. in upload
return ulist.UList{}
}
// uploads to S3 server if configured
fn upload() ! {
}
fn install() ! {
console.print_header('install osirisrunner')
// For osirisrunner, we build from source instead of downloading
build()!
}
fn build() ! {
console.print_header('build osirisrunner')
mut cfg := get()!
// Ensure rust is installed
console.print_debug('Checking if Rust is installed...')
mut rust_installer := rust.get()!
res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)!
if res.exit_code != 0 {
console.print_header('Installing Rust first...')
rust_installer.install()!
} else {
console.print_debug('Rust is already installed: ${res.output.trim_space()}')
}
// Clone or get the repository
console.print_debug('Cloning/updating horus repository...')
mut gs := gittools.new()!
mut repo := gs.get_repo(
url: 'https://git.ourworld.tf/herocode/horus.git'
pull: true
reset: false
)!
// Update the path to the actual cloned repo
cfg.repo_path = repo.path()
set(cfg)!
console.print_debug('Repository path: ${cfg.repo_path}')
// Build the osirisrunner binary from the horus workspace
console.print_header('Building osirisrunner binary (this may take several minutes)...')
console.print_debug('Running: cargo build -p runner-osiris --release')
console.print_debug('Build output:')
cmd := 'cd ${cfg.repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p runner-osiris --release'
osal.execute_stdout(cmd)!
console.print_debug('Build completed successfully')
// Ensure binary directory exists and copy the binary
console.print_debug('Preparing binary directory: ${cfg.binary_path}')
mut binary_path_obj := pathlib.get(cfg.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
// Copy the built binary to the configured location
source_binary := '${cfg.repo_path}/target/release/runner_osiris'
console.print_debug('Copying binary from: ${source_binary}')
console.print_debug('Copying binary to: ${cfg.binary_path}')
mut source_file := pathlib.get_file(path: source_binary)!
source_file.copy(dest: cfg.binary_path, rsync: false)!
console.print_header('osirisrunner built successfully at ${cfg.binary_path}')
}
fn destroy() ! {
mut server := get()!
server.stop()!
osal.process_kill_recursive(name: 'runner_osiris')!
// Remove the built binary
osal.rm(server.binary_path)!
}

View File

@@ -0,0 +1,320 @@
module osirisrunner
import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console
import json
import incubaid.herolib.osal.startupmanager
import time
__global (
osirisrunner_global map[string]&OsirisrunnerServer
osirisrunner_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
binary_path string
redis_addr string
log_level string
repo_path string
fromdb bool // will load from filesystem
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&OsirisrunnerServer {
mut obj := OsirisrunnerServer{
name: args.name
binary_path: args.binary_path
redis_addr: args.redis_addr
log_level: args.log_level
repo_path: args.repo_path
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&OsirisrunnerServer {
mut context := base.context()!
osirisrunner_default = args.name
if args.fromdb || args.name !in osirisrunner_global {
mut r := context.redis()!
if r.hexists('context:osirisrunner', args.name)! {
data := r.hget('context:osirisrunner', args.name)!
if data.len == 0 {
print_backtrace()
return error('OsirisrunnerServer with name: ${args.name} does not exist, prob bug.')
}
mut obj := json.decode(OsirisrunnerServer, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
print_backtrace()
return error("OsirisrunnerServer with name '${args.name}' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return osirisrunner_global[args.name] or {
print_backtrace()
return error('could not get config for osirisrunner with name:${args.name}')
}
}
// register the config for the future
pub fn set(o OsirisrunnerServer) ! {
mut o2 := set_in_mem(o)!
osirisrunner_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:osirisrunner', o2.name, json.encode(o2))!
}
// does the config exists?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:osirisrunner', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:osirisrunner', args.name)!
}
@[params]
pub struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&OsirisrunnerServer {
mut res := []&OsirisrunnerServer{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
osirisrunner_global = map[string]&OsirisrunnerServer{}
osirisrunner_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:osirisrunner')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in osirisrunner_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o OsirisrunnerServer) !OsirisrunnerServer {
mut o2 := obj_init(o)!
osirisrunner_global[o2.name] = &o2
osirisrunner_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'osirisrunner.') {
return
}
mut install_actions := plbook.find(filter: 'osirisrunner.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
install_action.done = true
}
}
mut other_actions := plbook.find(filter: 'osirisrunner.')!
for mut other_action in other_actions {
if other_action.name in ['destroy', 'install', 'build'] {
mut p := other_action.params
reset := p.get_default_false('reset')
if other_action.name == 'destroy' || reset {
console.print_debug('install action osirisrunner.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action osirisrunner.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut osirisrunner_obj := get(name: name)!
console.print_debug('action object:\n${osirisrunner_obj}')
if other_action.name == 'start' {
console.print_debug('install action osirisrunner.${other_action.name}')
osirisrunner_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action osirisrunner.${other_action.name}')
osirisrunner_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action osirisrunner.${other_action.name}')
osirisrunner_obj.restart()!
}
}
other_action.done = true
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.screen {
console.print_debug("installer: osirisrunner' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: osirisrunner' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: osirisrunner' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: osirisrunner' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self OsirisrunnerServer) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self OsirisrunnerServer) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('installer: osirisrunner start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: osirisrunner starting with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.start(zprocess.name)!
}
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('osirisrunner did not install properly.')
}
pub fn (mut self OsirisrunnerServer) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self OsirisrunnerServer) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
}
pub fn (mut self OsirisrunnerServer) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self OsirisrunnerServer) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
if zprocess.startuptype != .screen {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
}
return running()!
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self OsirisrunnerServer) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self OsirisrunnerServer) build() ! {
switch(self.name)
build()!
}
pub fn (mut self OsirisrunnerServer) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for osirisrunner
pub fn switch(name string) {
osirisrunner_default = name
}

View File

@@ -0,0 +1,62 @@
module osirisrunner
import os
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.pathlib
const version = '0.1.0'
const singleton = true
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct OsirisrunnerServer {
pub mut:
name string = 'default'
binary_path string = os.join_path(os.home_dir(), 'hero/bin/runner_osiris')
redis_addr string = '127.0.0.1:6379'
log_level string = 'info'
repo_path string = '/root/code/git.ourworld.tf/herocode/horus'
}
// your checking & initialization code if needed
fn obj_init(mycfg_ OsirisrunnerServer) !OsirisrunnerServer {
mut mycfg := mycfg_
if mycfg.name == '' {
mycfg.name = 'default'
}
if mycfg.binary_path == '' {
mycfg.binary_path = os.join_path(os.home_dir(), 'hero/bin/runner_osiris')
}
if mycfg.redis_addr == '' {
mycfg.redis_addr = '127.0.0.1:6379'
}
if mycfg.log_level == '' {
mycfg.log_level = 'info'
}
if mycfg.repo_path == '' {
mycfg.repo_path = '/root/code/git.ourworld.tf/herocode/horus'
}
return mycfg
}
// called before start if done
fn configure() ! {
mut server := get()!
// Ensure the binary directory exists
mut binary_path_obj := pathlib.get(server.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj OsirisrunnerServer) !string {
return encoderhero.encode[OsirisrunnerServer](obj)!
}
pub fn heroscript_loads(heroscript string) !OsirisrunnerServer {
mut obj := encoderhero.decode[OsirisrunnerServer](heroscript)!
return obj
}

View File

@@ -0,0 +1,104 @@
# Osirisrunner Installer
A V language installer module for building and managing the Hero Runner service. This installer handles the complete lifecycle of the Osirisrunner binary from the Horus workspace.
## Features
- **Automatic Rust Installation**: Installs Rust toolchain if not present
- **Git Repository Management**: Clones and manages the horus repository
- **Binary Building**: Compiles the osirisrunner binary from the horus workspace
- **Service Management**: Start/stop/restart via zinit
- **Configuration**: Customizable Redis connection
## Quick Start
### Manual Usage
```v
import freeflowuniverse.herolib.installers.horus.osirisrunner as osirisrunner_installer
mut osirisrunner := osirisrunner_installer.get()!
osirisrunner.install()!
osirisrunner.start()!
```
## Configuration
```bash
!!osirisrunner.configure
name:'default'
binary_path:'/hero/var/bin/osirisrunner'
redis_addr:'127.0.0.1:6379'
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
```
### Configuration Fields
- **name**: Instance name (default: 'default')
- **binary_path**: Path where the osirisrunner binary will be installed (default: '/hero/var/bin/osirisrunner')
- **redis_addr**: Redis server address (default: '127.0.0.1:6379')
- **log_level**: Rust log level - trace, debug, info, warn, error (default: 'info')
- **repo_path**: Path to clone the horus repository (default: '/root/code/git.ourworld.tf/herocode/horus')
## Commands
### Install
Builds the osirisrunner binary from the horus workspace. This will:
1. Install Rust if not present
2. Clone the horus repository from git.ourworld.tf
3. Build the osirisrunner binary with `cargo build -p runner-osiris --release`
```bash
hero osirisrunner.install
```
### Start
Starts the osirisrunner service using zinit:
```bash
hero osirisrunner.start
```
### Stop
Stops the running service:
```bash
hero osirisrunner.stop
```
### Restart
Restarts the service:
```bash
hero osirisrunner.restart
```
### Destroy
Stops the service and removes all files:
```bash
hero osirisrunner.destroy
```
## Requirements
- **Dependencies**:
- Rust toolchain (automatically installed)
- Git (for cloning repository)
- Redis (must be running separately)
## Architecture
The installer follows the standard herolib installer pattern:
- **osirisrunner_model.v**: Configuration structure and initialization
- **osirisrunner_actions.v**: Build, install, start, stop, destroy logic
- **osirisrunner_factory_.v**: Factory pattern for instance management
## Notes
- The installer builds from source rather than downloading pre-built binaries
- Redis must be running and accessible at the configured address
- The binary is built with `RUSTFLAGS="-A warnings"` to suppress warnings
- Service management uses zinit by default

View File

@@ -0,0 +1,5 @@
name: ${cfg.configpath}

View File

@@ -0,0 +1,12 @@
!!hero_code.generate_installer
name:''
classname:'SalrunnerServer'
singleton:0
templates:1
default:1
title:''
supported_platforms:''
startupmanager:1
hasconfig:1
build:1

View File

@@ -0,0 +1,104 @@
# Salrunner Installer
A V language installer module for building and managing the Hero Runner service. This installer handles the complete lifecycle of the Salrunner binary from the Horus workspace.
## Features
- **Automatic Rust Installation**: Installs Rust toolchain if not present
- **Git Repository Management**: Clones and manages the horus repository
- **Binary Building**: Compiles the salrunner binary from the horus workspace
- **Service Management**: Start/stop/restart via zinit
- **Configuration**: Customizable Redis connection
## Quick Start
### Manual Usage
```v
import freeflowuniverse.herolib.installers.horus.salrunner as salrunner_installer
mut salrunner := salrunner_installer.get()!
salrunner.install()!
salrunner.start()!
```
## Configuration
```bash
!!salrunner.configure
name:'default'
binary_path:'/hero/var/bin/salrunner'
redis_addr:'127.0.0.1:6379'
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
```
### Configuration Fields
- **name**: Instance name (default: 'default')
- **binary_path**: Path where the salrunner binary will be installed (default: '/hero/var/bin/salrunner')
- **redis_addr**: Redis server address (default: '127.0.0.1:6379')
- **log_level**: Rust log level - trace, debug, info, warn, error (default: 'info')
- **repo_path**: Path to clone the horus repository (default: '/root/code/git.ourworld.tf/herocode/horus')
## Commands
### Install
Builds the salrunner binary from the horus workspace. This will:
1. Install Rust if not present
2. Clone the horus repository from git.ourworld.tf
3. Build the salrunner binary with `cargo build -p runner-sal --release`
```bash
hero salrunner.install
```
### Start
Starts the salrunner service using zinit:
```bash
hero salrunner.start
```
### Stop
Stops the running service:
```bash
hero salrunner.stop
```
### Restart
Restarts the service:
```bash
hero salrunner.restart
```
### Destroy
Stops the service and removes all files:
```bash
hero salrunner.destroy
```
## Requirements
- **Dependencies**:
- Rust toolchain (automatically installed)
- Git (for cloning repository)
- Redis (must be running separately)
## Architecture
The installer follows the standard herolib installer pattern:
- **salrunner_model.v**: Configuration structure and initialization
- **salrunner_actions.v**: Build, install, start, stop, destroy logic
- **salrunner_factory_.v**: Factory pattern for instance management
## Notes
- The installer builds from source rather than downloading pre-built binaries
- Redis must be running and accessible at the configured address
- The binary is built with `RUSTFLAGS="-A warnings"` to suppress warnings
- Service management uses zinit by default

View File

@@ -0,0 +1,142 @@
module salrunner
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
import incubaid.herolib.core.pathlib
import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist
import incubaid.herolib.installers.lang.rust
import incubaid.herolib.develop.gittools
import os
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
mut cfg := get()!
mut res := []startupmanager.ZProcessNewArgs{}
res << startupmanager.ZProcessNewArgs{
name: 'runner_sal'
cmd: '${cfg.binary_path} --redis-addr ${cfg.redis_addr}'
env: {
'HOME': os.home_dir()
'RUST_LOG': cfg.log_level
'RUST_LOG_STYLE': 'never'
}
}
return res
}
fn running() !bool {
mut cfg := get()!
// Check if the process is running
res := osal.exec(cmd: 'pgrep -f runner_sal', stdout: false, raise_error: false)!
return res.exit_code == 0
}
fn start_pre() ! {
}
fn start_post() ! {
}
fn stop_pre() ! {
}
fn stop_post() ! {
}
//////////////////// following actions are not specific to instance of the object
// checks if a certain version or above is installed
fn installed() !bool {
mut cfg := get()!
// Check if the binary exists
mut binary := pathlib.get(cfg.binary_path)
if !binary.exists() {
return false
}
return true
}
// get the Upload List of the files
fn ulist_get() !ulist.UList {
// optionally build a UList which is all paths which are result of building, is then used e.g. in upload
return ulist.UList{}
}
// uploads to S3 server if configured
fn upload() ! {
}
fn install() ! {
console.print_header('install salrunner')
// For salrunner, we build from source instead of downloading
build()!
}
fn build() ! {
console.print_header('build salrunner')
mut cfg := get()!
// Ensure rust is installed
console.print_debug('Checking if Rust is installed...')
mut rust_installer := rust.get()!
res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)!
if res.exit_code != 0 {
console.print_header('Installing Rust first...')
rust_installer.install()!
} else {
console.print_debug('Rust is already installed: ${res.output.trim_space()}')
}
// Clone or get the repository
console.print_debug('Cloning/updating horus repository...')
mut gs := gittools.new()!
mut repo := gs.get_repo(
url: 'https://git.ourworld.tf/herocode/horus.git'
pull: true
reset: false
)!
// Update the path to the actual cloned repo
cfg.repo_path = repo.path()
set(cfg)!
console.print_debug('Repository path: ${cfg.repo_path}')
// Build the salrunner binary from the horus workspace
console.print_header('Building salrunner binary (this may take several minutes)...')
console.print_debug('Running: cargo build -p runner-sal --release')
console.print_debug('Build output:')
cmd := 'cd ${cfg.repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p runner-sal --release'
osal.execute_stdout(cmd)!
console.print_debug('Build completed successfully')
// Ensure binary directory exists and copy the binary
console.print_debug('Preparing binary directory: ${cfg.binary_path}')
mut binary_path_obj := pathlib.get(cfg.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
// Copy the built binary to the configured location
source_binary := '${cfg.repo_path}/target/release/runner_sal'
console.print_debug('Copying binary from: ${source_binary}')
console.print_debug('Copying binary to: ${cfg.binary_path}')
mut source_file := pathlib.get_file(path: source_binary)!
source_file.copy(dest: cfg.binary_path, rsync: false)!
console.print_header('salrunner built successfully at ${cfg.binary_path}')
}
fn destroy() ! {
mut server := get()!
server.stop()!
osal.process_kill_recursive(name: 'runner_sal')!
// Remove the built binary
osal.rm(server.binary_path)!
}

View File

@@ -0,0 +1,320 @@
module salrunner
import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console
import json
import incubaid.herolib.osal.startupmanager
import time
__global (
salrunner_global map[string]&SalrunnerServer
salrunner_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
binary_path string
redis_addr string
log_level string
repo_path string
fromdb bool // will load from filesystem
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&SalrunnerServer {
mut obj := SalrunnerServer{
name: args.name
binary_path: args.binary_path
redis_addr: args.redis_addr
log_level: args.log_level
repo_path: args.repo_path
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&SalrunnerServer {
mut context := base.context()!
salrunner_default = args.name
if args.fromdb || args.name !in salrunner_global {
mut r := context.redis()!
if r.hexists('context:salrunner', args.name)! {
data := r.hget('context:salrunner', args.name)!
if data.len == 0 {
print_backtrace()
return error('SalrunnerServer with name: ${args.name} does not exist, prob bug.')
}
mut obj := json.decode(SalrunnerServer, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
print_backtrace()
return error("SalrunnerServer with name '${args.name}' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return salrunner_global[args.name] or {
print_backtrace()
return error('could not get config for salrunner with name:${args.name}')
}
}
// register the config for the future
pub fn set(o SalrunnerServer) ! {
mut o2 := set_in_mem(o)!
salrunner_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:salrunner', o2.name, json.encode(o2))!
}
// does the config exists?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:salrunner', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:salrunner', args.name)!
}
@[params]
pub struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&SalrunnerServer {
mut res := []&SalrunnerServer{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
salrunner_global = map[string]&SalrunnerServer{}
salrunner_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:salrunner')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in salrunner_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o SalrunnerServer) !SalrunnerServer {
mut o2 := obj_init(o)!
salrunner_global[o2.name] = &o2
salrunner_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'salrunner.') {
return
}
mut install_actions := plbook.find(filter: 'salrunner.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
install_action.done = true
}
}
mut other_actions := plbook.find(filter: 'salrunner.')!
for mut other_action in other_actions {
if other_action.name in ['destroy', 'install', 'build'] {
mut p := other_action.params
reset := p.get_default_false('reset')
if other_action.name == 'destroy' || reset {
console.print_debug('install action salrunner.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action salrunner.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut salrunner_obj := get(name: name)!
console.print_debug('action object:\n${salrunner_obj}')
if other_action.name == 'start' {
console.print_debug('install action salrunner.${other_action.name}')
salrunner_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action salrunner.${other_action.name}')
salrunner_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action salrunner.${other_action.name}')
salrunner_obj.restart()!
}
}
other_action.done = true
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.screen {
console.print_debug("installer: salrunner' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: salrunner' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: salrunner' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: salrunner' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self SalrunnerServer) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self SalrunnerServer) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('installer: salrunner start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: salrunner starting with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.start(zprocess.name)!
}
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('salrunner did not install properly.')
}
pub fn (mut self SalrunnerServer) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self SalrunnerServer) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
}
pub fn (mut self SalrunnerServer) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self SalrunnerServer) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
if zprocess.startuptype != .screen {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
}
return running()!
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self SalrunnerServer) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self SalrunnerServer) build() ! {
switch(self.name)
build()!
}
pub fn (mut self SalrunnerServer) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for salrunner
pub fn switch(name string) {
salrunner_default = name
}

View File

@@ -0,0 +1,62 @@
module salrunner
import os
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.pathlib
const version = '0.1.0'
const singleton = true
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct SalrunnerServer {
pub mut:
name string = 'default'
binary_path string = os.join_path(os.home_dir(), 'hero/bin/runner_sal')
redis_addr string = '127.0.0.1:6379'
log_level string = 'info'
repo_path string = '/root/code/git.ourworld.tf/herocode/horus'
}
// your checking & initialization code if needed
fn obj_init(mycfg_ SalrunnerServer) !SalrunnerServer {
mut mycfg := mycfg_
if mycfg.name == '' {
mycfg.name = 'default'
}
if mycfg.binary_path == '' {
mycfg.binary_path = os.join_path(os.home_dir(), 'hero/var/bin/runner_sal')
}
if mycfg.redis_addr == '' {
mycfg.redis_addr = '127.0.0.1:6379'
}
if mycfg.log_level == '' {
mycfg.log_level = 'info'
}
if mycfg.repo_path == '' {
mycfg.repo_path = '/root/code/git.ourworld.tf/herocode/horus'
}
return mycfg
}
// called before start if done
fn configure() ! {
mut server := get()!
// Ensure the binary directory exists
mut binary_path_obj := pathlib.get(server.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj SalrunnerServer) !string {
return encoderhero.encode[SalrunnerServer](obj)!
}
pub fn heroscript_loads(heroscript string) !SalrunnerServer {
mut obj := encoderhero.decode[SalrunnerServer](heroscript)!
return obj
}

View File

@@ -0,0 +1,5 @@
name: ${cfg.configpath}

View File

@@ -0,0 +1,12 @@
!!hero_code.generate_installer
name:''
classname:'SupervisorServer'
singleton:0
templates:1
default:1
title:''
supported_platforms:''
startupmanager:1
hasconfig:1
build:1

View File

@@ -0,0 +1,144 @@
# Supervisor Installer
A V language installer module for building and managing the Supervisor service. This installer handles the complete lifecycle of the Supervisor binary from the Horus workspace.
## Features
- **Automatic Rust Installation**: Installs Rust toolchain if not present
- **Git Repository Management**: Clones and manages the horus repository
- **Binary Building**: Compiles the supervisor binary from the horus workspace
- **Service Management**: Start/stop/restart via zinit
- **Configuration**: Customizable Redis, HTTP, and WebSocket ports
## Quick Start
### Using the Example Script
```bash
cd /root/code/github/freeflowuniverse/herolib/examples/installers/horus
./supervisor.vsh
```
### Manual Usage
```v
import freeflowuniverse.herolib.installers.horus.supervisor as supervisor_installer
mut supervisor := supervisor_installer.get()!
supervisor.install()!
supervisor.start()!
```
## Configuration
```bash
!!supervisor.configure
name:'default'
binary_path:'/hero/var/bin/supervisor'
redis_addr:'127.0.0.1:6379'
http_port:8082
ws_port:9654
log_level:'info'
repo_path:'/root/code/git.ourworld.tf/herocode/horus'
```
### Configuration Fields
- **name**: Instance name (default: 'default')
- **binary_path**: Path where the supervisor binary will be installed (default: '/hero/var/bin/supervisor')
- **redis_addr**: Redis server address (default: '127.0.0.1:6379')
- **http_port**: HTTP API port (default: 8082)
- **ws_port**: WebSocket API port (default: 9654)
- **log_level**: Rust log level - trace, debug, info, warn, error (default: 'info')
- **repo_path**: Path to clone the horus repository (default: '/root/code/git.ourworld.tf/herocode/horus')
## Commands
### Install
Builds the supervisor binary from the horus workspace. This will:
1. Install Rust if not present
2. Clone the horus repository from git.ourworld.tf
3. Build the supervisor binary with `cargo build -p hero-supervisor --release`
```bash
hero supervisor.install
```
### Start
Starts the supervisor service using zinit:
```bash
hero supervisor.start
```
### Stop
Stops the running service:
```bash
hero supervisor.stop
```
### Restart
Restarts the service:
```bash
hero supervisor.restart
```
### Destroy
Stops the service and removes all files:
```bash
hero supervisor.destroy
```
## Requirements
- **Dependencies**:
- Rust toolchain (automatically installed)
- Git (for cloning repository)
- Redis (must be running separately)
- Mycelium (must be installed and running separately)
## Architecture
The installer follows the standard herolib installer pattern:
- **supervisor_model.v**: Configuration structure and initialization
- **supervisor_actions.v**: Build, install, start, stop, destroy logic
- **supervisor_factory_.v**: Factory pattern for instance management
## Notes
- The installer builds from source rather than downloading pre-built binaries
- Mycelium is expected to be already installed and running in the environment
- Redis must be running and accessible at the configured address
- The binary is built with `RUSTFLAGS="-A warnings"` to suppress warnings
- Service management uses zinit by default
## Example Workflow
```v
import freeflowuniverse.herolib.installers.horus.supervisor as sv
// Get installer instance
mut supervisor := sv.get()!
// Customize configuration
supervisor.redis_addr = '127.0.0.1:6379'
supervisor.http_port = 8082
supervisor.log_level = 'debug'
sv.set(supervisor)!
// Build and start
supervisor.install()!
supervisor.start()!
// Check status
if supervisor.running()! {
println('Supervisor is running on port ${supervisor.http_port}')
}
// Later: cleanup
supervisor.destroy()!
```

View File

@@ -0,0 +1,253 @@
module supervisor
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
import incubaid.herolib.core.texttools
import incubaid.herolib.core.pathlib
import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist
import incubaid.herolib.installers.lang.rust
import incubaid.herolib.develop.gittools
import os
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
mut cfg := get()!
mut res := []startupmanager.ZProcessNewArgs{}
res << startupmanager.ZProcessNewArgs{
name: 'supervisor'
cmd: '${cfg.binary_path} --redis-addr ${cfg.redis_addr} --api-http-port ${cfg.http_port} --api-ws-port ${cfg.ws_port}'
env: {
'HOME': os.home_dir()
'RUST_LOG': cfg.log_level
'RUST_LOG_STYLE': 'never'
}
}
return res
}
fn running() !bool {
mut cfg := get()!
// Check if the process is running by checking the HTTP port
res := osal.exec(cmd: 'curl -fsSL http://127.0.0.1:${cfg.http_port} || exit 1', stdout: false, raise_error: false)!
return res.exit_code == 0
}
fn start_pre() ! {
}
fn start_post() ! {
}
fn stop_pre() ! {
}
fn stop_post() ! {
}
//////////////////// following actions are not specific to instance of the object
// checks if a certain version or above is installed
fn installed() !bool {
mut cfg := get()!
// Check if the binary exists
mut binary := pathlib.get(cfg.binary_path)
if !binary.exists() {
return false
}
return true
}
// get the Upload List of the files
fn ulist_get() !ulist.UList {
// optionally build a UList which is all paths which are result of building, is then used e.g. in upload
return ulist.UList{}
}
// uploads to S3 server if configured
fn upload() ! {
// installers.upload(
// cmdname: 'supervisor'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/supervisor'
// )!
}
fn install() ! {
console.print_header('install supervisor')
// For supervisor, we build from source instead of downloading
build()!
}
// Public function to build supervisor without requiring factory/redis
pub fn build_supervisor() ! {
console.print_header('build supervisor')
println('📦 Starting supervisor build process...\n')
// Use default config instead of getting from factory
println(' Initializing configuration...')
mut cfg := SupervisorServer{}
println(' Configuration initialized')
println(' - Binary path: ${cfg.binary_path}')
println(' - Redis address: ${cfg.redis_addr}')
println(' - HTTP port: ${cfg.http_port}')
println(' - WS port: ${cfg.ws_port}\n')
// Ensure Redis is installed and running (required for supervisor)
println('🔍 Step 1/4: Checking Redis dependency...')
// First check if redis-server is installed
if !osal.cmd_exists_profile('redis-server') {
println(' Redis is not installed')
println('📥 Installing Redis...')
osal.package_install('redis-server')!
println(' Redis installed')
} else {
println(' Redis is already installed')
}
// Now check if it's running
println('🔍 Checking if Redis is running...')
redis_check := osal.exec(cmd: 'redis-cli -c -p 6379 ping', stdout: false, raise_error: false)!
if redis_check.exit_code != 0 {
println(' Redis is not running')
println('🚀 Starting Redis...')
osal.exec(cmd: 'systemctl start redis-server')!
println(' Redis started successfully\n')
} else {
println(' Redis is already running\n')
}
// Ensure rust is installed
println('🔍 Step 2/4: Checking Rust dependency...')
mut rust_installer := rust.get()!
res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)!
if res.exit_code != 0 {
println('📥 Installing Rust...')
rust_installer.install()!
println(' Rust installed\n')
} else {
println(' Rust is already installed: ${res.output.trim_space()}\n')
}
// Clone or get the repository
println('🔍 Step 3/4: Cloning/updating horus repository...')
mut gs := gittools.new()!
mut repo := gs.get_repo(
url: 'https://git.ourworld.tf/herocode/horus.git'
pull: true
reset: false
)!
// Update the path to the actual cloned repo
cfg.repo_path = repo.path()
println(' Repository ready at: ${cfg.repo_path}\n')
// Build the supervisor binary from the horus workspace
println('🔍 Step 4/4: Building supervisor binary...')
println(' This may take several minutes (compiling Rust code)...')
println('📝 Running: cargo build -p hero-supervisor --release\n')
cmd := 'cd ${cfg.repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p hero-supervisor --release'
osal.execute_stdout(cmd)!
println('\n Build completed successfully')
// Ensure binary directory exists and copy the binary
println('📁 Preparing binary directory: ${cfg.binary_path}')
mut binary_path_obj := pathlib.get(cfg.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
// Copy the built binary to the configured location
source_binary := '${cfg.repo_path}/target/release/supervisor'
println('📋 Copying binary from: ${source_binary}')
println('📋 Copying binary to: ${cfg.binary_path}')
mut source_file := pathlib.get_file(path: source_binary)!
source_file.copy(dest: cfg.binary_path, rsync: false)!
println('\n🎉 Supervisor built successfully!')
println('📍 Binary location: ${cfg.binary_path}')
}
fn build() ! {
console.print_header('build supervisor')
mut cfg := get()!
// Ensure Redis is installed and running (required for supervisor)
console.print_debug('Checking if Redis is installed and running...')
redis_check := osal.exec(cmd: 'redis-cli -c -p 6379 ping', stdout: false, raise_error: false)!
if redis_check.exit_code != 0 {
console.print_header('Redis is not running, checking if installed...')
if !osal.cmd_exists_profile('redis-server') {
console.print_header('Installing Redis...')
osal.package_install('redis-server')!
}
console.print_header('Starting Redis...')
osal.exec(cmd: 'systemctl start redis-server')!
console.print_debug('Redis started successfully')
} else {
console.print_debug('Redis is already running')
}
// Ensure rust is installed
console.print_debug('Checking if Rust is installed...')
mut rust_installer := rust.get()!
res := osal.exec(cmd: 'rustc -V', stdout: false, raise_error: false)!
if res.exit_code != 0 {
console.print_header('Installing Rust first...')
rust_installer.install()!
} else {
console.print_debug('Rust is already installed: ${res.output.trim_space()}')
}
// Clone or get the repository
console.print_debug('Cloning/updating horus repository...')
mut gs := gittools.new()!
mut repo := gs.get_repo(
url: 'https://git.ourworld.tf/herocode/horus.git'
pull: true
reset: false
)!
// Update the path to the actual cloned repo
cfg.repo_path = repo.path()
set(cfg)!
console.print_debug('Repository path: ${cfg.repo_path}')
// Build the supervisor binary from the horus workspace
console.print_header('Building supervisor binary (this may take several minutes)...')
console.print_debug('Running: cargo build -p hero-supervisor --release')
console.print_debug('Build output:')
cmd := 'cd ${cfg.repo_path} && . ~/.cargo/env && RUSTFLAGS="-A warnings" cargo build -p hero-supervisor --release'
osal.execute_stdout(cmd)!
console.print_debug('Build completed successfully')
// Ensure binary directory exists and copy the binary
console.print_debug('Preparing binary directory: ${cfg.binary_path}')
mut binary_path_obj := pathlib.get(cfg.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
// Copy the built binary to the configured location
source_binary := '${cfg.repo_path}/target/release/supervisor'
console.print_debug('Copying binary from: ${source_binary}')
console.print_debug('Copying binary to: ${cfg.binary_path}')
mut source_file := pathlib.get_file(path: source_binary)!
source_file.copy(dest: cfg.binary_path, rsync: false)!
console.print_header('supervisor built successfully at ${cfg.binary_path}')
}
fn destroy() ! {
mut server := get()!
server.stop()!
osal.process_kill_recursive(name: 'supervisor')!
// Remove the built binary
osal.rm(server.binary_path)!
}

View File

@@ -0,0 +1,324 @@
module supervisor
import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console
import json
import incubaid.herolib.osal.startupmanager
import time
__global (
supervisor_global map[string]&SupervisorServer
supervisor_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
binary_path string
redis_addr string
http_port int
ws_port int
log_level string
repo_path string
fromdb bool // will load from filesystem
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&SupervisorServer {
mut obj := SupervisorServer{
name: args.name
binary_path: args.binary_path
redis_addr: args.redis_addr
http_port: args.http_port
ws_port: args.ws_port
log_level: args.log_level
repo_path: args.repo_path
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&SupervisorServer {
mut context := base.context()!
supervisor_default = args.name
if args.fromdb || args.name !in supervisor_global {
mut r := context.redis()!
if r.hexists('context:supervisor', args.name)! {
data := r.hget('context:supervisor', args.name)!
if data.len == 0 {
print_backtrace()
return error('SupervisorServer with name: ${args.name} does not exist, prob bug.')
}
mut obj := json.decode(SupervisorServer, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
print_backtrace()
return error("SupervisorServer with name '${args.name}' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return supervisor_global[args.name] or {
print_backtrace()
return error('could not get config for supervisor with name:${args.name}')
}
}
// register the config for the future
pub fn set(o SupervisorServer) ! {
mut o2 := set_in_mem(o)!
supervisor_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:supervisor', o2.name, json.encode(o2))!
}
// does the config exists?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:supervisor', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:supervisor', args.name)!
}
@[params]
pub struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&SupervisorServer {
mut res := []&SupervisorServer{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
supervisor_global = map[string]&SupervisorServer{}
supervisor_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:supervisor')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in supervisor_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o SupervisorServer) !SupervisorServer {
mut o2 := obj_init(o)!
supervisor_global[o2.name] = &o2
supervisor_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'supervisor.') {
return
}
mut install_actions := plbook.find(filter: 'supervisor.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
install_action.done = true
}
}
mut other_actions := plbook.find(filter: 'supervisor.')!
for mut other_action in other_actions {
if other_action.name in ['destroy', 'install', 'build'] {
mut p := other_action.params
reset := p.get_default_false('reset')
if other_action.name == 'destroy' || reset {
console.print_debug('install action supervisor.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action supervisor.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut supervisor_obj := get(name: name)!
console.print_debug('action object:\n${supervisor_obj}')
if other_action.name == 'start' {
console.print_debug('install action supervisor.${other_action.name}')
supervisor_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action supervisor.${other_action.name}')
supervisor_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action supervisor.${other_action.name}')
supervisor_obj.restart()!
}
}
other_action.done = true
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.screen {
console.print_debug("installer: supervisor' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: supervisor' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: supervisor' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: supervisor' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self SupervisorServer) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self SupervisorServer) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('installer: supervisor start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: supervisor starting with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.start(zprocess.name)!
}
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('supervisor did not install properly.')
}
pub fn (mut self SupervisorServer) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self SupervisorServer) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
}
pub fn (mut self SupervisorServer) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self SupervisorServer) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
if zprocess.startuptype != .screen {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
}
return running()!
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self SupervisorServer) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self SupervisorServer) build() ! {
switch(self.name)
build()!
}
pub fn (mut self SupervisorServer) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for supervisor
pub fn switch(name string) {
supervisor_default = name
}

View File

@@ -0,0 +1,70 @@
module supervisor
import os
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.pathlib
const version = '0.1.0'
const singleton = true
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct SupervisorServer {
pub mut:
name string = 'default'
binary_path string = os.join_path(os.home_dir(), 'hero/bin/supervisor')
redis_addr string = '127.0.0.1:6379'
http_port int = 8082
ws_port int = 9654
log_level string = 'info'
repo_path string = '/root/code/git.ourworld.tf/herocode/horus'
}
// your checking & initialization code if needed
fn obj_init(mycfg_ SupervisorServer) !SupervisorServer {
mut mycfg := mycfg_
if mycfg.name == '' {
mycfg.name = 'default'
}
if mycfg.binary_path == '' {
mycfg.binary_path = os.join_path(os.home_dir(), 'hero/bin/supervisor')
}
if mycfg.redis_addr == '' {
mycfg.redis_addr = '127.0.0.1:6379'
}
if mycfg.http_port == 0 {
mycfg.http_port = 8082
}
if mycfg.ws_port == 0 {
mycfg.ws_port = 9654
}
if mycfg.log_level == '' {
mycfg.log_level = 'info'
}
if mycfg.repo_path == '' {
mycfg.repo_path = '/root/code/git.ourworld.tf/herocode/horus'
}
return mycfg
}
// called before start if done
fn configure() ! {
mut server := get()!
// Ensure the binary directory exists
mut binary_path_obj := pathlib.get(server.binary_path)
osal.dir_ensure(binary_path_obj.path_dir())!
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj SupervisorServer) !string {
return encoderhero.encode[SupervisorServer](obj)!
}
pub fn heroscript_loads(heroscript string) !SupervisorServer {
mut obj := encoderhero.decode[SupervisorServer](heroscript)!
return obj
}

View File

@@ -0,0 +1,5 @@
name: ${cfg.configpath}

View File

@@ -29,7 +29,7 @@ fn startupcmd() ![]startupmanager.ZProcessNewArgs {
start: true start: true
} }
} }
osal.dir_ensure(os.home_dir() + '/hero/cfg/zinit')! osal.dir_ensure(os.join_path(os.home_dir(), 'hero/cfg/zinit'))!
return res return res
} }

View File

@@ -16,9 +16,20 @@ pub fn get(cat StartupManagerType) !StartupManager {
mut sm := StartupManager{ mut sm := StartupManager{
cat: cat cat: cat
} }
if sm.cat == .auto { match sm.cat {
.zinit {
mut zinit_client_test := zinit.get()! // 'create:true' ensures a client object is initiated even if the socket isn't active.
if _ := zinit_client_test.rpc_discover() {
sm.cat = .zinit
} else {
return error('zinit not found ${err}')
}
}
.auto {
// Try to get a ZinitRPC client and check if it can discover RPC methods. // Try to get a ZinitRPC client and check if it can discover RPC methods.
// This implies the zinit daemon is running and accessible via its socket. // This implies the zinit daemon is running and accessible via its socket.
// Since it's auto doesn't insist and die if it can't find zinit.
mut zinit_client_test := zinit.get(create: true)! // 'create:true' ensures a client object is initiated even if the socket isn't active. mut zinit_client_test := zinit.get(create: true)! // 'create:true' ensures a client object is initiated even if the socket isn't active.
if _ := zinit_client_test.rpc_discover() { if _ := zinit_client_test.rpc_discover() {
sm.cat = .zinit sm.cat = .zinit
@@ -26,10 +37,14 @@ pub fn get(cat StartupManagerType) !StartupManager {
sm.cat = .screen sm.cat = .screen
} }
} }
if sm.cat == .unknown { .unknown {
print_backtrace() print_backtrace()
return error("can't determine startup manager type, need to be a known one.") return error("can't determine startup manager type, need to be a known one.")
} }
else {
return sm
}
}
return sm return sm
} }

View File

@@ -70,7 +70,7 @@ pub fn decode_request(data string) !Request {
// Returns: // Returns:
// - A JSON string representation of the Request // - A JSON string representation of the Request
pub fn (req Request) encode() string { pub fn (req Request) encode() string {
return json2.encode(req, prettify: true) return json2.encode(req)
} }
// validate checks if the Request object contains all required fields // validate checks if the Request object contains all required fields