refactor: move coordinator installer to horus directory and fix Redis installer permissions

- Moved coordinator installer from installers/infra to installers/horus
- Renamed HerocoordinatorServer to CoordinatorServer
- Fixed Redis installer permissions for /var/lib/redis directory
- Integrated coordinator with new modular Redis installer
This commit is contained in:
peternashaat
2025-11-16 13:05:29 +00:00
parent bf79d6d198
commit eb0fe4d3a9
15 changed files with 550 additions and 515 deletions

View File

@@ -1,39 +1,46 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.base import incubaid.herolib.installers.base.redis
import incubaid.herolib.osal.core as osal
import time
println('=== Redis Installer Example ===\n') println('=== Redis Installer Example ===\n')
// Check if redis is already installed // Create configuration
if osal.cmd_exists_profile('redis-server') { // You can customize port, datadir, and ipaddr as needed
println(' redis-server is already installed') config := redis.RedisInstall{
port: 6379 // Redis port
// Check if it's running datadir: '/var/lib/redis' // Data directory (standard location)
if base.check(port: 6379) { ipaddr: 'localhost' // Bind address
println(' Redis is running and responding')
} else {
println(' Redis is installed but not running, starting it...')
// Use systemctl to start redis
osal.exec(cmd: 'systemctl start redis-server')!
// Wait a moment for redis to start
time.sleep(1000)
if base.check(port: 6379) {
println(' Redis started successfully')
} else {
println(' Failed to start redis')
}
}
} else {
println('Redis not found, installing...')
// Install and start redis
base.redis_install(port: 6379, start: true)!
println(' Redis installed and started successfully')
} }
println('\n Done!') // Check if Redis is already running
if redis.check(config) {
println('INFO: Redis is already running on port ${config.port}')
println(' To reinstall, stop Redis first: redis.stop()!')
} else {
// Install and start Redis
println('Installing and starting Redis...')
println(' Port: ${config.port}')
println(' Data directory: ${config.datadir}')
println(' Bind address: ${config.ipaddr}\n')
redis.redis_install(config)!
// Verify installation
if redis.check(config) {
println('\nSUCCESS: Redis installed and started successfully!')
println(' You can now connect to Redis on port ${config.port}')
println(' Test with: redis-cli ping')
} else {
println('\nERROR: Redis installation completed but failed to start')
println(' Check logs: journalctl -u redis-server -n 20')
}
}
println('\n=== Available Functions ===')
println(' redis.redis_install(config)! - Install and start Redis')
println(' redis.start(config)! - Start Redis')
println(' redis.stop()! - Stop Redis')
println(' redis.restart(config)! - Restart Redis')
println(' redis.check(config) - Check if running')
println('\nDone!')

Binary file not shown.

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.horus.coordinator
// Example usage of coordinator installer
// This will:
// 1. Check and install Redis if not running (required dependency)
// 2. Install Rust if not already installed
// 3. Clone the horus repository
// 4. Build the coordinator binary
println('Building coordinator from horus repository...')
println('(This will install Redis and Rust if not already installed)\n')
// Create coordinator instance - will auto-install Redis if needed
mut coord := coordinator.new()!
// Build and install
coord.install()!
println('\nCoordinator built and installed successfully!')
println('Binary location: ${coord.binary_path}')
// Note: To start the service, uncomment the lines below
// (requires proper zinit or screen session setup)
// coord.start()!
// if coord.running()! {
// println('Coordinator is running!')
// }
// coord.stop()!
// coord.destroy()!

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.installers.infra.herocoordinator
// Example usage of herocoordinator installer
// This will:
// 1. Check and install Redis if not running (required dependency)
// 2. Install Rust if not already installed
// 3. Clone the horus repository
// 4. Build the herocoordinator binary
// Build and install herocoordinator
// This will automatically check and install Redis and Rust if needed
println('Building coordinator from horus repository...')
println('(This will install Redis and Rust if not already installed)\n')
// Call build_coordinator - it will handle all dependencies
herocoordinator.build_coordinator()!
println('\n Herocoordinator built and installed successfully!')
println('Binary location: /hero/var/bin/coordinator')
// Note: To start the service, uncomment the lines below
// (requires proper zinit or screen session setup)
// herocoordinator.start()!
// if herocoordinator.running()! {
// println('Herocoordinator is running!')
// }
// herocoordinator.stop()!
// herocoordinator.destroy()!

View File

@@ -1,80 +0,0 @@
# Redis Installer Migration Notes
## Old Installer Logic (redis.v)
### Key Behaviors:
1. **datadir**: `${os.home_dir()}/hero/var/redis` (NOT `/var/lib/redis`)
2. **Template Usage**: YES - always applies template before starting
3. **macOS Support**: YES - uses `--daemonize yes` flag
4. **Linux Support**: YES - uses startupmanager
5. **Check before start**: Returns early if already running
6. **Config path**:
- Linux: `/etc/redis/redis.conf`
- macOS: `${datadir}/redis.conf`
### Flow:
```
redis_install()
→ checks if running (unless reset)
→ installs package (redis-server on Linux, redis on macOS)
→ creates datadir
→ calls start()
start()
→ returns if already running
→ configure() - applies template
→ kills existing processes
→ macOS: starts with daemonize
→ Linux: uses startupmanager
→ waits for ping response
```
## New Installer Logic (redis/)
### Matching Behaviors:
1.**datadir**: `${os.home_dir()}/hero/var/redis` - FIXED
2.**Template Usage**: YES - `configure()` called in `start_pre()`
3.**macOS Support**: YES - handled in `start_pre()`
4.**Linux Support**: YES - via `startupcmd()` and startupmanager
5.**Check before start**: Added in `start_pre()`
6.**Config path**: Same logic in `configfilepath()`
### Flow:
```
install()
→ checks if installed
→ installs package (redis-server on Linux, redis on macOS)
→ creates datadir
start()
→ calls start_pre()
→ on Linux: uses startupmanager with startupcmd()
→ calls start_post()
start_pre()
→ returns if already running
→ configure() - applies template
→ kills existing processes
→ macOS: starts with daemonize
start_post()
→ waits for ping response
```
## Template Fixes
Fixed incompatible directives for Redis 7.0.15:
- ✅ Commented out `locale-collate ""`
- ✅ Commented out `set-max-listpack-entries 128`
- ✅ Commented out `set-max-listpack-value 64`
- ✅ Commented out `zset-max-listpack-entries 128`
- ✅ Commented out `zset-max-listpack-value 64`
## Verification
The new installer now matches the old installer's logic exactly:
- Same default datadir
- Same template usage
- Same platform handling
- Same startup flow
- Template is compatible with Redis 7.0.15

View File

@@ -1,44 +1,119 @@
# redis # Redis Installer
A modular Redis installer that works across multiple platforms (Ubuntu, Debian, Alpine, Arch, macOS, containers).
## Features
To get started - Cross-platform support (systemd and non-systemd systems)
- Automatic package installation via package managers
- Configurable data directory, port, and IP address
- Smart startup (uses systemctl when available, falls back to direct start)
- No circular dependencies (works without Redis being pre-installed)
## Quick Start
### Simple Installation
```v ```v
import incubaid.herolib.installers.base.redis
// Create configuration
config := redis.RedisInstall{
port: 6379
datadir: '/var/lib/redis'
ipaddr: 'localhost'
}
import incubaid.herolib.installers.something.redis as redis_installer // Install and start Redis
redis.redis_install(config)!
heroscript:="
!!redis.configure name:'test'
password: '1234'
port: 7701
!!redis.start name:'test' reset:1
"
redis_installer.play(heroscript=heroscript)!
//or we can call the default and do a start with reset
//mut installer:= redis_installer.get()!
//installer.start(reset:true)!
// Check if running
if redis.check(config) {
println('Redis is running!')
}
``` ```
## example heroscript ### Using Individual Functions
```v
import incubaid.herolib.installers.base.redis
```hero config := redis.RedisInstall{
!!redis.configure port: 6379
homedir: '/home/user/redis' datadir: '/var/lib/redis'
username: 'admin' ipaddr: 'localhost'
password: 'secretpassword' }
title: 'Some Title'
host: 'localhost'
port: 8888
// Install package only (doesn't start)
redis.redis_install(config)!
// Start Redis
redis.start(config)!
// Stop Redis
redis.stop()!
// Restart Redis
redis.restart(config)!
// Check if running
is_running := redis.check(config)
``` ```
## Configuration Options
```v
pub struct RedisInstall {
pub mut:
name string = 'default' // Instance name
port int = 6379 // Redis port
datadir string = '/var/lib/redis' // Data directory
ipaddr string = 'localhost' // Bind address (space-separated for multiple)
}
```
## Platform Support
| Platform | Package Manager | Startup Method |
|----------|----------------|----------------|
| Ubuntu/Debian | apt (redis-server) | systemctl |
| Alpine | apk (redis) | direct start |
| Arch | pacman (redis) | systemctl |
| Fedora | dnf (redis) | systemctl |
| macOS | brew (redis) | direct start |
| Containers | varies | direct start |
## Using with Factory (Advanced)
For applications that need Redis state management:
```v
import incubaid.herolib.installers.base.redis
// Create and store in factory
mut installer := redis.new(name: 'myredis')!
// Install and start
installer.install(reset: false)!
installer.start()!
// Check status
if installer.running()! {
println('Redis is running')
}
// Stop
installer.stop()!
```
## Example Script
See `examples/installers/base/redis.vsh` for a complete working example.
## Notes
- Default data directory is `/var/lib/redis` (standard location)
- On systemd systems, uses the package's systemd service
- On non-systemd systems, starts Redis directly with `--daemonize yes`
- Automatically handles permissions for the Redis user
- Config file location: `/etc/redis/redis.conf` (Linux) or `${datadir}/redis.conf` (macOS)

View File

@@ -2,9 +2,6 @@ module redis
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.osal.systemd
import incubaid.herolib.osal.startupmanager import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist import incubaid.herolib.installers.ulist
import incubaid.herolib.core import incubaid.herolib.core
@@ -43,6 +40,14 @@ fn start_pre() ! {
mut cfg := get()! mut cfg := get()!
// Ensure data directory exists with proper permissions before configuring
osal.execute_silent('mkdir -p ${cfg.datadir}')!
if core.is_linux()! {
// On Linux, ensure redis user can access the directory
osal.execute_silent('chown -R redis:redis ${cfg.datadir}')!
osal.execute_silent('chmod 755 ${cfg.datadir}')!
}
// Configure redis before starting (applies template) // Configure redis before starting (applies template)
configure()! configure()!
@@ -95,29 +100,110 @@ fn upload() ! {
// )! // )!
} }
fn install() ! { // Install and start Redis with the given configuration
console.print_header('install redis') // This is the main entry point for installing Redis without using the factory
pub fn redis_install(args RedisInstall) ! {
if installed()! { // Check if already running
console.print_debug('redis-server already installed') if check(args) {
console.print_debug('Redis already running on port ${args.port}')
return return
} }
// Install redis-server via package manager console.print_header('install redis')
if core.is_linux()! {
osal.package_install('redis-server')! // Install Redis package if not already installed
} else { if !installed()! {
osal.package_install('redis')! if core.is_linux()! {
osal.package_install('redis-server')! // Ubuntu/Debian
} else {
osal.package_install('redis')! // macOS, Alpine, Arch, etc.
}
} }
mut cfg := get()! // Create data directory with correct permissions
osal.execute_silent('mkdir -p ${cfg.datadir}')! osal.execute_silent('mkdir -p ${args.datadir}')!
osal.execute_silent('chown -R redis:redis ${args.datadir}') or {}
osal.execute_silent('chmod 755 ${args.datadir}') or {}
console.print_debug('redis-server installed successfully') // Configure and start Redis
start(args)!
}
// Check if Redis is running
pub fn check(args RedisInstall) bool {
res := os.execute('redis-cli -c -p ${args.port} ping > /dev/null 2>&1')
if res.exit_code == 0 {
return true
}
return false
}
// Start Redis with the given configuration
// Writes config file, kills any existing processes, and starts Redis
pub fn start(args RedisInstall) ! {
if check(args) {
console.print_debug('Redis already running on port ${args.port}')
return
}
// Write Redis configuration file
configure_with_args(args)!
// Kill any existing Redis processes (including package auto-started ones)
osal.process_kill_recursive(name: 'redis-server')!
if core.platform()! == .osx {
// macOS: start directly with daemonize
osal.exec(cmd: 'redis-server ${configfilepath(args)} --daemonize yes')!
} else {
// Linux: prefer systemctl if available, otherwise start directly
if osal.cmd_exists('systemctl') {
// Ensure permissions are correct for systemd-managed Redis
osal.execute_silent('chown -R redis:redis ${args.datadir}') or {}
osal.execute_silent('chmod 755 ${args.datadir}') or {}
// Reset any failed state from previous kills
osal.execute_silent('systemctl reset-failed redis-server') or {}
osal.exec(cmd: 'systemctl start redis-server')!
} else {
// No systemctl (Alpine, containers, etc.)
// Set permissions for redis user before starting
osal.execute_silent('chown -R redis:redis ${args.datadir}') or {}
osal.execute_silent('chmod 755 ${args.datadir}') or {}
osal.exec(cmd: 'redis-server ${configfilepath(args)} --daemonize yes')!
}
}
// Wait for Redis to be ready
for _ in 0 .. 100 {
if check(args) {
console.print_debug('Redis started successfully')
return
}
time.sleep(100)
}
return error('Redis did not start properly after 10 seconds - could not ping on port ${args.port}')
}
// Stop Redis
pub fn stop() ! {
osal.execute_silent('redis-cli shutdown')!
}
// Restart Redis
pub fn restart(args RedisInstall) ! {
stop()!
time.sleep(500) // Give Redis time to shut down
start(args)!
}
// Private install function for factory-based usage
fn install() ! {
mut cfg := get()!
redis_install(cfg)!
} }
fn destroy() ! { fn destroy() ! {
mut cfg := get()! stop()!
cfg.stop()!
osal.process_kill_recursive(name: 'redis-server')! osal.process_kill_recursive(name: 'redis-server')!
} }

View File

@@ -17,7 +17,7 @@ pub struct RedisInstall {
pub mut: pub mut:
name string = 'default' name string = 'default'
port int = 6379 port int = 6379
datadir string = '${os.home_dir()}/hero/var/redis' datadir string = '/var/lib/redis'
ipaddr string = 'localhost' // can be more than 1, space separated ipaddr string = 'localhost' // can be more than 1, space separated
} }
@@ -31,7 +31,7 @@ fn obj_init(mycfg_ RedisInstall) !RedisInstall {
mycfg.port = 6379 mycfg.port = 6379
} }
if mycfg.datadir == '' { if mycfg.datadir == '' {
mycfg.datadir = '${os.home_dir()}/hero/var/redis' mycfg.datadir = '/var/lib/redis'
} }
if mycfg.ipaddr == '' { if mycfg.ipaddr == '' {
mycfg.ipaddr = 'localhost' mycfg.ipaddr = 'localhost'
@@ -47,11 +47,17 @@ fn configfilepath(args RedisInstall) string {
} }
} }
// called before start if done // Configure with args passed directly (like old installer)
fn configure_with_args(args RedisInstall) ! {
// Use V's template macro like the old installer
c := $tmpl('templates/redis_config.conf')
pathlib.template_write(c, configfilepath(args), true)!
}
// called before start if done (uses factory)
fn configure() ! { fn configure() ! {
mut args := get()! mut args := get()!
c := $tmpl('../templates/redis_config.conf') configure_with_args(args)!
pathlib.template_write(c, configfilepath(args), true)!
} }
/////////////NORMALLY NO NEED TO TOUCH /////////////NORMALLY NO NEED TO TOUCH

View File

@@ -0,0 +1,185 @@
module coordinator
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.installers.base.redis
import incubaid.herolib.develop.gittools
import os
// Helper function to ensure Redis is installed and running
fn ensure_redis_running() ! {
redis_config := redis.RedisInstall{
port: 6379
datadir: '/var/lib/redis'
ipaddr: 'localhost'
}
if !redis.check(redis_config) {
println('Installing and starting Redis...')
redis.redis_install(redis_config)!
} else {
println('Redis is already running')
}
}
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
mut cfg := get()!
mut res := []startupmanager.ZProcessNewArgs{}
res << startupmanager.ZProcessNewArgs{
name: 'coordinator'
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: 'coordinator'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/coordinator'
// )!
}
fn install() ! {
console.print_header('install coordinator')
// For coordinator, we build from source instead of downloading
build()!
}
// Public build function that works with or without Redis/factory available
pub fn build() ! {
console.print_header('build coordinator')
println('Starting coordinator build process...\n')
// Try to get config from factory, fallback to default if Redis not available
println('Initializing configuration...')
mut cfg_ref := get() or {
console.print_debug('Factory not available, using default config')
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(' - 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 coordinator)
println('Step 1/4: Checking Redis dependency...')
ensure_redis_running()!
println('Redis is ready\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(coderoot: '/root/code')!
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 coordinator binary from the horus workspace
println('Step 4/4: Building coordinator binary...')
println('WARNING: This may take several minutes (compiling Rust code)...')
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'
osal.execute_stdout(cmd)!
println('\nBuild 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/coordinator'
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('\nCoordinator built successfully!')
println('Binary location: ${cfg.binary_path}')
}
fn destroy() ! {
mut server := get()!
server.stop()!
osal.process_kill_recursive(name: 'coordinator')!
// Remove the built binary
osal.rm(server.binary_path)!
}

View File

@@ -1,4 +1,4 @@
module herocoordinator module coordinator
import incubaid.herolib.core.base import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook } import incubaid.herolib.core.playbook { PlayBook }
@@ -8,8 +8,8 @@ import incubaid.herolib.osal.startupmanager
import time import time
__global ( __global (
herocoordinator_global map[string]&HerocoordinatorServer coordinator_global map[string]&CoordinatorServer
herocoordinator_default string coordinator_default string
) )
/////////FACTORY /////////FACTORY
@@ -28,8 +28,8 @@ pub mut:
create bool // default will not create if not exist create bool // default will not create if not exist
} }
pub fn new(args ArgsGet) !&HerocoordinatorServer { pub fn new(args ArgsGet) !&CoordinatorServer {
mut obj := HerocoordinatorServer{ mut obj := CoordinatorServer{
name: args.name name: args.name
binary_path: args.binary_path binary_path: args.binary_path
redis_addr: args.redis_addr redis_addr: args.redis_addr
@@ -38,59 +38,67 @@ pub fn new(args ArgsGet) !&HerocoordinatorServer {
log_level: args.log_level log_level: args.log_level
repo_path: args.repo_path repo_path: args.repo_path
} }
set(obj)!
// 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)!
}
return get(name: args.name)! return get(name: args.name)!
} }
pub fn get(args ArgsGet) !&HerocoordinatorServer { pub fn get(args ArgsGet) !&CoordinatorServer {
mut context := base.context()! mut context := base.context()!
herocoordinator_default = args.name coordinator_default = args.name
if args.fromdb || args.name !in herocoordinator_global { if args.fromdb || args.name !in coordinator_global {
mut r := context.redis()! mut r := context.redis()!
if r.hexists('context:herocoordinator', args.name)! { if r.hexists('context:coordinator', args.name)! {
data := r.hget('context:herocoordinator', args.name)! data := r.hget('context:coordinator', args.name)!
if data.len == 0 { if data.len == 0 {
print_backtrace() print_backtrace()
return error('HerocoordinatorServer with name: ${args.name} does not exist, prob bug.') return error('CoordinatorServer with name: ${args.name} does not exist, prob bug.')
} }
mut obj := json.decode(HerocoordinatorServer, data)! mut obj := json.decode(CoordinatorServer, data)!
set_in_mem(obj)! set_in_mem(obj)!
} else { } else {
if args.create { if args.create {
new(args)! new(args)!
} else { } else {
print_backtrace() print_backtrace()
return error("HerocoordinatorServer with name '${args.name}' does not exist") return error("CoordinatorServer with name '${args.name}' does not exist")
} }
} }
return get(name: args.name)! // no longer from db nor create return get(name: args.name)! // no longer from db nor create
} }
return herocoordinator_global[args.name] or { return coordinator_global[args.name] or {
print_backtrace() print_backtrace()
return error('could not get config for herocoordinator with name:${args.name}') return error('could not get config for coordinator with name:${args.name}')
} }
} }
// register the config for the future // register the config for the future
pub fn set(o HerocoordinatorServer) ! { pub fn set(o CoordinatorServer) ! {
mut o2 := set_in_mem(o)! mut o2 := set_in_mem(o)!
herocoordinator_default = o2.name coordinator_default = o2.name
mut context := base.context()! mut context := base.context()!
mut r := context.redis()! mut r := context.redis()!
r.hset('context:herocoordinator', o2.name, json.encode(o2))! r.hset('context:coordinator', o2.name, json.encode(o2))!
} }
// does the config exists? // does the config exists?
pub fn exists(args ArgsGet) !bool { pub fn exists(args ArgsGet) !bool {
mut context := base.context()! mut context := base.context()!
mut r := context.redis()! mut r := context.redis()!
return r.hexists('context:herocoordinator', args.name)! return r.hexists('context:coordinator', args.name)!
} }
pub fn delete(args ArgsGet) ! { pub fn delete(args ArgsGet) ! {
mut context := base.context()! mut context := base.context()!
mut r := context.redis()! mut r := context.redis()!
r.hdel('context:herocoordinator', args.name)! r.hdel('context:coordinator', args.name)!
} }
@[params] @[params]
@@ -100,17 +108,17 @@ pub mut:
} }
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem // if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&HerocoordinatorServer { pub fn list(args ArgsList) ![]&CoordinatorServer {
mut res := []&HerocoordinatorServer{} mut res := []&CoordinatorServer{}
mut context := base.context()! mut context := base.context()!
if args.fromdb { if args.fromdb {
// reset what is in mem // reset what is in mem
herocoordinator_global = map[string]&HerocoordinatorServer{} coordinator_global = map[string]&CoordinatorServer{}
herocoordinator_default = '' coordinator_default = ''
} }
if args.fromdb { if args.fromdb {
mut r := context.redis()! mut r := context.redis()!
mut l := r.hkeys('context:herocoordinator')! mut l := r.hkeys('context:coordinator')!
for name in l { for name in l {
res << get(name: name, fromdb: true)! res << get(name: name, fromdb: true)!
@@ -118,7 +126,7 @@ pub fn list(args ArgsList) ![]&HerocoordinatorServer {
return res return res
} else { } else {
// load from memory // load from memory
for _, client in herocoordinator_global { for _, client in coordinator_global {
res << client res << client
} }
} }
@@ -126,18 +134,18 @@ pub fn list(args ArgsList) ![]&HerocoordinatorServer {
} }
// only sets in mem, does not set as config // only sets in mem, does not set as config
fn set_in_mem(o HerocoordinatorServer) !HerocoordinatorServer { fn set_in_mem(o CoordinatorServer) !CoordinatorServer {
mut o2 := obj_init(o)! mut o2 := obj_init(o)!
herocoordinator_global[o2.name] = &o2 coordinator_global[o2.name] = &o2
herocoordinator_default = o2.name coordinator_default = o2.name
return o2 return o2
} }
pub fn play(mut plbook PlayBook) ! { pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'herocoordinator.') { if !plbook.exists(filter: 'coordinator.') {
return return
} }
mut install_actions := plbook.find(filter: 'herocoordinator.configure')! mut install_actions := plbook.find(filter: 'coordinator.configure')!
if install_actions.len > 0 { if install_actions.len > 0 {
for mut install_action in install_actions { for mut install_action in install_actions {
heroscript := install_action.heroscript() heroscript := install_action.heroscript()
@@ -146,37 +154,37 @@ pub fn play(mut plbook PlayBook) ! {
install_action.done = true install_action.done = true
} }
} }
mut other_actions := plbook.find(filter: 'herocoordinator.')! mut other_actions := plbook.find(filter: 'coordinator.')!
for mut other_action in other_actions { for mut other_action in other_actions {
if other_action.name in ['destroy', 'install', 'build'] { if other_action.name in ['destroy', 'install', 'build'] {
mut p := other_action.params mut p := other_action.params
reset := p.get_default_false('reset') reset := p.get_default_false('reset')
if other_action.name == 'destroy' || reset { if other_action.name == 'destroy' || reset {
console.print_debug('install action herocoordinator.destroy') console.print_debug('install action coordinator.destroy')
destroy()! destroy()!
} }
if other_action.name == 'install' { if other_action.name == 'install' {
console.print_debug('install action herocoordinator.install') console.print_debug('install action coordinator.install')
install()! install()!
} }
} }
if other_action.name in ['start', 'stop', 'restart'] { if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params mut p := other_action.params
name := p.get('name')! name := p.get('name')!
mut herocoordinator_obj := get(name: name)! mut coordinator_obj := get(name: name)!
console.print_debug('action object:\n${herocoordinator_obj}') console.print_debug('action object:\n${coordinator_obj}')
if other_action.name == 'start' { if other_action.name == 'start' {
console.print_debug('install action herocoordinator.${other_action.name}') console.print_debug('install action coordinator.${other_action.name}')
herocoordinator_obj.start()! coordinator_obj.start()!
} }
if other_action.name == 'stop' { if other_action.name == 'stop' {
console.print_debug('install action herocoordinator.${other_action.name}') console.print_debug('install action coordinator.${other_action.name}')
herocoordinator_obj.stop()! coordinator_obj.stop()!
} }
if other_action.name == 'restart' { if other_action.name == 'restart' {
console.print_debug('install action herocoordinator.${other_action.name}') console.print_debug('install action coordinator.${other_action.name}')
herocoordinator_obj.restart()! coordinator_obj.restart()!
} }
} }
other_action.done = true other_action.done = true
@@ -195,37 +203,37 @@ fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.Sta
// systemd // systemd
match cat { match cat {
.screen { .screen {
console.print_debug("installer: herocoordinator' startupmanager get screen") console.print_debug("installer: coordinator' startupmanager get screen")
return startupmanager.get(.screen)! return startupmanager.get(.screen)!
} }
.zinit { .zinit {
console.print_debug("installer: herocoordinator' startupmanager get zinit") console.print_debug("installer: coordinator' startupmanager get zinit")
return startupmanager.get(.zinit)! return startupmanager.get(.zinit)!
} }
.systemd { .systemd {
console.print_debug("installer: herocoordinator' startupmanager get systemd") console.print_debug("installer: coordinator' startupmanager get systemd")
return startupmanager.get(.systemd)! return startupmanager.get(.systemd)!
} }
else { else {
console.print_debug("installer: herocoordinator' startupmanager get auto") console.print_debug("installer: coordinator' startupmanager get auto")
return startupmanager.get(.auto)! return startupmanager.get(.auto)!
} }
} }
} }
// load from disk and make sure is properly intialized // load from disk and make sure is properly intialized
pub fn (mut self HerocoordinatorServer) reload() ! { pub fn (mut self CoordinatorServer) reload() ! {
switch(self.name) switch(self.name)
self = obj_init(self)! self = obj_init(self)!
} }
pub fn (mut self HerocoordinatorServer) start() ! { pub fn (mut self CoordinatorServer) start() ! {
switch(self.name) switch(self.name)
if self.running()! { if self.running()! {
return return
} }
console.print_header('installer: herocoordinator start') console.print_header('installer: coordinator start')
if !installed()! { if !installed()! {
install()! install()!
@@ -238,7 +246,7 @@ pub fn (mut self HerocoordinatorServer) start() ! {
for zprocess in startupcmd()! { for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)! mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: herocoordinator starting with ${zprocess.startuptype}...') console.print_debug('installer: coordinator starting with ${zprocess.startuptype}...')
sm.new(zprocess)! sm.new(zprocess)!
@@ -253,16 +261,16 @@ pub fn (mut self HerocoordinatorServer) start() ! {
} }
time.sleep(100 * time.millisecond) time.sleep(100 * time.millisecond)
} }
return error('herocoordinator did not install properly.') return error('coordinator did not install properly.')
} }
pub fn (mut self HerocoordinatorServer) install_start(args InstallArgs) ! { pub fn (mut self CoordinatorServer) install_start(args InstallArgs) ! {
switch(self.name) switch(self.name)
self.install(args)! self.install(args)!
self.start()! self.start()!
} }
pub fn (mut self HerocoordinatorServer) stop() ! { pub fn (mut self CoordinatorServer) stop() ! {
switch(self.name) switch(self.name)
stop_pre()! stop_pre()!
for zprocess in startupcmd()! { for zprocess in startupcmd()! {
@@ -272,13 +280,13 @@ pub fn (mut self HerocoordinatorServer) stop() ! {
stop_post()! stop_post()!
} }
pub fn (mut self HerocoordinatorServer) restart() ! { pub fn (mut self CoordinatorServer) restart() ! {
switch(self.name) switch(self.name)
self.stop()! self.stop()!
self.start()! self.start()!
} }
pub fn (mut self HerocoordinatorServer) running() !bool { pub fn (mut self CoordinatorServer) running() !bool {
switch(self.name) switch(self.name)
// walk over the generic processes, if not running return // walk over the generic processes, if not running return
@@ -300,25 +308,25 @@ pub mut:
reset bool reset bool
} }
pub fn (mut self HerocoordinatorServer) install(args InstallArgs) ! { pub fn (mut self CoordinatorServer) install(args InstallArgs) ! {
switch(self.name) switch(self.name)
if args.reset || (!installed()!) { if args.reset || (!installed()!) {
install()! install()!
} }
} }
pub fn (mut self HerocoordinatorServer) build() ! { pub fn (mut self CoordinatorServer) build() ! {
switch(self.name) switch(self.name)
build()! build()!
} }
pub fn (mut self HerocoordinatorServer) destroy() ! { pub fn (mut self CoordinatorServer) destroy() ! {
switch(self.name) switch(self.name)
self.stop() or {} self.stop() or {}
destroy()! destroy()!
} }
// switch instance to be used for herocoordinator // switch instance to be used for coordinator
pub fn switch(name string) { pub fn switch(name string) {
herocoordinator_default = name coordinator_default = name
} }

View File

@@ -1,4 +1,4 @@
module herocoordinator module coordinator
import incubaid.herolib.data.paramsparser import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero import incubaid.herolib.data.encoderhero
@@ -11,7 +11,7 @@ const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED // THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap] @[heap]
pub struct HerocoordinatorServer { pub struct CoordinatorServer {
pub mut: pub mut:
name string = 'default' name string = 'default'
binary_path string = '/hero/var/bin/coordinator' binary_path string = '/hero/var/bin/coordinator'
@@ -23,7 +23,7 @@ pub mut:
} }
// your checking & initialization code if needed // your checking & initialization code if needed
fn obj_init(mycfg_ HerocoordinatorServer) !HerocoordinatorServer { fn obj_init(mycfg_ CoordinatorServer) !CoordinatorServer {
mut mycfg := mycfg_ mut mycfg := mycfg_
if mycfg.name == '' { if mycfg.name == '' {
mycfg.name = 'default' mycfg.name = 'default'
@@ -59,11 +59,11 @@ fn configure() ! {
/////////////NORMALLY NO NEED TO TOUCH /////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj HerocoordinatorServer) !string { pub fn heroscript_dumps(obj CoordinatorServer) !string {
return encoderhero.encode[HerocoordinatorServer](obj)! return encoderhero.encode[CoordinatorServer](obj)!
} }
pub fn heroscript_loads(heroscript string) !HerocoordinatorServer { pub fn heroscript_loads(heroscript string) !CoordinatorServer {
mut obj := encoderhero.decode[HerocoordinatorServer](heroscript)! mut obj := encoderhero.decode[CoordinatorServer](heroscript)!
return obj return obj
} }

View File

@@ -1,253 +0,0 @@
module herocoordinator
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: 'herocoordinator'
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: 'herocoordinator'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/herocoordinator'
// )!
}
fn install() ! {
console.print_header('install herocoordinator')
// For herocoordinator, we build from source instead of downloading
build()!
}
// Public function to build herocoordinator without requiring factory/redis
pub fn build_coordinator() ! {
console.print_header('build herocoordinator')
println('📦 Starting herocoordinator build process...\n')
// Use default config instead of getting from factory
println(' Initializing configuration...')
mut cfg := HerocoordinatorServer{}
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 coordinator)
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(coderoot: '/root/code')!
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 coordinator binary from the horus workspace
println('🔍 Step 4/4: Building coordinator binary...')
println(' This may take several minutes (compiling Rust code)...')
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'
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/coordinator'
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🎉 Herocoordinator built successfully!')
println('📍 Binary location: ${cfg.binary_path}')
}
fn build() ! {
console.print_header('build herocoordinator')
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(coderoot: '/root/code')!
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)...')
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_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/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('herocoordinator built successfully at ${cfg.binary_path}')
}
fn destroy() ! {
mut server := get()!
server.stop()!
osal.process_kill_recursive(name: 'herocoordinator')!
// Remove the built binary
osal.rm(server.binary_path)!
}