This commit is contained in:
2025-08-25 06:28:42 +02:00
parent 5f683ec4a8
commit 836c87fbec
44 changed files with 891 additions and 942 deletions

View File

@@ -25,7 +25,7 @@ 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')
@@ -62,4 +62,4 @@ if agent.keys.len > 0 {
}
*/
console.print_header('SSH Agent example completed successfully')
console.print_header('SSH Agent example completed successfully')

View File

@@ -25,19 +25,19 @@ fn test_user_mgmt() ! {
mut lf := linux.new()!
// Test user creation
lf.user_create(
name: 'testuser'
name: 'testuser'
sshkey: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM3/2K7R8A/l0kM0/d'
)!
// Test ssh key creation
lf.sshkey_create(
username: 'testuser'
username: 'testuser'
sshkey_name: 'testkey'
)!
// Test ssh key deletion
lf.sshkey_delete(
username: 'testuser'
username: 'testuser'
sshkey_name: 'testkey'
)!

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tmux
mut t := tmux.new()!

View File

@@ -6,7 +6,7 @@ import time
mut t := tmux.new()!
if !t.is_running()! {
t.start()!
t.start()!
}
// Create a session and window
@@ -18,15 +18,15 @@ time.sleep(1000 * time.millisecond)
// Get the active pane
if mut pane := window.pane_active() {
// Get process info for the pane and its children
process_map := pane.processinfo()!
println('Process tree for pane ${pane.id}:')
for process in process_map.processes {
println(' PID: ${process.pid}, CPU: ${process.cpu_perc}%, Memory: ${process.mem_perc}%, Command: ${process.cmd}')
}
// Get just the main process info
main_process := pane.processinfo_main()!
println('\nMain process: PID ${main_process.pid}, Command: ${main_process.cmd}')
}
// Get process info for the pane and its children
process_map := pane.processinfo()!
println('Process tree for pane ${pane.id}:')
for process in process_map.processes {
println(' PID: ${process.pid}, CPU: ${process.cpu_perc}%, Memory: ${process.mem_perc}%, Command: ${process.cmd}')
}
// Get just the main process info
main_process := pane.processinfo_main()!
println('\nMain process: PID ${main_process.pid}, Command: ${main_process.cmd}')
}

View File

@@ -7,13 +7,13 @@ import time
fn (mut c LivekitClient) post(path string, body any) !http.Response {
mut token := c.new_access_token(
identity: 'api'
name: 'API User'
ttl: 10 * 60 // 10 minutes
name: 'API User'
ttl: 10 * 60 // 10 minutes
)!
token.add_video_grant(VideoGrant{
room_create: true
room_admin: true
room_list: true
room_admin: true
room_list: true
})
jwt := token.to_jwt()!
@@ -25,13 +25,13 @@ fn (mut c LivekitClient) post(path string, body any) !http.Response {
data := json.encode(body)
mut req := http.Request{
method: .post
url: url
url: url
header: header
data: data
data: data
}
resp := http.fetch(req)!
if resp.status_code != 200 {
return error('failed to execute request: ${resp.body}')
}
return resp
}
}

View File

@@ -3,13 +3,16 @@ module livekit
import freeflowuniverse.herolib.data.caching
import os
const CACHING_METHOD = caching.CachingMethod.once_per_process
// const CACHING_METHOD = caching.CachingMethod.once_per_process
fn _init() ! {
if caching.is_set(key: 'livekit_clients') {
return
}
caching.set[map[string]LivekitClient](key: 'livekit_clients', val: map[string]LivekitClient{}, CachingMethod.once_per_process)!
caching.set[map[string]LivekitClient](
key: 'livekit_clients'
val: map[string]LivekitClient{}
)!
}
fn _get() !map[string]LivekitClient {
@@ -25,10 +28,10 @@ pub fn get(name string) !LivekitClient {
pub fn set(client LivekitClient) ! {
mut clients := _get()!
clients[client.name] = client
caching.set[map[string]LivekitClient](key: 'livekit_clients', val: clients, CachingMethod.once_per_process)!
caching.set[map[string]LivekitClient](key: 'livekit_clients', val: clients)!
}
pub fn exists(name string) !bool {
mut clients := _get()!
return name in clients
}
}

View File

@@ -2,9 +2,9 @@ module livekit
pub struct SendDataArgs {
pub mut:
room_name string
data []u8
kind DataPacket_Kind
room_name string
data []u8
kind DataPacket_Kind
destination_sids []string
}
@@ -15,4 +15,4 @@ pub enum DataPacket_Kind {
pub fn (mut c LivekitClient) send_data(args SendDataArgs) ! {
_ = c.post('twirp/livekit.RoomService/SendData', args)!
}
}

View File

@@ -4,40 +4,40 @@ import json
pub struct EgressInfo {
pub mut:
egress_id string
room_id string
status string
egress_id string
room_id string
status string
started_at i64
ended_at i64
error string
ended_at i64
error string
}
pub struct StartRoomCompositeEgressArgs {
pub mut:
room_name string
layout string
audio_only bool
video_only bool
room_name string
layout string
audio_only bool
video_only bool
custom_base_url string
}
pub struct StartTrackCompositeEgressArgs {
pub mut:
room_name string
room_name string
audio_track_id string
video_track_id string
}
pub struct StartWebEgressArgs {
pub mut:
url string
url string
audio_only bool
video_only bool
}
pub struct UpdateStreamArgs {
pub mut:
add_output_urls []string
add_output_urls []string
remove_output_urls []string
}
@@ -60,25 +60,36 @@ pub fn (mut c LivekitClient) start_web_egress(args StartWebEgressArgs) !EgressIn
}
pub fn (mut c LivekitClient) update_layout(egress_id string, layout string) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/UpdateLayout', {'egress_id': egress_id, 'layout': layout})!
mut resp := c.post('twirp/livekit.Egress/UpdateLayout', {
'egress_id': egress_id
'layout': layout
})!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
pub fn (mut c LivekitClient) update_stream(egress_id string, args UpdateStreamArgs) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/UpdateStream', {'egress_id': egress_id, 'add_output_urls': args.add_output_urls, 'remove_output_urls': args.remove_output_urls})!
mut resp := c.post('twirp/livekit.Egress/UpdateStream', {
'egress_id': egress_id
'add_output_urls': args.add_output_urls
'remove_output_urls': args.remove_output_urls
})!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
pub fn (mut c LivekitClient) list_egress(room_name string) ![]EgressInfo {
mut resp := c.post('twirp/livekit.Egress/ListEgress', {'room_name': room_name})!
mut resp := c.post('twirp/livekit.Egress/ListEgress', {
'room_name': room_name
})!
egress_infos := json.decode[[]EgressInfo](resp.body)!
return egress_infos
}
pub fn (mut c LivekitClient) stop_egress(egress_id string) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/StopEgress', {'egress_id': egress_id})!
mut resp := c.post('twirp/livekit.Egress/StopEgress', {
'egress_id': egress_id
})!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
}

View File

@@ -77,30 +77,20 @@ pub mut:
pub struct InputAudioState {
pub mut:
mime_type string
channels u32
mime_type string
channels u32
sample_rate u32
}
pub struct CreateIngressArgs {
pub mut:
name string
room_name string
name string
room_name string
participant_identity string
participant_name string
input_type IngressInput
audio IngressAudioOptions
video IngressVideoOptions
}
pub struct UpdateIngressArgs {
pub mut:
name string
room_name string
participant_identity string
participant_name string
audio IngressAudioOptions
video IngressVideoOptions
input_type IngressInput
audio IngressAudioOptions
video IngressVideoOptions
}
pub fn (mut c LivekitClient) create_ingress(args CreateIngressArgs) !IngressInfo {
@@ -109,20 +99,43 @@ pub fn (mut c LivekitClient) create_ingress(args CreateIngressArgs) !IngressInfo
return ingress_info
}
pub fn (mut c LivekitClient) update_ingress(ingress_id string, args UpdateIngressArgs) !IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/UpdateIngress', {'ingress_id': ingress_id, ...args})!
pub struct UpdateIngressArgs {
pub mut:
ingress_id string
name string
room_name string
participant_identity string
participant_name string
audio IngressAudioOptions
video IngressVideoOptions
}
pub fn (mut c LivekitClient) update_ingress(args UpdateIngressArgs) !IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/UpdateIngress', {
'ingress_id': args.ingress_id
'name': args.name
'room_name': args.room_name
'participant_identity': args.participant_identity
'participant_name': args.participant_name
'audio': args.audio
'video': args.video
})!
ingress_info := json.decode[IngressInfo](resp.body)!
return ingress_info
}
pub fn (mut c LivekitClient) list_ingress(room_name string) ![]IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/ListIngress', {'room_name': room_name})!
mut resp := c.post('twirp/livekit.Ingress/ListIngress', {
'room_name': room_name
})!
ingress_infos := json.decode[[]IngressInfo](resp.body)!
return ingress_infos
}
pub fn (mut c LivekitClient) delete_ingress(ingress_id string) !IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/DeleteIngress', {'ingress_id': ingress_id})!
mut resp := c.post('twirp/livekit.Ingress/DeleteIngress', {
'ingress_id': ingress_id
})!
ingress_info := json.decode[IngressInfo](resp.body)!
return ingress_info
}
}

View File

@@ -41,4 +41,4 @@ pub fn heroscript_dumps(obj LivekitClient) !string {
pub fn heroscript_loads(heroscript string) !LivekitClient {
mut obj := encoderhero.decode[LivekitClient](heroscript)!
return obj
}
}

View File

@@ -4,23 +4,23 @@ import json
pub struct ParticipantInfo {
pub mut:
sid string
identity string
state string
metadata string
joined_at i64
name string
version u32
sid string
identity string
state string
metadata string
joined_at i64
name string
version u32
permission string
region string
publisher bool
region string
publisher bool
}
pub struct UpdateParticipantArgs {
pub mut:
room_name string
identity string
metadata string
room_name string
identity string
metadata string
permission string
}
@@ -33,19 +33,27 @@ pub mut:
}
pub fn (mut c LivekitClient) list_participants(room_name string) ![]ParticipantInfo {
mut resp := c.post('twirp/livekit.RoomService/ListParticipants', {'room': room_name})!
mut resp := c.post('twirp/livekit.RoomService/ListParticipants', {
'room': room_name
})!
participants := json.decode[[]ParticipantInfo](resp.body)!
return participants
}
pub fn (mut c LivekitClient) get_participant(room_name string, identity string) !ParticipantInfo {
mut resp := c.post('twirp/livekit.RoomService/GetParticipant', {'room': room_name, 'identity': identity})!
mut resp := c.post('twirp/livekit.RoomService/GetParticipant', {
'room': room_name
'identity': identity
})!
participant := json.decode[ParticipantInfo](resp.body)!
return participant
}
pub fn (mut c LivekitClient) remove_participant(room_name string, identity string) ! {
_ = c.post('twirp/livekit.RoomService/RemoveParticipant', {'room': room_name, 'identity': identity})!
_ = c.post('twirp/livekit.RoomService/RemoveParticipant', {
'room': room_name
'identity': identity
})!
}
pub fn (mut c LivekitClient) update_participant(args UpdateParticipantArgs) ! {
@@ -54,4 +62,4 @@ pub fn (mut c LivekitClient) update_participant(args UpdateParticipantArgs) ! {
pub fn (mut c LivekitClient) mute_published_track(args MutePublishedTrackArgs) ! {
_ = c.post('twirp/livekit.RoomService/MutePublishedTrack', args)!
}
}

View File

@@ -13,19 +13,19 @@ pub fn play(mut plbook PlayBook) ! {
if plbook.exists_once(filter: 'livekit.init') {
mut action := plbook.get(filter: 'livekit.init')!
mut p := action.params
name := texttools.name_fix(p.get_default('name', 'default')!)
url := p.get('url')!
api_key := p.get('api_key')!
api_secret := p.get('api_secret')!
mut client := LivekitClient{
name: name
url: url
api_key: api_key
name: name
url: url
api_key: api_key
api_secret: api_secret
}
set(client)!
console.print_header('LiveKit client "${name}" configured')
action.done = true
@@ -35,22 +35,22 @@ pub fn play(mut plbook PlayBook) ! {
mut room_create_actions := plbook.find(filter: 'livekit.room_create')!
for mut action in room_create_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('name')!
empty_timeout := p.get_u32_default('empty_timeout', 300)!
max_participants := p.get_u32_default('max_participants', 50)!
metadata := p.get_default('metadata', '')!
mut client := get(name: client_name)!
room := client.create_room(
name: room_name
empty_timeout: empty_timeout
name: room_name
empty_timeout: empty_timeout
max_participants: max_participants
metadata: metadata
metadata: metadata
)!
console.print_header('Room "${room_name}" created successfully')
action.done = true
}
@@ -59,13 +59,13 @@ pub fn play(mut plbook PlayBook) ! {
mut room_delete_actions := plbook.find(filter: 'livekit.room_delete')!
for mut action in room_delete_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('name')!
mut client := get(name: client_name)!
client.delete_room(room_name)!
console.print_header('Room "${room_name}" deleted successfully')
action.done = true
}
@@ -74,14 +74,14 @@ pub fn play(mut plbook PlayBook) ! {
mut participant_remove_actions := plbook.find(filter: 'livekit.participant_remove')!
for mut action in participant_remove_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')!
identity := p.get('identity')!
mut client := get(name: client_name)!
client.remove_participant(room_name, identity)!
console.print_header('Participant "${identity}" removed from room "${room_name}"')
action.done = true
}
@@ -90,21 +90,21 @@ pub fn play(mut plbook PlayBook) ! {
mut participant_mute_actions := plbook.find(filter: 'livekit.participant_mute')!
for mut action in participant_mute_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')!
identity := p.get('identity')!
track_sid := p.get('track_sid')!
muted := p.get_default_true('muted')
mut client := get(name: client_name)!
client.mute_published_track(
room_name: room_name
identity: identity
identity: identity
track_sid: track_sid
muted: muted
muted: muted
)!
status := if muted { 'muted' } else { 'unmuted' }
console.print_header('Track "${track_sid}" ${status} for participant "${identity}"')
action.done = true
@@ -114,17 +114,17 @@ pub fn play(mut plbook PlayBook) ! {
mut room_update_actions := plbook.find(filter: 'livekit.room_update')!
for mut action in room_update_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')!
metadata := p.get('metadata')!
mut client := get(name: client_name)!
client.update_room_metadata(
room_name: room_name
metadata: metadata
metadata: metadata
)!
console.print_header('Room "${room_name}" metadata updated')
action.done = true
}
@@ -133,7 +133,7 @@ pub fn play(mut plbook PlayBook) ! {
mut token_create_actions := plbook.find(filter: 'livekit.token_create')!
for mut action in token_create_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
identity := p.get('identity')!
name := p.get_default('name', identity)!
@@ -142,26 +142,26 @@ pub fn play(mut plbook PlayBook) ! {
can_publish := p.get_default_false('can_publish')
can_subscribe := p.get_default_true('can_subscribe')
can_publish_data := p.get_default_false('can_publish_data')
mut client := get(name: client_name)!
mut token := client.new_access_token(
identity: identity
name: name
ttl: ttl
name: name
ttl: ttl
)!
token.add_video_grant(VideoGrant{
room: room
room_join: true
can_publish: can_publish
can_subscribe: can_subscribe
room: room
room_join: true
can_publish: can_publish
can_subscribe: can_subscribe
can_publish_data: can_publish_data
})
jwt := token.to_jwt()!
console.print_header('Access token generated for "${identity}"')
console.print_debug('Token: ${jwt}')
action.done = true
}
}
}

View File

@@ -5,25 +5,25 @@ import net.http
pub struct Room {
pub mut:
sid string
name string
empty_timeout u32
max_participants u32
creation_time i64
turn_password string
enabled_codecs []string
metadata string
num_participants u32
sid string
name string
empty_timeout u32
max_participants u32
creation_time i64
turn_password string
enabled_codecs []string
metadata string
num_participants u32
num_connected_participants u32
active_recording bool
active_recording bool
}
pub struct CreateRoomArgs {
pub mut:
name string
empty_timeout u32
max_participants u32
metadata string
name string
empty_timeout u32
max_participants u32
metadata string
}
pub struct UpdateRoomMetadataArgs {
@@ -39,9 +39,11 @@ pub fn (mut c LivekitClient) create_room(args CreateRoomArgs) !Room {
}
pub fn (mut c LivekitClient) delete_room(room_name string) ! {
_ = c.post('twirp/livekit.RoomService/DeleteRoom', {'room': room_name})!
_ = c.post('twirp/livekit.RoomService/DeleteRoom', {
'room': room_name
})!
}
pub fn (mut c LivekitClient) update_room_metadata(args UpdateRoomMetadataArgs) ! {
_ = c.post('twirp/livekit.RoomService/UpdateRoomMetadata', args)!
}
}

View File

@@ -15,19 +15,19 @@ pub:
unix_micro string
}
pub struct Room {
pub:
active_recording bool
creation_time string
departure_timeout int
empty_timeout int
enabled_codecs []Codec
max_participants int
metadata string
name string
num_participants int
num_publishers int
sid string
turn_password string
version Version
}
// pub struct Room {
// pub:
// active_recording bool
// creation_time string
// departure_timeout int
// empty_timeout int
// enabled_codecs []Codec
// max_participants int
// metadata string
// name string
// num_participants int
// num_publishers int
// sid string
// turn_password string
// version Version
// }

View File

@@ -5,33 +5,33 @@ import time
pub struct AccessToken {
pub mut:
api_key string
api_secret string
identity string
name string
ttl int
api_key string
api_secret string
identity string
name string
ttl int
video_grant VideoGrant
}
pub struct VideoGrant {
pub mut:
room_create bool
room_admin bool
room_join bool
room_list bool
can_publish bool
can_subscribe bool
room_create bool
room_admin bool
room_join bool
room_list bool
can_publish bool
can_subscribe bool
can_publish_data bool
room string
room string
}
pub fn (mut c LivekitClient) new_access_token(identity string, name string, ttl int) !AccessToken {
return AccessToken{
api_key: c.api_key
api_key: c.api_key
api_secret: c.api_secret
identity: identity
name: name
ttl: ttl
identity: identity
name: name
ttl: ttl
}
}
@@ -49,4 +49,4 @@ pub fn (t AccessToken) to_jwt() !string {
claims.name = t.name
claims.video = t.video_grant
return jwt.encode(claims, t.api_secret, .hs256)
}
}

View File

@@ -18,60 +18,60 @@ pub mut:
name string
}
// VideoGrant struct placeholder
pub struct VideoGrant {
pub mut:
room string
room_join bool @[json: 'roomJoin']
room_list bool @[json: 'roomList']
can_publish bool @[json: 'canPublish']
can_publish_data bool @[json: 'canPublishData']
can_subscribe bool @[json: 'canSubscribe']
}
// // VideoGrant struct placeholder
// pub struct VideoGrant {
// pub mut:
// room string
// room_join bool @[json: 'roomJoin']
// room_list bool @[json: 'roomList']
// can_publish bool @[json: 'canPublish']
// can_publish_data bool @[json: 'canPublishData']
// can_subscribe bool @[json: 'canSubscribe']
// }
// SIPGrant struct placeholder
struct SIPGrant {}
// AccessToken class
pub struct AccessToken {
mut:
api_key string
api_secret string
grants ClaimGrants
identity string
ttl int
}
// // AccessToken class
// pub struct AccessToken {
// mut:
// api_key string
// api_secret string
// grants ClaimGrants
// identity string
// ttl int
// }
// Method to add a video grant to the token
pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) {
token.grants.video = grant
}
// pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) {
// token.grants.video = grant
// }
// Method to generate a JWT token
pub fn (token AccessToken) to_jwt() !string {
// Create JWT payload
payload := json.encode(token.grants)
// // Method to generate a JWT token
// pub fn (token AccessToken) to_jwt() !string {
// // Create JWT payload
// payload := json.encode(token.grants)
println('payload: ${payload}')
// println('payload: ${payload}')
// Create JWT header
header := '{"alg":"HS256","typ":"JWT"}'
// // Create JWT header
// header := '{"alg":"HS256","typ":"JWT"}'
// Encode header and payload in base64
header_encoded := base64.url_encode_str(header)
payload_encoded := base64.url_encode_str(payload)
// // Encode header and payload in base64
// header_encoded := base64.url_encode_str(header)
// payload_encoded := base64.url_encode_str(payload)
// Create the unsigned token
unsigned_token := '${header_encoded}.${payload_encoded}'
// // Create the unsigned token
// unsigned_token := '${header_encoded}.${payload_encoded}'
// Create the HMAC-SHA256 signature
signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum,
sha256.block_size)
// // Create the HMAC-SHA256 signature
// signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum,
// sha256.block_size)
// Encode the signature in base64
signature_encoded := base64.url_encode(signature)
// // Encode the signature in base64
// signature_encoded := base64.url_encode(signature)
// Create the final JWT
jwt := '${unsigned_token}.${signature_encoded}'
return jwt
}
// // Create the final JWT
// jwt := '${unsigned_token}.${signature_encoded}'
// return jwt
// }

View File

@@ -15,8 +15,8 @@ pub const default_url = 'http://localhost:8990'
@[heap]
pub struct MyceliumRPC {
pub mut:
name string = 'default'
url string = default_url // RPC server URL
name string = 'default'
url string = default_url // RPC server URL
// rpc_client ?&jsonrpc.Client @[skip]
}

View File

@@ -22,16 +22,16 @@ pub fn new(args FactoryArgs) !&TraefikManager {
}
mut redis := redisclient.core_get(redisclient.get_redis_url(args.redis_url)!)!
mut manager := &TraefikManager{
name: name
redis: redis
name: name
redis: redis
config: osal_traefik.new_traefik_config()
}
// Set redis connection in config
manager.config.redis = redis
traefik_managers[name] = manager
return manager
}
@@ -48,4 +48,4 @@ pub fn default() !&TraefikManager {
return new(name: 'default')!
}
return get(name: 'default')!
}
}

View File

@@ -23,9 +23,9 @@ pub mut:
@[params]
pub struct RouterAddArgs {
pub mut:
name string @[required]
rule string @[required]
service string @[required]
name string @[required]
rule string @[required]
service string @[required]
entrypoints []string
middlewares []string
tls bool
@@ -43,8 +43,8 @@ pub mut:
@[params]
pub struct MiddlewareAddArgs {
pub mut:
name string @[required]
typ string @[required]
name string @[required]
typ string @[required]
settings map[string]string
}
@@ -78,7 +78,7 @@ pub fn (mut tm TraefikManager) service_add(args ServiceAddArgs) ! {
}
tm.config.add_service(
name: texttools.name_fix(args.name)
name: texttools.name_fix(args.name)
load_balancer: osal_traefik.LoadBalancerConfig{
servers: servers
}
@@ -101,7 +101,7 @@ pub fn (mut tm TraefikManager) entrypoint_add(args EntryPointAddArgs) ! {
address: args.address
tls: args.tls
}
// Check if entrypoint already exists
for mut ep in tm.entrypoints {
if ep.name == entrypoint.name {
@@ -110,7 +110,7 @@ pub fn (mut tm TraefikManager) entrypoint_add(args EntryPointAddArgs) ! {
return
}
}
tm.entrypoints << entrypoint
}
@@ -118,7 +118,7 @@ pub fn (mut tm TraefikManager) entrypoint_add(args EntryPointAddArgs) ! {
pub fn (mut tm TraefikManager) apply() ! {
// Apply dynamic configuration (routers, services, middlewares)
tm.config.set()!
// Store entrypoints separately (these would typically be in static config)
for ep in tm.entrypoints {
tm.redis.hset('traefik:entrypoints', ep.name, '${ep.address}|${ep.tls}')!
@@ -135,7 +135,7 @@ pub fn (mut tm TraefikManager) clear() ! {
tm.config = osal_traefik.new_traefik_config()
tm.config.redis = tm.redis
tm.entrypoints = []EntryPointConfig{}
// Clear Redis keys
keys := tm.redis.keys('traefik/*')!
for key in keys {
@@ -151,4 +151,4 @@ pub fn (mut tm TraefikManager) status() !map[string]int {
'middlewares': tm.config.middlewares.len
'entrypoints': tm.entrypoints.len
}
}
}

View File

@@ -14,44 +14,44 @@ pub fn play(mut plbook PlayBook) ! {
// Process entrypoints first
play_entrypoints(mut plbook, mut manager)!
// Process services (before routers that might reference them)
play_services(mut plbook, mut manager)!
// Process middlewares (before routers that might reference them)
play_middlewares(mut plbook, mut manager)!
// Process routers
play_routers(mut plbook, mut manager)!
// Apply all configurations to Redis
manager.apply()!
console.print_debug('Traefik configuration applied successfully')
}
fn play_entrypoints(mut plbook PlayBook, mut manager TraefikManager) ! {
entrypoint_actions := plbook.find(filter: 'traefik.entrypoint')!
for mut action in entrypoint_actions {
mut p := action.params
manager.entrypoint_add(
name: p.get('name')!
address: p.get('address')!
tls: p.get_default_false('tls')
)!
action.done = true
}
}
fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! {
router_actions := plbook.find(filter: 'traefik.router')!
for mut action in router_actions {
mut p := action.params
// Parse entrypoints list
mut entrypoints := []string{}
if entrypoints_str := p.get_default('entrypoints', '') {
@@ -59,7 +59,7 @@ fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! {
entrypoints = entrypoints_str.split(',').map(it.trim_space())
}
}
// Parse middlewares list
mut middlewares := []string{}
if middlewares_str := p.get_default('middlewares', '') {
@@ -67,7 +67,7 @@ fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! {
middlewares = middlewares_str.split(',').map(it.trim_space())
}
}
manager.router_add(
name: p.get('name')!
rule: p.get('rule')!
@@ -77,42 +77,42 @@ fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! {
tls: p.get_default_false('tls')
priority: p.get_int_default('priority', 0)
)!
action.done = true
}
}
fn play_services(mut plbook PlayBook, mut manager TraefikManager) ! {
service_actions := plbook.find(filter: 'traefik.service')!
for mut action in service_actions {
mut p := action.params
// Parse servers list
servers_str := p.get('servers')!
servers := servers_str.split(',').map(it.trim_space())
manager.service_add(
name: p.get('name')!
servers: servers
strategy: p.get_default('strategy', 'wrr')!
)!
action.done = true
}
}
fn play_middlewares(mut plbook PlayBook, mut manager TraefikManager) ! {
middleware_actions := plbook.find(filter: 'traefik.middleware')!
for mut action in middleware_actions {
mut p := action.params
// Build settings map from remaining parameters
mut settings := map[string]string{}
middleware_type := p.get('type')!
// Handle common middleware types
match middleware_type {
'basicAuth' {
@@ -156,13 +156,13 @@ fn play_middlewares(mut plbook PlayBook, mut manager TraefikManager) ! {
}
}
}
manager.middleware_add(
name: p.get('name')!
typ: middleware_type
settings: settings
)!
action.done = true
}
}
}

View File

@@ -16,9 +16,9 @@ pub fn scan(args_ GeneratorArgs) ! {
// now walk over all directories, find .heroscript
mut pathroot := pathlib.get_dir(path: args.path, create: false)!
mut plist := pathroot.list(
recursive: true
recursive: true
ignore_default: false
regex: ['.heroscript']
regex: ['.heroscript']
)!
for mut p in plist.paths {

View File

@@ -8,12 +8,12 @@ import freeflowuniverse.herolib.ui.console
@[params]
pub struct ListArgs {
pub mut:
regex []string
recursive bool = true
regex []string
recursive bool = true
ignore_default bool = true // ignore files starting with . and _
include_links bool // wether to include links in list
dirs_only bool
files_only bool
include_links bool // wether to include links in list
dirs_only bool
files_only bool
}
// the result of pathlist
@@ -54,12 +54,12 @@ pub fn (mut path Path) list(args_ ListArgs) !PathList {
r << re
}
mut args := ListArgsInternal{
regex: r
recursive: args_.recursive
regex: r
recursive: args_.recursive
ignore_default: args_.ignore_default
dirs_only: args_.dirs_only
files_only: args_.files_only
include_links: args_.include_links
dirs_only: args_.dirs_only
files_only: args_.files_only
include_links: args_.include_links
}
paths := path.list_internal(args)!
mut pl := PathList{
@@ -72,12 +72,12 @@ pub fn (mut path Path) list(args_ ListArgs) !PathList {
@[params]
pub struct ListArgsInternal {
mut:
regex []regex.RE // only put files in which follow one of the regexes
recursive bool = true
regex []regex.RE // only put files in which follow one of the regexes
recursive bool = true
ignore_default bool = true // ignore files starting with . and _
dirs_only bool
files_only bool
include_links bool
dirs_only bool
files_only bool
include_links bool
}
fn (mut path Path) list_internal(args ListArgsInternal) ![]Path {

View File

@@ -40,12 +40,6 @@ pub fn (key SSHKey) private_key() !string {
return content
}
module core
import freeflowuniverse.herolib.core.pathlib
import os
@[params]
pub struct SSHConfig {
pub:

View File

@@ -24,6 +24,6 @@ pub:
pub fn new(args LinuxNewArgs) !LinuxFactory {
mut t := LinuxFactory{
username: args.username
}
}
return t
}

View File

@@ -11,34 +11,34 @@ pub fn play(mut plbook PlayBook) ! {
// Process user_create actions
play_user_create(mut plbook, mut lf)!
// Process user_delete actions
play_user_delete(mut plbook, mut lf)!
// Process sshkey_create actions
play_sshkey_create(mut plbook, mut lf)!
// Process sshkey_delete actions
play_sshkey_delete(mut plbook, mut lf)!
}
fn play_user_create(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.user_create')!
for mut action in actions {
mut p := action.params
mut args := UserCreateArgs{
name: p.get('name')!
giteakey: p.get_default('giteakey', '')!
giteaurl: p.get_default('giteaurl', '')!
passwd: p.get_default('passwd', '')!
name: p.get('name')!
giteakey: p.get_default('giteakey', '')!
giteaurl: p.get_default('giteaurl', '')!
passwd: p.get_default('passwd', '')!
description: p.get_default('description', '')!
email: p.get_default('email', '')!
tel: p.get_default('tel', '')!
sshkey: p.get_default('sshkey', '')! // SSH public key
email: p.get_default('email', '')!
tel: p.get_default('tel', '')!
sshkey: p.get_default('sshkey', '')! // SSH public key
}
lf.user_create(args)!
action.done = true
}
@@ -46,14 +46,14 @@ fn play_user_create(mut plbook PlayBook, mut lf LinuxFactory) ! {
fn play_user_delete(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.user_delete')!
for mut action in actions {
mut p := action.params
mut args := UserDeleteArgs{
name: p.get('name')!
}
lf.user_delete(args)!
action.done = true
}
@@ -61,17 +61,17 @@ fn play_user_delete(mut plbook PlayBook, mut lf LinuxFactory) ! {
fn play_sshkey_create(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.sshkey_create')!
for mut action in actions {
mut p := action.params
mut args := SSHKeyCreateArgs{
username: p.get('username')!
username: p.get('username')!
sshkey_name: p.get('sshkey_name')!
sshkey_pub: p.get_default('sshkey_pub', '')!
sshkey_pub: p.get_default('sshkey_pub', '')!
sshkey_priv: p.get_default('sshkey_priv', '')!
}
lf.sshkey_create(args)!
action.done = true
}
@@ -79,16 +79,16 @@ fn play_sshkey_create(mut plbook PlayBook, mut lf LinuxFactory) ! {
fn play_sshkey_delete(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.sshkey_delete')!
for mut action in actions {
mut p := action.params
mut args := SSHKeyDeleteArgs{
username: p.get('username')!
username: p.get('username')!
sshkey_name: p.get('sshkey_name')!
}
lf.sshkey_delete(args)!
action.done = true
}
}
}

View File

@@ -0,0 +1,12 @@
# Auto-start ssh-agent if not running
SSH_AGENT_PID_FILE="$HOME/.ssh/agent.pid"
SSH_AUTH_SOCK_FILE="$HOME/.ssh/agent.sock"
chown "$NEWUSER":"$NEWUSER" "$PROFILE_SCRIPT"
chmod 644 "$PROFILE_SCRIPT"
# --- source it on login ---
#TODO should be done in vcode
if ! grep -q ".profile_sshagent" "$USERHOME/.bashrc"; then
echo "[ -f ~/.profile_sshagent ] && source ~/.profile_sshagent" >> "$USERHOME/.bashrc"
fi

View File

@@ -57,19 +57,4 @@ chown root:ourworld /code
chmod 2775 /code # rwx for user+group, SGID bit so new files inherit group
echo "✅ /code prepared (group=ourworld, rwx for group, SGID bit set)"
# --- create login helper script for ssh-agent ---
PROFILE_SCRIPT="$USERHOME/.profile_sshagent"
cat > "$PROFILE_SCRIPT" <<'EOF'
# Auto-start ssh-agent if not running
SSH_AGENT_PID_FILE="$HOME/.ssh/agent.pid"
SSH_AUTH_SOCK_FILE="$HOME/.ssh/agent.sock"
chown "$NEWUSER":"$NEWUSER" "$PROFILE_SCRIPT"
chmod 644 "$PROFILE_SCRIPT"
# --- source it on login ---
if ! grep -q ".profile_sshagent" "$USERHOME/.bashrc"; then
echo "[ -f ~/.profile_sshagent ] && source ~/.profile_sshagent" >> "$USERHOME/.bashrc"
fi
echo "🎉 Setup complete for user $NEWUSER"

View File

@@ -119,7 +119,9 @@ pub fn (mut lf LinuxFactory) sshkey_create(args SSHKeyCreateArgs) ! {
} else {
// Generate new SSH key (modern ed25519)
key_path := '${ssh_dir}/${args.sshkey_name}'
osal.exec(cmd: 'ssh-keygen -t ed25519 -f ${key_path} -N "" -C "${args.username}@$(hostname)"')!
osal.exec(
cmd: 'ssh-keygen -t ed25519 -f ${key_path} -N "" -C "${args.username}@$(hostname)"'
)!
console.print_green(' New SSH key generated for ${args.username}')
}
@@ -175,12 +177,12 @@ fn (mut lf LinuxFactory) save_user_config(args UserCreateArgs) ! {
}
new_config := UserConfig{
name: args.name
giteakey: args.giteakey
giteaurl: args.giteaurl
email: args.email
name: args.name
giteakey: args.giteakey
giteaurl: args.giteaurl
email: args.email
description: args.description
tel: args.tel
tel: args.tel
}
if found_idx >= 0 {
@@ -201,7 +203,7 @@ fn (mut lf LinuxFactory) remove_user_config(username string) ! {
config_path := '${config_dir}/myconfig.json'
if !os.exists(config_path) {
return // Nothing to remove
return
}
content := osal.file_read(config_path)!
@@ -243,7 +245,9 @@ fn (mut lf LinuxFactory) create_user_system(args UserCreateArgs) ! {
// Ensure ourworld group exists
group_check := osal.exec(cmd: 'getent group ourworld', raise_error: false) or {
osal.Job{ exit_code: 1 }
osal.Job{
exit_code: 1
}
}
if group_check.exit_code != 0 {
console.print_item(' Creating group ourworld')
@@ -284,58 +288,9 @@ fn (mut lf LinuxFactory) create_ssh_agent_profile(username string) ! {
user_home := '/home/${username}'
profile_script := '${user_home}/.profile_sshagent'
script_content := '# Auto-start ssh-agent if not running
SSH_AGENT_PID_FILE="$HOME/.ssh/agent.pid"
SSH_AUTH_SOCK_FILE="$HOME/.ssh/agent.sock"
// script_content := ''
# Function to start ssh-agent
start_ssh_agent() {
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
# Start ssh-agent and save connection info
ssh-agent -s > "$SSH_AGENT_PID_FILE"
source "$SSH_AGENT_PID_FILE"
# Save socket path for future sessions
echo "$SSH_AUTH_SOCK" > "$SSH_AUTH_SOCK_FILE"
# Load all private keys found in ~/.ssh
if [ -d "$HOME/.ssh" ]; then
for KEY in "$HOME"/.ssh/*; do
if [ -f "$KEY" ] && [ ! "${KEY##*.}" = "pub" ] && grep -q "PRIVATE KEY" "$KEY" 2>/dev/null; then
'ssh-' + 'add "$KEY" >/dev/null 2>&1 && echo "🔑 Loaded key: $(basename $KEY)"'
fi
done
fi
}
# Check if ssh-agent is running
if [ -f "$SSH_AGENT_PID_FILE" ]; then
source "$SSH_AGENT_PID_FILE" >/dev/null 2>&1
# Test if agent is responsive
if ! ('ssh-' + 'add -l >/dev/null 2>&1'); then
start_ssh_agent
else
# Agent is running, restore socket path
if [ -f "$SSH_AUTH_SOCK_FILE" ]; then
export SSH_AUTH_SOCK=$(cat "$SSH_AUTH_SOCK_FILE")
fi
fi
else
start_ssh_agent
fi
# For interactive shells
if [[ $- == *i* ]]; then
echo "🔑 SSH Agent ready at $SSH_AUTH_SOCK"
# Show loaded keys
KEY_COUNT=$('ssh-' + 'add -l 2>/dev/null | wc -l')
if [ "$KEY_COUNT" -gt 0 ]; then
echo "🔑 $KEY_COUNT SSH key(s) loaded"
fi
fi
'
panic('implement')
osal.file_write(profile_script, script_content)!
osal.exec(cmd: 'chown ${username}:${username} ${profile_script}')!
@@ -351,4 +306,4 @@ fi
}
console.print_green(' SSH agent profile created for ${username}')
}
}

View File

@@ -3,27 +3,27 @@ module sshagent
// Check if SSH agent is properly configured and all is good
fn agent_check(mut agent SSHAgent) ! {
console.print_header('SSH Agent Check')
// Ensure single agent is running
agent.ensure_single_agent()!
// Get diagnostics
diag := agent.diagnostics()
for key, value in diag {
console.print_item('${key}: ${value}')
}
// Verify agent is responsive
if !agent.is_agent_responsive() {
return error('SSH agent is not responsive')
}
// Load all existing keys from ~/.ssh that aren't loaded yet
agent.init()!
console.print_green(' SSH Agent is properly configured and running')
// Show loaded keys
loaded_keys := agent.keys_loaded()!
console.print_item('Loaded keys: ${loaded_keys.len}')
@@ -35,17 +35,17 @@ fn agent_check(mut agent SSHAgent) ! {
// Create a new SSH key
fn sshkey_create(mut agent SSHAgent, name string, passphrase string) ! {
console.print_header('Creating SSH key: ${name}')
// Check if key already exists
if agent.exists(name: name) {
console.print_debug('SSH key "${name}" already exists')
return
}
// Generate new key
mut key := agent.generate(name, passphrase)!
console.print_green(' SSH key "${name}" created successfully')
// Automatically load the key
key.load()!
console.print_green(' SSH key "${name}" loaded into agent')
@@ -54,13 +54,13 @@ fn sshkey_create(mut agent SSHAgent, name string, passphrase string) ! {
// Delete an SSH key
fn sshkey_delete(mut agent SSHAgent, name string) ! {
console.print_header('Deleting SSH key: ${name}')
// Check if key exists
mut key := agent.get(name: name) or {
console.print_debug('SSH key "${name}" does not exist')
return
}
// Get key paths before deletion
key_path := key.keypath() or {
console.print_debug('Private key path not available for "${name}"')
@@ -70,12 +70,12 @@ fn sshkey_delete(mut agent SSHAgent, name string) ! {
console.print_debug('Public key path not available for "${name}"')
return
}
// Remove from agent if loaded (temporarily disabled due to reset_ssh panic)
// if key.loaded {
// key.forget()!
// }
// Delete key files
if key_path.exists() {
key_path.delete()!
@@ -85,26 +85,24 @@ fn sshkey_delete(mut agent SSHAgent, name string) ! {
key_pub_path.delete()!
console.print_debug('Deleted public key: ${key_pub_path.path}')
}
// Reinitialize agent to update key list
agent.init()!
console.print_green(' SSH key "${name}" deleted successfully')
}
// Load SSH key into agent
fn sshkey_load(mut agent SSHAgent, name string) ! {
console.print_header('Loading SSH key: ${name}')
mut key := agent.get(name: name) or {
return error('SSH key "${name}" not found')
}
mut key := agent.get(name: name) or { return error('SSH key "${name}" not found') }
if key.loaded {
console.print_debug('SSH key "${name}" is already loaded')
return
}
key.load()!
console.print_green(' SSH key "${name}" loaded into agent')
}
@@ -112,28 +110,22 @@ fn sshkey_load(mut agent SSHAgent, name string) ! {
// Check if SSH key is valid
fn sshkey_check(mut agent SSHAgent, name string) ! {
console.print_header('Checking SSH key: ${name}')
mut key := agent.get(name: name) or {
return error('SSH key "${name}" not found')
}
mut key := agent.get(name: name) or { return error('SSH key "${name}" not found') }
// Check if key files exist
key_path := key.keypath() or {
return error('Private key file not found for "${name}"')
}
key_pub_path := key.keypath_pub() or {
return error('Public key file not found for "${name}"')
}
key_path := key.keypath() or { return error('Private key file not found for "${name}"') }
key_pub_path := key.keypath_pub() or { return error('Public key file not found for "${name}"') }
if !key_path.exists() {
return error('Private key file does not exist: ${key_path.path}')
}
if !key_pub_path.exists() {
return error('Public key file does not exist: ${key_pub_path.path}')
}
// Verify key can be loaded (if not already loaded)
if !key.loaded {
// Test load without actually loading (since forget is disabled)
@@ -142,70 +134,68 @@ fn sshkey_check(mut agent SSHAgent, name string) ! {
return error('Invalid private key format in "${name}"')
}
}
console.print_item('Key type: ${key.cat}')
console.print_item('Loaded: ${key.loaded}')
console.print_item('Email: ${key.email}')
console.print_item('Private key: ${key_path.path}')
console.print_item('Public key: ${key_pub_path.path}')
console.print_green(' SSH key "${name}" is valid')
}
// Copy private key to remote node
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')
}
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
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
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}')
}
}

View File

@@ -10,27 +10,27 @@ pub fn (mut agent SSHAgent) push_key_to_node(mut node builder.Node, key_name str
if node_info['category'] != 'ssh' {
return error('Can only push keys to SSH nodes, got: ${node_info['category']}')
}
// Find the key
mut key := agent.get(name: key_name) or {
return error('SSH key "${key_name}" not found in agent')
}
// Get public key content
pubkey_content := key.keypub()!
// Check if authorized_keys file exists on remote
home_dir := node.environ_get()!['HOME'] or {
return error('Could not determine HOME directory on remote node')
}
ssh_dir := '${home_dir}/.ssh'
authorized_keys_path := '${ssh_dir}/authorized_keys'
// Ensure .ssh directory exists with correct permissions
node.exec_silent('mkdir -p ${ssh_dir}')!
node.exec_silent('chmod 700 ${ssh_dir}')!
// Check if key already exists
if node.file_exists(authorized_keys_path) {
existing_keys := node.file_read(authorized_keys_path)!
@@ -39,11 +39,11 @@ pub fn (mut agent SSHAgent) push_key_to_node(mut node builder.Node, key_name str
return
}
}
// Add key to authorized_keys
node.exec_silent('echo "${pubkey_content}" >> ${authorized_keys_path}')!
node.exec_silent('chmod 600 ${authorized_keys_path}')!
console.print_debug('SSH key "${key_name}" successfully pushed to node')
}
@@ -54,31 +54,31 @@ pub fn (mut agent SSHAgent) remove_key_from_node(mut node builder.Node, key_name
if node_info['category'] != 'ssh' {
return error('Can only remove keys from SSH nodes, got: ${node_info['category']}')
}
// Find the key
mut key := agent.get(name: key_name) or {
return error('SSH key "${key_name}" not found in agent')
}
// Get public key content
pubkey_content := key.keypub()!
// Get authorized_keys path
home_dir := node.environ_get()!['HOME'] or {
return error('Could not determine HOME directory on remote node')
}
authorized_keys_path := '${home_dir}/.ssh/authorized_keys'
if !node.file_exists(authorized_keys_path) {
console.print_debug('authorized_keys file does not exist on remote node')
return
}
// Remove the key line from authorized_keys
escaped_key := pubkey_content.replace('/', '\\/')
node.exec_silent('sed -i "\\|${escaped_key}|d" ${authorized_keys_path}')!
console.print_debug('SSH key "${key_name}" removed from remote node')
}
@@ -90,11 +90,9 @@ pub fn (mut agent SSHAgent) verify_key_access(mut node builder.Node, key_name st
if node_info['category'] != 'ssh' {
return error('Can only verify access to SSH nodes')
}
// Test basic connectivity
result := node.exec_silent('echo "SSH key verification successful"') or {
return false
}
result := node.exec_silent('echo "SSH key verification successful"') or { return false }
return result.contains('SSH key verification successful')
}
}

View File

@@ -25,7 +25,7 @@ pub fn play(mut plbook PlayBook) ! {
mut p := action.params
name := p.get('name')!
passphrase := p.get_default('passphrase', '')!
sshkey_create(mut agent, name, passphrase)!
action.done = true
}
@@ -35,7 +35,7 @@ pub fn play(mut plbook PlayBook) ! {
for mut action in delete_actions {
mut p := action.params
name := p.get('name')!
sshkey_delete(mut agent, name)!
action.done = true
}
@@ -45,7 +45,7 @@ pub fn play(mut plbook PlayBook) ! {
for mut action in load_actions {
mut p := action.params
name := p.get('name')!
sshkey_load(mut agent, name)!
action.done = true
}
@@ -55,7 +55,7 @@ pub fn play(mut plbook PlayBook) ! {
for mut action in check_key_actions {
mut p := action.params
name := p.get('name')!
sshkey_check(mut agent, name)!
action.done = true
}
@@ -66,7 +66,7 @@ pub fn play(mut plbook PlayBook) ! {
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
}
@@ -77,7 +77,7 @@ pub fn play(mut plbook PlayBook) ! {
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
}

View File

@@ -16,19 +16,19 @@ pub mut:
pub fn (mut agent SSHAgent) ensure_single_agent() ! {
user := os.getenv('USER')
socket_path := get_agent_socket_path(user)
// Check if we have a valid agent already
if agent.is_agent_responsive() {
console.print_debug('SSH agent already running and responsive')
return
}
// Kill any orphaned agents
agent.cleanup_orphaned_agents()!
// Start new agent with consistent socket
agent.start_agent_with_socket(socket_path)!
// Set environment variables
os.setenv('SSH_AUTH_SOCK', socket_path, true)
agent.active = true
@@ -44,7 +44,7 @@ pub fn (mut agent SSHAgent) is_agent_responsive() bool {
if os.getenv('SSH_AUTH_SOCK') == '' {
return false
}
res := os.execute('ssh-add -l 2>/dev/null')
return res.exit_code == 0 || res.exit_code == 1 // 1 means no keys, but agent is running
}
@@ -52,12 +52,12 @@ pub fn (mut agent SSHAgent) is_agent_responsive() bool {
// cleanup orphaned ssh-agent processes
pub fn (mut agent SSHAgent) cleanup_orphaned_agents() ! {
user := os.getenv('USER')
// Find ssh-agent processes for current user
res := os.execute('pgrep -u ${user} ssh-agent')
if res.exit_code == 0 && res.output.len > 0 {
pids := res.output.trim_space().split('\n')
for pid in pids {
if pid.trim_space() != '' {
// Check if this agent has a valid socket
@@ -77,7 +77,7 @@ fn (mut agent SSHAgent) is_agent_pid_valid(pid int) bool {
if res.exit_code != 0 {
return false
}
for socket_path in res.output.split('\n') {
if socket_path.trim_space() != '' {
// Test if this socket responds
@@ -85,7 +85,7 @@ fn (mut agent SSHAgent) is_agent_pid_valid(pid int) bool {
os.setenv('SSH_AUTH_SOCK', socket_path, true)
test_res := os.execute('ssh-add -l 2>/dev/null')
os.setenv('SSH_AUTH_SOCK', old_sock, true)
if test_res.exit_code == 0 || test_res.exit_code == 1 {
return true
}
@@ -100,45 +100,45 @@ pub fn (mut agent SSHAgent) start_agent_with_socket(socket_path string) ! {
if os.exists(socket_path) {
os.rm(socket_path)!
}
// Start ssh-agent with specific socket
cmd := 'ssh-agent -a ${socket_path}'
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Failed to start ssh-agent: ${res.output}')
}
// Verify socket was created
if !os.exists(socket_path) {
return error('SSH agent socket was not created at ${socket_path}')
}
// Set environment variable
os.setenv('SSH_AUTH_SOCK', socket_path, true)
// Verify agent is responsive
if !agent.is_agent_responsive() {
return error('SSH agent started but is not responsive')
}
console.print_debug('SSH agent started with socket: ${socket_path}')
}
// get agent status and diagnostics
pub fn (mut agent SSHAgent) diagnostics() map[string]string {
mut diag := map[string]string{}
diag['socket_path'] = os.getenv('SSH_AUTH_SOCK')
diag['socket_exists'] = os.exists(diag['socket_path']).str()
diag['agent_responsive'] = agent.is_agent_responsive().str()
diag['loaded_keys_count'] = agent.keys.filter(it.loaded).len.str()
diag['total_keys_count'] = agent.keys.len.str()
// Count running ssh-agent processes
user := os.getenv('USER')
res := os.execute('pgrep -u ${user} ssh-agent | wc -l')
diag['agent_processes'] = if res.exit_code == 0 { res.output.trim_space() } else { '0' }
return diag
}

View File

@@ -11,7 +11,7 @@ pub fn play(mut plbook PlayBook) ! {
// Create tmux instance
mut tmux_instance := new()!
// Start tmux if not running
if !tmux_instance.is_running()! {
tmux_instance.start()!
@@ -44,7 +44,7 @@ fn parse_window_name(name string) !ParsedWindowName {
}
return ParsedWindowName{
session: texttools.name_fix(parts[0])
window: texttools.name_fix(parts[1])
window: texttools.name_fix(parts[1])
}
}
@@ -55,8 +55,8 @@ fn parse_pane_name(name string) !ParsedPaneName {
}
return ParsedPaneName{
session: texttools.name_fix(parts[0])
window: texttools.name_fix(parts[1])
pane: texttools.name_fix(parts[2])
window: texttools.name_fix(parts[1])
pane: texttools.name_fix(parts[2])
}
}
@@ -66,12 +66,12 @@ fn play_session_create(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut p := action.params
session_name := p.get('name')!
reset := p.get_default_false('reset')
tmux_instance.session_create(
name: session_name
name: session_name
reset: reset
)!
action.done = true
}
}
@@ -81,9 +81,9 @@ fn play_session_delete(mut plbook PlayBook, mut tmux_instance Tmux) ! {
for mut action in actions {
mut p := action.params
session_name := p.get('name')!
tmux_instance.session_delete(session_name)!
action.done = true
}
}
@@ -96,7 +96,7 @@ fn play_window_create(mut plbook PlayBook, mut tmux_instance Tmux) ! {
parsed := parse_window_name(name)!
cmd := p.get_default('cmd', '')!
reset := p.get_default_false('reset')
// Parse environment variables if provided
mut env := map[string]string{}
if env_str := p.get_default('env', '') {
@@ -109,21 +109,21 @@ fn play_window_create(mut plbook PlayBook, mut tmux_instance Tmux) ! {
}
}
}
// Get or create session
mut session := if tmux_instance.session_exist(parsed.session) {
tmux_instance.session_get(parsed.session)!
} else {
tmux_instance.session_create(name: parsed.session)!
}
session.window_new(
name: parsed.window
cmd: cmd
env: env
name: parsed.window
cmd: cmd
env: env
reset: reset
)!
action.done = true
}
}
@@ -134,12 +134,12 @@ fn play_window_delete(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut p := action.params
name := p.get('name')!
parsed := parse_window_name(name)!
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
session.window_delete(name: parsed.window)!
}
action.done = true
}
}
@@ -151,19 +151,19 @@ fn play_pane_execute(mut plbook PlayBook, mut tmux_instance Tmux) ! {
name := p.get('name')!
cmd := p.get('cmd')!
parsed := parse_pane_name(name)!
// Find the session and window
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
if session.window_exist(name: parsed.window) {
mut window := session.window_get(name: parsed.window)!
// Send command to the window (goes to active pane by default)
tmux_cmd := 'tmux send-keys -t ${session.name}:@${window.id} "${cmd}" Enter'
osal.exec(cmd: tmux_cmd, stdout: false, name: 'tmux_pane_execute')!
}
}
action.done = true
}
}
@@ -174,21 +174,26 @@ fn play_pane_kill(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut p := action.params
name := p.get('name')!
parsed := parse_pane_name(name)!
// Find the session and window, then kill the active pane
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
if session.window_exist(name: parsed.window) {
mut window := session.window_get(name: parsed.window)!
// Kill the active pane in the window
if pane := window.pane_active() {
tmux_cmd := 'tmux kill-pane -t ${session.name}:@${window.id}.%${pane.id}'
osal.exec(cmd: tmux_cmd, stdout: false, name: 'tmux_pane_kill', ignore_error: true)!
osal.exec(
cmd: tmux_cmd
stdout: false
name: 'tmux_pane_kill'
ignore_error: true
)!
}
}
}
action.done = true
}
}
}

View File

@@ -14,7 +14,6 @@ pub mut:
sessionid string // unique link to job
}
// get session (session has windows) .
// returns none if not found
pub fn (mut t Tmux) session_get(name_ string) !&Session {
@@ -56,8 +55,6 @@ pub mut:
reset bool
}
// create session, if reset will re-create
pub fn (mut t Tmux) session_create(args SessionCreateArgs) !&Session {
name := texttools.name_fix(args.name)
@@ -83,7 +80,6 @@ pub fn (mut t Tmux) session_create(args SessionCreateArgs) !&Session {
return s
}
@[params]
pub struct TmuxNewArgs {
sessionid string
@@ -116,17 +112,16 @@ pub fn (mut t Tmux) window_new(args WindowNewArgs) !&Window {
} else {
t.session_create(name: args.session_name)!
}
// Create window in session
return session.window_new(
name: args.name
cmd: args.cmd
env: args.env
name: args.name
cmd: args.cmd
env: args.env
reset: args.reset
)!
}
pub fn (mut t Tmux) stop() ! {
$if debug {
console.print_debug('Stopping tmux...')
@@ -156,7 +151,6 @@ pub fn (mut t Tmux) start() ! {
t.scan()!
}
// print list of tmux sessions
pub fn (mut t Tmux) list_print() {
// os.log('TMUX - Start listing ....')

View File

@@ -10,141 +10,134 @@ import freeflowuniverse.herolib.ui.console
@[heap]
struct Pane {
pub mut:
window &Window @[str: skip]
id int // pane id (e.g., %1, %2)
pid int // process id
active bool // is this the active pane
cmd string // command running in pane
env map[string]string
created_at time.Time
last_output_offset int // for tracking new logs
window &Window @[str: skip]
id int // pane id (e.g., %1, %2)
pid int // process id
active bool // is this the active pane
cmd string // command running in pane
env map[string]string
created_at time.Time
last_output_offset int // for tracking new logs
}
pub fn (mut p Pane) stats() !ProcessStats {
if p.pid == 0 {
return ProcessStats{}
}
if p.pid == 0 {
return ProcessStats{}
}
// Use ps command to get CPU and memory stats
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers'
result := osal.execute_silent(cmd) or {
return error('Cannot get stats for PID ${p.pid}: ${err}')
}
// Use ps command to get CPU and memory stats
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers'
result := osal.execute_silent(cmd) or {
return error('Cannot get stats for PID ${p.pid}: ${err}')
}
if result.trim_space() == '' {
return error('Process ${p.pid} not found')
}
if result.trim_space() == '' {
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}')
}
parts := result.trim_space().split_any(' \t').filter(it != '')
if parts.len < 3 {
return error('Invalid ps output: ${result}')
}
return ProcessStats{
cpu_percent: parts[0].f64()
memory_percent: parts[1].f64()
memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes
}
return ProcessStats{
cpu_percent: parts[0].f64()
memory_percent: parts[1].f64()
memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes
}
}
pub struct TMuxLogEntry {
pub mut:
content string
timestamp time.Time
offset int
content string
timestamp time.Time
offset int
}
pub fn (mut p Pane) logs_get_new(reset bool) ![]TMuxLogEntry {
if reset {
p.last_output_offset = 0
}
// Capture pane content with line numbers
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p'
result := osal.execute_silent(cmd) or { return error('Cannot capture pane output: ${err}') }
if reset{
p.last_output_offset = 0
}
// Capture pane content with line numbers
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p'
result := osal.execute_silent(cmd) or {
return error('Cannot capture pane output: ${err}')
}
lines := result.split_into_lines()
mut entries := []TMuxLogEntry{}
lines := result.split_into_lines()
mut entries := []TMuxLogEntry{}
mut i:= 0
for line in lines {
if line.trim_space() != '' {
entries << TMuxLogEntry{
content: line
timestamp: time.now()
offset: p.last_output_offset + i + 1
}
}
}
// Update offset to avoid duplicates next time
if entries.len > 0 {
p.last_output_offset = entries.last().offset
}
return entries
mut i := 0
for line in lines {
if line.trim_space() != '' {
entries << TMuxLogEntry{
content: line
timestamp: time.now()
offset: p.last_output_offset + i + 1
}
}
}
// Update offset to avoid duplicates next time
if entries.len > 0 {
p.last_output_offset = entries.last().offset
}
return entries
}
pub fn (mut p Pane) exit_status() !ProcessStatus {
// Get the last few lines to see if there's an exit status
logs := p.logs_all()!
lines := logs.split_into_lines()
// Get the last few lines to see if there's an exit status
logs := p.logs_all()!
lines := logs.split_into_lines()
// Look for shell prompt indicating command finished
for line in lines.reverse() {
line_clean := line.trim_space()
if line_clean.contains('$') || line_clean.contains('#') || line_clean.contains('>') {
// Found shell prompt, command likely finished
// Could also check for specific exit codes in history
return .finished_ok
}
}
return .finished_error
// Look for shell prompt indicating command finished
for line in lines.reverse() {
line_clean := line.trim_space()
if line_clean.contains('$') || line_clean.contains('#') || line_clean.contains('>') {
// Found shell prompt, command likely finished
// Could also check for specific exit codes in history
return .finished_ok
}
}
return .finished_error
}
pub fn (mut p Pane) logs_all() !string {
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S -2000 -p'
return osal.execute_silent(cmd) or {
error('Cannot capture pane output: ${err}')
}
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S -2000 -p'
return osal.execute_silent(cmd) or { error('Cannot capture pane output: ${err}') }
}
// Fix the output_wait method to use correct method name
pub fn (mut p Pane) output_wait(c_ string, timeoutsec int) ! {
mut t := ourtime.now()
start := t.unix()
c := c_.replace('\n', '')
for i in 0 .. 2000 {
entries := p.logs_get_new(reset: false)!
for entry in entries {
if entry.content.replace('\n', '').contains(c) {
return
}
}
mut t2 := ourtime.now()
if t2.unix() > start + timeoutsec {
return error('timeout on output wait for tmux.\n${p} .\nwaiting for:\n${c}')
}
time.sleep(100 * time.millisecond)
}
mut t := ourtime.now()
start := t.unix()
c := c_.replace('\n', '')
for i in 0 .. 2000 {
entries := p.logs_get_new(reset: false)!
for entry in entries {
if entry.content.replace('\n', '').contains(c) {
return
}
}
mut t2 := ourtime.now()
if t2.unix() > start + timeoutsec {
return error('timeout on output wait for tmux.\n${p} .\nwaiting for:\n${c}')
}
time.sleep(100 * time.millisecond)
}
}
// Get process information for this pane and all its children
pub fn (mut p Pane) processinfo() !osal.ProcessMap {
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_with_children(p.pid)!
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_with_children(p.pid)!
}
// Get process information for just this pane's main process
pub fn (mut p Pane) processinfo_main() !osal.ProcessInfo {
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_get(p.pid)!
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_get(p.pid)!
}

View File

@@ -1,21 +1,15 @@
module tmux
pub struct ProcessStats {
pub mut:
cpu_percent f64
memory_bytes u64
memory_percent f64
cpu_percent f64
memory_bytes u64
memory_percent f64
}
enum ProcessStatus {
running
finished_ok
finished_error
not_found
}
running
finished_ok
finished_error
not_found
}

View File

@@ -6,66 +6,66 @@ import freeflowuniverse.herolib.ui.console
import time
fn (mut t Tmux) scan_add(line string) !&Pane {
// Parse the line to get session, window, and pane info
line_arr := line.split('|')
session_name := line_arr[0]
window_name := line_arr[1]
window_id := line_arr[2]
pane_active := line_arr[3]
pane_id := line_arr[4]
pane_pid := line_arr[5]
pane_start_command := line_arr[6] or { '' }
// Parse the line to get session, window, and pane info
line_arr := line.split('|')
session_name := line_arr[0]
window_name := line_arr[1]
window_id := line_arr[2]
pane_active := line_arr[3]
pane_id := line_arr[4]
pane_pid := line_arr[5]
pane_start_command := line_arr[6] or { '' }
wid := (window_id.replace('@', '')).int()
pid := (pane_id.replace('%', '')).int()
wid := (window_id.replace('@', '')).int()
pid := (pane_id.replace('%', '')).int()
mut s := t.session_get(session_name)!
mut s := t.session_get(session_name)!
// Get or create window
mut w := if s.window_exist(name: window_name, id: wid) {
s.window_get(name: window_name, id: wid)!
} else {
mut new_w := Window{
session: s
name: texttools.name_fix(window_name)
id: wid
panes: []&Pane{}
}
s.windows << &new_w
&new_w
}
// Get or create window
mut w := if s.window_exist(name: window_name, id: wid) {
s.window_get(name: window_name, id: wid)!
} else {
mut new_w := Window{
session: s
name: texttools.name_fix(window_name)
id: wid
panes: []&Pane{}
}
s.windows << &new_w
&new_w
}
// Create or update pane
mut p := Pane{
window: w
id: pid
pid: pane_pid.int()
active: pane_active == '1'
cmd: pane_start_command
created_at: time.now()
}
// Create or update pane
mut p := Pane{
window: w
id: pid
pid: pane_pid.int()
active: pane_active == '1'
cmd: pane_start_command
created_at: time.now()
}
// Check if pane already exists
mut found := false
for mut existing_pane in w.panes {
if existing_pane.id == pid {
existing_pane.pid = p.pid
existing_pane.active = p.active
existing_pane.cmd = p.cmd
found = true
break
}
}
// Check if pane already exists
mut found := false
for mut existing_pane in w.panes {
if existing_pane.id == pid {
existing_pane.pid = p.pid
existing_pane.active = p.active
existing_pane.cmd = p.cmd
found = true
break
}
}
if !found {
w.panes << &p
}
if !found {
w.panes << &p
}
return &p
return &p
}
// scan the system to detect sessions .
//TODO needs to be done differently, here only find the sessions, then per session call the scan() which will find the windows, call scan() there as well ...
// TODO needs to be done differently, here only find the sessions, then per session call the scan() which will find the windows, call scan() there as well ...
pub fn (mut t Tmux) scan() ! {
// os.log('TMUX - Scanning ....')

View File

@@ -21,87 +21,86 @@ pub mut:
env map[string]string
reset bool
}
@[params]
pub struct WindowGetArgs {
pub mut:
name string
id int
name string
id int
}
pub fn (mut s Session) create() ! {
// Check if session already exists
cmd_check := "tmux has-session -t ${s.name}"
check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or {
// Session doesn't exist, this is expected
osal.Job{}
}
if check_result.exit_code == 0 {
return error('duplicate session: ${s.name}')
}
// Create new session
cmd := "tmux new-session -d -s ${s.name}"
osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or {
return error("Can't create session ${s.name}: ${err}")
}
// Check if session already exists
cmd_check := 'tmux has-session -t ${s.name}'
check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or {
// Session doesn't exist, this is expected
osal.Job{}
}
if check_result.exit_code == 0 {
return error('duplicate session: ${s.name}')
}
// Create new session
cmd := 'tmux new-session -d -s ${s.name}'
osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or {
return error("Can't create session ${s.name}: ${err}")
}
}
//load info from reality
// load info from reality
pub fn (mut s Session) scan() ! {
// Get current windows from tmux for this session
cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'"
result := osal.execute_silent(cmd) or {
if err.msg().contains('session not found') {
return // Session doesn't exist anymore
}
return error('Cannot list windows for session ${s.name}: ${err}')
}
mut current_windows := map[string]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 2 {
window_name := texttools.name_fix(parts[0])
window_id := parts[1].replace('@', '').int()
window_active := parts[2] == '1'
current_windows[window_name] = true
// Update existing window or create new one
mut found := false
for mut w in s.windows {
if w.name == window_name {
w.id = window_id
w.active = window_active
w.scan()! // Scan panes for this window
found = true
break
}
}
if !found {
mut new_window := Window{
session: &s
name: window_name
id: window_id
active: window_active
panes: []&Pane{}
env: map[string]string{}
}
new_window.scan()! // Scan panes for new window
s.windows << &new_window
}
}
}
}
// Remove windows that no longer exist in tmux
s.windows = s.windows.filter(current_windows[it.name] == true)
}
// Get current windows from tmux for this session
cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'"
result := osal.execute_silent(cmd) or {
if err.msg().contains('session not found') {
return
}
return error('Cannot list windows for session ${s.name}: ${err}')
}
mut current_windows := map[string]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 2 {
window_name := texttools.name_fix(parts[0])
window_id := parts[1].replace('@', '').int()
window_active := parts[2] == '1'
current_windows[window_name] = true
// Update existing window or create new one
mut found := false
for mut w in s.windows {
if w.name == window_name {
w.id = window_id
w.active = window_active
w.scan()! // Scan panes for this window
found = true
break
}
}
if !found {
mut new_window := Window{
session: &s
name: window_name
id: window_id
active: window_active
panes: []&Pane{}
env: map[string]string{}
}
new_window.scan()! // Scan panes for new window
s.windows << &new_window
}
}
}
}
// Remove windows that no longer exist in tmux
s.windows = s.windows.filter(current_windows[it.name] == true)
}
// window_name is the name of the window in session main (will always be called session main)
// cmd to execute e.g. bash file
@@ -139,14 +138,10 @@ pub fn (mut s Session) window_new(args WindowArgs) !Window {
// Create the window with the specified command
w.create(args.cmd)!
s.scan()!
return w
}
// get all windows as found in a session
pub fn (mut s Session) windows_get() []&Window {
mut res := []&Window{}
@@ -179,14 +174,14 @@ pub fn (mut s Session) str() string {
}
pub fn (mut s Session) stats() !ProcessStats {
mut total := ProcessStats{}
for mut window in s.windows {
stats := window.stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
mut total := ProcessStats{}
for mut window in s.windows {
stats := window.stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
}
// pub fn (mut s Session) activate()! {
@@ -208,8 +203,6 @@ pub fn (mut s Session) stats() !ProcessStats {
// }
// }
fn (mut s Session) window_exist(args_ WindowGetArgs) bool {
mut args := args_
s.window_get(args) or { return false }
@@ -249,7 +242,6 @@ pub fn (mut s Session) window_delete(args_ WindowGetArgs) ! {
s.windows.delete(i) // i is now the one in the list which needs to be removed
}
pub fn (mut s Session) restart() ! {
s.stop()!
s.create()!
@@ -259,4 +251,4 @@ pub fn (mut s Session) stop() ! {
osal.execute_silent('tmux kill-session -t ${s.name}') or {
return error("Can't delete session ${s.name} - This may happen when session is not found: ${err}")
}
}
}

View File

@@ -45,37 +45,37 @@ fn test_stop() ! {
}
fn test_windows_get() ! {
mut tmux := new()!
tmux.start()!
// After start, scan to get the initial session
tmux.scan()!
windows := tmux.windows_get()
assert windows.len >= 0 // At least the default session should exist
tmux.stop()!
mut tmux := new()!
tmux.start()!
// After start, scan to get the initial session
tmux.scan()!
windows := tmux.windows_get()
assert windows.len >= 0 // At least the default session should exist
tmux.stop()!
}
fn test_scan() ! {
console.print_debug('-----Testing scan------')
mut tmux := new()!
tmux.start()!
console.print_debug('-----Testing scan------')
mut tmux := new()!
tmux.start()!
// Test initial scan
tmux.scan()!
sessions_before := tmux.sessions.len
// Create a test session
mut session := tmux.session_create(name: 'test_scan')!
// Scan again
tmux.scan()!
sessions_after := tmux.sessions.len
assert sessions_after >= sessions_before
tmux.stop()!
// Test initial scan
tmux.scan()!
sessions_before := tmux.sessions.len
// Create a test session
mut session := tmux.session_create(name: 'test_scan')!
// Scan again
tmux.scan()!
sessions_after := tmux.sessions.len
assert sessions_after >= sessions_before
tmux.stop()!
}
// //TODO: fix test

View File

@@ -13,7 +13,7 @@ pub mut:
session &Session @[skip]
name string
id int
panes []&Pane // windows contain multiple panes
panes []&Pane // windows contain multiple panes
active bool
env map[string]string
}
@@ -22,105 +22,104 @@ pub mut:
pub struct PaneNewArgs {
pub mut:
name string
reset bool //means we reset the pane if it already exists
reset bool // means we reset the pane if it already exists
cmd string
env map[string]string
env map[string]string
}
pub fn (mut w Window) scan() ! {
// Get current panes for this window
cmd := "tmux list-panes -t ${w.session.name}:@${w.id} -F '#{pane_id}|#{pane_pid}|#{pane_active}|#{pane_start_command}'"
result := osal.execute_silent(cmd) or {
// Window might not exist anymore
return
}
mut current_panes := map[int]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 3 {
pane_id := parts[0].replace('%', '').int()
pane_pid := parts[1].int()
pane_active := parts[2] == '1'
pane_cmd := if parts.len > 3 { parts[3] } else { '' }
current_panes[pane_id] = true
// Update existing pane or create new one
mut found := false
for mut p in w.panes {
if p.id == pane_id {
p.pid = pane_pid
p.active = pane_active
p.cmd = pane_cmd
found = true
break
}
}
if !found {
mut new_pane := Pane{
window: &w
id: pane_id
pid: pane_pid
active: pane_active
cmd: pane_cmd
env: map[string]string{}
created_at: time.now()
last_output_offset: 0
}
w.panes << &new_pane
}
}
}
}
// Remove panes that no longer exist
w.panes = w.panes.filter(current_panes[it.id] == true)
}
// Get current panes for this window
cmd := "tmux list-panes -t ${w.session.name}:@${w.id} -F '#{pane_id}|#{pane_pid}|#{pane_active}|#{pane_start_command}'"
result := osal.execute_silent(cmd) or {
// Window might not exist anymore
return
}
mut current_panes := map[int]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 3 {
pane_id := parts[0].replace('%', '').int()
pane_pid := parts[1].int()
pane_active := parts[2] == '1'
pane_cmd := if parts.len > 3 { parts[3] } else { '' }
current_panes[pane_id] = true
// Update existing pane or create new one
mut found := false
for mut p in w.panes {
if p.id == pane_id {
p.pid = pane_pid
p.active = pane_active
p.cmd = pane_cmd
found = true
break
}
}
if !found {
mut new_pane := Pane{
window: &w
id: pane_id
pid: pane_pid
active: pane_active
cmd: pane_cmd
env: map[string]string{}
created_at: time.now()
last_output_offset: 0
}
w.panes << &new_pane
}
}
}
}
// Remove panes that no longer exist
w.panes = w.panes.filter(current_panes[it.id] == true)
}
pub fn (mut w Window) stop() ! {
w.kill()!
w.kill()!
}
//helper function
//TODO env variables are not inserted in pane
// helper function
// TODO env variables are not inserted in pane
pub fn (mut w Window) create(cmd_ string) ! {
mut final_cmd := cmd_
if cmd_.contains('\n') {
os.mkdir_all('/tmp/tmux/${w.session.name}')!
// Fix: osal.exec_string doesn't exist, use file writing instead
script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh'
script_content := '#!/bin/bash\n' + cmd_
os.write_file(script_path, script_content)!
os.chmod(script_path, 0o755)!
final_cmd = script_path
}
mut final_cmd := cmd_
if cmd_.contains('\n') {
os.mkdir_all('/tmp/tmux/${w.session.name}')!
// Fix: osal.exec_string doesn't exist, use file writing instead
script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh'
script_content := '#!/bin/bash\n' + cmd_
os.write_file(script_path, script_content)!
os.chmod(script_path, 0o755)!
final_cmd = script_path
}
mut newcmd := '/bin/bash -c "${final_cmd}"'
if cmd_ == "" {
newcmd = '/bin/bash'
}
mut newcmd := '/bin/bash -c "${final_cmd}"'
if cmd_ == '' {
newcmd = '/bin/bash'
}
// Build environment arguments
mut env_args := ''
for key, value in w.env {
env_args += ' -e ${key}="${value}"'
}
// Build environment arguments
mut env_args := ''
for key, value in w.env {
env_args += ' -e ${key}="${value}"'
}
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
cmd := 'tmux new-window ${res_opt}${env_args} -t ${w.session.name} -n ${w.name} \'${newcmd}\''
console.print_debug(cmd)
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or {
return error("Can't create new window ${w.name} \n${cmd}\n${err}")
}
line_arr := res.output.split('|')
wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') }
w.id = wid.replace('@', '').int()
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
cmd := 'tmux new-window ${res_opt}${env_args} -t ${w.session.name} -n ${w.name} \'${newcmd}\''
console.print_debug(cmd)
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or {
return error("Can't create new window ${w.name} \n${cmd}\n${err}")
}
line_arr := res.output.split('|')
wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') }
w.id = wid.replace('@', '').int()
}
// stop the window
@@ -143,14 +142,14 @@ pub fn (window Window) str() string {
}
pub fn (mut w Window) stats() !ProcessStats {
mut total := ProcessStats{}
for mut pane in w.panes {
stats := pane.stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
mut total := ProcessStats{}
for mut pane in w.panes {
stats := pane.stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
}
// will select the current window so with tmux a we can go there .
@@ -169,10 +168,10 @@ pub fn (mut w Window) pane_list() []&Pane {
// Get active pane in window
pub fn (mut w Window) pane_active() ?&Pane {
for pane in w.panes {
if pane.active {
return pane
}
}
return none
for pane in w.panes {
if pane.active {
return pane
}
}
return none
}

View File

@@ -24,23 +24,23 @@ fn testsuite_end() {
}
fn test_window_new() ! {
mut tmux := new()!
tmux.start()!
mut tmux := new()!
tmux.start()!
// Create session first
mut session := tmux.session_create(name: 'main')!
// Test window creation
mut window := session.window_new(
name: 'TestWindow'
cmd: 'bash'
reset: true
)!
assert window.name == 'testwindow' // name_fix converts to lowercase
assert session.window_exist(name: 'testwindow')
tmux.stop()!
// Create session first
mut session := tmux.session_create(name: 'main')!
// Test window creation
mut window := session.window_new(
name: 'TestWindow'
cmd: 'bash'
reset: true
)!
assert window.name == 'testwindow' // name_fix converts to lowercase
assert session.window_exist(name: 'testwindow')
tmux.stop()!
}
// tests creating duplicate windows

View File

@@ -1,18 +1,18 @@
module datamodel
// I can bid for infra, and optionally get accepted
@[heap]
//I can bid for infra, and optionally get accepted
pub struct Bid {
pub mut:
id u32
customer_id u32 //links back to customer for this capacity (user on ledger)
compute_slices_nr int //nr of slices I need in 1 machine
compute_slice f64 //price per 1 GB slice I want to accept
storage_slices []u32
status BidStatus
obligation bool //if obligation then will be charged and money needs to be in escrow, otherwise its an intent
start_date u32 //epoch
end_date u32
id u32
customer_id u32 // links back to customer for this capacity (user on ledger)
compute_slices_nr int // nr of slices I need in 1 machine
compute_slice f64 // price per 1 GB slice I want to accept
storage_slices []u32
status BidStatus
obligation bool // if obligation then will be charged and money needs to be in escrow, otherwise its an intent
start_date u32 // epoch
end_date u32
}
pub enum BidStatus {
@@ -21,4 +21,4 @@ pub enum BidStatus {
assigned
cancelled
done
}
}

View File

@@ -3,13 +3,13 @@ module datamodel
@[heap]
pub struct Reservation {
pub mut:
id u32
customer_id u32 //links back to customer for this capacity
id u32
customer_id u32 // links back to customer for this capacity
compute_slices []u32
storage_slices []u32
status ReservationStatus
start_date u32 //epoch
end_date u32
status ReservationStatus
start_date u32 // epoch
end_date u32
}
pub enum ReservationStatus {
@@ -18,4 +18,4 @@ pub enum ReservationStatus {
assigned
cancelled
done
}
}

View File

@@ -1,5 +1,7 @@
module datamodel
import freeflowuniverse.herolib.threefold.grid4.datamodel { Node }
pub struct NodeSim {
Node
pub mut: