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,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
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
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)!
// Install and start Redis
redis.redis_install(config)!
// Check if running
if redis.check(config) {
println('Redis is running!')
}
```
## example heroscript
### Using Individual Functions
```v
import incubaid.herolib.installers.base.redis
```hero
!!redis.configure
homedir: '/home/user/redis'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
config := redis.RedisInstall{
port: 6379
datadir: '/var/lib/redis'
ipaddr: 'localhost'
}
// 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.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.installers.ulist
import incubaid.herolib.core
@@ -43,6 +40,14 @@ fn start_pre() ! {
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()!
@@ -95,29 +100,110 @@ fn upload() ! {
// )!
}
fn install() ! {
console.print_header('install redis')
if installed()! {
console.print_debug('redis-server already installed')
// Install and start Redis with the given configuration
// This is the main entry point for installing Redis without using the factory
pub fn redis_install(args RedisInstall) ! {
// Check if already running
if check(args) {
console.print_debug('Redis already running on port ${args.port}')
return
}
// Install redis-server via package manager
if core.is_linux()! {
osal.package_install('redis-server')!
} else {
osal.package_install('redis')!
console.print_header('install redis')
// Install Redis package if not already installed
if !installed()! {
if core.is_linux()! {
osal.package_install('redis-server')! // Ubuntu/Debian
} else {
osal.package_install('redis')! // macOS, Alpine, Arch, etc.
}
}
mut cfg := get()!
osal.execute_silent('mkdir -p ${cfg.datadir}')!
// Create data directory with correct permissions
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() ! {
mut cfg := get()!
cfg.stop()!
stop()!
osal.process_kill_recursive(name: 'redis-server')!
}

View File

@@ -17,7 +17,7 @@ pub struct RedisInstall {
pub mut:
name string = 'default'
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
}
@@ -31,7 +31,7 @@ fn obj_init(mycfg_ RedisInstall) !RedisInstall {
mycfg.port = 6379
}
if mycfg.datadir == '' {
mycfg.datadir = '${os.home_dir()}/hero/var/redis'
mycfg.datadir = '/var/lib/redis'
}
if mycfg.ipaddr == '' {
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() ! {
mut args := get()!
c := $tmpl('../templates/redis_config.conf')
pathlib.template_write(c, configfilepath(args), true)!
configure_with_args(args)!
}
/////////////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.playbook { PlayBook }
@@ -8,8 +8,8 @@ import incubaid.herolib.osal.startupmanager
import time
__global (
herocoordinator_global map[string]&HerocoordinatorServer
herocoordinator_default string
coordinator_global map[string]&CoordinatorServer
coordinator_default string
)
/////////FACTORY
@@ -28,8 +28,8 @@ pub mut:
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&HerocoordinatorServer {
mut obj := HerocoordinatorServer{
pub fn new(args ArgsGet) !&CoordinatorServer {
mut obj := CoordinatorServer{
name: args.name
binary_path: args.binary_path
redis_addr: args.redis_addr
@@ -38,59 +38,67 @@ pub fn new(args ArgsGet) !&HerocoordinatorServer {
log_level: args.log_level
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)!
}
pub fn get(args ArgsGet) !&HerocoordinatorServer {
pub fn get(args ArgsGet) !&CoordinatorServer {
mut context := base.context()!
herocoordinator_default = args.name
if args.fromdb || args.name !in herocoordinator_global {
coordinator_default = args.name
if args.fromdb || args.name !in coordinator_global {
mut r := context.redis()!
if r.hexists('context:herocoordinator', args.name)! {
data := r.hget('context:herocoordinator', args.name)!
if r.hexists('context:coordinator', args.name)! {
data := r.hget('context:coordinator', args.name)!
if data.len == 0 {
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)!
} else {
if args.create {
new(args)!
} else {
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 herocoordinator_global[args.name] or {
return coordinator_global[args.name] or {
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
pub fn set(o HerocoordinatorServer) ! {
pub fn set(o CoordinatorServer) ! {
mut o2 := set_in_mem(o)!
herocoordinator_default = o2.name
coordinator_default = o2.name
mut context := base.context()!
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?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:herocoordinator', args.name)!
return r.hexists('context:coordinator', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:herocoordinator', args.name)!
r.hdel('context:coordinator', args.name)!
}
@[params]
@@ -100,17 +108,17 @@ pub mut:
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&HerocoordinatorServer {
mut res := []&HerocoordinatorServer{}
pub fn list(args ArgsList) ![]&CoordinatorServer {
mut res := []&CoordinatorServer{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
herocoordinator_global = map[string]&HerocoordinatorServer{}
herocoordinator_default = ''
coordinator_global = map[string]&CoordinatorServer{}
coordinator_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:herocoordinator')!
mut l := r.hkeys('context:coordinator')!
for name in l {
res << get(name: name, fromdb: true)!
@@ -118,7 +126,7 @@ pub fn list(args ArgsList) ![]&HerocoordinatorServer {
return res
} else {
// load from memory
for _, client in herocoordinator_global {
for _, client in coordinator_global {
res << client
}
}
@@ -126,18 +134,18 @@ pub fn list(args ArgsList) ![]&HerocoordinatorServer {
}
// 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)!
herocoordinator_global[o2.name] = &o2
herocoordinator_default = o2.name
coordinator_global[o2.name] = &o2
coordinator_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'herocoordinator.') {
if !plbook.exists(filter: 'coordinator.') {
return
}
mut install_actions := plbook.find(filter: 'herocoordinator.configure')!
mut install_actions := plbook.find(filter: 'coordinator.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
@@ -146,37 +154,37 @@ pub fn play(mut plbook PlayBook) ! {
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 {
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 herocoordinator.destroy')
console.print_debug('install action coordinator.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action herocoordinator.install')
console.print_debug('install action coordinator.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut herocoordinator_obj := get(name: name)!
console.print_debug('action object:\n${herocoordinator_obj}')
mut coordinator_obj := get(name: name)!
console.print_debug('action object:\n${coordinator_obj}')
if other_action.name == 'start' {
console.print_debug('install action herocoordinator.${other_action.name}')
herocoordinator_obj.start()!
console.print_debug('install action coordinator.${other_action.name}')
coordinator_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action herocoordinator.${other_action.name}')
herocoordinator_obj.stop()!
console.print_debug('install action coordinator.${other_action.name}')
coordinator_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action herocoordinator.${other_action.name}')
herocoordinator_obj.restart()!
console.print_debug('install action coordinator.${other_action.name}')
coordinator_obj.restart()!
}
}
other_action.done = true
@@ -195,37 +203,37 @@ fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.Sta
// systemd
match cat {
.screen {
console.print_debug("installer: herocoordinator' startupmanager get screen")
console.print_debug("installer: coordinator' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: herocoordinator' startupmanager get zinit")
console.print_debug("installer: coordinator' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: herocoordinator' startupmanager get systemd")
console.print_debug("installer: coordinator' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: herocoordinator' startupmanager get auto")
console.print_debug("installer: coordinator' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self HerocoordinatorServer) reload() ! {
pub fn (mut self CoordinatorServer) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self HerocoordinatorServer) start() ! {
pub fn (mut self CoordinatorServer) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('installer: herocoordinator start')
console.print_header('installer: coordinator start')
if !installed()! {
install()!
@@ -238,7 +246,7 @@ pub fn (mut self HerocoordinatorServer) start() ! {
for zprocess in startupcmd()! {
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)!
@@ -253,16 +261,16 @@ pub fn (mut self HerocoordinatorServer) start() ! {
}
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)
self.install(args)!
self.start()!
}
pub fn (mut self HerocoordinatorServer) stop() ! {
pub fn (mut self CoordinatorServer) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
@@ -272,13 +280,13 @@ pub fn (mut self HerocoordinatorServer) stop() ! {
stop_post()!
}
pub fn (mut self HerocoordinatorServer) restart() ! {
pub fn (mut self CoordinatorServer) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self HerocoordinatorServer) running() !bool {
pub fn (mut self CoordinatorServer) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
@@ -300,25 +308,25 @@ pub mut:
reset bool
}
pub fn (mut self HerocoordinatorServer) install(args InstallArgs) ! {
pub fn (mut self CoordinatorServer) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self HerocoordinatorServer) build() ! {
pub fn (mut self CoordinatorServer) build() ! {
switch(self.name)
build()!
}
pub fn (mut self HerocoordinatorServer) destroy() ! {
pub fn (mut self CoordinatorServer) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for herocoordinator
// switch instance to be used for coordinator
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.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
@[heap]
pub struct HerocoordinatorServer {
pub struct CoordinatorServer {
pub mut:
name string = 'default'
binary_path string = '/hero/var/bin/coordinator'
@@ -23,7 +23,7 @@ pub mut:
}
// your checking & initialization code if needed
fn obj_init(mycfg_ HerocoordinatorServer) !HerocoordinatorServer {
fn obj_init(mycfg_ CoordinatorServer) !CoordinatorServer {
mut mycfg := mycfg_
if mycfg.name == '' {
mycfg.name = 'default'
@@ -59,11 +59,11 @@ fn configure() ! {
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj HerocoordinatorServer) !string {
return encoderhero.encode[HerocoordinatorServer](obj)!
pub fn heroscript_dumps(obj CoordinatorServer) !string {
return encoderhero.encode[CoordinatorServer](obj)!
}
pub fn heroscript_loads(heroscript string) !HerocoordinatorServer {
mut obj := encoderhero.decode[HerocoordinatorServer](heroscript)!
pub fn heroscript_loads(heroscript string) !CoordinatorServer {
mut obj := encoderhero.decode[CoordinatorServer](heroscript)!
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)!
}