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...') console.print_header('No keys found, generating example key...')
mut key := agent.generate('example_key', '')! mut key := agent.generate('example_key', '')!
console.print_debug('Generated key: ${key}') console.print_debug('Generated key: ${key}')
// Load the generated key // Load the generated key
key.load()! key.load()!
console.print_debug('Key loaded into agent') 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()! mut lf := linux.new()!
// Test user creation // Test user creation
lf.user_create( lf.user_create(
name: 'testuser' name: 'testuser'
sshkey: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM3/2K7R8A/l0kM0/d' sshkey: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM3/2K7R8A/l0kM0/d'
)! )!
// Test ssh key creation // Test ssh key creation
lf.sshkey_create( lf.sshkey_create(
username: 'testuser' username: 'testuser'
sshkey_name: 'testkey' sshkey_name: 'testkey'
)! )!
// Test ssh key deletion // Test ssh key deletion
lf.sshkey_delete( lf.sshkey_delete(
username: 'testuser' username: 'testuser'
sshkey_name: 'testkey' 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 #!/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.tmux
mut t := tmux.new()! mut t := tmux.new()!

View File

@@ -6,7 +6,7 @@ import time
mut t := tmux.new()! mut t := tmux.new()!
if !t.is_running()! { if !t.is_running()! {
t.start()! t.start()!
} }
// Create a session and window // Create a session and window
@@ -18,15 +18,15 @@ time.sleep(1000 * time.millisecond)
// Get the active pane // Get the active pane
if mut pane := window.pane_active() { if mut pane := window.pane_active() {
// Get process info for the pane and its children // Get process info for the pane and its children
process_map := pane.processinfo()! process_map := pane.processinfo()!
println('Process tree for pane ${pane.id}:') println('Process tree for pane ${pane.id}:')
for process in process_map.processes { for process in process_map.processes {
println(' PID: ${process.pid}, CPU: ${process.cpu_perc}%, Memory: ${process.mem_perc}%, Command: ${process.cmd}') println(' PID: ${process.pid}, CPU: ${process.cpu_perc}%, Memory: ${process.mem_perc}%, Command: ${process.cmd}')
} }
// Get just the main process info // Get just the main process info
main_process := pane.processinfo_main()! main_process := pane.processinfo_main()!
println('\nMain process: PID ${main_process.pid}, Command: ${main_process.cmd}') 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 { fn (mut c LivekitClient) post(path string, body any) !http.Response {
mut token := c.new_access_token( mut token := c.new_access_token(
identity: 'api' identity: 'api'
name: 'API User' name: 'API User'
ttl: 10 * 60 // 10 minutes ttl: 10 * 60 // 10 minutes
)! )!
token.add_video_grant(VideoGrant{ token.add_video_grant(VideoGrant{
room_create: true room_create: true
room_admin: true room_admin: true
room_list: true room_list: true
}) })
jwt := token.to_jwt()! jwt := token.to_jwt()!
@@ -25,13 +25,13 @@ fn (mut c LivekitClient) post(path string, body any) !http.Response {
data := json.encode(body) data := json.encode(body)
mut req := http.Request{ mut req := http.Request{
method: .post method: .post
url: url url: url
header: header header: header
data: data data: data
} }
resp := http.fetch(req)! resp := http.fetch(req)!
if resp.status_code != 200 { if resp.status_code != 200 {
return error('failed to execute request: ${resp.body}') return error('failed to execute request: ${resp.body}')
} }
return resp return resp
} }

View File

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

View File

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

View File

@@ -4,40 +4,40 @@ import json
pub struct EgressInfo { pub struct EgressInfo {
pub mut: pub mut:
egress_id string egress_id string
room_id string room_id string
status string status string
started_at i64 started_at i64
ended_at i64 ended_at i64
error string error string
} }
pub struct StartRoomCompositeEgressArgs { pub struct StartRoomCompositeEgressArgs {
pub mut: pub mut:
room_name string room_name string
layout string layout string
audio_only bool audio_only bool
video_only bool video_only bool
custom_base_url string custom_base_url string
} }
pub struct StartTrackCompositeEgressArgs { pub struct StartTrackCompositeEgressArgs {
pub mut: pub mut:
room_name string room_name string
audio_track_id string audio_track_id string
video_track_id string video_track_id string
} }
pub struct StartWebEgressArgs { pub struct StartWebEgressArgs {
pub mut: pub mut:
url string url string
audio_only bool audio_only bool
video_only bool video_only bool
} }
pub struct UpdateStreamArgs { pub struct UpdateStreamArgs {
pub mut: pub mut:
add_output_urls []string add_output_urls []string
remove_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 { 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)! egress_info := json.decode[EgressInfo](resp.body)!
return egress_info return egress_info
} }
pub fn (mut c LivekitClient) update_stream(egress_id string, args UpdateStreamArgs) !EgressInfo { 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)! egress_info := json.decode[EgressInfo](resp.body)!
return egress_info return egress_info
} }
pub fn (mut c LivekitClient) list_egress(room_name string) ![]EgressInfo { 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)! egress_infos := json.decode[[]EgressInfo](resp.body)!
return egress_infos return egress_infos
} }
pub fn (mut c LivekitClient) stop_egress(egress_id string) !EgressInfo { 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)! egress_info := json.decode[EgressInfo](resp.body)!
return egress_info return egress_info
} }

View File

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

View File

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

View File

@@ -4,23 +4,23 @@ import json
pub struct ParticipantInfo { pub struct ParticipantInfo {
pub mut: pub mut:
sid string sid string
identity string identity string
state string state string
metadata string metadata string
joined_at i64 joined_at i64
name string name string
version u32 version u32
permission string permission string
region string region string
publisher bool publisher bool
} }
pub struct UpdateParticipantArgs { pub struct UpdateParticipantArgs {
pub mut: pub mut:
room_name string room_name string
identity string identity string
metadata string metadata string
permission string permission string
} }
@@ -33,19 +33,27 @@ pub mut:
} }
pub fn (mut c LivekitClient) list_participants(room_name string) ![]ParticipantInfo { 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)! participants := json.decode[[]ParticipantInfo](resp.body)!
return participants return participants
} }
pub fn (mut c LivekitClient) get_participant(room_name string, identity string) !ParticipantInfo { 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)! participant := json.decode[ParticipantInfo](resp.body)!
return participant return participant
} }
pub fn (mut c LivekitClient) remove_participant(room_name string, identity string) ! { 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) ! { 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) ! { pub fn (mut c LivekitClient) mute_published_track(args MutePublishedTrackArgs) ! {
_ = c.post('twirp/livekit.RoomService/MutePublishedTrack', args)! _ = 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') { if plbook.exists_once(filter: 'livekit.init') {
mut action := plbook.get(filter: 'livekit.init')! mut action := plbook.get(filter: 'livekit.init')!
mut p := action.params mut p := action.params
name := texttools.name_fix(p.get_default('name', 'default')!) name := texttools.name_fix(p.get_default('name', 'default')!)
url := p.get('url')! url := p.get('url')!
api_key := p.get('api_key')! api_key := p.get('api_key')!
api_secret := p.get('api_secret')! api_secret := p.get('api_secret')!
mut client := LivekitClient{ mut client := LivekitClient{
name: name name: name
url: url url: url
api_key: api_key api_key: api_key
api_secret: api_secret api_secret: api_secret
} }
set(client)! set(client)!
console.print_header('LiveKit client "${name}" configured') console.print_header('LiveKit client "${name}" configured')
action.done = true action.done = true
@@ -35,22 +35,22 @@ pub fn play(mut plbook PlayBook) ! {
mut room_create_actions := plbook.find(filter: 'livekit.room_create')! mut room_create_actions := plbook.find(filter: 'livekit.room_create')!
for mut action in room_create_actions { for mut action in room_create_actions {
mut p := action.params mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!) client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('name')! room_name := p.get('name')!
empty_timeout := p.get_u32_default('empty_timeout', 300)! empty_timeout := p.get_u32_default('empty_timeout', 300)!
max_participants := p.get_u32_default('max_participants', 50)! max_participants := p.get_u32_default('max_participants', 50)!
metadata := p.get_default('metadata', '')! metadata := p.get_default('metadata', '')!
mut client := get(name: client_name)! mut client := get(name: client_name)!
room := client.create_room( room := client.create_room(
name: room_name name: room_name
empty_timeout: empty_timeout empty_timeout: empty_timeout
max_participants: max_participants max_participants: max_participants
metadata: metadata metadata: metadata
)! )!
console.print_header('Room "${room_name}" created successfully') console.print_header('Room "${room_name}" created successfully')
action.done = true action.done = true
} }
@@ -59,13 +59,13 @@ pub fn play(mut plbook PlayBook) ! {
mut room_delete_actions := plbook.find(filter: 'livekit.room_delete')! mut room_delete_actions := plbook.find(filter: 'livekit.room_delete')!
for mut action in room_delete_actions { for mut action in room_delete_actions {
mut p := action.params mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!) client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('name')! room_name := p.get('name')!
mut client := get(name: client_name)! mut client := get(name: client_name)!
client.delete_room(room_name)! client.delete_room(room_name)!
console.print_header('Room "${room_name}" deleted successfully') console.print_header('Room "${room_name}" deleted successfully')
action.done = true action.done = true
} }
@@ -74,14 +74,14 @@ pub fn play(mut plbook PlayBook) ! {
mut participant_remove_actions := plbook.find(filter: 'livekit.participant_remove')! mut participant_remove_actions := plbook.find(filter: 'livekit.participant_remove')!
for mut action in participant_remove_actions { for mut action in participant_remove_actions {
mut p := action.params mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!) client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')! room_name := p.get('room')!
identity := p.get('identity')! identity := p.get('identity')!
mut client := get(name: client_name)! mut client := get(name: client_name)!
client.remove_participant(room_name, identity)! client.remove_participant(room_name, identity)!
console.print_header('Participant "${identity}" removed from room "${room_name}"') console.print_header('Participant "${identity}" removed from room "${room_name}"')
action.done = true action.done = true
} }
@@ -90,21 +90,21 @@ pub fn play(mut plbook PlayBook) ! {
mut participant_mute_actions := plbook.find(filter: 'livekit.participant_mute')! mut participant_mute_actions := plbook.find(filter: 'livekit.participant_mute')!
for mut action in participant_mute_actions { for mut action in participant_mute_actions {
mut p := action.params mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!) client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')! room_name := p.get('room')!
identity := p.get('identity')! identity := p.get('identity')!
track_sid := p.get('track_sid')! track_sid := p.get('track_sid')!
muted := p.get_default_true('muted') muted := p.get_default_true('muted')
mut client := get(name: client_name)! mut client := get(name: client_name)!
client.mute_published_track( client.mute_published_track(
room_name: room_name room_name: room_name
identity: identity identity: identity
track_sid: track_sid track_sid: track_sid
muted: muted muted: muted
)! )!
status := if muted { 'muted' } else { 'unmuted' } status := if muted { 'muted' } else { 'unmuted' }
console.print_header('Track "${track_sid}" ${status} for participant "${identity}"') console.print_header('Track "${track_sid}" ${status} for participant "${identity}"')
action.done = true action.done = true
@@ -114,17 +114,17 @@ pub fn play(mut plbook PlayBook) ! {
mut room_update_actions := plbook.find(filter: 'livekit.room_update')! mut room_update_actions := plbook.find(filter: 'livekit.room_update')!
for mut action in room_update_actions { for mut action in room_update_actions {
mut p := action.params mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!) client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')! room_name := p.get('room')!
metadata := p.get('metadata')! metadata := p.get('metadata')!
mut client := get(name: client_name)! mut client := get(name: client_name)!
client.update_room_metadata( client.update_room_metadata(
room_name: room_name room_name: room_name
metadata: metadata metadata: metadata
)! )!
console.print_header('Room "${room_name}" metadata updated') console.print_header('Room "${room_name}" metadata updated')
action.done = true action.done = true
} }
@@ -133,7 +133,7 @@ pub fn play(mut plbook PlayBook) ! {
mut token_create_actions := plbook.find(filter: 'livekit.token_create')! mut token_create_actions := plbook.find(filter: 'livekit.token_create')!
for mut action in token_create_actions { for mut action in token_create_actions {
mut p := action.params mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!) client_name := texttools.name_fix(p.get_default('client', 'default')!)
identity := p.get('identity')! identity := p.get('identity')!
name := p.get_default('name', 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_publish := p.get_default_false('can_publish')
can_subscribe := p.get_default_true('can_subscribe') can_subscribe := p.get_default_true('can_subscribe')
can_publish_data := p.get_default_false('can_publish_data') can_publish_data := p.get_default_false('can_publish_data')
mut client := get(name: client_name)! mut client := get(name: client_name)!
mut token := client.new_access_token( mut token := client.new_access_token(
identity: identity identity: identity
name: name name: name
ttl: ttl ttl: ttl
)! )!
token.add_video_grant(VideoGrant{ token.add_video_grant(VideoGrant{
room: room room: room
room_join: true room_join: true
can_publish: can_publish can_publish: can_publish
can_subscribe: can_subscribe can_subscribe: can_subscribe
can_publish_data: can_publish_data can_publish_data: can_publish_data
}) })
jwt := token.to_jwt()! jwt := token.to_jwt()!
console.print_header('Access token generated for "${identity}"') console.print_header('Access token generated for "${identity}"')
console.print_debug('Token: ${jwt}') console.print_debug('Token: ${jwt}')
action.done = true action.done = true
} }
} }

View File

@@ -5,25 +5,25 @@ import net.http
pub struct Room { pub struct Room {
pub mut: pub mut:
sid string sid string
name string name string
empty_timeout u32 empty_timeout u32
max_participants u32 max_participants u32
creation_time i64 creation_time i64
turn_password string turn_password string
enabled_codecs []string enabled_codecs []string
metadata string metadata string
num_participants u32 num_participants u32
num_connected_participants u32 num_connected_participants u32
active_recording bool active_recording bool
} }
pub struct CreateRoomArgs { pub struct CreateRoomArgs {
pub mut: pub mut:
name string name string
empty_timeout u32 empty_timeout u32
max_participants u32 max_participants u32
metadata string metadata string
} }
pub struct UpdateRoomMetadataArgs { 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) ! { 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) ! { pub fn (mut c LivekitClient) update_room_metadata(args UpdateRoomMetadataArgs) ! {
_ = c.post('twirp/livekit.RoomService/UpdateRoomMetadata', args)! _ = c.post('twirp/livekit.RoomService/UpdateRoomMetadata', args)!
} }

View File

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

View File

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

View File

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

View File

@@ -15,8 +15,8 @@ pub const default_url = 'http://localhost:8990'
@[heap] @[heap]
pub struct MyceliumRPC { pub struct MyceliumRPC {
pub mut: pub mut:
name string = 'default' name string = 'default'
url string = default_url // RPC server URL url string = default_url // RPC server URL
// rpc_client ?&jsonrpc.Client @[skip] // 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 redis := redisclient.core_get(redisclient.get_redis_url(args.redis_url)!)!
mut manager := &TraefikManager{ mut manager := &TraefikManager{
name: name name: name
redis: redis redis: redis
config: osal_traefik.new_traefik_config() config: osal_traefik.new_traefik_config()
} }
// Set redis connection in config // Set redis connection in config
manager.config.redis = redis manager.config.redis = redis
traefik_managers[name] = manager traefik_managers[name] = manager
return manager return manager
} }
@@ -48,4 +48,4 @@ pub fn default() !&TraefikManager {
return new(name: 'default')! return new(name: 'default')!
} }
return get(name: 'default')! return get(name: 'default')!
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,34 +11,34 @@ pub fn play(mut plbook PlayBook) ! {
// Process user_create actions // Process user_create actions
play_user_create(mut plbook, mut lf)! play_user_create(mut plbook, mut lf)!
// Process user_delete actions // Process user_delete actions
play_user_delete(mut plbook, mut lf)! play_user_delete(mut plbook, mut lf)!
// Process sshkey_create actions // Process sshkey_create actions
play_sshkey_create(mut plbook, mut lf)! play_sshkey_create(mut plbook, mut lf)!
// Process sshkey_delete actions // Process sshkey_delete actions
play_sshkey_delete(mut plbook, mut lf)! play_sshkey_delete(mut plbook, mut lf)!
} }
fn play_user_create(mut plbook PlayBook, mut lf LinuxFactory) ! { fn play_user_create(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.user_create')! mut actions := plbook.find(filter: 'usermgmt.user_create')!
for mut action in actions { for mut action in actions {
mut p := action.params mut p := action.params
mut args := UserCreateArgs{ mut args := UserCreateArgs{
name: p.get('name')! name: p.get('name')!
giteakey: p.get_default('giteakey', '')! giteakey: p.get_default('giteakey', '')!
giteaurl: p.get_default('giteaurl', '')! giteaurl: p.get_default('giteaurl', '')!
passwd: p.get_default('passwd', '')! passwd: p.get_default('passwd', '')!
description: p.get_default('description', '')! description: p.get_default('description', '')!
email: p.get_default('email', '')! email: p.get_default('email', '')!
tel: p.get_default('tel', '')! tel: p.get_default('tel', '')!
sshkey: p.get_default('sshkey', '')! // SSH public key sshkey: p.get_default('sshkey', '')! // SSH public key
} }
lf.user_create(args)! lf.user_create(args)!
action.done = true 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) ! { fn play_user_delete(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.user_delete')! mut actions := plbook.find(filter: 'usermgmt.user_delete')!
for mut action in actions { for mut action in actions {
mut p := action.params mut p := action.params
mut args := UserDeleteArgs{ mut args := UserDeleteArgs{
name: p.get('name')! name: p.get('name')!
} }
lf.user_delete(args)! lf.user_delete(args)!
action.done = true 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) ! { fn play_sshkey_create(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.sshkey_create')! mut actions := plbook.find(filter: 'usermgmt.sshkey_create')!
for mut action in actions { for mut action in actions {
mut p := action.params mut p := action.params
mut args := SSHKeyCreateArgs{ mut args := SSHKeyCreateArgs{
username: p.get('username')! username: p.get('username')!
sshkey_name: p.get('sshkey_name')! 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', '')! sshkey_priv: p.get_default('sshkey_priv', '')!
} }
lf.sshkey_create(args)! lf.sshkey_create(args)!
action.done = true 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) ! { fn play_sshkey_delete(mut plbook PlayBook, mut lf LinuxFactory) ! {
mut actions := plbook.find(filter: 'usermgmt.sshkey_delete')! mut actions := plbook.find(filter: 'usermgmt.sshkey_delete')!
for mut action in actions { for mut action in actions {
mut p := action.params mut p := action.params
mut args := SSHKeyDeleteArgs{ mut args := SSHKeyDeleteArgs{
username: p.get('username')! username: p.get('username')!
sshkey_name: p.get('sshkey_name')! sshkey_name: p.get('sshkey_name')!
} }
lf.sshkey_delete(args)! lf.sshkey_delete(args)!
action.done = true 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 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)" 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" echo "🎉 Setup complete for user $NEWUSER"

View File

@@ -119,7 +119,9 @@ pub fn (mut lf LinuxFactory) sshkey_create(args SSHKeyCreateArgs) ! {
} else { } else {
// Generate new SSH key (modern ed25519) // Generate new SSH key (modern ed25519)
key_path := '${ssh_dir}/${args.sshkey_name}' 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}') 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{ new_config := UserConfig{
name: args.name name: args.name
giteakey: args.giteakey giteakey: args.giteakey
giteaurl: args.giteaurl giteaurl: args.giteaurl
email: args.email email: args.email
description: args.description description: args.description
tel: args.tel tel: args.tel
} }
if found_idx >= 0 { if found_idx >= 0 {
@@ -201,7 +203,7 @@ fn (mut lf LinuxFactory) remove_user_config(username string) ! {
config_path := '${config_dir}/myconfig.json' config_path := '${config_dir}/myconfig.json'
if !os.exists(config_path) { if !os.exists(config_path) {
return // Nothing to remove return
} }
content := osal.file_read(config_path)! content := osal.file_read(config_path)!
@@ -243,7 +245,9 @@ fn (mut lf LinuxFactory) create_user_system(args UserCreateArgs) ! {
// Ensure ourworld group exists // Ensure ourworld group exists
group_check := osal.exec(cmd: 'getent group ourworld', raise_error: false) or { 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 { if group_check.exit_code != 0 {
console.print_item(' Creating group ourworld') console.print_item(' Creating group ourworld')
@@ -284,58 +288,9 @@ fn (mut lf LinuxFactory) create_ssh_agent_profile(username string) ! {
user_home := '/home/${username}' user_home := '/home/${username}'
profile_script := '${user_home}/.profile_sshagent' profile_script := '${user_home}/.profile_sshagent'
script_content := '# Auto-start ssh-agent if not running // script_content := ''
SSH_AGENT_PID_FILE="$HOME/.ssh/agent.pid"
SSH_AUTH_SOCK_FILE="$HOME/.ssh/agent.sock"
# Function to start ssh-agent panic('implement')
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
'
osal.file_write(profile_script, script_content)! osal.file_write(profile_script, script_content)!
osal.exec(cmd: 'chown ${username}:${username} ${profile_script}')! osal.exec(cmd: 'chown ${username}:${username} ${profile_script}')!
@@ -351,4 +306,4 @@ fi
} }
console.print_green(' SSH agent profile created for ${username}') 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 // Check if SSH agent is properly configured and all is good
fn agent_check(mut agent SSHAgent) ! { fn agent_check(mut agent SSHAgent) ! {
console.print_header('SSH Agent Check') console.print_header('SSH Agent Check')
// Ensure single agent is running // Ensure single agent is running
agent.ensure_single_agent()! agent.ensure_single_agent()!
// Get diagnostics // Get diagnostics
diag := agent.diagnostics() diag := agent.diagnostics()
for key, value in diag { for key, value in diag {
console.print_item('${key}: ${value}') console.print_item('${key}: ${value}')
} }
// Verify agent is responsive // Verify agent is responsive
if !agent.is_agent_responsive() { if !agent.is_agent_responsive() {
return error('SSH agent is not responsive') return error('SSH agent is not responsive')
} }
// Load all existing keys from ~/.ssh that aren't loaded yet // Load all existing keys from ~/.ssh that aren't loaded yet
agent.init()! agent.init()!
console.print_green(' SSH Agent is properly configured and running') console.print_green(' SSH Agent is properly configured and running')
// Show loaded keys // Show loaded keys
loaded_keys := agent.keys_loaded()! loaded_keys := agent.keys_loaded()!
console.print_item('Loaded keys: ${loaded_keys.len}') console.print_item('Loaded keys: ${loaded_keys.len}')
@@ -35,17 +35,17 @@ fn agent_check(mut agent SSHAgent) ! {
// Create a new SSH key // Create a new SSH key
fn sshkey_create(mut agent SSHAgent, name string, passphrase string) ! { fn sshkey_create(mut agent SSHAgent, name string, passphrase string) ! {
console.print_header('Creating SSH key: ${name}') console.print_header('Creating SSH key: ${name}')
// Check if key already exists // Check if key already exists
if agent.exists(name: name) { if agent.exists(name: name) {
console.print_debug('SSH key "${name}" already exists') console.print_debug('SSH key "${name}" already exists')
return return
} }
// Generate new key // Generate new key
mut key := agent.generate(name, passphrase)! mut key := agent.generate(name, passphrase)!
console.print_green(' SSH key "${name}" created successfully') console.print_green(' SSH key "${name}" created successfully')
// Automatically load the key // Automatically load the key
key.load()! key.load()!
console.print_green(' SSH key "${name}" loaded into agent') 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 // Delete an SSH key
fn sshkey_delete(mut agent SSHAgent, name string) ! { fn sshkey_delete(mut agent SSHAgent, name string) ! {
console.print_header('Deleting SSH key: ${name}') console.print_header('Deleting SSH key: ${name}')
// Check if key exists // Check if key exists
mut key := agent.get(name: name) or { mut key := agent.get(name: name) or {
console.print_debug('SSH key "${name}" does not exist') console.print_debug('SSH key "${name}" does not exist')
return return
} }
// Get key paths before deletion // Get key paths before deletion
key_path := key.keypath() or { key_path := key.keypath() or {
console.print_debug('Private key path not available for "${name}"') 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}"') console.print_debug('Public key path not available for "${name}"')
return return
} }
// Remove from agent if loaded (temporarily disabled due to reset_ssh panic) // Remove from agent if loaded (temporarily disabled due to reset_ssh panic)
// if key.loaded { // if key.loaded {
// key.forget()! // key.forget()!
// } // }
// Delete key files // Delete key files
if key_path.exists() { if key_path.exists() {
key_path.delete()! key_path.delete()!
@@ -85,26 +85,24 @@ fn sshkey_delete(mut agent SSHAgent, name string) ! {
key_pub_path.delete()! key_pub_path.delete()!
console.print_debug('Deleted public key: ${key_pub_path.path}') console.print_debug('Deleted public key: ${key_pub_path.path}')
} }
// Reinitialize agent to update key list // Reinitialize agent to update key list
agent.init()! agent.init()!
console.print_green(' SSH key "${name}" deleted successfully') console.print_green(' SSH key "${name}" deleted successfully')
} }
// Load SSH key into agent // Load SSH key into agent
fn sshkey_load(mut agent SSHAgent, name string) ! { fn sshkey_load(mut agent SSHAgent, name string) ! {
console.print_header('Loading SSH key: ${name}') console.print_header('Loading SSH key: ${name}')
mut key := agent.get(name: name) or { mut key := agent.get(name: name) or { return error('SSH key "${name}" not found') }
return error('SSH key "${name}" not found')
}
if key.loaded { if key.loaded {
console.print_debug('SSH key "${name}" is already loaded') console.print_debug('SSH key "${name}" is already loaded')
return return
} }
key.load()! key.load()!
console.print_green(' SSH key "${name}" loaded into agent') 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 // Check if SSH key is valid
fn sshkey_check(mut agent SSHAgent, name string) ! { fn sshkey_check(mut agent SSHAgent, name string) ! {
console.print_header('Checking SSH key: ${name}') console.print_header('Checking SSH key: ${name}')
mut key := agent.get(name: name) or { mut key := agent.get(name: name) or { return error('SSH key "${name}" not found') }
return error('SSH key "${name}" not found')
}
// Check if key files exist // Check if key files exist
key_path := key.keypath() or { key_path := key.keypath() or { return error('Private key file not found for "${name}"') }
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_pub_path := key.keypath_pub() or {
return error('Public key file not found for "${name}"')
}
if !key_path.exists() { if !key_path.exists() {
return error('Private key file does not exist: ${key_path.path}') return error('Private key file does not exist: ${key_path.path}')
} }
if !key_pub_path.exists() { if !key_pub_path.exists() {
return error('Public key file does not exist: ${key_pub_path.path}') return error('Public key file does not exist: ${key_pub_path.path}')
} }
// Verify key can be loaded (if not already loaded) // Verify key can be loaded (if not already loaded)
if !key.loaded { if !key.loaded {
// Test load without actually loading (since forget is disabled) // 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}"') return error('Invalid private key format in "${name}"')
} }
} }
console.print_item('Key type: ${key.cat}') console.print_item('Key type: ${key.cat}')
console.print_item('Loaded: ${key.loaded}') console.print_item('Loaded: ${key.loaded}')
console.print_item('Email: ${key.email}') console.print_item('Email: ${key.email}')
console.print_item('Private key: ${key_path.path}') console.print_item('Private key: ${key_path.path}')
console.print_item('Public key: ${key_pub_path.path}') console.print_item('Public key: ${key_pub_path.path}')
console.print_green(' SSH key "${name}" is valid') console.print_green(' SSH key "${name}" is valid')
} }
// Copy private key to remote node // Copy private key to remote node
fn remote_copy(mut agent SSHAgent, node_addr string, key_name string) ! { fn remote_copy(mut agent SSHAgent, node_addr string, key_name string) ! {
console.print_header('Copying SSH key "${key_name}" to ${node_addr}') console.print_header('Copying SSH key "${key_name}" to ${node_addr}')
// Get the key // Get the key
mut key := agent.get(name: key_name) or { mut key := agent.get(name: key_name) or { return error('SSH key "${key_name}" not found') }
return error('SSH key "${key_name}" not found')
}
// Create builder node // Create builder node
mut b := builder.new()! mut b := builder.new()!
mut node := b.node_new(ipaddr: node_addr)! mut node := b.node_new(ipaddr: node_addr)!
// Get private key content // Get private key content
key_path := key.keypath()! key_path := key.keypath()!
if !key_path.exists() { if !key_path.exists() {
return error('Private key file not found: ${key_path.path}') return error('Private key file not found: ${key_path.path}')
} }
private_key_content := key_path.read()! private_key_content := key_path.read()!
// Get home directory on remote // Get home directory on remote
home_dir := node.environ_get()!['HOME'] or { home_dir := node.environ_get()!['HOME'] or {
return error('Could not determine HOME directory on remote node') return error('Could not determine HOME directory on remote node')
} }
remote_ssh_dir := '${home_dir}/.ssh' remote_ssh_dir := '${home_dir}/.ssh'
remote_key_path := '${remote_ssh_dir}/${key_name}' remote_key_path := '${remote_ssh_dir}/${key_name}'
// Ensure .ssh directory exists with correct permissions // Ensure .ssh directory exists with correct permissions
node.exec_silent('mkdir -p ${remote_ssh_dir}')! node.exec_silent('mkdir -p ${remote_ssh_dir}')!
node.exec_silent('chmod 700 ${remote_ssh_dir}')! node.exec_silent('chmod 700 ${remote_ssh_dir}')!
// Copy private key to remote // Copy private key to remote
node.file_write(remote_key_path, private_key_content)! node.file_write(remote_key_path, private_key_content)!
node.exec_silent('chmod 600 ${remote_key_path}')! node.exec_silent('chmod 600 ${remote_key_path}')!
// Generate public key on remote // Generate public key on remote
node.exec_silent('ssh-keygen -y -f ${remote_key_path} > ${remote_key_path}.pub')! node.exec_silent('ssh-keygen -y -f ${remote_key_path} > ${remote_key_path}.pub')!
node.exec_silent('chmod 644 ${remote_key_path}.pub')! node.exec_silent('chmod 644 ${remote_key_path}.pub')!
console.print_green(' SSH key "${key_name}" copied to ${node_addr}') console.print_green(' SSH key "${key_name}" copied to ${node_addr}')
} }
// Add public key to authorized_keys on remote node // Add public key to authorized_keys on remote node
fn remote_auth(mut agent SSHAgent, node_addr string, key_name string) ! { 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}') console.print_header('Adding SSH key "${key_name}" to authorized_keys on ${node_addr}')
// Create builder node // Create builder node
mut b := builder.new()! mut b := builder.new()!
mut node := b.node_new(ipaddr: node_addr)! mut node := b.node_new(ipaddr: node_addr)!
// Use existing builder integration // Use existing builder integration
agent.push_key_to_node(mut node, key_name)! agent.push_key_to_node(mut node, key_name)!
console.print_green(' SSH key "${key_name}" added to authorized_keys on ${node_addr}') 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' { if node_info['category'] != 'ssh' {
return error('Can only push keys to SSH nodes, got: ${node_info['category']}') return error('Can only push keys to SSH nodes, got: ${node_info['category']}')
} }
// Find the key // Find the key
mut key := agent.get(name: key_name) or { mut key := agent.get(name: key_name) or {
return error('SSH key "${key_name}" not found in agent') return error('SSH key "${key_name}" not found in agent')
} }
// Get public key content // Get public key content
pubkey_content := key.keypub()! pubkey_content := key.keypub()!
// Check if authorized_keys file exists on remote // Check if authorized_keys file exists on remote
home_dir := node.environ_get()!['HOME'] or { home_dir := node.environ_get()!['HOME'] or {
return error('Could not determine HOME directory on remote node') return error('Could not determine HOME directory on remote node')
} }
ssh_dir := '${home_dir}/.ssh' ssh_dir := '${home_dir}/.ssh'
authorized_keys_path := '${ssh_dir}/authorized_keys' authorized_keys_path := '${ssh_dir}/authorized_keys'
// Ensure .ssh directory exists with correct permissions // Ensure .ssh directory exists with correct permissions
node.exec_silent('mkdir -p ${ssh_dir}')! node.exec_silent('mkdir -p ${ssh_dir}')!
node.exec_silent('chmod 700 ${ssh_dir}')! node.exec_silent('chmod 700 ${ssh_dir}')!
// Check if key already exists // Check if key already exists
if node.file_exists(authorized_keys_path) { if node.file_exists(authorized_keys_path) {
existing_keys := node.file_read(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 return
} }
} }
// Add key to authorized_keys // Add key to authorized_keys
node.exec_silent('echo "${pubkey_content}" >> ${authorized_keys_path}')! node.exec_silent('echo "${pubkey_content}" >> ${authorized_keys_path}')!
node.exec_silent('chmod 600 ${authorized_keys_path}')! node.exec_silent('chmod 600 ${authorized_keys_path}')!
console.print_debug('SSH key "${key_name}" successfully pushed to node') 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' { if node_info['category'] != 'ssh' {
return error('Can only remove keys from SSH nodes, got: ${node_info['category']}') return error('Can only remove keys from SSH nodes, got: ${node_info['category']}')
} }
// Find the key // Find the key
mut key := agent.get(name: key_name) or { mut key := agent.get(name: key_name) or {
return error('SSH key "${key_name}" not found in agent') return error('SSH key "${key_name}" not found in agent')
} }
// Get public key content // Get public key content
pubkey_content := key.keypub()! pubkey_content := key.keypub()!
// Get authorized_keys path // Get authorized_keys path
home_dir := node.environ_get()!['HOME'] or { home_dir := node.environ_get()!['HOME'] or {
return error('Could not determine HOME directory on remote node') return error('Could not determine HOME directory on remote node')
} }
authorized_keys_path := '${home_dir}/.ssh/authorized_keys' authorized_keys_path := '${home_dir}/.ssh/authorized_keys'
if !node.file_exists(authorized_keys_path) { if !node.file_exists(authorized_keys_path) {
console.print_debug('authorized_keys file does not exist on remote node') console.print_debug('authorized_keys file does not exist on remote node')
return return
} }
// Remove the key line from authorized_keys // Remove the key line from authorized_keys
escaped_key := pubkey_content.replace('/', '\\/') escaped_key := pubkey_content.replace('/', '\\/')
node.exec_silent('sed -i "\\|${escaped_key}|d" ${authorized_keys_path}')! node.exec_silent('sed -i "\\|${escaped_key}|d" ${authorized_keys_path}')!
console.print_debug('SSH key "${key_name}" removed from remote node') 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' { if node_info['category'] != 'ssh' {
return error('Can only verify access to SSH nodes') return error('Can only verify access to SSH nodes')
} }
// Test basic connectivity // Test basic connectivity
result := node.exec_silent('echo "SSH key verification successful"') or { result := node.exec_silent('echo "SSH key verification successful"') or { return false }
return false
}
return result.contains('SSH key verification successful') return result.contains('SSH key verification successful')
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,87 +21,86 @@ pub mut:
env map[string]string env map[string]string
reset bool reset bool
} }
@[params] @[params]
pub struct WindowGetArgs { pub struct WindowGetArgs {
pub mut: pub mut:
name string name string
id int id int
} }
pub fn (mut s Session) create() ! { pub fn (mut s Session) create() ! {
// Check if session already exists // Check if session already exists
cmd_check := "tmux has-session -t ${s.name}" cmd_check := 'tmux has-session -t ${s.name}'
check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or { check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or {
// Session doesn't exist, this is expected // Session doesn't exist, this is expected
osal.Job{} osal.Job{}
} }
if check_result.exit_code == 0 { if check_result.exit_code == 0 {
return error('duplicate session: ${s.name}') return error('duplicate session: ${s.name}')
} }
// Create new session // Create new session
cmd := "tmux new-session -d -s ${s.name}" cmd := 'tmux new-session -d -s ${s.name}'
osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or { osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or {
return error("Can't create session ${s.name}: ${err}") return error("Can't create session ${s.name}: ${err}")
} }
} }
//load info from reality // load info from reality
pub fn (mut s Session) scan() ! { pub fn (mut s Session) scan() ! {
// Get current windows from tmux for this session // Get current windows from tmux for this session
cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'" cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'"
result := osal.execute_silent(cmd) or { result := osal.execute_silent(cmd) or {
if err.msg().contains('session not found') { if err.msg().contains('session not found') {
return // Session doesn't exist anymore return
} }
return error('Cannot list windows for session ${s.name}: ${err}') 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)
}
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) // window_name is the name of the window in session main (will always be called session main)
// cmd to execute e.g. bash file // 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 // Create the window with the specified command
w.create(args.cmd)! w.create(args.cmd)!
s.scan()! s.scan()!
return w return w
} }
// get all windows as found in a session // get all windows as found in a session
pub fn (mut s Session) windows_get() []&Window { pub fn (mut s Session) windows_get() []&Window {
mut res := []&Window{} mut res := []&Window{}
@@ -179,14 +174,14 @@ pub fn (mut s Session) str() string {
} }
pub fn (mut s Session) stats() !ProcessStats { pub fn (mut s Session) stats() !ProcessStats {
mut total := ProcessStats{} mut total := ProcessStats{}
for mut window in s.windows { for mut window in s.windows {
stats := window.stats() or { continue } stats := window.stats() or { continue }
total.cpu_percent += stats.cpu_percent total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent total.memory_percent += stats.memory_percent
} }
return total return total
} }
// pub fn (mut s Session) activate()! { // 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 { fn (mut s Session) window_exist(args_ WindowGetArgs) bool {
mut args := args_ mut args := args_
s.window_get(args) or { return false } 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 s.windows.delete(i) // i is now the one in the list which needs to be removed
} }
pub fn (mut s Session) restart() ! { pub fn (mut s Session) restart() ! {
s.stop()! s.stop()!
s.create()! s.create()!
@@ -259,4 +251,4 @@ pub fn (mut s Session) stop() ! {
osal.execute_silent('tmux kill-session -t ${s.name}') or { 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}") 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() ! { fn test_windows_get() ! {
mut tmux := new()! mut tmux := new()!
tmux.start()! tmux.start()!
// After start, scan to get the initial session // After start, scan to get the initial session
tmux.scan()! tmux.scan()!
windows := tmux.windows_get() windows := tmux.windows_get()
assert windows.len >= 0 // At least the default session should exist assert windows.len >= 0 // At least the default session should exist
tmux.stop()! tmux.stop()!
} }
fn test_scan() ! { fn test_scan() ! {
console.print_debug('-----Testing scan------') console.print_debug('-----Testing scan------')
mut tmux := new()! mut tmux := new()!
tmux.start()! tmux.start()!
// Test initial scan // Test initial scan
tmux.scan()! tmux.scan()!
sessions_before := tmux.sessions.len sessions_before := tmux.sessions.len
// Create a test session // Create a test session
mut session := tmux.session_create(name: 'test_scan')! mut session := tmux.session_create(name: 'test_scan')!
// Scan again // Scan again
tmux.scan()! tmux.scan()!
sessions_after := tmux.sessions.len sessions_after := tmux.sessions.len
assert sessions_after >= sessions_before assert sessions_after >= sessions_before
tmux.stop()! tmux.stop()!
} }
// //TODO: fix test // //TODO: fix test

View File

@@ -13,7 +13,7 @@ pub mut:
session &Session @[skip] session &Session @[skip]
name string name string
id int id int
panes []&Pane // windows contain multiple panes panes []&Pane // windows contain multiple panes
active bool active bool
env map[string]string env map[string]string
} }
@@ -22,105 +22,104 @@ pub mut:
pub struct PaneNewArgs { pub struct PaneNewArgs {
pub mut: pub mut:
name string 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 cmd string
env map[string]string env map[string]string
} }
pub fn (mut w Window) scan() ! { pub fn (mut w Window) scan() ! {
// Get current panes for this window // 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}'" 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 { result := osal.execute_silent(cmd) or {
// Window might not exist anymore // Window might not exist anymore
return 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)
}
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() ! { 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) ! { pub fn (mut w Window) create(cmd_ string) ! {
mut final_cmd := cmd_ mut final_cmd := cmd_
if cmd_.contains('\n') { if cmd_.contains('\n') {
os.mkdir_all('/tmp/tmux/${w.session.name}')! os.mkdir_all('/tmp/tmux/${w.session.name}')!
// Fix: osal.exec_string doesn't exist, use file writing instead // Fix: osal.exec_string doesn't exist, use file writing instead
script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh' script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh'
script_content := '#!/bin/bash\n' + cmd_ script_content := '#!/bin/bash\n' + cmd_
os.write_file(script_path, script_content)! os.write_file(script_path, script_content)!
os.chmod(script_path, 0o755)! os.chmod(script_path, 0o755)!
final_cmd = script_path final_cmd = script_path
} }
mut newcmd := '/bin/bash -c "${final_cmd}"' mut newcmd := '/bin/bash -c "${final_cmd}"'
if cmd_ == "" { if cmd_ == '' {
newcmd = '/bin/bash' newcmd = '/bin/bash'
} }
// Build environment arguments // Build environment arguments
mut env_args := '' mut env_args := ''
for key, value in w.env { for key, value in w.env {
env_args += ' -e ${key}="${value}"' env_args += ' -e ${key}="${value}"'
} }
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'" 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}\'' cmd := 'tmux new-window ${res_opt}${env_args} -t ${w.session.name} -n ${w.name} \'${newcmd}\''
console.print_debug(cmd) console.print_debug(cmd)
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or { 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}") return error("Can't create new window ${w.name} \n${cmd}\n${err}")
} }
line_arr := res.output.split('|') line_arr := res.output.split('|')
wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') } wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') }
w.id = wid.replace('@', '').int() w.id = wid.replace('@', '').int()
} }
// stop the window // stop the window
@@ -143,14 +142,14 @@ pub fn (window Window) str() string {
} }
pub fn (mut w Window) stats() !ProcessStats { pub fn (mut w Window) stats() !ProcessStats {
mut total := ProcessStats{} mut total := ProcessStats{}
for mut pane in w.panes { for mut pane in w.panes {
stats := pane.stats() or { continue } stats := pane.stats() or { continue }
total.cpu_percent += stats.cpu_percent total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent total.memory_percent += stats.memory_percent
} }
return total return total
} }
// will select the current window so with tmux a we can go there . // 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 // Get active pane in window
pub fn (mut w Window) pane_active() ?&Pane { pub fn (mut w Window) pane_active() ?&Pane {
for pane in w.panes { for pane in w.panes {
if pane.active { if pane.active {
return pane return pane
} }
} }
return none return none
} }

View File

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

View File

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

View File

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

View File

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