Merge branch 'development' into development_sshagent
This commit is contained in:
85
examples/osal/sshagent/sshagent_example2.vsh
Executable file
85
examples/osal/sshagent/sshagent_example2.vsh
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.sshagent
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
console.print_header('SSH Agent Management Example')
|
||||
|
||||
// Create SSH agent with single instance guarantee
|
||||
mut agent := sshagent.new_single()!
|
||||
println('SSH Agent initialized and ensured single instance')
|
||||
|
||||
// Show diagnostics
|
||||
diag := agent.diagnostics()
|
||||
console.print_header('SSH Agent Diagnostics:')
|
||||
for key, value in diag {
|
||||
console.print_item('${key}: ${value}')
|
||||
}
|
||||
|
||||
// Show current agent status
|
||||
println(agent)
|
||||
|
||||
// Example: Generate a test key if no keys exist
|
||||
if agent.keys.len == 0 {
|
||||
console.print_header('No keys found, generating example key...')
|
||||
mut key := agent.generate('example_key', '')!
|
||||
console.print_debug('Generated key: ${key}')
|
||||
|
||||
// Load the generated key
|
||||
key.load()!
|
||||
console.print_debug('Key loaded into agent')
|
||||
}
|
||||
|
||||
// Example: Working with existing keys
|
||||
if agent.keys.len > 0 {
|
||||
console.print_header('Working with existing keys...')
|
||||
|
||||
for i, key in agent.keys {
|
||||
console.print_debug('Key ${i + 1}: ${key.name}')
|
||||
console.print_debug(' Type: ${key.cat}')
|
||||
console.print_debug(' Loaded: ${key.loaded}')
|
||||
console.print_debug(' Email: ${key.email}')
|
||||
|
||||
if !key.loaded {
|
||||
console.print_debug(' Loading key...')
|
||||
mut key_mut := key
|
||||
key_mut.load() or {
|
||||
console.print_debug(' Failed to load: ${err}')
|
||||
continue
|
||||
}
|
||||
console.print_debug(' ✓ Key loaded successfully')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Add a key from private key content
|
||||
console.print_header('Example: Adding a key from content...')
|
||||
console.print_debug('Note: This would normally use real private key content')
|
||||
console.print_debug('For security, we skip this in the example')
|
||||
|
||||
// Example: Generate and manage a new key
|
||||
console.print_header('Example: Generate a new test key...')
|
||||
test_key_name := 'test_key_example'
|
||||
|
||||
// Check if test key already exists
|
||||
existing_key := agent.get(name: test_key_name) or {
|
||||
console.print_debug('Test key does not exist, generating...')
|
||||
|
||||
// Generate new key
|
||||
mut new_key := agent.generate(test_key_name, '')!
|
||||
console.print_debug('✓ Generated new key: ${new_key.name}')
|
||||
|
||||
// Load it
|
||||
new_key.load()!
|
||||
console.print_debug('✓ Key loaded into agent')
|
||||
|
||||
new_key
|
||||
}
|
||||
|
||||
console.print_debug('Test key exists: ${existing_key.name}')
|
||||
|
||||
// Show final agent status
|
||||
console.print_header('Final SSH Agent Status:')
|
||||
println(agent)
|
||||
|
||||
console.print_header('SSH Agent example completed successfully')
|
||||
389
examples/tmux/server_dashboard.vsh
Executable file
389
examples/tmux/server_dashboard.vsh
Executable file
@@ -0,0 +1,389 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import time
|
||||
import os
|
||||
|
||||
// Configuration
|
||||
const session_name = 'server_dashboard'
|
||||
const window_name = 'dashboard'
|
||||
const python_port = 8000
|
||||
const ttyd_port = 7890
|
||||
|
||||
// Command line argument handling
|
||||
fn show_help() {
|
||||
println('=== Tmux Server Dashboard ===')
|
||||
println('Usage:')
|
||||
println(' ${os.args[0]} # Start the dashboard')
|
||||
println(' ${os.args[0]} -editable # Start dashboard with editable ttyd')
|
||||
println(' ${os.args[0]} -down # Stop dashboard and cleanup')
|
||||
println(' ${os.args[0]} -status # Show dashboard status')
|
||||
println(' ${os.args[0]} -restart # Restart the dashboard')
|
||||
println(' ${os.args[0]} -help # Show this help')
|
||||
println('')
|
||||
println('Dashboard includes:')
|
||||
println(' • Python HTTP Server (port ${python_port})')
|
||||
println(' • Counter service (updates every 5 seconds)')
|
||||
println(' • Hero Web (compile and run hero web server)')
|
||||
println(' • CPU Monitor (htop)')
|
||||
println(' • Web access via ttyd (port ${ttyd_port})')
|
||||
println('')
|
||||
println('ttyd modes:')
|
||||
println(' • Default: read-only access to terminal')
|
||||
println(' • -editable: allows writing/editing in the terminal')
|
||||
}
|
||||
|
||||
fn stop_dashboard() ! {
|
||||
println('=== Stopping Dashboard ===')
|
||||
|
||||
// Kill ttyd processes
|
||||
println('Stopping ttyd processes...')
|
||||
os.execute('pkill ttyd')
|
||||
|
||||
// Kill tmux session
|
||||
println('Stopping tmux session...')
|
||||
mut t := tmux.new()!
|
||||
if t.session_exist(session_name) {
|
||||
mut session := t.session_get(session_name)!
|
||||
session.stop()!
|
||||
println('✓ Tmux session "${session_name}" stopped')
|
||||
} else {
|
||||
println('• Session "${session_name}" not found')
|
||||
}
|
||||
|
||||
// Check for any remaining processes on our ports
|
||||
println('Checking for processes on ports...')
|
||||
|
||||
// Check Python server port
|
||||
python_check := os.execute('lsof -i :${python_port}')
|
||||
if python_check.exit_code == 0 {
|
||||
println('• Found processes on port ${python_port}')
|
||||
println(python_check.output)
|
||||
} else {
|
||||
println('✓ Port ${python_port} is free')
|
||||
}
|
||||
|
||||
// Check ttyd port
|
||||
ttyd_check := os.execute('lsof -i :${ttyd_port}')
|
||||
if ttyd_check.exit_code == 0 {
|
||||
println('• Found processes on port ${ttyd_port}')
|
||||
println(ttyd_check.output)
|
||||
} else {
|
||||
println('✓ Port ${ttyd_port} is free')
|
||||
}
|
||||
|
||||
println('=== Dashboard stopped ===')
|
||||
}
|
||||
|
||||
fn show_status() ! {
|
||||
println('=== Dashboard Status ===')
|
||||
|
||||
mut t := tmux.new()!
|
||||
|
||||
// Check tmux session
|
||||
if t.session_exist(session_name) {
|
||||
println('✓ Tmux session "${session_name}" is running')
|
||||
|
||||
mut session := t.session_get(session_name)!
|
||||
mut window := session.window_get(name: window_name) or {
|
||||
println('✗ Window "${window_name}" not found')
|
||||
return
|
||||
}
|
||||
println('✓ Window "${window_name}" exists with ${window.panes.len} panes')
|
||||
|
||||
// Show pane details
|
||||
for i, pane in window.panes {
|
||||
service_name := match i {
|
||||
0 { 'Python HTTP Server' }
|
||||
1 { 'Counter Service' }
|
||||
2 { 'Hero Web Service' }
|
||||
3 { 'CPU Monitor' }
|
||||
else { 'Service ${i + 1}' }
|
||||
}
|
||||
|
||||
mut pane_mut := pane
|
||||
stats := pane_mut.stats() or {
|
||||
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, PID=${pane.pid} (stats unavailable)')
|
||||
continue
|
||||
}
|
||||
|
||||
memory_mb := f64(stats.memory_bytes) / (1024.0 * 1024.0)
|
||||
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, CPU=${stats.cpu_percent:.1f}%, Memory=${memory_mb:.1f}MB')
|
||||
}
|
||||
} else {
|
||||
println('✗ Tmux session "${session_name}" not running')
|
||||
}
|
||||
|
||||
// Check ports
|
||||
python_check := os.execute('lsof -i :${python_port}')
|
||||
if python_check.exit_code == 0 {
|
||||
println('✓ Python server running on port ${python_port}')
|
||||
} else {
|
||||
println('✗ No process on port ${python_port}')
|
||||
}
|
||||
|
||||
ttyd_check := os.execute('lsof -i :${ttyd_port}')
|
||||
if ttyd_check.exit_code == 0 {
|
||||
println('✓ ttyd running on port ${ttyd_port}')
|
||||
} else {
|
||||
println('✗ No process on port ${ttyd_port}')
|
||||
}
|
||||
|
||||
println('')
|
||||
println('Access URLs:')
|
||||
println(' • Python Server: http://localhost:${python_port}')
|
||||
println(' • Web Terminal: http://localhost:${ttyd_port}')
|
||||
println(' • Tmux attach: tmux attach-session -t ${session_name}')
|
||||
}
|
||||
|
||||
fn restart_dashboard() ! {
|
||||
println('=== Restarting Dashboard ===')
|
||||
stop_dashboard()!
|
||||
time.sleep(2000 * time.millisecond) // Wait 2 seconds
|
||||
start_dashboard()!
|
||||
}
|
||||
|
||||
fn start_dashboard_with_mode(ttyd_editable bool) ! {
|
||||
println('=== Server Dashboard with 4 Panes ===')
|
||||
println('Setting up tmux session with:')
|
||||
println(' 1. Python HTTP Server (port ${python_port})')
|
||||
println(' 2. Counter Service (updates every 5 seconds)')
|
||||
println(' 3. Hero Web (compile and run hero web server)')
|
||||
println(' 4. CPU Monitor (htop)')
|
||||
println('')
|
||||
|
||||
// Initialize tmux
|
||||
mut t := tmux.new()!
|
||||
|
||||
if !t.is_running()! {
|
||||
println('Starting tmux server...')
|
||||
t.start()!
|
||||
}
|
||||
|
||||
// Clean up existing session if it exists
|
||||
if t.session_exist(session_name) {
|
||||
println('Cleaning up existing ${session_name} session...')
|
||||
t.session_delete(session_name)!
|
||||
}
|
||||
|
||||
// Create new session
|
||||
println('Creating ${session_name} session...')
|
||||
mut session := t.session_create(name: session_name)!
|
||||
|
||||
// Create main window with initial bash shell
|
||||
println('Creating dashboard window...')
|
||||
mut window := session.window_new(name: window_name, cmd: 'bash', reset: true)!
|
||||
|
||||
// Wait for initial setup
|
||||
time.sleep(500 * time.millisecond)
|
||||
t.scan()!
|
||||
|
||||
println('\n=== Setting up 4-pane layout ===')
|
||||
|
||||
// Get the main window
|
||||
window = session.window_get(name: window_name)!
|
||||
|
||||
// Split horizontally first (left and right halves)
|
||||
println('1. Splitting horizontally for left/right layout...')
|
||||
mut right_pane := window.pane_split_horizontal('bash')!
|
||||
time.sleep(300 * time.millisecond)
|
||||
window.scan()!
|
||||
|
||||
// Split left pane vertically (top-left and bottom-left)
|
||||
println('2. Splitting left pane vertically...')
|
||||
window.scan()!
|
||||
if window.panes.len >= 2 {
|
||||
mut left_pane := window.panes[0] // First pane should be the left one
|
||||
left_pane.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
mut bottom_left_pane := window.pane_split_vertical('bash')!
|
||||
time.sleep(300 * time.millisecond)
|
||||
window.scan()!
|
||||
}
|
||||
|
||||
// Split right pane vertically (top-right and bottom-right)
|
||||
println('3. Splitting right pane vertically...')
|
||||
window.scan()!
|
||||
if window.panes.len >= 3 {
|
||||
// Find the rightmost pane (should be the last one after horizontal split)
|
||||
mut right_pane_current := window.panes[window.panes.len - 1]
|
||||
right_pane_current.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
mut bottom_right_pane := window.pane_split_vertical('bash')!
|
||||
time.sleep(300 * time.millisecond)
|
||||
window.scan()!
|
||||
}
|
||||
|
||||
// Set a proper 2x2 tiled layout using tmux command
|
||||
println('4. Setting 2x2 tiled layout...')
|
||||
os.execute('tmux select-layout -t ${session_name}:${window_name} tiled')
|
||||
time.sleep(500 * time.millisecond)
|
||||
window.scan()!
|
||||
|
||||
println('5. Layout complete! We now have 4 panes in 2x2 grid.')
|
||||
|
||||
// Refresh to get all panes
|
||||
window.scan()!
|
||||
println('\nCurrent panes: ${window.panes.len}')
|
||||
for i, pane in window.panes {
|
||||
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}')
|
||||
}
|
||||
|
||||
if window.panes.len < 4 {
|
||||
eprintln('Expected 4 panes, but got ${window.panes.len}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
println('\n=== Starting services in each pane ===')
|
||||
|
||||
// Pane 1 (top-left): Python HTTP Server
|
||||
println('Starting Python HTTP Server in pane 1...')
|
||||
mut pane1 := window.panes[0]
|
||||
pane1.select()!
|
||||
pane1.send_command('echo "=== Python HTTP Server Port 8000 ==="')!
|
||||
pane1.send_command('cd /tmp && python3 -m http.server ${python_port}')!
|
||||
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Pane 2 (bottom-left): Counter Service
|
||||
println('Starting Counter Service in pane 2...')
|
||||
mut pane2 := window.panes[1]
|
||||
pane2.select()!
|
||||
pane2.send_command('echo "=== Counter Service - Updates every 5 seconds ==="')!
|
||||
pane2.send_command('while true; do echo "Count: $(date)"; sleep 5; done')!
|
||||
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Pane 3 (top-right): Hero Web
|
||||
println('Starting Hero Web in pane 3...')
|
||||
mut pane3 := window.panes[2]
|
||||
pane3.select()!
|
||||
pane3.send_command('echo "=== Hero Web Server ==="')!
|
||||
pane3.send_command('hero web')!
|
||||
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Pane 4 (bottom-right): CPU Monitor
|
||||
println('Starting CPU Monitor in pane 4...')
|
||||
mut pane4 := window.panes[3]
|
||||
pane4.select()!
|
||||
pane4.send_command('echo "=== CPU Monitor ==="')!
|
||||
pane4.send_command('htop')!
|
||||
|
||||
println('\n=== All services started! ===')
|
||||
|
||||
// Wait a moment for services to initialize
|
||||
time.sleep(2000 * time.millisecond)
|
||||
|
||||
// Refresh and show current state
|
||||
t.scan()!
|
||||
window = session.window_get(name: window_name)!
|
||||
|
||||
println('\n=== Current Dashboard State ===')
|
||||
for i, mut pane in window.panes {
|
||||
stats := pane.stats() or {
|
||||
println(' Pane ${i + 1}: ID=%${pane.id}, PID=${pane.pid} (stats unavailable)')
|
||||
continue
|
||||
}
|
||||
memory_mb := f64(stats.memory_bytes) / (1024.0 * 1024.0)
|
||||
service_name := match i {
|
||||
0 { 'Python Server' }
|
||||
1 { 'Counter Service' }
|
||||
2 { 'Hero Web' }
|
||||
3 { 'CPU Monitor' }
|
||||
else { 'Unknown' }
|
||||
}
|
||||
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, CPU=${stats.cpu_percent:.1f}%, Memory=${memory_mb:.1f}MB')
|
||||
}
|
||||
|
||||
println('\n=== Access Information ===')
|
||||
println('• Python HTTP Server: http://localhost:${python_port}')
|
||||
println('• Tmux Session: tmux attach-session -t ${session_name}')
|
||||
println('')
|
||||
println('=== Pane Resize Commands ===')
|
||||
println('To resize panes, attach to the session and use:')
|
||||
println(' Ctrl+B then Arrow Keys (hold Ctrl+B and press arrow keys)')
|
||||
println(' Or programmatically:')
|
||||
for i, pane in window.panes {
|
||||
service_name := match i {
|
||||
0 { 'Python Server' }
|
||||
1 { 'Counter Service' }
|
||||
2 { 'Hero Web' }
|
||||
3 { 'CPU Monitor' }
|
||||
else { 'Unknown' }
|
||||
}
|
||||
println(' # Resize ${service_name} pane:')
|
||||
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -U 5 # Up')
|
||||
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -D 5 # Down')
|
||||
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -L 5 # Left')
|
||||
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -R 5 # Right')
|
||||
}
|
||||
|
||||
println('\n=== Dashboard is running! ===')
|
||||
println('Attach to view: tmux attach-session -t ${session_name}')
|
||||
println('Press Ctrl+B then d to detach from session')
|
||||
println('To stop all services: tmux kill-session -t ${session_name}')
|
||||
println('Running the browser-based dashboard: TTYD')
|
||||
|
||||
mode_str := if ttyd_editable { 'editable' } else { 'read-only' }
|
||||
println('Starting ttyd in ${mode_str} mode...')
|
||||
|
||||
window.run_ttyd(port: ttyd_port, editable: ttyd_editable) or {
|
||||
println('Failed to start ttyd: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
fn start_dashboard() ! {
|
||||
start_dashboard_with_mode(false)!
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut ttyd_editable := false // Local flag for ttyd editable mode
|
||||
|
||||
// Main execution with argument handling
|
||||
if os.args.len > 1 {
|
||||
command := os.args[1]
|
||||
match command {
|
||||
'-editable' {
|
||||
ttyd_editable = true
|
||||
start_dashboard_with_mode(ttyd_editable) or {
|
||||
eprintln('Error starting dashboard: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
'-down' {
|
||||
stop_dashboard() or {
|
||||
eprintln('Error stopping dashboard: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
'-status' {
|
||||
show_status() or {
|
||||
eprintln('Error getting status: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
'-restart' {
|
||||
restart_dashboard() or {
|
||||
eprintln('Error restarting dashboard: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
'-help', '--help', '-h' {
|
||||
show_help()
|
||||
}
|
||||
else {
|
||||
eprintln('Unknown command: ${command}')
|
||||
show_help()
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No arguments - start the dashboard
|
||||
start_dashboard_with_mode(ttyd_editable) or {
|
||||
eprintln('Error starting dashboard: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
120
examples/tmux/tmux.vsh
Executable file
120
examples/tmux/tmux.vsh
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import time
|
||||
|
||||
// Constants for display formatting
|
||||
const bytes_to_mb = 1024.0 * 1024.0
|
||||
const cpu_precision = 1
|
||||
const memory_precision = 3
|
||||
|
||||
println('=== Tmux Pane Example ===')
|
||||
|
||||
mut t := tmux.new()!
|
||||
|
||||
if !t.is_running()! {
|
||||
println('Starting tmux server...')
|
||||
t.start()!
|
||||
}
|
||||
|
||||
if t.session_exist('demo') {
|
||||
println('Deleting existing demo session...')
|
||||
t.session_delete('demo')!
|
||||
}
|
||||
|
||||
// Create session and window
|
||||
println('Creating demo session...')
|
||||
mut session := t.session_create(name: 'demo')!
|
||||
|
||||
println('Creating main window with htop...')
|
||||
mut window := session.window_new(name: 'main', cmd: 'htop', reset: true)!
|
||||
|
||||
// Wait a moment for the window to be created
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Refresh to get current state
|
||||
t.scan()!
|
||||
|
||||
println('\n=== Current Tmux State ===')
|
||||
println(t)
|
||||
|
||||
// Get the window and demonstrate pane functionality
|
||||
mut main_window := session.window_get(name: 'main')!
|
||||
|
||||
println('\n=== Window Pane Information ===')
|
||||
println('Window: ${main_window.name} (ID: ${main_window.id})')
|
||||
println('Number of panes: ${main_window.panes.len}')
|
||||
|
||||
for i, mut pane in main_window.panes {
|
||||
println('Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Active=${pane.active}, Cmd="${pane.cmd}"')
|
||||
|
||||
// Get pane stats
|
||||
stats := pane.stats() or {
|
||||
println(' Could not get stats: ${err}')
|
||||
continue
|
||||
}
|
||||
memory_mb := f64(stats.memory_bytes) / bytes_to_mb
|
||||
println(' CPU: ${stats.cpu_percent:.1f}%, Memory: ${stats.memory_percent:.3f}% (${memory_mb:.1f} MB)')
|
||||
}
|
||||
|
||||
// Get the active pane
|
||||
if mut active_pane := main_window.pane_active() {
|
||||
println('\n=== Active Pane Details ===')
|
||||
println('Active pane ID: %${active_pane.id}')
|
||||
println('Process ID: ${active_pane.pid}')
|
||||
println('Command: ${active_pane.cmd}')
|
||||
|
||||
// Get process information
|
||||
process_info := active_pane.processinfo_main() or {
|
||||
println('Could not get process info: ${err}')
|
||||
osal.ProcessInfo{}
|
||||
}
|
||||
if process_info.pid > 0 {
|
||||
println('Process info: PID=${process_info.pid}, Command=${process_info.cmd}')
|
||||
}
|
||||
|
||||
// Get recent logs
|
||||
println('\n=== Recent Pane Output ===')
|
||||
logs := active_pane.logs_all() or {
|
||||
println('Could not get logs: ${err}')
|
||||
''
|
||||
}
|
||||
if logs.len > 0 {
|
||||
lines := logs.split_into_lines()
|
||||
// Show last 5 lines
|
||||
start_idx := if lines.len > 5 { lines.len - 5 } else { 0 }
|
||||
for i in start_idx .. lines.len {
|
||||
if lines[i].trim_space().len > 0 {
|
||||
println(' ${lines[i]}')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println('No active pane found')
|
||||
}
|
||||
|
||||
println('\n=== Creating Additional Windows ===')
|
||||
|
||||
// Create more windows to demonstrate multiple panes
|
||||
mut monitor_window := session.window_new(name: 'monitor', cmd: 'top', reset: true)!
|
||||
mut logs_window := session.window_new(name: 'logs', cmd: 'tail -f /var/log/system.log', reset: true)!
|
||||
|
||||
time.sleep(500 * time.millisecond)
|
||||
t.scan()!
|
||||
|
||||
println('\n=== Final Tmux State ===')
|
||||
println(t)
|
||||
|
||||
println('\n=== Window Statistics ===')
|
||||
for mut win in session.windows {
|
||||
println('Window: ${win.name}')
|
||||
window_stats := win.stats() or {
|
||||
println(' Could not get window stats: ${err}')
|
||||
continue
|
||||
}
|
||||
memory_mb := f64(window_stats.memory_bytes) / bytes_to_mb
|
||||
println(' Total CPU: ${window_stats.cpu_percent:.1f}%')
|
||||
println(' Total Memory: ${window_stats.memory_percent:.3f}% (${memory_mb:.1f} MB)')
|
||||
println(' Panes: ${win.panes.len}')
|
||||
}
|
||||
143
examples/tmux/tmux_pane_resize.vsh
Executable file
143
examples/tmux/tmux_pane_resize.vsh
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import time
|
||||
|
||||
println('=== Tmux Pane Resizing Example ===')
|
||||
|
||||
mut t := tmux.new()!
|
||||
|
||||
if !t.is_running()! {
|
||||
println('Starting tmux server...')
|
||||
t.start()!
|
||||
}
|
||||
|
||||
if t.session_exist('resize_demo') {
|
||||
println('Deleting existing resize_demo session...')
|
||||
t.session_delete('resize_demo')!
|
||||
}
|
||||
|
||||
// Create session and window
|
||||
println('Creating resize_demo session...')
|
||||
mut session := t.session_create(name: 'resize_demo')!
|
||||
|
||||
println('Creating main window...')
|
||||
mut window := session.window_new(name: 'main', cmd: 'bash', reset: true)!
|
||||
|
||||
time.sleep(500 * time.millisecond)
|
||||
t.scan()!
|
||||
|
||||
// Create a 2x2 grid of panes
|
||||
println('\n=== Creating 2x2 Grid of Panes ===')
|
||||
|
||||
// Split horizontally first (left | right)
|
||||
mut right_pane := window.pane_split_horizontal('htop')!
|
||||
time.sleep(300 * time.millisecond)
|
||||
|
||||
// Split left pane vertically (top-left, bottom-left)
|
||||
window.scan()!
|
||||
if window.panes.len > 1 {
|
||||
mut left_pane := window.panes[1] // The original bash pane
|
||||
left_pane.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
}
|
||||
mut bottom_left_pane := window.pane_split_vertical('top')!
|
||||
time.sleep(300 * time.millisecond)
|
||||
|
||||
// Split right pane vertically (top-right, bottom-right)
|
||||
window.scan()!
|
||||
for mut pane in window.panes {
|
||||
if pane.cmd.contains('htop') {
|
||||
pane.select()!
|
||||
break
|
||||
}
|
||||
}
|
||||
time.sleep(200 * time.millisecond)
|
||||
mut bottom_right_pane := window.pane_split_vertical('tail -f /var/log/system.log')!
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
window.scan()!
|
||||
println('Created 2x2 grid with ${window.panes.len} panes:')
|
||||
for i, pane in window.panes {
|
||||
println(' Pane ${i}: ID=%${pane.id}, Cmd="${pane.cmd}"')
|
||||
}
|
||||
|
||||
// Demonstrate resizing operations
|
||||
println('\n=== Demonstrating Pane Resizing ===')
|
||||
|
||||
// Get references to panes for resizing
|
||||
window.scan()!
|
||||
if window.panes.len >= 4 {
|
||||
mut top_left := window.panes[1] // bash
|
||||
mut top_right := window.panes[0] // htop
|
||||
mut bottom_left := window.panes[2] // top
|
||||
mut bottom_right := window.panes[3] // tail
|
||||
|
||||
println('Resizing top-left pane (bash) to be wider...')
|
||||
top_left.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
top_left.resize_right(10)!
|
||||
time.sleep(1000 * time.millisecond)
|
||||
|
||||
println('Resizing top-right pane (htop) to be taller...')
|
||||
top_right.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
top_right.resize_down(5)!
|
||||
time.sleep(1000 * time.millisecond)
|
||||
|
||||
println('Resizing bottom-left pane (top) to be narrower...')
|
||||
bottom_left.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
bottom_left.resize_left(5)!
|
||||
time.sleep(1000 * time.millisecond)
|
||||
|
||||
println('Resizing bottom-right pane (tail) to be shorter...')
|
||||
bottom_right.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
bottom_right.resize_up(3)!
|
||||
time.sleep(1000 * time.millisecond)
|
||||
|
||||
// Demonstrate using the generic resize method
|
||||
println('Using generic resize method to make top-left pane taller...')
|
||||
top_left.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
top_left.resize(direction: 'down', cells: 3)!
|
||||
time.sleep(1000 * time.millisecond)
|
||||
}
|
||||
|
||||
// Send some commands to make the panes more interesting
|
||||
println('\n=== Adding Content to Panes ===')
|
||||
window.scan()!
|
||||
if window.panes.len >= 4 {
|
||||
// Send commands to bash pane
|
||||
mut bash_pane := window.panes[1]
|
||||
bash_pane.send_command('echo "=== Bash Pane ==="')!
|
||||
bash_pane.send_command('ls -la')!
|
||||
bash_pane.send_command('pwd')!
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Send command to top pane
|
||||
mut top_pane := window.panes[2]
|
||||
top_pane.send_command('echo "=== Top Pane ==="')!
|
||||
time.sleep(500 * time.millisecond)
|
||||
}
|
||||
|
||||
println('\n=== Final Layout ===')
|
||||
t.scan()!
|
||||
println('Session: ${session.name}')
|
||||
println('Window: ${window.name} (${window.panes.len} panes)')
|
||||
for i, pane in window.panes {
|
||||
println(' ${i + 1}. Pane %${pane.id} - ${pane.cmd}')
|
||||
}
|
||||
|
||||
println('\n=== Pane Resize Operations Available ===')
|
||||
println('✓ resize_up(cells) - Make pane taller by shrinking pane above')
|
||||
println('✓ resize_down(cells) - Make pane taller by shrinking pane below')
|
||||
println('✓ resize_left(cells) - Make pane wider by shrinking pane to the left')
|
||||
println('✓ resize_right(cells) - Make pane wider by shrinking pane to the right')
|
||||
println('✓ resize(direction: "up/down/left/right", cells: N) - Generic resize method')
|
||||
|
||||
println('\nExample completed! You can attach to the session with:')
|
||||
println(' tmux attach-session -t resize_demo')
|
||||
println('\nThen use Ctrl+B followed by arrow keys to manually resize panes,')
|
||||
println('or Ctrl+B followed by Alt+arrow keys for larger resize steps.')
|
||||
170
examples/tmux/tmux_panes.vsh
Executable file
170
examples/tmux/tmux_panes.vsh
Executable file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import time
|
||||
|
||||
println('=== Tmux Pane Splitting Example ===')
|
||||
|
||||
mut t := tmux.new()!
|
||||
|
||||
if !t.is_running()! {
|
||||
println('Starting tmux server...')
|
||||
t.start()!
|
||||
}
|
||||
|
||||
if t.session_exist('panes_demo') {
|
||||
println('Deleting existing panes_demo session...')
|
||||
t.session_delete('panes_demo')!
|
||||
}
|
||||
|
||||
// Create session and initial window
|
||||
println('Creating panes_demo session...')
|
||||
mut session := t.session_create(name: 'panes_demo')!
|
||||
|
||||
println('Creating main window...')
|
||||
mut window := session.window_new(name: 'main', cmd: 'bash', reset: true)!
|
||||
|
||||
// Wait for initial setup
|
||||
time.sleep(500 * time.millisecond)
|
||||
t.scan()!
|
||||
|
||||
println('\n=== Initial State ===')
|
||||
println('Window: ${window.name} (ID: ${window.id})')
|
||||
println('Number of panes: ${window.panes.len}')
|
||||
|
||||
// Split the window horizontally (side by side)
|
||||
println('\n=== Splitting Horizontally (Side by Side) ===')
|
||||
mut right_pane := window.pane_split_horizontal('htop')!
|
||||
time.sleep(500 * time.millisecond)
|
||||
window.scan()!
|
||||
|
||||
println('After horizontal split:')
|
||||
println('Number of panes: ${window.panes.len}')
|
||||
for i, mut pane in window.panes {
|
||||
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Active=${pane.active}, Cmd="${pane.cmd}"')
|
||||
}
|
||||
|
||||
// Split the right pane vertically (top and bottom)
|
||||
println('\n=== Splitting Right Pane Vertically (Top and Bottom) ===')
|
||||
// Get a fresh reference to the right pane after the first split
|
||||
window.scan()!
|
||||
if window.panes.len > 0 {
|
||||
// Find the pane with htop command (the one we just created)
|
||||
mut right_pane_fresh := &window.panes[0]
|
||||
for mut pane in window.panes {
|
||||
if pane.cmd.contains('htop') {
|
||||
right_pane_fresh = pane
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Select the right pane to make it active
|
||||
right_pane_fresh.select()!
|
||||
time.sleep(200 * time.millisecond)
|
||||
}
|
||||
|
||||
mut bottom_pane := window.pane_split_vertical('top')!
|
||||
time.sleep(500 * time.millisecond)
|
||||
window.scan()!
|
||||
|
||||
println('After vertical split of right pane:')
|
||||
println('Number of panes: ${window.panes.len}')
|
||||
for i, mut pane in window.panes {
|
||||
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Active=${pane.active}, Cmd="${pane.cmd}"')
|
||||
}
|
||||
|
||||
// Send commands to different panes
|
||||
println('\n=== Sending Commands to Panes ===')
|
||||
|
||||
// Get the first pane (left side) and send some commands
|
||||
if window.panes.len > 0 {
|
||||
mut left_pane := window.panes[0]
|
||||
println('Sending commands to left pane (ID: %${left_pane.id})')
|
||||
|
||||
left_pane.send_command('echo "Hello from left pane!"')!
|
||||
time.sleep(200 * time.millisecond)
|
||||
|
||||
left_pane.send_command('ls -la')!
|
||||
time.sleep(200 * time.millisecond)
|
||||
|
||||
left_pane.send_command('pwd')!
|
||||
time.sleep(200 * time.millisecond)
|
||||
}
|
||||
|
||||
// Send command to bottom pane
|
||||
if window.panes.len > 2 {
|
||||
mut bottom_pane_ref := window.panes[2]
|
||||
println('Sending command to bottom pane (ID: %${bottom_pane_ref.id})')
|
||||
bottom_pane_ref.send_command('echo "Hello from bottom pane!"')!
|
||||
time.sleep(200 * time.millisecond)
|
||||
}
|
||||
|
||||
// Capture output from panes
|
||||
println('\n=== Capturing Pane Output ===')
|
||||
for i, mut pane in window.panes {
|
||||
println('Output from Pane ${i} (ID: %${pane.id}):')
|
||||
logs := pane.logs_all() or {
|
||||
println(' Could not get logs: ${err}')
|
||||
continue
|
||||
}
|
||||
|
||||
if logs.len > 0 {
|
||||
lines := logs.split_into_lines()
|
||||
// Show last 3 lines
|
||||
start_idx := if lines.len > 3 { lines.len - 3 } else { 0 }
|
||||
for j in start_idx .. lines.len {
|
||||
if lines[j].trim_space().len > 0 {
|
||||
println(' ${lines[j]}')
|
||||
}
|
||||
}
|
||||
}
|
||||
println('')
|
||||
}
|
||||
|
||||
// Demonstrate pane selection
|
||||
println('\n=== Demonstrating Pane Selection ===')
|
||||
for i, mut pane in window.panes {
|
||||
println('Selecting pane ${i} (ID: %${pane.id})')
|
||||
pane.select()!
|
||||
time.sleep(300 * time.millisecond)
|
||||
}
|
||||
|
||||
// Final state
|
||||
println('\n=== Final Tmux State ===')
|
||||
t.scan()!
|
||||
println(t)
|
||||
|
||||
println('\n=== Pane Management Summary ===')
|
||||
println('Created ${window.panes.len} panes in window "${window.name}":')
|
||||
for i, pane in window.panes {
|
||||
println(' ${i + 1}. Pane %${pane.id} - PID: ${pane.pid} - Command: ${pane.cmd}')
|
||||
}
|
||||
|
||||
// Demonstrate killing a pane
|
||||
println('\n=== Demonstrating Pane Killing ===')
|
||||
if window.panes.len > 2 {
|
||||
mut pane_to_kill := window.panes[2] // Kill the bottom pane
|
||||
println('Killing pane %${pane_to_kill.id} (${pane_to_kill.cmd})')
|
||||
pane_to_kill.kill()!
|
||||
time.sleep(500 * time.millisecond)
|
||||
window.scan()!
|
||||
|
||||
println('After killing pane:')
|
||||
println('Number of panes: ${window.panes.len}')
|
||||
for i, pane in window.panes {
|
||||
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Cmd="${pane.cmd}"')
|
||||
}
|
||||
}
|
||||
|
||||
println('\n=== Available Pane Operations ===')
|
||||
println('✓ Split panes horizontally (side by side)')
|
||||
println('✓ Split panes vertically (top and bottom)')
|
||||
println('✓ Send commands to specific panes')
|
||||
println('✓ Send raw keys to panes')
|
||||
println('✓ Select/activate panes')
|
||||
println('✓ Capture pane output')
|
||||
println('✓ Get pane process information')
|
||||
println('✓ Kill individual panes')
|
||||
|
||||
println('\nExample completed! You can attach to the session with:')
|
||||
println(' tmux attach-session -t panes_demo')
|
||||
@@ -1,6 +1,5 @@
|
||||
module gittools
|
||||
|
||||
import freeflowuniverse.herolib.osal.sshagent
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.develop.vscode
|
||||
@@ -106,7 +105,9 @@ fn (self GitRepo) get_repo_url_for_clone() !string {
|
||||
// return url
|
||||
// }
|
||||
|
||||
if sshagent.loaded() {
|
||||
// Check if SSH agent is loaded (avoid importing sshagent to prevent circular dependency)
|
||||
ssh_check := os.execute('ssh-add -l')
|
||||
if ssh_check.exit_code == 0 {
|
||||
return self.get_ssh_url()!
|
||||
} else {
|
||||
return self.get_http_url()!
|
||||
|
||||
33
lib/osal/netns/instructions.md
Normal file
33
lib/osal/netns/instructions.md
Normal file
@@ -0,0 +1,33 @@
|
||||
### 1. **Network namespaces**
|
||||
|
||||
* Each namespace can have its own interfaces, routing table, firewall rules, etc.
|
||||
* You can move the user’s processes into a network namespace that only has access to a given bridge.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
# Create a new netns for user "alice"
|
||||
ip netns add alice
|
||||
|
||||
# Add a veth pair
|
||||
ip link add veth-alice type veth peer name veth-alice-br
|
||||
|
||||
# Attach one side to the bridge
|
||||
ip link set veth-alice-br master br0
|
||||
ip link set veth-alice-br up
|
||||
|
||||
# Move the other side into the netns
|
||||
ip link set veth-alice netns alice
|
||||
|
||||
# Configure inside namespace
|
||||
ip netns exec alice ip addr add 192.168.100.2/24 dev veth-alice
|
||||
ip netns exec alice ip link set veth-alice up
|
||||
ip netns exec alice ip route add default via 192.168.100.1
|
||||
|
||||
# Now run a shell in alice’s namespace
|
||||
sudo -u alice ip netns exec alice bash
|
||||
```
|
||||
|
||||
Now all processes run by `alice` will use only that veth → bridge → network.
|
||||
|
||||
|
||||
@@ -148,62 +148,3 @@ pub fn sshkey_check(mut agent SSHAgent, name string) ! {
|
||||
|
||||
console.print_green('✓ SSH key "${name}" is valid')
|
||||
}
|
||||
|
||||
// Copy private key to remote node
|
||||
pub fn remote_copy(mut agent SSHAgent, node_addr string, key_name string) ! {
|
||||
console.print_header('Copying SSH key "${key_name}" to ${node_addr}')
|
||||
|
||||
// Get the key
|
||||
mut key := agent.get(name: key_name) or { return error('SSH key "${key_name}" not found') }
|
||||
|
||||
// Create builder node
|
||||
mut b := builder.new() or { return error('Failed to create builder') }
|
||||
mut node := b.node_new(ipaddr: node_addr) or { return error('Failed to create node') }
|
||||
|
||||
// Get private key content
|
||||
mut key_path := key.keypath()!
|
||||
if !key_path.exists() {
|
||||
return error('Private key file not found: ${key_path.path}')
|
||||
}
|
||||
|
||||
private_key_content := key_path.read()!
|
||||
|
||||
// Get home directory on remote
|
||||
home_dir_map := node.environ_get() or {
|
||||
return error('Could not get environment on remote node')
|
||||
}
|
||||
home_dir := home_dir_map['HOME'] or {
|
||||
return error('Could not determine HOME directory on remote node')
|
||||
}
|
||||
|
||||
remote_ssh_dir := '${home_dir}/.ssh'
|
||||
remote_key_path := '${remote_ssh_dir}/${key_name}'
|
||||
|
||||
// Ensure .ssh directory exists with correct permissions
|
||||
node.exec_silent('mkdir -p ${remote_ssh_dir}')!
|
||||
node.exec_silent('chmod 700 ${remote_ssh_dir}')!
|
||||
|
||||
// Copy private key to remote
|
||||
node.file_write(remote_key_path, private_key_content)!
|
||||
node.exec_silent('chmod 600 ${remote_key_path}')!
|
||||
|
||||
// Generate public key on remote
|
||||
node.exec_silent('ssh-keygen -y -f ${remote_key_path} > ${remote_key_path}.pub')!
|
||||
node.exec_silent('chmod 644 ${remote_key_path}.pub')!
|
||||
|
||||
console.print_green('✓ SSH key "${key_name}" copied to ${node_addr}')
|
||||
}
|
||||
|
||||
// Add public key to authorized_keys on remote node
|
||||
pub fn remote_auth(mut agent SSHAgent, node_addr string, key_name string) ! {
|
||||
console.print_header('Adding SSH key "${key_name}" to authorized_keys on ${node_addr}')
|
||||
|
||||
// Create builder node
|
||||
mut b := builder.new() or { return error('Failed to create builder') }
|
||||
mut node := b.node_new(ipaddr: node_addr) or { return error('Failed to create node') }
|
||||
|
||||
// Use existing builder integration
|
||||
agent.push_key_to_node(mut node, key_name)!
|
||||
|
||||
console.print_green('✓ SSH key "${key_name}" added to authorized_keys on ${node_addr}')
|
||||
}
|
||||
|
||||
@@ -96,3 +96,59 @@ pub fn (mut agent SSHAgent) verify_key_access(mut node builder.Node, key_name st
|
||||
|
||||
return result.contains('SSH key verification successful')
|
||||
}
|
||||
|
||||
// Copy private key to remote node
|
||||
pub fn remote_copy(mut agent SSHAgent, node_addr string, key_name string) ! {
|
||||
console.print_header('Copying SSH key "${key_name}" to ${node_addr}')
|
||||
|
||||
// Get the key
|
||||
mut key := agent.get(name: key_name) or { return error('SSH key "${key_name}" not found') }
|
||||
|
||||
// Create builder node
|
||||
mut b := builder.new()!
|
||||
mut node := b.node_new(ipaddr: node_addr)!
|
||||
|
||||
// Get private key content
|
||||
mut key_path := key.keypath()!
|
||||
if !key_path.exists() {
|
||||
return error('Private key file not found: ${key_path.path}')
|
||||
}
|
||||
|
||||
private_key_content := key_path.read()!
|
||||
|
||||
// Get home directory on remote
|
||||
home_dir := node.environ_get()!['HOME'] or {
|
||||
return error('Could not determine HOME directory on remote node')
|
||||
}
|
||||
|
||||
remote_ssh_dir := '${home_dir}/.ssh'
|
||||
remote_key_path := '${remote_ssh_dir}/${key_name}'
|
||||
|
||||
// Ensure .ssh directory exists with correct permissions
|
||||
node.exec_silent('mkdir -p ${remote_ssh_dir}')!
|
||||
node.exec_silent('chmod 700 ${remote_ssh_dir}')!
|
||||
|
||||
// Copy private key to remote
|
||||
node.file_write(remote_key_path, private_key_content)!
|
||||
node.exec_silent('chmod 600 ${remote_key_path}')!
|
||||
|
||||
// Generate public key on remote
|
||||
node.exec_silent('ssh-keygen -y -f ${remote_key_path} > ${remote_key_path}.pub')!
|
||||
node.exec_silent('chmod 644 ${remote_key_path}.pub')!
|
||||
|
||||
console.print_green('✓ SSH key "${key_name}" copied to ${node_addr}')
|
||||
}
|
||||
|
||||
// Add public key to authorized_keys on remote node
|
||||
pub fn remote_auth(mut agent SSHAgent, node_addr string, key_name string) ! {
|
||||
console.print_header('Adding SSH key "${key_name}" to authorized_keys on ${node_addr}')
|
||||
|
||||
// Create builder node
|
||||
mut b := builder.new()!
|
||||
mut node := b.node_new(ipaddr: node_addr)!
|
||||
|
||||
// Use existing builder integration
|
||||
agent.push_key_to_node(mut node, key_name)!
|
||||
|
||||
console.print_green('✓ SSH key "${key_name}" added to authorized_keys on ${node_addr}')
|
||||
}
|
||||
|
||||
82
lib/osal/sshagent/play.v
Normal file
82
lib/osal/sshagent/play.v
Normal file
@@ -0,0 +1,82 @@
|
||||
module sshagent
|
||||
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
if !plbook.exists(filter: 'sshagent.') {
|
||||
return
|
||||
}
|
||||
|
||||
// Get or create a single SSH agent instance
|
||||
mut agent := new_single()!
|
||||
|
||||
// Process sshagent.check actions
|
||||
mut check_actions := plbook.find(filter: 'sshagent.check')!
|
||||
for mut action in check_actions {
|
||||
agent_check(mut agent)!
|
||||
action.done = true
|
||||
}
|
||||
|
||||
// Process sshagent.sshkey_create actions
|
||||
mut create_actions := plbook.find(filter: 'sshagent.sshkey_create')!
|
||||
for mut action in create_actions {
|
||||
mut p := action.params
|
||||
name := p.get('name')!
|
||||
passphrase := p.get_default('passphrase', '')!
|
||||
|
||||
sshkey_create(mut agent, name, passphrase)!
|
||||
action.done = true
|
||||
}
|
||||
|
||||
// Process sshagent.sshkey_delete actions
|
||||
mut delete_actions := plbook.find(filter: 'sshagent.sshkey_delete')!
|
||||
for mut action in delete_actions {
|
||||
mut p := action.params
|
||||
name := p.get('name')!
|
||||
|
||||
sshkey_delete(mut agent, name)!
|
||||
action.done = true
|
||||
}
|
||||
|
||||
// Process sshagent.sshkey_load actions
|
||||
mut load_actions := plbook.find(filter: 'sshagent.sshkey_load')!
|
||||
for mut action in load_actions {
|
||||
mut p := action.params
|
||||
name := p.get('name')!
|
||||
|
||||
sshkey_load(mut agent, name)!
|
||||
action.done = true
|
||||
}
|
||||
|
||||
// Process sshagent.sshkey_check actions
|
||||
mut check_key_actions := plbook.find(filter: 'sshagent.sshkey_check')!
|
||||
for mut action in check_key_actions {
|
||||
mut p := action.params
|
||||
name := p.get('name')!
|
||||
|
||||
sshkey_check(mut agent, name)!
|
||||
action.done = true
|
||||
}
|
||||
|
||||
// Process sshagent.remote_copy actions
|
||||
mut remote_copy_actions := plbook.find(filter: 'sshagent.remote_copy')!
|
||||
for mut action in remote_copy_actions {
|
||||
mut p := action.params
|
||||
node_addr := p.get('node')!
|
||||
key_name := p.get('name')!
|
||||
|
||||
remote_copy(mut agent, node_addr, key_name)!
|
||||
action.done = true
|
||||
}
|
||||
|
||||
// Process sshagent.remote_auth actions
|
||||
mut remote_auth_actions := plbook.find(filter: 'sshagent.remote_auth')!
|
||||
for mut action in remote_auth_actions {
|
||||
mut p := action.params
|
||||
node_addr := p.get('node')!
|
||||
key_name := p.get('name')!
|
||||
|
||||
remote_auth(mut agent, node_addr, key_name)!
|
||||
action.done = true
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ pub fn (mut t Tmux) windows_get() []&Window {
|
||||
pub fn (mut t Tmux) is_running() !bool {
|
||||
res := os.execute('tmux info')
|
||||
if res.exit_code != 0 {
|
||||
if res.output.contains('no server running') {
|
||||
if is_tmux_server_not_running_error(res.output) {
|
||||
// console.print_debug(" TMUX NOT RUNNING")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,83 @@ import time
|
||||
import os
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// Constants for memory calculations
|
||||
const kb_to_bytes_factor = 1024
|
||||
const memory_display_precision = 3
|
||||
const memory_cache_ttl_seconds = 300 // Cache system memory for 5 minutes
|
||||
|
||||
// Global cache for system memory to avoid repeated syscalls
|
||||
struct MemoryCache {
|
||||
mut:
|
||||
total_bytes u64
|
||||
cached_at time.Time
|
||||
}
|
||||
|
||||
__global (
|
||||
memory_cache MemoryCache
|
||||
)
|
||||
|
||||
// Platform-specific memory detection
|
||||
fn get_total_system_memory() !u64 {
|
||||
$if macos {
|
||||
result := osal.execute_silent('sysctl -n hw.memsize') or {
|
||||
return error('Failed to get system memory on macOS: ${err}')
|
||||
}
|
||||
return result.trim_space().u64()
|
||||
} $else $if linux {
|
||||
// Read from /proc/meminfo
|
||||
content := os.read_file('/proc/meminfo') or {
|
||||
return error('Failed to read /proc/meminfo on Linux: ${err}')
|
||||
}
|
||||
for line in content.split_into_lines() {
|
||||
if line.starts_with('MemTotal:') {
|
||||
parts := line.split_any(' \t').filter(it.len > 0)
|
||||
if parts.len >= 2 {
|
||||
kb_value := parts[1].u64()
|
||||
return kb_value * kb_to_bytes_factor
|
||||
}
|
||||
}
|
||||
}
|
||||
return error('Could not parse MemTotal from /proc/meminfo')
|
||||
} $else {
|
||||
return error('Unsupported platform for memory detection')
|
||||
}
|
||||
}
|
||||
|
||||
// Get cached or fresh system memory
|
||||
fn get_system_memory_cached() u64 {
|
||||
now := time.now()
|
||||
|
||||
// Check if cache is valid
|
||||
if memory_cache.total_bytes > 0
|
||||
&& now.unix() - memory_cache.cached_at.unix() < memory_cache_ttl_seconds {
|
||||
return memory_cache.total_bytes
|
||||
}
|
||||
|
||||
// Refresh cache
|
||||
total_memory := get_total_system_memory() or {
|
||||
console.print_debug('Failed to get system memory: ${err}')
|
||||
return 0
|
||||
}
|
||||
|
||||
memory_cache.total_bytes = total_memory
|
||||
memory_cache.cached_at = now
|
||||
|
||||
return total_memory
|
||||
}
|
||||
|
||||
// Calculate accurate memory percentage
|
||||
fn calculate_memory_percentage(memory_bytes u64, ps_fallback_percent f64) f64 {
|
||||
total_memory := get_system_memory_cached()
|
||||
|
||||
if total_memory > 0 {
|
||||
return (f64(memory_bytes) / f64(total_memory)) * 100.0
|
||||
}
|
||||
|
||||
// Fallback to ps value if system memory detection fails
|
||||
return ps_fallback_percent
|
||||
}
|
||||
|
||||
@[heap]
|
||||
struct Pane {
|
||||
pub mut:
|
||||
@@ -22,28 +99,47 @@ pub mut:
|
||||
|
||||
pub fn (mut p Pane) stats() !ProcessStats {
|
||||
if p.pid == 0 {
|
||||
return ProcessStats{}
|
||||
return ProcessStats{
|
||||
cpu_percent: 0.0
|
||||
memory_percent: 0.0
|
||||
memory_bytes: 0
|
||||
}
|
||||
}
|
||||
|
||||
// Use ps command to get CPU and memory stats
|
||||
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers'
|
||||
// Use ps command to get CPU and memory stats (cross-platform compatible)
|
||||
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss'
|
||||
result := osal.execute_silent(cmd) or {
|
||||
return error('Cannot get stats for PID ${p.pid}: ${err}')
|
||||
}
|
||||
|
||||
if result.trim_space() == '' {
|
||||
lines := result.split_into_lines()
|
||||
if lines.len < 2 {
|
||||
return error('Process ${p.pid} not found')
|
||||
}
|
||||
|
||||
parts := result.trim_space().split_any(' \t').filter(it != '')
|
||||
if parts.len < 3 {
|
||||
return error('Invalid ps output: ${result}')
|
||||
// Skip header line, get data line
|
||||
data_line := lines[1].trim_space()
|
||||
if data_line == '' {
|
||||
return error('Process ${p.pid} not found')
|
||||
}
|
||||
|
||||
parts := data_line.split_any(' \t').filter(it != '')
|
||||
if parts.len < 3 {
|
||||
return error('Invalid ps output: ${data_line}')
|
||||
}
|
||||
|
||||
// Parse values from ps output
|
||||
cpu_percent := parts[0].f64()
|
||||
ps_memory_percent := parts[1].f64()
|
||||
memory_bytes := parts[2].u64() * kb_to_bytes_factor
|
||||
|
||||
// Calculate accurate memory percentage using cached system memory
|
||||
memory_percent := calculate_memory_percentage(memory_bytes, ps_memory_percent)
|
||||
|
||||
return ProcessStats{
|
||||
cpu_percent: parts[0].f64()
|
||||
memory_percent: parts[1].f64()
|
||||
memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes
|
||||
cpu_percent: cpu_percent
|
||||
memory_percent: memory_percent
|
||||
memory_bytes: memory_bytes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +150,14 @@ pub mut:
|
||||
offset int
|
||||
}
|
||||
|
||||
pub fn (mut p Pane) logs_get_new(reset bool) ![]TMuxLogEntry {
|
||||
if reset {
|
||||
pub struct LogsGetArgs {
|
||||
pub mut:
|
||||
reset bool
|
||||
}
|
||||
|
||||
// get new logs since last call
|
||||
pub fn (mut p Pane) logs_get_new(args LogsGetArgs) ![]TMuxLogEntry {
|
||||
if args.reset {
|
||||
p.last_output_offset = 0
|
||||
}
|
||||
// Capture pane content with line numbers
|
||||
@@ -141,3 +243,66 @@ pub fn (mut p Pane) processinfo_main() !osal.ProcessInfo {
|
||||
|
||||
return osal.processinfo_get(p.pid)!
|
||||
}
|
||||
|
||||
// Send a command to this pane
|
||||
pub fn (mut p Pane) send_command(command string) ! {
|
||||
cmd := 'tmux send-keys -t ${p.window.session.name}:@${p.window.id}.%${p.id} "${command}" Enter'
|
||||
osal.execute_silent(cmd) or { return error('Cannot send command to pane %${p.id}: ${err}') }
|
||||
}
|
||||
|
||||
// Send raw keys to this pane (without Enter)
|
||||
pub fn (mut p Pane) send_keys(keys string) ! {
|
||||
cmd := 'tmux send-keys -t ${p.window.session.name}:@${p.window.id}.%${p.id} "${keys}"'
|
||||
osal.execute_silent(cmd) or { return error('Cannot send keys to pane %${p.id}: ${err}') }
|
||||
}
|
||||
|
||||
// Kill this specific pane
|
||||
pub fn (mut p Pane) kill() ! {
|
||||
cmd := 'tmux kill-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id}'
|
||||
osal.execute_silent(cmd) or { return error('Cannot kill pane %${p.id}: ${err}') }
|
||||
}
|
||||
|
||||
// Select/activate this pane
|
||||
pub fn (mut p Pane) select() ! {
|
||||
cmd := 'tmux select-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id}'
|
||||
osal.execute_silent(cmd) or { return error('Cannot select pane %${p.id}: ${err}') }
|
||||
p.active = true
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PaneResizeArgs {
|
||||
pub mut:
|
||||
direction string = 'right' // 'up', 'down', 'left', 'right'
|
||||
cells int = 5 // number of cells to resize by
|
||||
}
|
||||
|
||||
// Resize this pane
|
||||
pub fn (mut p Pane) resize(args PaneResizeArgs) ! {
|
||||
direction_flag := match args.direction.to_lower() {
|
||||
'up', 'u' { '-U' }
|
||||
'down', 'd' { '-D' }
|
||||
'left', 'l' { '-L' }
|
||||
'right', 'r' { '-R' }
|
||||
else { return error('Invalid resize direction: ${args.direction}. Use up, down, left, or right') }
|
||||
}
|
||||
|
||||
cmd := 'tmux resize-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} ${direction_flag} ${args.cells}'
|
||||
osal.execute_silent(cmd) or { return error('Cannot resize pane %${p.id}: ${err}') }
|
||||
}
|
||||
|
||||
// Convenience methods for resizing
|
||||
pub fn (mut p Pane) resize_up(cells int) ! {
|
||||
p.resize(direction: 'up', cells: cells)!
|
||||
}
|
||||
|
||||
pub fn (mut p Pane) resize_down(cells int) ! {
|
||||
p.resize(direction: 'down', cells: cells)!
|
||||
}
|
||||
|
||||
pub fn (mut p Pane) resize_left(cells int) ! {
|
||||
p.resize(direction: 'left', cells: cells)!
|
||||
}
|
||||
|
||||
pub fn (mut p Pane) resize_right(cells int) ! {
|
||||
p.resize(direction: 'right', cells: cells)!
|
||||
}
|
||||
|
||||
@@ -5,9 +5,30 @@ import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import time
|
||||
|
||||
// Check if error message indicates tmux server is not running
|
||||
fn is_tmux_server_not_running_error(error_msg string) bool {
|
||||
// Common tmux server not running error patterns
|
||||
tmux_not_running_patterns := [
|
||||
'no server running',
|
||||
'error connecting to',
|
||||
'No such file or directory', // when socket doesn't exist
|
||||
]
|
||||
|
||||
error_lower := error_msg.to_lower()
|
||||
for pattern in tmux_not_running_patterns {
|
||||
if error_lower.contains(pattern.to_lower()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut t Tmux) scan_add(line string) !&Pane {
|
||||
// Parse the line to get session, window, and pane info
|
||||
line_arr := line.split('|')
|
||||
if line_arr.len < 6 {
|
||||
return error('Invalid tmux pane line format: ${line}')
|
||||
}
|
||||
session_name := line_arr[0]
|
||||
window_name := line_arr[1]
|
||||
window_id := line_arr[2]
|
||||
@@ -16,6 +37,11 @@ fn (mut t Tmux) scan_add(line string) !&Pane {
|
||||
pane_pid := line_arr[5]
|
||||
pane_start_command := line_arr[6] or { '' }
|
||||
|
||||
// Skip if window name is empty
|
||||
if window_name.len == 0 {
|
||||
return error('Window name is empty in line: ${line}')
|
||||
}
|
||||
|
||||
wid := (window_id.replace('@', '')).int()
|
||||
pid := (pane_id.replace('%', '')).int()
|
||||
|
||||
@@ -71,7 +97,7 @@ pub fn (mut t Tmux) scan() ! {
|
||||
|
||||
cmd_list_session := "tmux list-sessions -F '#{session_name}'"
|
||||
exec_list := osal.exec(cmd: cmd_list_session, stdout: false, name: 'tmux_list') or {
|
||||
if err.msg().contains('no server running') {
|
||||
if is_tmux_server_not_running_error(err.msg()) {
|
||||
return
|
||||
}
|
||||
return error('could not execute list sessions.\n${err}')
|
||||
|
||||
@@ -63,8 +63,11 @@ pub fn (mut s Session) scan() ! {
|
||||
for line in result.split_into_lines() {
|
||||
if line.contains('|') {
|
||||
parts := line.split('|')
|
||||
if parts.len >= 2 {
|
||||
if parts.len >= 3 && parts[0].len > 0 && parts[1].len > 0 {
|
||||
window_name := texttools.name_fix(parts[0])
|
||||
if window_name.len == 0 {
|
||||
continue
|
||||
}
|
||||
window_id := parts[1].replace('@', '').int()
|
||||
window_active := parts[2] == '1'
|
||||
|
||||
@@ -73,7 +76,7 @@ pub fn (mut s Session) scan() ! {
|
||||
// Update existing window or create new one
|
||||
mut found := false
|
||||
for mut w in s.windows {
|
||||
if w.name == window_name {
|
||||
if w.name.len > 0 && window_name.len > 0 && w.name == window_name {
|
||||
w.id = window_id
|
||||
w.active = window_active
|
||||
w.scan()! // Scan panes for this window
|
||||
@@ -99,7 +102,7 @@ pub fn (mut s Session) scan() ! {
|
||||
}
|
||||
|
||||
// Remove windows that no longer exist in tmux
|
||||
s.windows = s.windows.filter(current_windows[it.name] == true)
|
||||
s.windows = s.windows.filter(it.name.len > 0 && current_windows[it.name] == true)
|
||||
}
|
||||
|
||||
// window_name is the name of the window in session main (will always be called session main)
|
||||
@@ -115,7 +118,7 @@ pub fn (mut s Session) scan() ! {
|
||||
// reset bool
|
||||
// }
|
||||
// ```
|
||||
pub fn (mut s Session) window_new(args WindowArgs) !Window {
|
||||
pub fn (mut s Session) window_new(args WindowArgs) !&Window {
|
||||
$if debug {
|
||||
console.print_header(' start window: \n${args}')
|
||||
}
|
||||
@@ -127,7 +130,7 @@ pub fn (mut s Session) window_new(args WindowArgs) !Window {
|
||||
return error('cannot create new window it already exists, window ${namel} in session:${s.name}')
|
||||
}
|
||||
}
|
||||
mut w := Window{
|
||||
mut w := &Window{
|
||||
session: &s
|
||||
name: namel
|
||||
panes: []&Pane{}
|
||||
@@ -211,9 +214,12 @@ fn (mut s Session) window_exist(args_ WindowGetArgs) bool {
|
||||
|
||||
pub fn (mut s Session) window_get(args_ WindowGetArgs) !&Window {
|
||||
mut args := args_
|
||||
if args.name.len == 0 {
|
||||
return error('Window name cannot be empty')
|
||||
}
|
||||
args.name = texttools.name_fix(args.name)
|
||||
for w in s.windows {
|
||||
if w.name == args.name {
|
||||
if w.name.len > 0 && w.name == args.name {
|
||||
if (args.id > 0 && w.id == args.id) || args.id == 0 {
|
||||
return w
|
||||
}
|
||||
@@ -252,3 +258,29 @@ pub fn (mut s Session) stop() ! {
|
||||
return error("Can't delete session ${s.name} - This may happen when session is not found: ${err}")
|
||||
}
|
||||
}
|
||||
|
||||
// Run ttyd for this session so it can be accessed in the browser
|
||||
pub fn (mut s Session) run_ttyd(args TtydArgs) ! {
|
||||
target := '${s.name}'
|
||||
|
||||
// Add -W flag for write access if editable mode is enabled
|
||||
mut ttyd_flags := '-p ${args.port}'
|
||||
if args.editable {
|
||||
ttyd_flags += ' -W'
|
||||
}
|
||||
|
||||
cmd := 'nohup ttyd ${ttyd_flags} tmux attach -t ${target} >/dev/null 2>&1 &'
|
||||
|
||||
code := os.system(cmd)
|
||||
if code != 0 {
|
||||
return error('Failed to start ttyd on port ${args.port} for session ${s.name}')
|
||||
}
|
||||
|
||||
mode_str := if args.editable { 'editable' } else { 'read-only' }
|
||||
println('ttyd started for session ${s.name} at http://localhost:${args.port} (${mode_str} mode)')
|
||||
}
|
||||
|
||||
// Backward compatibility method - runs ttyd in read-only mode
|
||||
pub fn (mut s Session) run_ttyd_readonly(port int) ! {
|
||||
s.run_ttyd(port: port, editable: false)!
|
||||
}
|
||||
|
||||
@@ -175,3 +175,117 @@ pub fn (mut w Window) pane_active() ?&Pane {
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PaneSplitArgs {
|
||||
pub mut:
|
||||
cmd string // command to run in new pane
|
||||
horizontal bool // true for horizontal split, false for vertical
|
||||
env map[string]string // environment variables
|
||||
}
|
||||
|
||||
// Split the active pane horizontally or vertically
|
||||
pub fn (mut w Window) pane_split(args PaneSplitArgs) !&Pane {
|
||||
mut cmd_to_run := args.cmd
|
||||
if cmd_to_run == '' {
|
||||
cmd_to_run = '/bin/bash'
|
||||
}
|
||||
|
||||
// Build environment arguments
|
||||
mut env_args := ''
|
||||
for key, value in args.env {
|
||||
env_args += ' -e ${key}="${value}"'
|
||||
}
|
||||
|
||||
// Choose split direction
|
||||
split_flag := if args.horizontal { '-h' } else { '-v' }
|
||||
|
||||
// Execute tmux split-window command
|
||||
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
|
||||
cmd := 'tmux split-window ${split_flag} ${res_opt}${env_args} -t ${w.session.name}:@${w.id} \'${cmd_to_run}\''
|
||||
|
||||
console.print_debug('Splitting pane: ${cmd}')
|
||||
|
||||
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_pane_split') or {
|
||||
return error("Can't split pane in window ${w.name}: ${err}")
|
||||
}
|
||||
|
||||
// Parse the result to get new pane info
|
||||
line_arr := res.output.split('|')
|
||||
if line_arr.len < 7 {
|
||||
return error('Invalid tmux split-window output: ${res.output}')
|
||||
}
|
||||
|
||||
pane_id := line_arr[4].replace('%', '').int()
|
||||
pane_pid := line_arr[5].int()
|
||||
pane_active := line_arr[3] == '1'
|
||||
pane_cmd := line_arr[6] or { '' }
|
||||
|
||||
// Create new pane object
|
||||
mut new_pane := Pane{
|
||||
window: &w
|
||||
id: pane_id
|
||||
pid: pane_pid
|
||||
active: pane_active
|
||||
cmd: pane_cmd
|
||||
env: args.env
|
||||
created_at: time.now()
|
||||
last_output_offset: 0
|
||||
}
|
||||
|
||||
// Add to window's panes and rescan to get current state
|
||||
w.panes << &new_pane
|
||||
w.scan()!
|
||||
|
||||
// Return reference to the new pane
|
||||
for mut pane in w.panes {
|
||||
if pane.id == pane_id {
|
||||
return pane
|
||||
}
|
||||
}
|
||||
|
||||
return error('Could not find newly created pane with ID ${pane_id}')
|
||||
}
|
||||
|
||||
// Split pane horizontally (side by side)
|
||||
pub fn (mut w Window) pane_split_horizontal(cmd string) !&Pane {
|
||||
return w.pane_split(cmd: cmd, horizontal: true)
|
||||
}
|
||||
|
||||
// Split pane vertically (top and bottom)
|
||||
pub fn (mut w Window) pane_split_vertical(cmd string) !&Pane {
|
||||
return w.pane_split(cmd: cmd, horizontal: false)
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct TtydArgs {
|
||||
pub mut:
|
||||
port int
|
||||
editable bool // if true, allows write access to the terminal
|
||||
}
|
||||
|
||||
// Run ttyd for this window so it can be accessed in the browser
|
||||
pub fn (mut w Window) run_ttyd(args TtydArgs) ! {
|
||||
target := '${w.session.name}:@${w.id}'
|
||||
|
||||
// Add -W flag for write access if editable mode is enabled
|
||||
mut ttyd_flags := '-p ${args.port}'
|
||||
if args.editable {
|
||||
ttyd_flags += ' -W'
|
||||
}
|
||||
|
||||
cmd := 'nohup ttyd ${ttyd_flags} tmux attach -t ${target} >/dev/null 2>&1 &'
|
||||
|
||||
code := os.system(cmd)
|
||||
if code != 0 {
|
||||
return error('Failed to start ttyd on port ${args.port} for window ${w.name}')
|
||||
}
|
||||
|
||||
mode_str := if args.editable { 'editable' } else { 'read-only' }
|
||||
println('ttyd started for window ${w.name} at http://localhost:${args.port} (${mode_str} mode)')
|
||||
}
|
||||
|
||||
// Backward compatibility method - runs ttyd in read-only mode
|
||||
pub fn (mut w Window) run_ttyd_readonly(port int) ! {
|
||||
w.run_ttyd(port: port, editable: false)!
|
||||
}
|
||||
|
||||
92
lib/virt/podman/factory.v
Normal file
92
lib/virt/podman/factory.v
Normal file
@@ -0,0 +1,92 @@
|
||||
module herocontainers
|
||||
|
||||
import freeflowuniverse.herolib.osal.core as osal { exec }
|
||||
import freeflowuniverse.herolib.core
|
||||
import freeflowuniverse.herolib.installers.virt.podman as podman_installer
|
||||
|
||||
@[heap]
|
||||
pub struct PodmanFactory {
|
||||
pub mut:
|
||||
// sshkeys_allowed []string // all keys here have access over ssh into the machine, when ssh enabled
|
||||
images []Image
|
||||
containers []Container
|
||||
buildpath string
|
||||
// cache bool = true
|
||||
// push bool
|
||||
// platform []BuildPlatformType // used to build
|
||||
// registries []BAHRegistry // one or more supported BAHRegistries
|
||||
prefix string
|
||||
}
|
||||
|
||||
|
||||
@[params]
|
||||
pub struct NewArgs {
|
||||
pub mut:
|
||||
install bool = true
|
||||
reset bool
|
||||
herocompile bool
|
||||
}
|
||||
|
||||
|
||||
if args.install {
|
||||
mut podman_installer0 := podman_installer.get()!
|
||||
podman_installer0.install()!
|
||||
}
|
||||
|
||||
|
||||
fn (mut e PodmanFactory) init() ! {
|
||||
if e.buildpath == '' {
|
||||
e.buildpath = '/tmp/builder'
|
||||
exec(cmd: 'mkdir -p ${e.buildpath}', stdout: false)!
|
||||
}
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// reload the state from system
|
||||
pub fn (mut e PodmanFactory) load() ! {
|
||||
e.builders_load()!
|
||||
e.images_load()!
|
||||
e.containers_load()!
|
||||
}
|
||||
|
||||
// reset all images & containers, CAREFUL!
|
||||
pub fn (mut e PodmanFactory) reset_all() ! {
|
||||
e.load()!
|
||||
for mut container in e.containers.clone() {
|
||||
container.delete()!
|
||||
}
|
||||
for mut image in e.images.clone() {
|
||||
image.delete(true)!
|
||||
}
|
||||
exec(cmd: 'podman rm -a -f', stdout: false)!
|
||||
exec(cmd: 'podman rmi -a -f', stdout: false)!
|
||||
e.builders_delete_all()!
|
||||
osal.done_reset()!
|
||||
if core.platform()! == core.PlatformType.arch {
|
||||
exec(cmd: 'systemctl status podman.socket', stdout: false)!
|
||||
}
|
||||
e.load()!
|
||||
}
|
||||
|
||||
// Get free port
|
||||
pub fn (mut e PodmanFactory) get_free_port() ?int {
|
||||
mut used_ports := []int{}
|
||||
mut range := []int{}
|
||||
|
||||
for c in e.containers {
|
||||
for p in c.forwarded_ports {
|
||||
used_ports << p.split(':')[0].int()
|
||||
}
|
||||
}
|
||||
|
||||
for i in 20000 .. 40000 {
|
||||
if i !in used_ports {
|
||||
range << i
|
||||
}
|
||||
}
|
||||
// arrays.shuffle<int>(mut range, 0)
|
||||
if range.len == 0 {
|
||||
return none
|
||||
}
|
||||
return range[0]
|
||||
}
|
||||
Reference in New Issue
Block a user