fixes installers

This commit is contained in:
2024-12-25 22:30:33 +01:00
parent d8d852cd5a
commit 4373ec21aa
22 changed files with 1061 additions and 160 deletions

View File

@@ -0,0 +1,7 @@
!!hero_code.generate_client
name: "rclone"
classname: "RCloneClient"
hasconfig: true
singleton: true
default: true
title: ""

View File

@@ -0,0 +1,83 @@
module rclone
import os
import freeflowuniverse.herolib.core.texttools
// // RCloneClient represents a configured rclone instance
// pub struct RCloneClient {
// pub mut:
// name string // name of the remote
// }
// new creates a new RCloneClient instance
pub fn new(name string) !RCloneClient {
return RCloneClient{
name: name
}
}
// mount mounts a remote at the specified path
pub fn (mut r RCloneClient) mount(remote_path string, local_path string) ! {
if !os.exists(local_path) {
os.mkdir_all(local_path) or { return error('Failed to create mount directory: ${err}') }
}
cmd := 'rclone mount ${r.name}:${remote_path} ${local_path} --daemon'
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Failed to mount remote: ${res.output}')
}
}
// unmount unmounts a mounted remote
pub fn (mut r RCloneClient) unmount(local_path string) ! {
if os.user_os() == 'macos' {
os.execute_opt('umount ${local_path}') or { return error('Failed to unmount: ${err}') }
} else {
os.execute_opt('fusermount -u ${local_path}') or {
return error('Failed to unmount: ${err}')
}
}
}
// upload uploads a file or directory to the remote
pub fn (mut r RCloneClient) upload(local_path string, remote_path string) ! {
if !os.exists(local_path) {
return error('Local path does not exist: ${local_path}')
}
cmd := 'rclone copy ${local_path} ${r.name}:${remote_path}'
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Failed to upload: ${res.output}')
}
}
// download downloads a file or directory from the remote
pub fn (mut r RCloneClient) download(remote_path string, local_path string) ! {
if !os.exists(local_path) {
os.mkdir_all(local_path) or { return error('Failed to create local directory: ${err}') }
}
cmd := 'rclone copy ${r.name}:${remote_path} ${local_path}'
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Failed to download: ${res.output}')
}
}
// list lists contents of a remote path
pub fn (mut r RCloneClient) list(remote_path string) !string {
cmd := 'rclone ls ${r.name}:${remote_path}'
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Failed to list remote contents: ${res.output}')
}
return res.output
}
// check_installed checks if rclone is installed
pub fn check_installed() bool {
res := os.execute('which rclone')
return res.exit_code == 0
}

View File

@@ -0,0 +1,104 @@
module rclone
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
__global (
rclone_global map[string]&RCloneClient
rclone_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string
}
fn args_get(args_ ArgsGet) ArgsGet {
mut model := args_
if model.name == '' {
model.name = rclone_default
}
if model.name == '' {
model.name = 'default'
}
return model
}
pub fn get(args_ ArgsGet) !&RCloneClient {
mut model := args_get(args_)
if model.name !in rclone_global {
if model.name == 'default' {
if !config_exists(model) {
if default {
config_save(model)!
}
}
config_load(model)!
}
}
return rclone_global[model.name] or {
println(rclone_global)
panic('could not get config for rclone with name:${model.name}')
}
}
fn config_exists(args_ ArgsGet) bool {
mut model := args_get(args_)
mut context := base.context() or { panic('bug') }
return context.hero_config_exists('rclone', model.name)
}
fn config_load(args_ ArgsGet) ! {
mut model := args_get(args_)
mut context := base.context()!
mut heroscript := context.hero_config_get('rclone', model.name)!
play(heroscript: heroscript)!
}
fn config_save(args_ ArgsGet) ! {
mut model := args_get(args_)
mut context := base.context()!
context.hero_config_set('rclone', model.name, heroscript_default()!)!
}
fn set(o RCloneClient) ! {
mut o2 := obj_init(o)!
rclone_global[o.name] = &o2
rclone_default = o.name
}
@[params]
pub struct PlayArgs {
pub mut:
heroscript string // if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
}
pub fn play(args_ PlayArgs) ! {
mut model := args_
if model.heroscript == '' {
model.heroscript = heroscript_default()!
}
mut plbook := model.plbook or { playbook.new(text: model.heroscript)! }
mut install_actions := plbook.find(filter: 'rclone.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
mut p := install_action.params
mycfg := cfg_play(p)!
console.print_debug('install action rclone.configure\n${mycfg}')
set(mycfg)!
}
}
}
// switch instance to be used for rclone
pub fn switch(name string) {
rclone_default = name
}

View File

@@ -0,0 +1,62 @@
module rclone
import freeflowuniverse.herolib.data.paramsparser
import os
pub const version = '0.0.0'
const singleton = true
const default = true
pub fn heroscript_default() !string {
name := os.getenv_opt('RCLONE_NAME') or { 'default' }
remote_type := os.getenv_opt('RCLONE_TYPE') or { 's3' }
provider := os.getenv_opt('RCLONE_PROVIDER') or { 'aws' }
access_key := os.getenv_opt('RCLONE_ACCESS_KEY') or { '' }
secret_key := os.getenv_opt('RCLONE_SECRET_KEY') or { '' }
region := os.getenv_opt('RCLONE_REGION') or { 'us-east-1' }
endpoint := os.getenv_opt('RCLONE_ENDPOINT') or { '' }
heroscript := "
!!rclone.configure
name: '${name}'
type: '${remote_type}'
provider: '${provider}'
access_key: '${access_key}'
secret_key: '${secret_key}'
region: '${region}'
endpoint: '${endpoint}'
"
return heroscript
}
@[heap]
pub struct RCloneClient {
pub mut:
name string = 'default'
type_ string = 's3' // remote type (s3, sftp, etc)
provider string = 'aws' // provider for s3 (aws, minio, etc)
access_key string // access key for authentication
secret_key string // secret key for authentication
region string = 'us-east-1' // region for s3
endpoint string // custom endpoint URL if needed
}
fn cfg_play(p paramsparser.Params) ! {
mut mycfg := RCloneClient{
name: p.get_default('name', 'default')!
type_: p.get_default('type', 's3')!
provider: p.get_default('provider', 'aws')!
access_key: p.get('access_key')!
secret_key: p.get('secret_key')!
region: p.get_default('region', 'us-east-1')!
endpoint: p.get_default('endpoint', '')!
}
set(mycfg)!
}
fn obj_init(obj_ RCloneClient) !RCloneClient {
// never call get here, only thing we can do here is work on object itself
mut obj := obj_
return obj
}

View File

@@ -0,0 +1,39 @@
module rclone
fn test_rclone_new() {
rclone := new('test_remote') or { panic(err) }
assert rclone.name == 'test_remote'
}
fn test_check_installed() {
installed := check_installed()
// This test will pass or fail depending on whether rclone is installed
// on the system. It's mainly for documentation purposes.
println('RCloneClient installed: ${installed}')
}
// Note: The following tests are commented out as they require an actual rclone
// configuration and remote to work with. They serve as examples of how to use
// the RCloneClient module.
/*
fn test_rclone_operations() ! {
mut rclone := new('my_remote')!
// Test upload
rclone.upload('./testdata', 'backup/testdata')!
// Test download
rclone.download('backup/testdata', './testdata_download')!
// Test mount
rclone.mount('backup', './mounted_backup')!
// Test list
content := rclone.list('backup')!
println(content)
// Test unmount
rclone.unmount('./mounted_backup')!
}
*/

View File

@@ -0,0 +1,68 @@
# RCloneClient Module
This module provides a V language interface to RCloneClient, a command line program to manage files on cloud storage.
## Features
- Mount/unmount remote storage
- Upload files and directories
- Download files and directories
- List remote contents
- Configuration management through heroscript format
## Prerequisites
RCloneClient must be installed on your system. Visit https://rclone.org/install/ for installation instructions.
## Usage
```v
import freeflowuniverse.herolib.osal.rclone
fn main() {
// Create a new RCloneClient instance
mut rc := rclone.new('my_remote') or { panic(err) }
// Upload a directory
rc.upload('./local_dir', 'backup/remote_dir') or { panic(err) }
// Download a directory
rc.download('backup/remote_dir', './downloaded_dir') or { panic(err) }
// Mount a remote
rc.mount('backup', './mounted_backup') or { panic(err) }
// List contents
content := rc.list('backup') or { panic(err) }
println(content)
// Unmount when done
rc.unmount('./mounted_backup') or { panic(err) }
}
```
## Configuration
Configuration is managed through heroscript format in `~/hero/config`. Example configuration:
```heroscript
!!config.s3server_define
name:'my_remote'
description:'My Remote Storage'
keyid:'your_key_id'
keyname:'your_key_name'
appkey:'your_app_key'
url:'your_url'
```
The configuration will be automatically loaded and applied when creating a new RCloneClient instance.
## Testing
To run the tests:
```bash
vtest ~/code/github/freeflowuniverse/herolib/lib/osal/rclone/rclone_test.v
```
Note: Some tests are commented out as they require an actual rclone configuration and remote to work with. They serve as examples of how to use the RCloneClient module.

View File

@@ -5,7 +5,6 @@ import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.lang.python
// import os
pub fn installll(args_ InstallArgs) ! {
mut args := args_

View File

@@ -5,7 +5,6 @@ import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import os
pub fn installlll(args_ InstallArgs) ! {
mut args := args_
version := '2.0.6'

View File

@@ -9,7 +9,6 @@ import freeflowuniverse.herolib.sysadmin.startupmanager
import os
import time
pub fn installll(args_ InstallArgs) ! {
mut args := args_

View File

@@ -124,12 +124,12 @@ pub fn (mut self Prometheus) running() !bool {
}
@[params]
pub struct InstallArgss {
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self Prometheus) install(model InstallArgss) ! {
pub fn (mut self Prometheus) install(model InstallArgs) ! {
switch(self.name)
if model.reset || (!installed()!) {
install()!

View File

@@ -9,28 +9,28 @@ import freeflowuniverse.herolib.sysadmin.startupmanager
import os
import time
@[params]
pub struct InstallArgs {
pub mut:
// homedir string
// configpath string
// username string = "admin"
// password string @[secret]
// secret string @[secret]
// title string = 'My Hero DAG'
reset bool
start bool = true
stop bool
restart bool
uninstall bool
// host string = 'localhost' // server host (default is localhost)
// port int = 8888
}
// @[params]
// pub struct InstallArgs {
// pub mut:
// // homedir string
// // configpath string
// // username string = "admin"
// // password string @[secret]
// // secret string @[secret]
// // title string = 'My Hero DAG'
// reset bool
// start bool = true
// stop bool
// restart bool
// uninstall bool
// // host string = 'localhost' // server host (default is localhost)
// // port int = 8888
// }
pub fn install(args_ InstallArgs) ! {
install_prometheus(args_)!
install_alertmanager(args_)!
install_node_exporter(args_)!
install_blackbox_exporter(args_)!
install_prom2json(args_)!
}
// pub fn install(args_ InstallArgs) ! {
// install_prometheus(args_)!
// install_alertmanager(args_)!
// install_node_exporter(args_)!
// install_blackbox_exporter(args_)!
// install_prom2json(args_)!
// }

View File

@@ -5,7 +5,6 @@ import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import os
pub fn installll(args_ InstallArgs) ! {
mut args := args_
version := '0.16.2'

View File

@@ -10,7 +10,6 @@ import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.screen
import os
// install lighttpd will return true if it was already installed
pub fn installll(args InstallArgs) ! {
// make sure we install base on the node

View File

@@ -0,0 +1,95 @@
# Hosts File Manager
This module provides functionality to manage the system's hosts file (`/etc/hosts`) in a safe and structured way. It supports both Linux and macOS systems, automatically handling sudo permissions when required.
## Features
- Read and parse the system's hosts file
- Add new host entries with IP and domain
- Remove host entries by domain
- Manage sections with comments
- Remove or clear entire sections
- Check for existing domains
- Automatic sudo handling for macOS and Linux when needed
## Usage
Create a file `example.vsh`:
```v
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.hostsfile
import os
// Create a new instance by reading the hosts file
mut hosts := hostsfile.new() or {
eprintln('Failed to read hosts file: ${err}')
exit(1)
}
// Add a new host entry to a section
hosts.add_host('127.0.0.1', 'mysite.local', 'Development') or {
eprintln('Failed to add host: ${err}')
exit(1)
}
// Remove a host entry
hosts.remove_host('mysite.local') or {
eprintln('Failed to remove host: ${err}')
exit(1)
}
// Check if a domain exists
if hosts.exists('example.com') {
println('Domain exists')
}
// Clear all entries in a section
hosts.clear_section('Development') or {
eprintln('Failed to clear section: ${err}')
exit(1)
}
// Remove an entire section
hosts.remove_section('Development') or {
eprintln('Failed to remove section: ${err}')
exit(1)
}
// Save changes back to the hosts file
// This will automatically use sudo when needed
hosts.save() or {
eprintln('Failed to save hosts file: ${err}')
exit(1)
}
```
## File Structure
The hosts file is organized into sections marked by comments. For example:
```
# Development
127.0.0.1 localhost
127.0.0.1 mysite.local
# Production
192.168.1.100 prod.example.com
```
## Error Handling
All functions that can fail return a Result type and should be handled appropriately:
```v
hosts.add_host('127.0.0.1', 'mysite.local', 'Development') or {
eprintln('Failed to add host: ${err}')
exit(1)
}
```
## Platform Support
- Linux: Direct write with fallback to sudo if needed
- macOS: Always uses sudo due to system restrictions

View File

@@ -3,7 +3,7 @@ module hostsfile
import os
import freeflowuniverse.herolib.osal
// TODO: will be broken now
const hosts_file_path = '/etc/hosts'
@[heap]
pub struct HostsFile {
@@ -23,119 +23,182 @@ pub mut:
domain string
}
// pub fn new() HostsFile {
// mut obj := HostsFile{}
// new creates a new HostsFile instance by reading the system's hosts file
pub fn new() !HostsFile {
mut obj := HostsFile{}
mut content := os.read_file(hosts_file_path) or {
return error('Failed to read hosts file: ${err}')
}
mut current_section := Section{
name: ''
}
// mut content := os.read_file('/etc/hosts') or { panic(err) }
// mut section := ''
for line in content.split_into_lines() {
trimmed := line.trim_space()
if trimmed == '' {
continue
}
// for mut line in content.split('\n') {
// line = line.trim_space()
// if line.starts_with('#') {
// section = line.trim('#').trim_space()
// continue
// }
if trimmed.starts_with('#') {
// If we have hosts in the current section, add it to sections
if current_section.hosts.len > 0 {
obj.sections << current_section
}
// Start a new section
current_section = Section{
name: trimmed[1..].trim_space()
}
continue
}
// mut splitted := line.fields()
// if splitted.len > 1 {
// if section !in obj.hosts {
// obj.hosts[section] = []map[string]string{}
// }
// obj.hosts[section] << {
// splitted[0]: splitted[1]
// }
// }
// }
// return obj
// }
// Parse host entries
parts := trimmed.fields()
if parts.len >= 2 {
current_section.hosts << Host{
ip: parts[0]
domain: parts[1]
}
}
}
// pub fn (mut hostsfile HostsFile) save(sudo bool) &HostsFile {
// mut str := ''
// for section, items in hostsfile.hosts {
// if section != '' {
// str = str + '# ${section}\n\n'
// }
// Add the last section if it has hosts
if current_section.hosts.len > 0 {
obj.sections << current_section
}
// for item in items {
// for ip, domain in item {
// str = str + '${ip}\t${domain}\n'
// }
// }
// str = str + '\n\n'
// }
// if sudo {
// osal.execute_interactive('sudo -- sh -c -e "echo \'${str}\' > /etc/hosts"') or {
// panic(err)
// }
// } else {
// os.write_file('/etc/hosts', str) or { panic(err) }
// }
// return hostsfile
// }
return obj
}
// pub fn (mut hostsfile HostsFile) reset(sections []string) &HostsFile {
// for section in sections {
// if section in hostsfile.hosts {
// hostsfile.hosts[section] = []map[string]string{}
// }
// }
// return hostsfile
// }
// add_host adds a new host entry to the specified section
pub fn (mut h HostsFile) add_host(ip string, domain string, section string) ! {
// Validate inputs
if ip == '' {
return error('IP address cannot be empty')
}
if domain == '' {
return error('Domain cannot be empty')
}
// pub struct HostItemArg{
// pub mut:
// ip string
// domain string
// section string = "main"
// }
// Check if domain already exists
if h.exists(domain) {
return error('Domain ${domain} already exists in hosts file')
}
// pub fn (mut hostsfile HostsFile) add(args HostItemArg) &HostsFile {
// if args.section !in hostsfile.hosts {
// hostsfile.hosts[args.section] = []map[string]string{}
// }
// hostsfile.hosts[args.section] << {
// ip: domain
// }
// return hostsfile
// }
// Find or create section
mut found_section := false
for mut s in h.sections {
if s.name == section {
s.hosts << Host{
ip: ip
domain: domain
}
found_section = true
break
}
}
// pub fn (mut hostsfile HostsFile) delete(domain string) &HostsFile {
// mut indexes := map[string][]int{}
if !found_section {
h.sections << Section{
name: section
hosts: [Host{
ip: ip
domain: domain
}]
}
}
}
// for section, items in hostsfile.hosts {
// indexes[section] = []int{}
// for i, item in items {
// for _, dom in item {
// if dom == domain {
// indexes[section] << i
// }
// }
// }
// }
// remove_host removes all entries for the specified domain
pub fn (mut h HostsFile) remove_host(domain string) ! {
mut found := false
for mut section in h.sections {
// Filter out hosts with matching domain
old_len := section.hosts.len
section.hosts = section.hosts.filter(it.domain != domain)
if section.hosts.len < old_len {
found = true
}
}
// for section, items in indexes {
// for i in items {
// hostsfile.hosts[section].delete(i)
// }
// }
if !found {
return error('Domain ${domain} not found in hosts file')
}
}
// return hostsfile
// }
// exists checks if a domain exists in any section
pub fn (h &HostsFile) exists(domain string) bool {
for section in h.sections {
for host in section.hosts {
if host.domain == domain {
return true
}
}
}
return false
}
// pub fn (mut hostsfile HostsFile) delete_section(section string) &HostsFile {
// hostsfile.hosts.delete(section)
// return hostsfile
// }
// save writes the hosts file back to disk
pub fn (h &HostsFile) save() ! {
mut content := ''
// pub fn (mut hostsfile HostsFile) exists(domain string) bool {
// for _, items in hostsfile.hosts {
// for item in items {
// for _, dom in item {
// if dom == domain {
// return true
// }
// }
// }
// }
// return false
// }
for section in h.sections {
if section.name != '' {
content += '# ${section.name}\n'
}
for host in section.hosts {
content += '${host.ip}\t${host.domain}\n'
}
content += '\n'
}
// Check if we're on macOS
is_macos := os.user_os() == 'macos'
if is_macos {
// On macOS, we need to use sudo
osal.execute_interactive('sudo -- sh -c -e "echo \'${content}\' > ${hosts_file_path}"') or {
return error('Failed to write hosts file with sudo: ${err}')
}
} else {
// On Linux, try direct write first, fallback to sudo if needed
os.write_file(hosts_file_path, content) or {
// If direct write fails, try with sudo
osal.execute_interactive('sudo -- sh -c -e "echo \'${content}\' > ${hosts_file_path}"') or {
return error('Failed to write hosts file: ${err}')
}
}
}
}
// remove_section removes an entire section and its hosts
pub fn (mut h HostsFile) remove_section(section_name string) ! {
mut found := false
for i, section in h.sections {
if section.name == section_name {
h.sections.delete(i)
found = true
break
}
}
if !found {
return error('Section ${section_name} not found')
}
}
// clear_section removes all hosts from a section but keeps the section
pub fn (mut h HostsFile) clear_section(section_name string) ! {
mut found := false
for mut section in h.sections {
if section.name == section_name {
section.hosts.clear()
found = true
break
}
}
if !found {
return error('Section ${section_name} not found')
}
}

View File

@@ -0,0 +1,110 @@
module hostsfile
fn test_hostsfile_basic() {
// Create new hosts file instance
mut hosts := new() or {
assert false, 'Failed to create hosts file: ${err}'
return
}
// Test adding a host
hosts.add_host('127.0.0.1', 'test.local', 'Test') or {
assert false, 'Failed to add host: ${err}'
return
}
// Verify host exists
assert hosts.exists('test.local'), 'Added host test.local not found'
// Test adding duplicate host (should fail)
hosts.add_host('127.0.0.1', 'test.local', 'Test') or {
assert err.str() == 'Domain test.local already exists in hosts file'
goto next_test
}
assert false, 'Adding duplicate host should fail'
next_test:
// Test removing host
hosts.remove_host('test.local') or {
assert false, 'Failed to remove host: ${err}'
return
}
// Verify host was removed
assert !hosts.exists('test.local'), 'Host test.local still exists after removal'
// Test removing non-existent host (should fail)
hosts.remove_host('nonexistent.local') or {
assert err.str() == 'Domain nonexistent.local not found in hosts file'
return
}
assert false, 'Removing non-existent host should fail'
}
fn test_hostsfile_sections() {
mut hosts := new() or {
assert false, 'Failed to create hosts file: ${err}'
return
}
// Add hosts to different sections
hosts.add_host('127.0.0.1', 'dev.local', 'Development') or {
assert false, 'Failed to add dev host: ${err}'
return
}
hosts.add_host('127.0.0.1', 'prod.local', 'Production') or {
assert false, 'Failed to add prod host: ${err}'
return
}
// Verify both hosts exist
assert hosts.exists('dev.local'), 'dev.local not found'
assert hosts.exists('prod.local'), 'prod.local not found'
// Test clearing a section
hosts.clear_section('Development') or {
assert false, 'Failed to clear Development section: ${err}'
return
}
// Verify Development host removed but Production remains
assert !hosts.exists('dev.local'), 'dev.local still exists after clearing section'
assert hosts.exists('prod.local'), 'prod.local was incorrectly removed'
// Test removing a section
hosts.remove_section('Production') or {
assert false, 'Failed to remove Production section: ${err}'
return
}
// Verify all hosts removed
assert !hosts.exists('dev.local'), 'dev.local still exists'
assert !hosts.exists('prod.local'), 'prod.local still exists'
// Test removing non-existent section (should fail)
hosts.remove_section('NonExistent') or {
assert err.str() == 'Section NonExistent not found'
return
}
assert false, 'Removing non-existent section should fail'
}
fn test_hostsfile_validation() {
mut hosts := new() or {
assert false, 'Failed to create hosts file: ${err}'
return
}
// Test empty IP
hosts.add_host('', 'test.local', 'Test') or {
assert err.str() == 'IP address cannot be empty'
goto next_test1
}
assert false, 'Empty IP should fail'
next_test1:
// Test empty domain
hosts.add_host('127.0.0.1', '', 'Test') or {
assert err.str() == 'Domain cannot be empty'
return
}
assert false, 'Empty domain should fail'
}

View File

@@ -1,13 +1,61 @@
# screen
# Screen
The Screen module provides a V interface to manage GNU Screen sessions.
## Example Script
Create a file `screen_example.vsh`:
```v
#!/usr/bin/env -S v run
import freeflowuniverse.herolib.osal.screen
// Create a new screen session with hardcoded parameters
mut s := screen.Screen{
name: 'test_session'
cmd: '/bin/bash' // Default shell
}
// Check if screen is running
is_running := s.is_running() or {
println('Error checking screen status: ${err}')
return
}
// Get session status
status := s.status() or {
println('Error getting status: ${err}')
return
}
// Send a command to the screen session
s.cmd_send('ls -la') or {
println('Error sending command: ${err}')
return
}
// Attach to the session
s.attach() or {
println('Error attaching: ${err}')
return
}
```
## Basic Screen Commands
```bash
#to see sessions which have been created
screen -ls
There is a screen on:
3230.test (Detached)
3230.test (Detached)
#now to attach to this screen
screen -r test
```
## Testing
```bash
vtest ~/code/github/freeflowuniverse/herolib/lib/osal/screen/screen_test.v

View File

@@ -1,17 +1,148 @@
module screen
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal
import os
import time
const test_screen_name = 'test_screen_session'
const test_cmd = 'echo "test command"'
// Initialize test environment
pub fn testsuite_begin() ! {
// Check if screen is installed
res := os.execute('which screen')
if res.exit_code != 0 {
return error('screen is not installed. Please install screen first.')
}
// Ensure screen directory exists with proper permissions
home := os.home_dir()
screen_dir := '${home}/.screen'
if !os.exists(screen_dir) {
// Create directory with proper permissions using mkdir -m
res2 := os.execute('mkdir -m 700 ${screen_dir}')
if res2.exit_code != 0 {
return error('Failed to create screen directory: ${res2.output}')
}
}
mut screen_factory := new(reset: true)!
cleanup_test_screens()!
}
pub fn test_screen_status() ! {
mut screen_factory := new()!
mut screen := screen_factory.add(name: 'testservice', cmd: 'redis-server --port 1234')!
// Cleanup after all tests
pub fn testsuite_end() ! {
cleanup_test_screens()!
}
fn cleanup_test_screens() ! {
mut screen_factory := new(reset: false)!
screen_factory.scan()!
// Clean up main test screen
if screen_factory.exists(test_screen_name) {
screen_factory.kill(test_screen_name)!
time.sleep(200 * time.millisecond) // Give time for cleanup
}
// Clean up multiple test screens
if screen_factory.exists('${test_screen_name}_1') {
screen_factory.kill('${test_screen_name}_1')!
time.sleep(200 * time.millisecond)
}
if screen_factory.exists('${test_screen_name}_2') {
screen_factory.kill('${test_screen_name}_2')!
time.sleep(200 * time.millisecond)
}
// Final scan to ensure cleanup
screen_factory.scan()!
}
// Helper function to create and verify screen
fn create_and_verify_screen(mut screen_factory ScreensFactory, name string, cmd string) !&Screen {
mut screen := screen_factory.add(
name: name
cmd: cmd
)!
// Give screen time to initialize
time.sleep(500 * time.millisecond)
// Verify screen exists and is running
screen_factory.scan()!
if !screen_factory.exists(name) {
return error('Screen ${name} was not found after creation')
}
mut result := screen_factory.get(name)!
return &result
}
// Test screen creation and basic status
pub fn test_screen_creation() ! {
mut screen_factory := new(reset: false)!
mut screen := create_and_verify_screen(mut &screen_factory, test_screen_name, '/bin/bash')!
assert screen.name == test_screen_name
status := screen.status()!
assert status == .active
}
// Test command sending functionality
pub fn test_screen_cmd_send() ! {
mut screen_factory := new(reset: false)!
mut screen := create_and_verify_screen(mut &screen_factory, test_screen_name, '/bin/bash')!
// Send a test command
screen.cmd_send(test_cmd)!
// Give some time for command execution
time.sleep(200 * time.millisecond)
// Verify screen status after command
status := screen.status()!
assert status == .active
}
// Test error cases
pub fn test_screen_errors() ! {
mut screen_factory := new(reset: false)!
// Test invalid screen name
if _ := screen_factory.get('nonexistent_screen') {
assert false, 'Should not find nonexistent screen'
} else {
assert true
}
// Test screen status after creation but before start
mut screen := screen_factory.add(
name: test_screen_name
cmd: '/bin/bash'
start: false
)!
status := screen.status()!
assert status == .inactive, 'Screen should be inactive before start'
}
// Test multiple screens
pub fn test_multiple_screens() ! {
mut screen_factory := new(reset: false)!
screen1_name := '${test_screen_name}_1'
screen2_name := '${test_screen_name}_2'
mut screen1 := create_and_verify_screen(mut &screen_factory, screen1_name, '/bin/bash')!
mut screen2 := create_and_verify_screen(mut &screen_factory, screen2_name, '/bin/bash')!
assert screen1.status()! == .active
assert screen2.status()! == .active
screen_factory.kill(screen1_name)!
time.sleep(200 * time.millisecond)
assert screen1.status()! == .inactive
assert screen2.status()! == .active
screen_factory.kill(screen2_name)!
time.sleep(200 * time.millisecond)
}

View File

@@ -14,18 +14,3 @@ sm.start(
```
## some basic commands for screen
```bash
#list the screens
screen -ls
#attach to the screens
screen -r myscreen
```
to exit a screen to
```
ctrl a d
```

View File

@@ -3,13 +3,31 @@ module startupmanager
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.screen
import freeflowuniverse.herolib.osal.systemd
import os
import time
const process_name = 'testprocess'
// Initialize test environment
pub fn testsuite_begin() ! {
// Initialize screen factory
mut screen_factory := screen.new(reset: true)!
// Ensure screen directory exists with proper permissions
home := os.home_dir()
screen_dir := '${home}/.screen'
if !os.exists(screen_dir) {
res := os.execute('mkdir -m 700 ${screen_dir}')
if res.exit_code != 0 {
return error('Failed to create screen directory: ${res.output}')
}
}
// Clean up any existing process
mut sm := get()!
if sm.exists(process_name)! {
sm.stop(process_name)!
time.sleep(200 * time.millisecond) // Give time for cleanup
}
}
@@ -17,22 +35,115 @@ pub fn testsuite_end() ! {
mut sm := get()!
if sm.exists(process_name)! {
sm.stop(process_name)!
time.sleep(200 * time.millisecond) // Give time for cleanup
}
// Clean up screen sessions
mut screen_factory := screen.new(reset: false)!
screen_factory.scan()!
if screen_factory.exists(process_name) {
screen_factory.kill(process_name)!
time.sleep(200 * time.millisecond)
}
}
// remove from the startup manager
// Test startup manager status functionality
pub fn test_status() ! {
mut sm := get()!
mut screen_factory := screen.new(reset: false)!
if sm.exists(process_name)! {
sm.stop(process_name)!
time.sleep(200 * time.millisecond)
sm.start(process_name)!
time.sleep(500 * time.millisecond) // Give time for startup
status := sm.status(process_name)!
assert status == .inactive
} else {
sm.new(name: process_name, cmd: 'sleep 100')!
// Create new process with screen session
sm.new(
name: process_name
cmd: 'sleep 100'
description: 'Test process for startup manager'
)!
time.sleep(200 * time.millisecond)
sm.start(process_name)!
time.sleep(500 * time.millisecond) // Give time for startup
status := sm.status(process_name)!
assert status == .active
// Verify screen session
screen_factory.scan()!
assert screen_factory.exists(process_name), 'Screen session not found'
}
// Cleanup
sm.stop(process_name)!
time.sleep(200 * time.millisecond)
}
// Test process creation with description
pub fn test_process_with_description() ! {
mut sm := get()!
mut screen_factory := screen.new(reset: false)!
description := 'Test process with custom description'
// Create new process
sm.new(
name: '${process_name}_desc'
cmd: 'sleep 50'
description: description
)!
time.sleep(200 * time.millisecond)
// Start and verify
sm.start('${process_name}_desc')!
time.sleep(500 * time.millisecond)
// Verify screen session
screen_factory.scan()!
assert screen_factory.exists('${process_name}_desc'), 'Screen session not found'
// Verify screen is running
mut screen := screen_factory.get('${process_name}_desc')!
assert screen.is_running()!, 'Screen should be running'
// Cleanup
sm.stop('${process_name}_desc')!
time.sleep(200 * time.millisecond)
// Verify screen is not running after cleanup
assert !screen.is_running()!, 'Screen should not be running after cleanup'
}
// Test error handling
pub fn test_error_handling() ! {
mut sm := get()!
mut screen_factory := screen.new(reset: false)!
// Test non-existent process
if _ := sm.status('nonexistent_process') {
assert false, 'Should not get status of non-existent process'
} else {
assert true
}
// Test invalid screen session
if _ := screen_factory.get('nonexistent_screen') {
assert false, 'Should not get non-existent screen'
} else {
assert true
}
// Test stopping non-existent process
if _ := sm.stop('nonexistent_process') {
assert false, 'Should not stop non-existent process'
} else {
assert true
}
}