From bf6dec48f17c9eadc46d181e4c208bb6575d6a85 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:57:40 +0100 Subject: [PATCH] add other horus installers, create examples, test startup --- compare_dirs.sh | 47 +++ examples/installers/horus/README.md | 209 +++++++++++ .../installers/horus/horus_config.heroscript | 60 ++++ .../installers/horus/horus_full_install.vsh | 60 ++++ examples/installers/horus/horus_start_all.vsh | 76 ++++ examples/installers/horus/horus_status.vsh | 56 +++ examples/installers/horus/horus_stop_all.vsh | 43 +++ examples/installers/horus/quick_start.vsh | 52 +++ lib/biz/bizmodel/export.v | 2 +- lib/installers/horus/coordinator/.heroscript | 2 +- .../horus/coordinator/coordinator_actions.v | 194 +++++++---- .../horus/coordinator/coordinator_factory_.v | 15 +- .../horus/coordinator/coordinator_model.v | 16 +- lib/installers/horus/coordinator/readme.md | 36 +- lib/installers/horus/herorunner/.heroscript | 12 + .../horus/herorunner/herorunner_actions.v | 139 ++++++++ .../horus/herorunner/herorunner_factory_.v | 318 +++++++++++++++++ .../horus/herorunner/herorunner_model.v | 58 ++++ lib/installers/horus/herorunner/readme.md | 104 ++++++ .../horus/herorunner/templates/atemplate.yaml | 5 + lib/installers/horus/osirisrunner/.heroscript | 12 + .../horus/osirisrunner/osirisrunner_actions.v | 142 ++++++++ .../osirisrunner/osirisrunner_factory_.v | 320 +++++++++++++++++ .../horus/osirisrunner/osirisrunner_model.v | 62 ++++ lib/installers/horus/osirisrunner/readme.md | 104 ++++++ .../osirisrunner/templates/atemplate.yaml | 5 + lib/installers/horus/salrunner/.heroscript | 12 + lib/installers/horus/salrunner/readme.md | 104 ++++++ .../horus/salrunner/salrunner_actions.v | 142 ++++++++ .../horus/salrunner/salrunner_factory_.v | 320 +++++++++++++++++ .../horus/salrunner/salrunner_model.v | 62 ++++ .../horus/salrunner/templates/atemplate.yaml | 5 + lib/installers/horus/supervisor/.heroscript | 12 + lib/installers/horus/supervisor/readme.md | 144 ++++++++ lib/installers/horus/supervisor/readme_new.md | 0 .../horus/supervisor/supervisor_actions.v | 253 ++++++++++++++ .../horus/supervisor/supervisor_factory_.v | 324 ++++++++++++++++++ .../horus/supervisor/supervisor_model.v | 70 ++++ .../horus/supervisor/templates/atemplate.yaml | 5 + lib/installers/infra/gitea/.heroscript | 2 +- .../zinit_installer/zinit_installer_actions.v | 2 +- lib/osal/startupmanager/startupmanager.v | 39 ++- lib/schemas/jsonrpc/model_request.v | 2 +- 43 files changed, 3515 insertions(+), 132 deletions(-) create mode 100755 compare_dirs.sh create mode 100644 examples/installers/horus/README.md create mode 100644 examples/installers/horus/horus_config.heroscript create mode 100755 examples/installers/horus/horus_full_install.vsh create mode 100755 examples/installers/horus/horus_start_all.vsh create mode 100755 examples/installers/horus/horus_status.vsh create mode 100755 examples/installers/horus/horus_stop_all.vsh create mode 100755 examples/installers/horus/quick_start.vsh create mode 100644 lib/installers/horus/herorunner/.heroscript create mode 100644 lib/installers/horus/herorunner/herorunner_actions.v create mode 100644 lib/installers/horus/herorunner/herorunner_factory_.v create mode 100644 lib/installers/horus/herorunner/herorunner_model.v create mode 100644 lib/installers/horus/herorunner/readme.md create mode 100644 lib/installers/horus/herorunner/templates/atemplate.yaml create mode 100644 lib/installers/horus/osirisrunner/.heroscript create mode 100644 lib/installers/horus/osirisrunner/osirisrunner_actions.v create mode 100644 lib/installers/horus/osirisrunner/osirisrunner_factory_.v create mode 100644 lib/installers/horus/osirisrunner/osirisrunner_model.v create mode 100644 lib/installers/horus/osirisrunner/readme.md create mode 100644 lib/installers/horus/osirisrunner/templates/atemplate.yaml create mode 100644 lib/installers/horus/salrunner/.heroscript create mode 100644 lib/installers/horus/salrunner/readme.md create mode 100644 lib/installers/horus/salrunner/salrunner_actions.v create mode 100644 lib/installers/horus/salrunner/salrunner_factory_.v create mode 100644 lib/installers/horus/salrunner/salrunner_model.v create mode 100644 lib/installers/horus/salrunner/templates/atemplate.yaml create mode 100644 lib/installers/horus/supervisor/.heroscript create mode 100644 lib/installers/horus/supervisor/readme.md create mode 100644 lib/installers/horus/supervisor/readme_new.md create mode 100644 lib/installers/horus/supervisor/supervisor_actions.v create mode 100644 lib/installers/horus/supervisor/supervisor_factory_.v create mode 100644 lib/installers/horus/supervisor/supervisor_model.v create mode 100644 lib/installers/horus/supervisor/templates/atemplate.yaml diff --git a/compare_dirs.sh b/compare_dirs.sh new file mode 100755 index 00000000..16956a2c --- /dev/null +++ b/compare_dirs.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Usage: ./compare_dirs.sh +# Example: ./compare_dirs.sh main feature-branch src + +if [ "$#" -ne 3 ]; then + echo "Usage: $0 " + 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 \ No newline at end of file diff --git a/examples/installers/horus/README.md b/examples/installers/horus/README.md new file mode 100644 index 00000000..a54bb1f3 --- /dev/null +++ b/examples/installers/horus/README.md @@ -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/` diff --git a/examples/installers/horus/horus_config.heroscript b/examples/installers/horus/horus_config.heroscript new file mode 100644 index 00000000..0782a6e9 --- /dev/null +++ b/examples/installers/horus/horus_config.heroscript @@ -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' diff --git a/examples/installers/horus/horus_full_install.vsh b/examples/installers/horus/horus_full_install.vsh new file mode 100755 index 00000000..15a40f45 --- /dev/null +++ b/examples/installers/horus/horus_full_install.vsh @@ -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') diff --git a/examples/installers/horus/horus_start_all.vsh b/examples/installers/horus/horus_start_all.vsh new file mode 100755 index 00000000..6dfc8852 --- /dev/null +++ b/examples/installers/horus/horus_start_all.vsh @@ -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') diff --git a/examples/installers/horus/horus_status.vsh b/examples/installers/horus/horus_status.vsh new file mode 100755 index 00000000..861689d5 --- /dev/null +++ b/examples/installers/horus/horus_status.vsh @@ -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') +} diff --git a/examples/installers/horus/horus_stop_all.vsh b/examples/installers/horus/horus_stop_all.vsh new file mode 100755 index 00000000..bf8cd0e3 --- /dev/null +++ b/examples/installers/horus/horus_stop_all.vsh @@ -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) diff --git a/examples/installers/horus/quick_start.vsh b/examples/installers/horus/quick_start.vsh new file mode 100755 index 00000000..a91eb0fb --- /dev/null +++ b/examples/installers/horus/quick_start.vsh @@ -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') diff --git a/lib/biz/bizmodel/export.v b/lib/biz/bizmodel/export.v index 25a0cd95..1de7a602 100644 --- a/lib/biz/bizmodel/export.v +++ b/lib/biz/bizmodel/export.v @@ -17,7 +17,7 @@ pub mut: pub fn (b BizModel) export(args ExportArgs) ! { name := if args.name != '' { args.name } else { texttools.snake_case(args.title) } 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 empty: true )! diff --git a/lib/installers/horus/coordinator/.heroscript b/lib/installers/horus/coordinator/.heroscript index 22fab455..b832cd18 100644 --- a/lib/installers/horus/coordinator/.heroscript +++ b/lib/installers/horus/coordinator/.heroscript @@ -1,7 +1,7 @@ !!hero_code.generate_installer name:'' - classname:'HerocoordinatorServer' + classname:'CoordinatorServer' singleton:0 templates:1 default:1 diff --git a/lib/installers/horus/coordinator/coordinator_actions.v b/lib/installers/horus/coordinator/coordinator_actions.v index 6040ff97..88e47efb 100644 --- a/lib/installers/horus/coordinator/coordinator_actions.v +++ b/lib/installers/horus/coordinator/coordinator_actions.v @@ -2,11 +2,11 @@ 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 @@ -34,21 +34,16 @@ fn running() !bool { return res.exit_code == 0 } -// Lifecycle hooks - can be implemented for custom pre/post actions fn start_pre() ! { - // Add any pre-start actions here if needed } fn start_post() ! { - // Add any post-start actions here if needed } fn stop_pre() ! { - // Add any pre-stop actions here if needed } fn stop_post() ! { - // Add any post-stop actions here if needed } //////////////////// 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() ! { 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() ! { +// Public function to build coordinator without requiring factory/redis +pub fn 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 - 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') + // Use default config instead of getting from factory + println('āš™ļø Initializing configuration...') + mut cfg := CoordinatorServer{} + 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...') - // Parse redis_addr to extract host - parts := cfg.redis_addr.split(':') - redis_host := if parts.len > 0 { parts[0] } else { 'localhost' } - ensure_redis_running(redis_port: cfg.redis_port, redis_addr: redis_host)! - println('Redis is ready\n') + 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...') + 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...') + println('šŸ“„ Installing Rust...') rust_installer.install()! - println('Rust installed\n') + println('āœ… Rust installed\n') } 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 - println('Step 3/4: Cloning/updating horus repository...') - // Use the configured repo_path or default coderoot - mut gs := gittools.new(coderoot: '/root/code')! + 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 @@ -162,40 +143,103 @@ pub fn build() ! { // Update the path to the actual cloned repo 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 - 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') + 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('\nBuild completed successfully') + println('\nāœ… Build completed successfully') // 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) 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}') - - // 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.') - } - + 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}') + println('\nšŸŽ‰ Coordinator built successfully!') + 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() ! { diff --git a/lib/installers/horus/coordinator/coordinator_factory_.v b/lib/installers/horus/coordinator/coordinator_factory_.v index f1e74ef4..b0d5ad63 100644 --- a/lib/installers/horus/coordinator/coordinator_factory_.v +++ b/lib/installers/horus/coordinator/coordinator_factory_.v @@ -38,15 +38,7 @@ pub fn new(args ArgsGet) !&CoordinatorServer { log_level: args.log_level 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)! } @@ -215,8 +207,9 @@ fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.Sta return startupmanager.get(.systemd)! } else { + // default to zinit 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() ! { switch(self.name) + if self.running()! { return } @@ -242,7 +236,6 @@ pub fn (mut self CoordinatorServer) start() ! { configure()! start_pre()! - for zprocess in startupcmd()! { mut sm := startupmanager_get(zprocess.startuptype)! diff --git a/lib/installers/horus/coordinator/coordinator_model.v b/lib/installers/horus/coordinator/coordinator_model.v index 453298bf..f3f3a9b1 100644 --- a/lib/installers/horus/coordinator/coordinator_model.v +++ b/lib/installers/horus/coordinator/coordinator_model.v @@ -1,5 +1,7 @@ module coordinator +import os +import incubaid.herolib.data.paramsparser import incubaid.herolib.data.encoderhero import incubaid.herolib.osal.core as osal import incubaid.herolib.core.pathlib @@ -13,33 +15,26 @@ const default = true pub struct CoordinatorServer { pub mut: 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_port int = 6379 http_port int = 8081 ws_port int = 9653 log_level string = 'info' repo_path string = '/root/code/git.ourworld.tf/herocode/horus' } -// Initialize configuration with defaults if not provided -// Note: Struct defaults are already set, this ensures runtime overrides work correctly +// your checking & initialization code if needed fn obj_init(mycfg_ CoordinatorServer) !CoordinatorServer { mut mycfg := mycfg_ - - // Only override if explicitly empty (struct defaults should handle most cases) if mycfg.name == '' { mycfg.name = 'default' } 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 == '' { mycfg.redis_addr = '127.0.0.1:6379' } - if mycfg.redis_port == 0 { - mycfg.redis_port = 6379 - } if mycfg.http_port == 0 { mycfg.http_port = 8081 } @@ -52,7 +47,6 @@ fn obj_init(mycfg_ CoordinatorServer) !CoordinatorServer { if mycfg.repo_path == '' { mycfg.repo_path = '/root/code/git.ourworld.tf/herocode/horus' } - return mycfg } diff --git a/lib/installers/horus/coordinator/readme.md b/lib/installers/horus/coordinator/readme.md index a7f4a4df..563ebdaa 100644 --- a/lib/installers/horus/coordinator/readme.md +++ b/lib/installers/horus/coordinator/readme.md @@ -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 @@ -16,23 +16,23 @@ A V language installer module for building and managing the Herocoordinator serv ```bash cd /root/code/github/incubaid/herolib/examples/installers/infra -./herocoordinator.vsh +./coordinator.vsh ``` ### Manual Usage ```v -import incubaid.herolib.installers.infra.herocoordinator as herocoordinator_installer +import incubaid.herolib.installers.infra.coordinator as coordinator_installer -mut herocoordinator := herocoordinator_installer.get()! -herocoordinator.install()! -herocoordinator.start()! +mut coordinator := coordinator_installer.get()! +coordinator.install()! +coordinator.start()! ``` ## Configuration ```bash -!!herocoordinator.configure +!!coordinator.configure name:'default' binary_path:'/hero/var/bin/coordinator' 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` ```bash -hero herocoordinator.install +hero coordinator.install ``` ### Start -Starts the herocoordinator service using zinit: +Starts the coordinator service using zinit: ```bash -hero herocoordinator.start +hero coordinator.start ``` ### Stop Stops the running service: ```bash -hero herocoordinator.stop +hero coordinator.stop ``` ### Restart Restarts the service: ```bash -hero herocoordinator.restart +hero coordinator.restart ``` ### Destroy Stops the service and removes all files: ```bash -hero herocoordinator.destroy +hero coordinator.destroy ``` ## Requirements @@ -104,9 +104,9 @@ hero herocoordinator.destroy The installer follows the standard herolib installer pattern: -- **herocoordinator_model.v**: Configuration structure and initialization -- **herocoordinator_actions.v**: Build, install, start, stop, destroy logic -- **herocoordinator_factory_.v**: Factory pattern for instance management +- **coordinator_model.v**: Configuration structure and initialization +- **coordinator_actions.v**: Build, install, start, stop, destroy logic +- **coordinator_factory_.v**: Factory pattern for instance management ## Notes @@ -119,7 +119,7 @@ The installer follows the standard herolib installer pattern: ## Example Workflow ```v -import incubaid.herolib.installers.infra.herocoordinator as hc +import incubaid.herolib.installers.infra.coordinator as hc // Get installer instance mut coordinator := hc.get()! diff --git a/lib/installers/horus/herorunner/.heroscript b/lib/installers/horus/herorunner/.heroscript new file mode 100644 index 00000000..8045c9f0 --- /dev/null +++ b/lib/installers/horus/herorunner/.heroscript @@ -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 diff --git a/lib/installers/horus/herorunner/herorunner_actions.v b/lib/installers/horus/herorunner/herorunner_actions.v new file mode 100644 index 00000000..ac7f541a --- /dev/null +++ b/lib/installers/horus/herorunner/herorunner_actions.v @@ -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)! +} diff --git a/lib/installers/horus/herorunner/herorunner_factory_.v b/lib/installers/horus/herorunner/herorunner_factory_.v new file mode 100644 index 00000000..586b92a5 --- /dev/null +++ b/lib/installers/horus/herorunner/herorunner_factory_.v @@ -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 +} diff --git a/lib/installers/horus/herorunner/herorunner_model.v b/lib/installers/horus/herorunner/herorunner_model.v new file mode 100644 index 00000000..07b9364a --- /dev/null +++ b/lib/installers/horus/herorunner/herorunner_model.v @@ -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 +} diff --git a/lib/installers/horus/herorunner/readme.md b/lib/installers/horus/herorunner/readme.md new file mode 100644 index 00000000..e53fe6f8 --- /dev/null +++ b/lib/installers/horus/herorunner/readme.md @@ -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 diff --git a/lib/installers/horus/herorunner/templates/atemplate.yaml b/lib/installers/horus/herorunner/templates/atemplate.yaml new file mode 100644 index 00000000..a4c386dd --- /dev/null +++ b/lib/installers/horus/herorunner/templates/atemplate.yaml @@ -0,0 +1,5 @@ + + +name: ${cfg.configpath} + + diff --git a/lib/installers/horus/osirisrunner/.heroscript b/lib/installers/horus/osirisrunner/.heroscript new file mode 100644 index 00000000..b53ebc30 --- /dev/null +++ b/lib/installers/horus/osirisrunner/.heroscript @@ -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 diff --git a/lib/installers/horus/osirisrunner/osirisrunner_actions.v b/lib/installers/horus/osirisrunner/osirisrunner_actions.v new file mode 100644 index 00000000..76d2c34c --- /dev/null +++ b/lib/installers/horus/osirisrunner/osirisrunner_actions.v @@ -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)! +} diff --git a/lib/installers/horus/osirisrunner/osirisrunner_factory_.v b/lib/installers/horus/osirisrunner/osirisrunner_factory_.v new file mode 100644 index 00000000..5bf9f2fb --- /dev/null +++ b/lib/installers/horus/osirisrunner/osirisrunner_factory_.v @@ -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 +} diff --git a/lib/installers/horus/osirisrunner/osirisrunner_model.v b/lib/installers/horus/osirisrunner/osirisrunner_model.v new file mode 100644 index 00000000..21cc0774 --- /dev/null +++ b/lib/installers/horus/osirisrunner/osirisrunner_model.v @@ -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 +} diff --git a/lib/installers/horus/osirisrunner/readme.md b/lib/installers/horus/osirisrunner/readme.md new file mode 100644 index 00000000..088e865d --- /dev/null +++ b/lib/installers/horus/osirisrunner/readme.md @@ -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 diff --git a/lib/installers/horus/osirisrunner/templates/atemplate.yaml b/lib/installers/horus/osirisrunner/templates/atemplate.yaml new file mode 100644 index 00000000..a4c386dd --- /dev/null +++ b/lib/installers/horus/osirisrunner/templates/atemplate.yaml @@ -0,0 +1,5 @@ + + +name: ${cfg.configpath} + + diff --git a/lib/installers/horus/salrunner/.heroscript b/lib/installers/horus/salrunner/.heroscript new file mode 100644 index 00000000..4cc852fe --- /dev/null +++ b/lib/installers/horus/salrunner/.heroscript @@ -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 diff --git a/lib/installers/horus/salrunner/readme.md b/lib/installers/horus/salrunner/readme.md new file mode 100644 index 00000000..476a03cb --- /dev/null +++ b/lib/installers/horus/salrunner/readme.md @@ -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 diff --git a/lib/installers/horus/salrunner/salrunner_actions.v b/lib/installers/horus/salrunner/salrunner_actions.v new file mode 100644 index 00000000..3f158c04 --- /dev/null +++ b/lib/installers/horus/salrunner/salrunner_actions.v @@ -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)! +} diff --git a/lib/installers/horus/salrunner/salrunner_factory_.v b/lib/installers/horus/salrunner/salrunner_factory_.v new file mode 100644 index 00000000..ba9194b3 --- /dev/null +++ b/lib/installers/horus/salrunner/salrunner_factory_.v @@ -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 +} diff --git a/lib/installers/horus/salrunner/salrunner_model.v b/lib/installers/horus/salrunner/salrunner_model.v new file mode 100644 index 00000000..776bc20a --- /dev/null +++ b/lib/installers/horus/salrunner/salrunner_model.v @@ -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 +} diff --git a/lib/installers/horus/salrunner/templates/atemplate.yaml b/lib/installers/horus/salrunner/templates/atemplate.yaml new file mode 100644 index 00000000..a4c386dd --- /dev/null +++ b/lib/installers/horus/salrunner/templates/atemplate.yaml @@ -0,0 +1,5 @@ + + +name: ${cfg.configpath} + + diff --git a/lib/installers/horus/supervisor/.heroscript b/lib/installers/horus/supervisor/.heroscript new file mode 100644 index 00000000..bbea5208 --- /dev/null +++ b/lib/installers/horus/supervisor/.heroscript @@ -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 \ No newline at end of file diff --git a/lib/installers/horus/supervisor/readme.md b/lib/installers/horus/supervisor/readme.md new file mode 100644 index 00000000..92d91bc9 --- /dev/null +++ b/lib/installers/horus/supervisor/readme.md @@ -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()! +``` diff --git a/lib/installers/horus/supervisor/readme_new.md b/lib/installers/horus/supervisor/readme_new.md new file mode 100644 index 00000000..e69de29b diff --git a/lib/installers/horus/supervisor/supervisor_actions.v b/lib/installers/horus/supervisor/supervisor_actions.v new file mode 100644 index 00000000..904cbe3c --- /dev/null +++ b/lib/installers/horus/supervisor/supervisor_actions.v @@ -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)! +} diff --git a/lib/installers/horus/supervisor/supervisor_factory_.v b/lib/installers/horus/supervisor/supervisor_factory_.v new file mode 100644 index 00000000..17d7364f --- /dev/null +++ b/lib/installers/horus/supervisor/supervisor_factory_.v @@ -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 +} diff --git a/lib/installers/horus/supervisor/supervisor_model.v b/lib/installers/horus/supervisor/supervisor_model.v new file mode 100644 index 00000000..34fb678a --- /dev/null +++ b/lib/installers/horus/supervisor/supervisor_model.v @@ -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 +} diff --git a/lib/installers/horus/supervisor/templates/atemplate.yaml b/lib/installers/horus/supervisor/templates/atemplate.yaml new file mode 100644 index 00000000..a4c386dd --- /dev/null +++ b/lib/installers/horus/supervisor/templates/atemplate.yaml @@ -0,0 +1,5 @@ + + +name: ${cfg.configpath} + + diff --git a/lib/installers/infra/gitea/.heroscript b/lib/installers/infra/gitea/.heroscript index ec828a2b..50fec5e6 100644 --- a/lib/installers/infra/gitea/.heroscript +++ b/lib/installers/infra/gitea/.heroscript @@ -9,4 +9,4 @@ build: true startupmanager: true supported_platforms: "" - // active: false + // active: false \ No newline at end of file diff --git a/lib/installers/infra/zinit_installer/zinit_installer_actions.v b/lib/installers/infra/zinit_installer/zinit_installer_actions.v index e1a90774..dd477579 100644 --- a/lib/installers/infra/zinit_installer/zinit_installer_actions.v +++ b/lib/installers/infra/zinit_installer/zinit_installer_actions.v @@ -29,7 +29,7 @@ fn startupcmd() ![]startupmanager.ZProcessNewArgs { 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 } diff --git a/lib/osal/startupmanager/startupmanager.v b/lib/osal/startupmanager/startupmanager.v index 9906b56a..6804e037 100644 --- a/lib/osal/startupmanager/startupmanager.v +++ b/lib/osal/startupmanager/startupmanager.v @@ -16,19 +16,34 @@ pub fn get(cat StartupManagerType) !StartupManager { mut sm := StartupManager{ cat: cat } - if sm.cat == .auto { - // 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. - 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() { - sm.cat = .zinit - } else { - sm.cat = .screen + 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. + // 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. + if _ := zinit_client_test.rpc_discover() { + sm.cat = .zinit + } else { + sm.cat = .screen + } + } + .unknown { + print_backtrace() + return error("can't determine startup manager type, need to be a known one.") + } + else { + return sm } - } - if sm.cat == .unknown { - print_backtrace() - return error("can't determine startup manager type, need to be a known one.") } return sm } diff --git a/lib/schemas/jsonrpc/model_request.v b/lib/schemas/jsonrpc/model_request.v index 00802ad4..40dab837 100644 --- a/lib/schemas/jsonrpc/model_request.v +++ b/lib/schemas/jsonrpc/model_request.v @@ -70,7 +70,7 @@ pub fn decode_request(data string) !Request { // Returns: // - A JSON string representation of the Request 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