diff --git a/examples/osal/sshagent.vsh b/examples/osal/sshagent.vsh index 5bcc6bb9..038c720b 100644 --- a/examples/osal/sshagent.vsh +++ b/examples/osal/sshagent.vsh @@ -25,7 +25,7 @@ if agent.keys.len == 0 { console.print_header('No keys found, generating example key...') mut key := agent.generate('example_key', '')! console.print_debug('Generated key: ${key}') - + // Load the generated key key.load()! console.print_debug('Key loaded into agent') @@ -62,4 +62,4 @@ if agent.keys.len > 0 { } */ -console.print_header('SSH Agent example completed successfully') \ No newline at end of file +console.print_header('SSH Agent example completed successfully') diff --git a/examples/osal/sshagent/sshagent_example.v b/examples/osal/sshagent/sshagent_example.v index fc20a9df..6f4780b4 100644 --- a/examples/osal/sshagent/sshagent_example.v +++ b/examples/osal/sshagent/sshagent_example.v @@ -25,19 +25,19 @@ fn test_user_mgmt() ! { mut lf := linux.new()! // Test user creation lf.user_create( - name: 'testuser' + name: 'testuser' sshkey: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM3/2K7R8A/l0kM0/d' )! // Test ssh key creation lf.sshkey_create( - username: 'testuser' + username: 'testuser' sshkey_name: 'testkey' )! // Test ssh key deletion lf.sshkey_delete( - username: 'testuser' + username: 'testuser' sshkey_name: 'testkey' )! diff --git a/examples/osal/tmux.vsh b/examples/osal/tmux.vsh index dba1fc32..5c18a170 100755 --- a/examples/osal/tmux.vsh +++ b/examples/osal/tmux.vsh @@ -1,6 +1,5 @@ #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run - import freeflowuniverse.herolib.osal.tmux mut t := tmux.new()! diff --git a/examples/osal/tmux_process_info.vsh b/examples/osal/tmux_process_info.vsh index 523af874..6d95c657 100644 --- a/examples/osal/tmux_process_info.vsh +++ b/examples/osal/tmux_process_info.vsh @@ -6,7 +6,7 @@ import time mut t := tmux.new()! if !t.is_running()! { - t.start()! + t.start()! } // Create a session and window @@ -18,15 +18,15 @@ time.sleep(1000 * time.millisecond) // Get the active pane if mut pane := window.pane_active() { - // Get process info for the pane and its children - process_map := pane.processinfo()! - - println('Process tree for pane ${pane.id}:') - for process in process_map.processes { - println(' PID: ${process.pid}, CPU: ${process.cpu_perc}%, Memory: ${process.mem_perc}%, Command: ${process.cmd}') - } - - // Get just the main process info - main_process := pane.processinfo_main()! - println('\nMain process: PID ${main_process.pid}, Command: ${main_process.cmd}') -} \ No newline at end of file + // Get process info for the pane and its children + process_map := pane.processinfo()! + + println('Process tree for pane ${pane.id}:') + for process in process_map.processes { + println(' PID: ${process.pid}, CPU: ${process.cpu_perc}%, Memory: ${process.mem_perc}%, Command: ${process.cmd}') + } + + // Get just the main process info + main_process := pane.processinfo_main()! + println('\nMain process: PID ${main_process.pid}, Command: ${main_process.cmd}') +} diff --git a/lib/clients/livekit/client.v b/lib/clients/livekit/client.v index 868b23a4..aa4e3811 100644 --- a/lib/clients/livekit/client.v +++ b/lib/clients/livekit/client.v @@ -7,13 +7,13 @@ import time fn (mut c LivekitClient) post(path string, body any) !http.Response { mut token := c.new_access_token( identity: 'api' - name: 'API User' - ttl: 10 * 60 // 10 minutes + name: 'API User' + ttl: 10 * 60 // 10 minutes )! token.add_video_grant(VideoGrant{ room_create: true - room_admin: true - room_list: true + room_admin: true + room_list: true }) jwt := token.to_jwt()! @@ -25,13 +25,13 @@ fn (mut c LivekitClient) post(path string, body any) !http.Response { data := json.encode(body) mut req := http.Request{ method: .post - url: url + url: url header: header - data: data + data: data } resp := http.fetch(req)! if resp.status_code != 200 { return error('failed to execute request: ${resp.body}') } return resp -} \ No newline at end of file +} diff --git a/lib/clients/livekit/client_mgmt.v b/lib/clients/livekit/client_mgmt.v index 553bb74f..8d5f52de 100644 --- a/lib/clients/livekit/client_mgmt.v +++ b/lib/clients/livekit/client_mgmt.v @@ -3,13 +3,16 @@ module livekit import freeflowuniverse.herolib.data.caching import os -const CACHING_METHOD = caching.CachingMethod.once_per_process +// const CACHING_METHOD = caching.CachingMethod.once_per_process fn _init() ! { if caching.is_set(key: 'livekit_clients') { return } - caching.set[map[string]LivekitClient](key: 'livekit_clients', val: map[string]LivekitClient{}, CachingMethod.once_per_process)! + caching.set[map[string]LivekitClient]( + key: 'livekit_clients' + val: map[string]LivekitClient{} + )! } fn _get() !map[string]LivekitClient { @@ -25,10 +28,10 @@ pub fn get(name string) !LivekitClient { pub fn set(client LivekitClient) ! { mut clients := _get()! clients[client.name] = client - caching.set[map[string]LivekitClient](key: 'livekit_clients', val: clients, CachingMethod.once_per_process)! + caching.set[map[string]LivekitClient](key: 'livekit_clients', val: clients)! } pub fn exists(name string) !bool { mut clients := _get()! return name in clients -} \ No newline at end of file +} diff --git a/lib/clients/livekit/data.v b/lib/clients/livekit/data.v index 77173ba4..657c6aeb 100644 --- a/lib/clients/livekit/data.v +++ b/lib/clients/livekit/data.v @@ -2,9 +2,9 @@ module livekit pub struct SendDataArgs { pub mut: - room_name string - data []u8 - kind DataPacket_Kind + room_name string + data []u8 + kind DataPacket_Kind destination_sids []string } @@ -15,4 +15,4 @@ pub enum DataPacket_Kind { pub fn (mut c LivekitClient) send_data(args SendDataArgs) ! { _ = c.post('twirp/livekit.RoomService/SendData', args)! -} \ No newline at end of file +} diff --git a/lib/clients/livekit/egress.v b/lib/clients/livekit/egress.v index 0a058f51..8c6b5781 100644 --- a/lib/clients/livekit/egress.v +++ b/lib/clients/livekit/egress.v @@ -4,40 +4,40 @@ import json pub struct EgressInfo { pub mut: - egress_id string - room_id string - status string + egress_id string + room_id string + status string started_at i64 - ended_at i64 - error string + ended_at i64 + error string } pub struct StartRoomCompositeEgressArgs { pub mut: - room_name string - layout string - audio_only bool - video_only bool + room_name string + layout string + audio_only bool + video_only bool custom_base_url string } pub struct StartTrackCompositeEgressArgs { pub mut: - room_name string + room_name string audio_track_id string video_track_id string } pub struct StartWebEgressArgs { pub mut: - url string + url string audio_only bool video_only bool } pub struct UpdateStreamArgs { pub mut: - add_output_urls []string + add_output_urls []string remove_output_urls []string } @@ -60,25 +60,36 @@ pub fn (mut c LivekitClient) start_web_egress(args StartWebEgressArgs) !EgressIn } pub fn (mut c LivekitClient) update_layout(egress_id string, layout string) !EgressInfo { - mut resp := c.post('twirp/livekit.Egress/UpdateLayout', {'egress_id': egress_id, 'layout': layout})! + mut resp := c.post('twirp/livekit.Egress/UpdateLayout', { + 'egress_id': egress_id + 'layout': layout + })! egress_info := json.decode[EgressInfo](resp.body)! return egress_info } pub fn (mut c LivekitClient) update_stream(egress_id string, args UpdateStreamArgs) !EgressInfo { - mut resp := c.post('twirp/livekit.Egress/UpdateStream', {'egress_id': egress_id, 'add_output_urls': args.add_output_urls, 'remove_output_urls': args.remove_output_urls})! + mut resp := c.post('twirp/livekit.Egress/UpdateStream', { + 'egress_id': egress_id + 'add_output_urls': args.add_output_urls + 'remove_output_urls': args.remove_output_urls + })! egress_info := json.decode[EgressInfo](resp.body)! return egress_info } pub fn (mut c LivekitClient) list_egress(room_name string) ![]EgressInfo { - mut resp := c.post('twirp/livekit.Egress/ListEgress', {'room_name': room_name})! + mut resp := c.post('twirp/livekit.Egress/ListEgress', { + 'room_name': room_name + })! egress_infos := json.decode[[]EgressInfo](resp.body)! return egress_infos } pub fn (mut c LivekitClient) stop_egress(egress_id string) !EgressInfo { - mut resp := c.post('twirp/livekit.Egress/StopEgress', {'egress_id': egress_id})! + mut resp := c.post('twirp/livekit.Egress/StopEgress', { + 'egress_id': egress_id + })! egress_info := json.decode[EgressInfo](resp.body)! return egress_info -} \ No newline at end of file +} diff --git a/lib/clients/livekit/ingress.v b/lib/clients/livekit/ingress.v index 7a64966a..6215c98f 100644 --- a/lib/clients/livekit/ingress.v +++ b/lib/clients/livekit/ingress.v @@ -77,30 +77,20 @@ pub mut: pub struct InputAudioState { pub mut: - mime_type string - channels u32 + mime_type string + channels u32 sample_rate u32 } pub struct CreateIngressArgs { pub mut: - name string - room_name string + name string + room_name string participant_identity string participant_name string - input_type IngressInput - audio IngressAudioOptions - video IngressVideoOptions -} - -pub struct UpdateIngressArgs { -pub mut: - name string - room_name string - participant_identity string - participant_name string - audio IngressAudioOptions - video IngressVideoOptions + input_type IngressInput + audio IngressAudioOptions + video IngressVideoOptions } pub fn (mut c LivekitClient) create_ingress(args CreateIngressArgs) !IngressInfo { @@ -109,20 +99,43 @@ pub fn (mut c LivekitClient) create_ingress(args CreateIngressArgs) !IngressInfo return ingress_info } -pub fn (mut c LivekitClient) update_ingress(ingress_id string, args UpdateIngressArgs) !IngressInfo { - mut resp := c.post('twirp/livekit.Ingress/UpdateIngress', {'ingress_id': ingress_id, ...args})! +pub struct UpdateIngressArgs { +pub mut: + ingress_id string + name string + room_name string + participant_identity string + participant_name string + audio IngressAudioOptions + video IngressVideoOptions +} + +pub fn (mut c LivekitClient) update_ingress(args UpdateIngressArgs) !IngressInfo { + mut resp := c.post('twirp/livekit.Ingress/UpdateIngress', { + 'ingress_id': args.ingress_id + 'name': args.name + 'room_name': args.room_name + 'participant_identity': args.participant_identity + 'participant_name': args.participant_name + 'audio': args.audio + 'video': args.video + })! ingress_info := json.decode[IngressInfo](resp.body)! return ingress_info } pub fn (mut c LivekitClient) list_ingress(room_name string) ![]IngressInfo { - mut resp := c.post('twirp/livekit.Ingress/ListIngress', {'room_name': room_name})! + mut resp := c.post('twirp/livekit.Ingress/ListIngress', { + 'room_name': room_name + })! ingress_infos := json.decode[[]IngressInfo](resp.body)! return ingress_infos } pub fn (mut c LivekitClient) delete_ingress(ingress_id string) !IngressInfo { - mut resp := c.post('twirp/livekit.Ingress/DeleteIngress', {'ingress_id': ingress_id})! + mut resp := c.post('twirp/livekit.Ingress/DeleteIngress', { + 'ingress_id': ingress_id + })! ingress_info := json.decode[IngressInfo](resp.body)! return ingress_info -} \ No newline at end of file +} diff --git a/lib/clients/livekit/livekit_model.v b/lib/clients/livekit/livekit_model.v index 2a49eaca..28dd15fb 100644 --- a/lib/clients/livekit/livekit_model.v +++ b/lib/clients/livekit/livekit_model.v @@ -41,4 +41,4 @@ pub fn heroscript_dumps(obj LivekitClient) !string { pub fn heroscript_loads(heroscript string) !LivekitClient { mut obj := encoderhero.decode[LivekitClient](heroscript)! return obj -} \ No newline at end of file +} diff --git a/lib/clients/livekit/participant.v b/lib/clients/livekit/participant.v index 9af04b49..743c192e 100644 --- a/lib/clients/livekit/participant.v +++ b/lib/clients/livekit/participant.v @@ -4,23 +4,23 @@ import json pub struct ParticipantInfo { pub mut: - sid string - identity string - state string - metadata string - joined_at i64 - name string - version u32 + sid string + identity string + state string + metadata string + joined_at i64 + name string + version u32 permission string - region string - publisher bool + region string + publisher bool } pub struct UpdateParticipantArgs { pub mut: - room_name string - identity string - metadata string + room_name string + identity string + metadata string permission string } @@ -33,19 +33,27 @@ pub mut: } pub fn (mut c LivekitClient) list_participants(room_name string) ![]ParticipantInfo { - mut resp := c.post('twirp/livekit.RoomService/ListParticipants', {'room': room_name})! + mut resp := c.post('twirp/livekit.RoomService/ListParticipants', { + 'room': room_name + })! participants := json.decode[[]ParticipantInfo](resp.body)! return participants } pub fn (mut c LivekitClient) get_participant(room_name string, identity string) !ParticipantInfo { - mut resp := c.post('twirp/livekit.RoomService/GetParticipant', {'room': room_name, 'identity': identity})! + mut resp := c.post('twirp/livekit.RoomService/GetParticipant', { + 'room': room_name + 'identity': identity + })! participant := json.decode[ParticipantInfo](resp.body)! return participant } pub fn (mut c LivekitClient) remove_participant(room_name string, identity string) ! { - _ = c.post('twirp/livekit.RoomService/RemoveParticipant', {'room': room_name, 'identity': identity})! + _ = c.post('twirp/livekit.RoomService/RemoveParticipant', { + 'room': room_name + 'identity': identity + })! } pub fn (mut c LivekitClient) update_participant(args UpdateParticipantArgs) ! { @@ -54,4 +62,4 @@ pub fn (mut c LivekitClient) update_participant(args UpdateParticipantArgs) ! { pub fn (mut c LivekitClient) mute_published_track(args MutePublishedTrackArgs) ! { _ = c.post('twirp/livekit.RoomService/MutePublishedTrack', args)! -} \ No newline at end of file +} diff --git a/lib/clients/livekit/play.v b/lib/clients/livekit/play.v index 489472ca..6e24fad9 100644 --- a/lib/clients/livekit/play.v +++ b/lib/clients/livekit/play.v @@ -13,19 +13,19 @@ pub fn play(mut plbook PlayBook) ! { if plbook.exists_once(filter: 'livekit.init') { mut action := plbook.get(filter: 'livekit.init')! mut p := action.params - + name := texttools.name_fix(p.get_default('name', 'default')!) url := p.get('url')! api_key := p.get('api_key')! api_secret := p.get('api_secret')! - + mut client := LivekitClient{ - name: name - url: url - api_key: api_key + name: name + url: url + api_key: api_key api_secret: api_secret } - + set(client)! console.print_header('LiveKit client "${name}" configured') action.done = true @@ -35,22 +35,22 @@ pub fn play(mut plbook PlayBook) ! { mut room_create_actions := plbook.find(filter: 'livekit.room_create')! for mut action in room_create_actions { mut p := action.params - + client_name := texttools.name_fix(p.get_default('client', 'default')!) room_name := p.get('name')! empty_timeout := p.get_u32_default('empty_timeout', 300)! max_participants := p.get_u32_default('max_participants', 50)! metadata := p.get_default('metadata', '')! - + mut client := get(name: client_name)! - + room := client.create_room( - name: room_name - empty_timeout: empty_timeout + name: room_name + empty_timeout: empty_timeout max_participants: max_participants - metadata: metadata + metadata: metadata )! - + console.print_header('Room "${room_name}" created successfully') action.done = true } @@ -59,13 +59,13 @@ pub fn play(mut plbook PlayBook) ! { mut room_delete_actions := plbook.find(filter: 'livekit.room_delete')! for mut action in room_delete_actions { mut p := action.params - + client_name := texttools.name_fix(p.get_default('client', 'default')!) room_name := p.get('name')! - + mut client := get(name: client_name)! client.delete_room(room_name)! - + console.print_header('Room "${room_name}" deleted successfully') action.done = true } @@ -74,14 +74,14 @@ pub fn play(mut plbook PlayBook) ! { mut participant_remove_actions := plbook.find(filter: 'livekit.participant_remove')! for mut action in participant_remove_actions { mut p := action.params - + client_name := texttools.name_fix(p.get_default('client', 'default')!) room_name := p.get('room')! identity := p.get('identity')! - + mut client := get(name: client_name)! client.remove_participant(room_name, identity)! - + console.print_header('Participant "${identity}" removed from room "${room_name}"') action.done = true } @@ -90,21 +90,21 @@ pub fn play(mut plbook PlayBook) ! { mut participant_mute_actions := plbook.find(filter: 'livekit.participant_mute')! for mut action in participant_mute_actions { mut p := action.params - + client_name := texttools.name_fix(p.get_default('client', 'default')!) room_name := p.get('room')! identity := p.get('identity')! track_sid := p.get('track_sid')! muted := p.get_default_true('muted') - + mut client := get(name: client_name)! client.mute_published_track( room_name: room_name - identity: identity + identity: identity track_sid: track_sid - muted: muted + muted: muted )! - + status := if muted { 'muted' } else { 'unmuted' } console.print_header('Track "${track_sid}" ${status} for participant "${identity}"') action.done = true @@ -114,17 +114,17 @@ pub fn play(mut plbook PlayBook) ! { mut room_update_actions := plbook.find(filter: 'livekit.room_update')! for mut action in room_update_actions { mut p := action.params - + client_name := texttools.name_fix(p.get_default('client', 'default')!) room_name := p.get('room')! metadata := p.get('metadata')! - + mut client := get(name: client_name)! client.update_room_metadata( room_name: room_name - metadata: metadata + metadata: metadata )! - + console.print_header('Room "${room_name}" metadata updated') action.done = true } @@ -133,7 +133,7 @@ pub fn play(mut plbook PlayBook) ! { mut token_create_actions := plbook.find(filter: 'livekit.token_create')! for mut action in token_create_actions { mut p := action.params - + client_name := texttools.name_fix(p.get_default('client', 'default')!) identity := p.get('identity')! name := p.get_default('name', identity)! @@ -142,26 +142,26 @@ pub fn play(mut plbook PlayBook) ! { can_publish := p.get_default_false('can_publish') can_subscribe := p.get_default_true('can_subscribe') can_publish_data := p.get_default_false('can_publish_data') - + mut client := get(name: client_name)! - + mut token := client.new_access_token( identity: identity - name: name - ttl: ttl + name: name + ttl: ttl )! - + token.add_video_grant(VideoGrant{ - room: room - room_join: true - can_publish: can_publish - can_subscribe: can_subscribe + room: room + room_join: true + can_publish: can_publish + can_subscribe: can_subscribe can_publish_data: can_publish_data }) - + jwt := token.to_jwt()! console.print_header('Access token generated for "${identity}"') console.print_debug('Token: ${jwt}') action.done = true } -} \ No newline at end of file +} diff --git a/lib/clients/livekit/room.v b/lib/clients/livekit/room.v index 6a07d5a9..7914898f 100644 --- a/lib/clients/livekit/room.v +++ b/lib/clients/livekit/room.v @@ -5,25 +5,25 @@ import net.http pub struct Room { pub mut: - sid string - name string - empty_timeout u32 - max_participants u32 - creation_time i64 - turn_password string - enabled_codecs []string - metadata string - num_participants u32 + sid string + name string + empty_timeout u32 + max_participants u32 + creation_time i64 + turn_password string + enabled_codecs []string + metadata string + num_participants u32 num_connected_participants u32 - active_recording bool + active_recording bool } pub struct CreateRoomArgs { pub mut: - name string - empty_timeout u32 - max_participants u32 - metadata string + name string + empty_timeout u32 + max_participants u32 + metadata string } pub struct UpdateRoomMetadataArgs { @@ -39,9 +39,11 @@ pub fn (mut c LivekitClient) create_room(args CreateRoomArgs) !Room { } pub fn (mut c LivekitClient) delete_room(room_name string) ! { - _ = c.post('twirp/livekit.RoomService/DeleteRoom', {'room': room_name})! + _ = c.post('twirp/livekit.RoomService/DeleteRoom', { + 'room': room_name + })! } pub fn (mut c LivekitClient) update_room_metadata(args UpdateRoomMetadataArgs) ! { _ = c.post('twirp/livekit.RoomService/UpdateRoomMetadata', args)! -} \ No newline at end of file +} diff --git a/lib/clients/livekit/room_model.v b/lib/clients/livekit/room_model.v index 4586b545..f99d06cd 100644 --- a/lib/clients/livekit/room_model.v +++ b/lib/clients/livekit/room_model.v @@ -15,19 +15,19 @@ pub: unix_micro string } -pub struct Room { -pub: - active_recording bool - creation_time string - departure_timeout int - empty_timeout int - enabled_codecs []Codec - max_participants int - metadata string - name string - num_participants int - num_publishers int - sid string - turn_password string - version Version -} +// pub struct Room { +// pub: +// active_recording bool +// creation_time string +// departure_timeout int +// empty_timeout int +// enabled_codecs []Codec +// max_participants int +// metadata string +// name string +// num_participants int +// num_publishers int +// sid string +// turn_password string +// version Version +// } diff --git a/lib/clients/livekit/token.v b/lib/clients/livekit/token.v index 9a01227e..324cd4fe 100644 --- a/lib/clients/livekit/token.v +++ b/lib/clients/livekit/token.v @@ -5,33 +5,33 @@ import time pub struct AccessToken { pub mut: - api_key string - api_secret string - identity string - name string - ttl int + api_key string + api_secret string + identity string + name string + ttl int video_grant VideoGrant } pub struct VideoGrant { pub mut: - room_create bool - room_admin bool - room_join bool - room_list bool - can_publish bool - can_subscribe bool + room_create bool + room_admin bool + room_join bool + room_list bool + can_publish bool + can_subscribe bool can_publish_data bool - room string + room string } pub fn (mut c LivekitClient) new_access_token(identity string, name string, ttl int) !AccessToken { return AccessToken{ - api_key: c.api_key + api_key: c.api_key api_secret: c.api_secret - identity: identity - name: name - ttl: ttl + identity: identity + name: name + ttl: ttl } } @@ -49,4 +49,4 @@ pub fn (t AccessToken) to_jwt() !string { claims.name = t.name claims.video = t.video_grant return jwt.encode(claims, t.api_secret, .hs256) -} \ No newline at end of file +} diff --git a/lib/clients/livekit/token_model.v b/lib/clients/livekit/token_model.v index 4cb33d67..bfb40bb7 100644 --- a/lib/clients/livekit/token_model.v +++ b/lib/clients/livekit/token_model.v @@ -18,60 +18,60 @@ pub mut: name string } -// VideoGrant struct placeholder -pub struct VideoGrant { -pub mut: - room string - room_join bool @[json: 'roomJoin'] - room_list bool @[json: 'roomList'] - can_publish bool @[json: 'canPublish'] - can_publish_data bool @[json: 'canPublishData'] - can_subscribe bool @[json: 'canSubscribe'] -} +// // VideoGrant struct placeholder +// pub struct VideoGrant { +// pub mut: +// room string +// room_join bool @[json: 'roomJoin'] +// room_list bool @[json: 'roomList'] +// can_publish bool @[json: 'canPublish'] +// can_publish_data bool @[json: 'canPublishData'] +// can_subscribe bool @[json: 'canSubscribe'] +// } // SIPGrant struct placeholder struct SIPGrant {} -// AccessToken class -pub struct AccessToken { -mut: - api_key string - api_secret string - grants ClaimGrants - identity string - ttl int -} +// // AccessToken class +// pub struct AccessToken { +// mut: +// api_key string +// api_secret string +// grants ClaimGrants +// identity string +// ttl int +// } // Method to add a video grant to the token -pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) { - token.grants.video = grant -} +// pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) { +// token.grants.video = grant +// } -// Method to generate a JWT token -pub fn (token AccessToken) to_jwt() !string { - // Create JWT payload - payload := json.encode(token.grants) +// // Method to generate a JWT token +// pub fn (token AccessToken) to_jwt() !string { +// // Create JWT payload +// payload := json.encode(token.grants) - println('payload: ${payload}') +// println('payload: ${payload}') - // Create JWT header - header := '{"alg":"HS256","typ":"JWT"}' +// // Create JWT header +// header := '{"alg":"HS256","typ":"JWT"}' - // Encode header and payload in base64 - header_encoded := base64.url_encode_str(header) - payload_encoded := base64.url_encode_str(payload) +// // Encode header and payload in base64 +// header_encoded := base64.url_encode_str(header) +// payload_encoded := base64.url_encode_str(payload) - // Create the unsigned token - unsigned_token := '${header_encoded}.${payload_encoded}' +// // Create the unsigned token +// unsigned_token := '${header_encoded}.${payload_encoded}' - // Create the HMAC-SHA256 signature - signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, - sha256.block_size) +// // Create the HMAC-SHA256 signature +// signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, +// sha256.block_size) - // Encode the signature in base64 - signature_encoded := base64.url_encode(signature) +// // Encode the signature in base64 +// signature_encoded := base64.url_encode(signature) - // Create the final JWT - jwt := '${unsigned_token}.${signature_encoded}' - return jwt -} +// // Create the final JWT +// jwt := '${unsigned_token}.${signature_encoded}' +// return jwt +// } diff --git a/lib/clients/mycelium_rpc/mycelium_rpc_model.v b/lib/clients/mycelium_rpc/mycelium_rpc_model.v index 92c99f22..82f19553 100644 --- a/lib/clients/mycelium_rpc/mycelium_rpc_model.v +++ b/lib/clients/mycelium_rpc/mycelium_rpc_model.v @@ -15,8 +15,8 @@ pub const default_url = 'http://localhost:8990' @[heap] pub struct MyceliumRPC { pub mut: - name string = 'default' - url string = default_url // RPC server URL + name string = 'default' + url string = default_url // RPC server URL // rpc_client ?&jsonrpc.Client @[skip] } diff --git a/lib/clients/traefik/factory.v b/lib/clients/traefik/factory.v index 2c6a7878..03f39b9a 100644 --- a/lib/clients/traefik/factory.v +++ b/lib/clients/traefik/factory.v @@ -22,16 +22,16 @@ pub fn new(args FactoryArgs) !&TraefikManager { } mut redis := redisclient.core_get(redisclient.get_redis_url(args.redis_url)!)! - + mut manager := &TraefikManager{ - name: name - redis: redis + name: name + redis: redis config: osal_traefik.new_traefik_config() } - + // Set redis connection in config manager.config.redis = redis - + traefik_managers[name] = manager return manager } @@ -48,4 +48,4 @@ pub fn default() !&TraefikManager { return new(name: 'default')! } return get(name: 'default')! -} \ No newline at end of file +} diff --git a/lib/clients/traefik/manager.v b/lib/clients/traefik/manager.v index b5466c02..3d2cd256 100644 --- a/lib/clients/traefik/manager.v +++ b/lib/clients/traefik/manager.v @@ -23,9 +23,9 @@ pub mut: @[params] pub struct RouterAddArgs { pub mut: - name string @[required] - rule string @[required] - service string @[required] + name string @[required] + rule string @[required] + service string @[required] entrypoints []string middlewares []string tls bool @@ -43,8 +43,8 @@ pub mut: @[params] pub struct MiddlewareAddArgs { pub mut: - name string @[required] - typ string @[required] + name string @[required] + typ string @[required] settings map[string]string } @@ -78,7 +78,7 @@ pub fn (mut tm TraefikManager) service_add(args ServiceAddArgs) ! { } tm.config.add_service( - name: texttools.name_fix(args.name) + name: texttools.name_fix(args.name) load_balancer: osal_traefik.LoadBalancerConfig{ servers: servers } @@ -101,7 +101,7 @@ pub fn (mut tm TraefikManager) entrypoint_add(args EntryPointAddArgs) ! { address: args.address tls: args.tls } - + // Check if entrypoint already exists for mut ep in tm.entrypoints { if ep.name == entrypoint.name { @@ -110,7 +110,7 @@ pub fn (mut tm TraefikManager) entrypoint_add(args EntryPointAddArgs) ! { return } } - + tm.entrypoints << entrypoint } @@ -118,7 +118,7 @@ pub fn (mut tm TraefikManager) entrypoint_add(args EntryPointAddArgs) ! { pub fn (mut tm TraefikManager) apply() ! { // Apply dynamic configuration (routers, services, middlewares) tm.config.set()! - + // Store entrypoints separately (these would typically be in static config) for ep in tm.entrypoints { tm.redis.hset('traefik:entrypoints', ep.name, '${ep.address}|${ep.tls}')! @@ -135,7 +135,7 @@ pub fn (mut tm TraefikManager) clear() ! { tm.config = osal_traefik.new_traefik_config() tm.config.redis = tm.redis tm.entrypoints = []EntryPointConfig{} - + // Clear Redis keys keys := tm.redis.keys('traefik/*')! for key in keys { @@ -151,4 +151,4 @@ pub fn (mut tm TraefikManager) status() !map[string]int { 'middlewares': tm.config.middlewares.len 'entrypoints': tm.entrypoints.len } -} \ No newline at end of file +} diff --git a/lib/clients/traefik/play.v b/lib/clients/traefik/play.v index 991ffc77..66e8189d 100644 --- a/lib/clients/traefik/play.v +++ b/lib/clients/traefik/play.v @@ -14,44 +14,44 @@ pub fn play(mut plbook PlayBook) ! { // Process entrypoints first play_entrypoints(mut plbook, mut manager)! - + // Process services (before routers that might reference them) play_services(mut plbook, mut manager)! - + // Process middlewares (before routers that might reference them) play_middlewares(mut plbook, mut manager)! - + // Process routers play_routers(mut plbook, mut manager)! - + // Apply all configurations to Redis manager.apply()! - + console.print_debug('Traefik configuration applied successfully') } fn play_entrypoints(mut plbook PlayBook, mut manager TraefikManager) ! { entrypoint_actions := plbook.find(filter: 'traefik.entrypoint')! - + for mut action in entrypoint_actions { mut p := action.params - + manager.entrypoint_add( name: p.get('name')! address: p.get('address')! tls: p.get_default_false('tls') )! - + action.done = true } } fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! { router_actions := plbook.find(filter: 'traefik.router')! - + for mut action in router_actions { mut p := action.params - + // Parse entrypoints list mut entrypoints := []string{} if entrypoints_str := p.get_default('entrypoints', '') { @@ -59,7 +59,7 @@ fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! { entrypoints = entrypoints_str.split(',').map(it.trim_space()) } } - + // Parse middlewares list mut middlewares := []string{} if middlewares_str := p.get_default('middlewares', '') { @@ -67,7 +67,7 @@ fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! { middlewares = middlewares_str.split(',').map(it.trim_space()) } } - + manager.router_add( name: p.get('name')! rule: p.get('rule')! @@ -77,42 +77,42 @@ fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! { tls: p.get_default_false('tls') priority: p.get_int_default('priority', 0) )! - + action.done = true } } fn play_services(mut plbook PlayBook, mut manager TraefikManager) ! { service_actions := plbook.find(filter: 'traefik.service')! - + for mut action in service_actions { mut p := action.params - + // Parse servers list servers_str := p.get('servers')! servers := servers_str.split(',').map(it.trim_space()) - + manager.service_add( name: p.get('name')! servers: servers strategy: p.get_default('strategy', 'wrr')! )! - + action.done = true } } fn play_middlewares(mut plbook PlayBook, mut manager TraefikManager) ! { middleware_actions := plbook.find(filter: 'traefik.middleware')! - + for mut action in middleware_actions { mut p := action.params - + // Build settings map from remaining parameters mut settings := map[string]string{} - + middleware_type := p.get('type')! - + // Handle common middleware types match middleware_type { 'basicAuth' { @@ -156,13 +156,13 @@ fn play_middlewares(mut plbook PlayBook, mut manager TraefikManager) ! { } } } - + manager.middleware_add( name: p.get('name')! typ: middleware_type settings: settings )! - + action.done = true } -} \ No newline at end of file +} diff --git a/lib/core/generator/generic/scanner.v b/lib/core/generator/generic/scanner.v index 02510d99..3fbd4fd3 100644 --- a/lib/core/generator/generic/scanner.v +++ b/lib/core/generator/generic/scanner.v @@ -16,9 +16,9 @@ pub fn scan(args_ GeneratorArgs) ! { // now walk over all directories, find .heroscript mut pathroot := pathlib.get_dir(path: args.path, create: false)! mut plist := pathroot.list( - recursive: true + recursive: true ignore_default: false - regex: ['.heroscript'] + regex: ['.heroscript'] )! for mut p in plist.paths { diff --git a/lib/core/pathlib/path_list.v b/lib/core/pathlib/path_list.v index 2057167c..cdd68b84 100644 --- a/lib/core/pathlib/path_list.v +++ b/lib/core/pathlib/path_list.v @@ -8,12 +8,12 @@ import freeflowuniverse.herolib.ui.console @[params] pub struct ListArgs { pub mut: - regex []string - recursive bool = true + regex []string + recursive bool = true ignore_default bool = true // ignore files starting with . and _ - include_links bool // wether to include links in list - dirs_only bool - files_only bool + include_links bool // wether to include links in list + dirs_only bool + files_only bool } // the result of pathlist @@ -54,12 +54,12 @@ pub fn (mut path Path) list(args_ ListArgs) !PathList { r << re } mut args := ListArgsInternal{ - regex: r - recursive: args_.recursive + regex: r + recursive: args_.recursive ignore_default: args_.ignore_default - dirs_only: args_.dirs_only - files_only: args_.files_only - include_links: args_.include_links + dirs_only: args_.dirs_only + files_only: args_.files_only + include_links: args_.include_links } paths := path.list_internal(args)! mut pl := PathList{ @@ -72,12 +72,12 @@ pub fn (mut path Path) list(args_ ListArgs) !PathList { @[params] pub struct ListArgsInternal { mut: - regex []regex.RE // only put files in which follow one of the regexes - recursive bool = true + regex []regex.RE // only put files in which follow one of the regexes + recursive bool = true ignore_default bool = true // ignore files starting with . and _ - dirs_only bool - files_only bool - include_links bool + dirs_only bool + files_only bool + include_links bool } fn (mut path Path) list_internal(args ListArgsInternal) ![]Path { diff --git a/lib/osal/core/ssh_key.v b/lib/osal/core/ssh_key.v index 9fabb996..f00d8085 100644 --- a/lib/osal/core/ssh_key.v +++ b/lib/osal/core/ssh_key.v @@ -40,12 +40,6 @@ pub fn (key SSHKey) private_key() !string { return content } - -module core - -import freeflowuniverse.herolib.core.pathlib -import os - @[params] pub struct SSHConfig { pub: diff --git a/lib/osal/linux/factory.v b/lib/osal/linux/factory.v index ba22a2ab..d97d0db9 100644 --- a/lib/osal/linux/factory.v +++ b/lib/osal/linux/factory.v @@ -24,6 +24,6 @@ pub: pub fn new(args LinuxNewArgs) !LinuxFactory { mut t := LinuxFactory{ username: args.username - } + } return t } diff --git a/lib/osal/linux/play.v b/lib/osal/linux/play.v index ccf2c82b..0e52fdd4 100644 --- a/lib/osal/linux/play.v +++ b/lib/osal/linux/play.v @@ -11,34 +11,34 @@ pub fn play(mut plbook PlayBook) ! { // Process user_create actions play_user_create(mut plbook, mut lf)! - + // Process user_delete actions play_user_delete(mut plbook, mut lf)! - + // Process sshkey_create actions play_sshkey_create(mut plbook, mut lf)! - + // Process sshkey_delete actions play_sshkey_delete(mut plbook, mut lf)! } fn play_user_create(mut plbook PlayBook, mut lf LinuxFactory) ! { mut actions := plbook.find(filter: 'usermgmt.user_create')! - + for mut action in actions { mut p := action.params - + mut args := UserCreateArgs{ - name: p.get('name')! - giteakey: p.get_default('giteakey', '')! - giteaurl: p.get_default('giteaurl', '')! - passwd: p.get_default('passwd', '')! + name: p.get('name')! + giteakey: p.get_default('giteakey', '')! + giteaurl: p.get_default('giteaurl', '')! + passwd: p.get_default('passwd', '')! description: p.get_default('description', '')! - email: p.get_default('email', '')! - tel: p.get_default('tel', '')! - sshkey: p.get_default('sshkey', '')! // SSH public key + email: p.get_default('email', '')! + tel: p.get_default('tel', '')! + sshkey: p.get_default('sshkey', '')! // SSH public key } - + lf.user_create(args)! action.done = true } @@ -46,14 +46,14 @@ fn play_user_create(mut plbook PlayBook, mut lf LinuxFactory) ! { fn play_user_delete(mut plbook PlayBook, mut lf LinuxFactory) ! { mut actions := plbook.find(filter: 'usermgmt.user_delete')! - + for mut action in actions { mut p := action.params - + mut args := UserDeleteArgs{ name: p.get('name')! } - + lf.user_delete(args)! action.done = true } @@ -61,17 +61,17 @@ fn play_user_delete(mut plbook PlayBook, mut lf LinuxFactory) ! { fn play_sshkey_create(mut plbook PlayBook, mut lf LinuxFactory) ! { mut actions := plbook.find(filter: 'usermgmt.sshkey_create')! - + for mut action in actions { mut p := action.params - + mut args := SSHKeyCreateArgs{ - username: p.get('username')! + username: p.get('username')! sshkey_name: p.get('sshkey_name')! - sshkey_pub: p.get_default('sshkey_pub', '')! + sshkey_pub: p.get_default('sshkey_pub', '')! sshkey_priv: p.get_default('sshkey_priv', '')! } - + lf.sshkey_create(args)! action.done = true } @@ -79,16 +79,16 @@ fn play_sshkey_create(mut plbook PlayBook, mut lf LinuxFactory) ! { fn play_sshkey_delete(mut plbook PlayBook, mut lf LinuxFactory) ! { mut actions := plbook.find(filter: 'usermgmt.sshkey_delete')! - + for mut action in actions { mut p := action.params - + mut args := SSHKeyDeleteArgs{ - username: p.get('username')! + username: p.get('username')! sshkey_name: p.get('sshkey_name')! } - + lf.sshkey_delete(args)! action.done = true } -} \ No newline at end of file +} diff --git a/lib/osal/linux/templates/profile_sshagent.sh b/lib/osal/linux/templates/profile_sshagent.sh new file mode 100644 index 00000000..237f8cfb --- /dev/null +++ b/lib/osal/linux/templates/profile_sshagent.sh @@ -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 \ No newline at end of file diff --git a/lib/osal/linux/templates/user_add.sh b/lib/osal/linux/templates/user_add.sh index 1ce713bf..0308f09b 100644 --- a/lib/osal/linux/templates/user_add.sh +++ b/lib/osal/linux/templates/user_add.sh @@ -57,19 +57,4 @@ chown root:ourworld /code chmod 2775 /code # rwx for user+group, SGID bit so new files inherit group echo "✅ /code prepared (group=ourworld, rwx for group, SGID bit set)" -# --- create login helper script for ssh-agent --- -PROFILE_SCRIPT="$USERHOME/.profile_sshagent" -cat > "$PROFILE_SCRIPT" <<'EOF' -# Auto-start ssh-agent if not running -SSH_AGENT_PID_FILE="$HOME/.ssh/agent.pid" -SSH_AUTH_SOCK_FILE="$HOME/.ssh/agent.sock" - -chown "$NEWUSER":"$NEWUSER" "$PROFILE_SCRIPT" -chmod 644 "$PROFILE_SCRIPT" - -# --- source it on login --- -if ! grep -q ".profile_sshagent" "$USERHOME/.bashrc"; then - echo "[ -f ~/.profile_sshagent ] && source ~/.profile_sshagent" >> "$USERHOME/.bashrc" -fi - echo "🎉 Setup complete for user $NEWUSER" \ No newline at end of file diff --git a/lib/osal/linux/user_mgmt.v b/lib/osal/linux/user_mgmt.v index 207256e2..9468b3ce 100644 --- a/lib/osal/linux/user_mgmt.v +++ b/lib/osal/linux/user_mgmt.v @@ -119,7 +119,9 @@ pub fn (mut lf LinuxFactory) sshkey_create(args SSHKeyCreateArgs) ! { } else { // Generate new SSH key (modern ed25519) key_path := '${ssh_dir}/${args.sshkey_name}' - osal.exec(cmd: 'ssh-keygen -t ed25519 -f ${key_path} -N "" -C "${args.username}@$(hostname)"')! + osal.exec( + cmd: 'ssh-keygen -t ed25519 -f ${key_path} -N "" -C "${args.username}@$(hostname)"' + )! console.print_green('✅ New SSH key generated for ${args.username}') } @@ -175,12 +177,12 @@ fn (mut lf LinuxFactory) save_user_config(args UserCreateArgs) ! { } new_config := UserConfig{ - name: args.name - giteakey: args.giteakey - giteaurl: args.giteaurl - email: args.email + name: args.name + giteakey: args.giteakey + giteaurl: args.giteaurl + email: args.email description: args.description - tel: args.tel + tel: args.tel } if found_idx >= 0 { @@ -201,7 +203,7 @@ fn (mut lf LinuxFactory) remove_user_config(username string) ! { config_path := '${config_dir}/myconfig.json' if !os.exists(config_path) { - return // Nothing to remove + return } content := osal.file_read(config_path)! @@ -243,7 +245,9 @@ fn (mut lf LinuxFactory) create_user_system(args UserCreateArgs) ! { // Ensure ourworld group exists group_check := osal.exec(cmd: 'getent group ourworld', raise_error: false) or { - osal.Job{ exit_code: 1 } + osal.Job{ + exit_code: 1 + } } if group_check.exit_code != 0 { console.print_item('➕ Creating group ourworld') @@ -284,58 +288,9 @@ fn (mut lf LinuxFactory) create_ssh_agent_profile(username string) ! { user_home := '/home/${username}' profile_script := '${user_home}/.profile_sshagent' - script_content := '# Auto-start ssh-agent if not running -SSH_AGENT_PID_FILE="$HOME/.ssh/agent.pid" -SSH_AUTH_SOCK_FILE="$HOME/.ssh/agent.sock" + // script_content := '' -# Function to start ssh-agent -start_ssh_agent() { - mkdir -p "$HOME/.ssh" - chmod 700 "$HOME/.ssh" - - # Start ssh-agent and save connection info - ssh-agent -s > "$SSH_AGENT_PID_FILE" - source "$SSH_AGENT_PID_FILE" - - # Save socket path for future sessions - echo "$SSH_AUTH_SOCK" > "$SSH_AUTH_SOCK_FILE" - - # Load all private keys found in ~/.ssh - if [ -d "$HOME/.ssh" ]; then - for KEY in "$HOME"/.ssh/*; do - if [ -f "$KEY" ] && [ ! "${KEY##*.}" = "pub" ] && grep -q "PRIVATE KEY" "$KEY" 2>/dev/null; then - 'ssh-' + 'add "$KEY" >/dev/null 2>&1 && echo "🔑 Loaded key: $(basename $KEY)"' - fi - done - fi -} - -# Check if ssh-agent is running -if [ -f "$SSH_AGENT_PID_FILE" ]; then - source "$SSH_AGENT_PID_FILE" >/dev/null 2>&1 - # Test if agent is responsive - if ! ('ssh-' + 'add -l >/dev/null 2>&1'); then - start_ssh_agent - else - # Agent is running, restore socket path - if [ -f "$SSH_AUTH_SOCK_FILE" ]; then - export SSH_AUTH_SOCK=$(cat "$SSH_AUTH_SOCK_FILE") - fi - fi -else - start_ssh_agent -fi - -# For interactive shells -if [[ $- == *i* ]]; then - echo "🔑 SSH Agent ready at $SSH_AUTH_SOCK" - # Show loaded keys - KEY_COUNT=$('ssh-' + 'add -l 2>/dev/null | wc -l') - if [ "$KEY_COUNT" -gt 0 ]; then - echo "🔑 $KEY_COUNT SSH key(s) loaded" - fi -fi -' + panic('implement') osal.file_write(profile_script, script_content)! osal.exec(cmd: 'chown ${username}:${username} ${profile_script}')! @@ -351,4 +306,4 @@ fi } console.print_green('✅ SSH agent profile created for ${username}') -} \ No newline at end of file +} diff --git a/lib/osal/sshagent/agent.v b/lib/osal/sshagent/agent.v index 6f27a057..bc6d19e6 100644 --- a/lib/osal/sshagent/agent.v +++ b/lib/osal/sshagent/agent.v @@ -3,27 +3,27 @@ module sshagent // Check if SSH agent is properly configured and all is good fn agent_check(mut agent SSHAgent) ! { console.print_header('SSH Agent Check') - + // Ensure single agent is running agent.ensure_single_agent()! - + // Get diagnostics diag := agent.diagnostics() - + for key, value in diag { console.print_item('${key}: ${value}') } - + // Verify agent is responsive if !agent.is_agent_responsive() { return error('SSH agent is not responsive') } - + // Load all existing keys from ~/.ssh that aren't loaded yet agent.init()! - + console.print_green('✓ SSH Agent is properly configured and running') - + // Show loaded keys loaded_keys := agent.keys_loaded()! console.print_item('Loaded keys: ${loaded_keys.len}') @@ -35,17 +35,17 @@ fn agent_check(mut agent SSHAgent) ! { // Create a new SSH key fn sshkey_create(mut agent SSHAgent, name string, passphrase string) ! { console.print_header('Creating SSH key: ${name}') - + // Check if key already exists if agent.exists(name: name) { console.print_debug('SSH key "${name}" already exists') return } - + // Generate new key mut key := agent.generate(name, passphrase)! console.print_green('✓ SSH key "${name}" created successfully') - + // Automatically load the key key.load()! console.print_green('✓ SSH key "${name}" loaded into agent') @@ -54,13 +54,13 @@ fn sshkey_create(mut agent SSHAgent, name string, passphrase string) ! { // Delete an SSH key fn sshkey_delete(mut agent SSHAgent, name string) ! { console.print_header('Deleting SSH key: ${name}') - + // Check if key exists mut key := agent.get(name: name) or { console.print_debug('SSH key "${name}" does not exist') return } - + // Get key paths before deletion key_path := key.keypath() or { console.print_debug('Private key path not available for "${name}"') @@ -70,12 +70,12 @@ fn sshkey_delete(mut agent SSHAgent, name string) ! { console.print_debug('Public key path not available for "${name}"') return } - + // Remove from agent if loaded (temporarily disabled due to reset_ssh panic) // if key.loaded { // key.forget()! // } - + // Delete key files if key_path.exists() { key_path.delete()! @@ -85,26 +85,24 @@ fn sshkey_delete(mut agent SSHAgent, name string) ! { key_pub_path.delete()! console.print_debug('Deleted public key: ${key_pub_path.path}') } - + // Reinitialize agent to update key list agent.init()! - + console.print_green('✓ SSH key "${name}" deleted successfully') } // Load SSH key into agent fn sshkey_load(mut agent SSHAgent, name string) ! { console.print_header('Loading SSH key: ${name}') - - mut key := agent.get(name: name) or { - return error('SSH key "${name}" not found') - } - + + mut key := agent.get(name: name) or { return error('SSH key "${name}" not found') } + if key.loaded { console.print_debug('SSH key "${name}" is already loaded') return } - + key.load()! console.print_green('✓ SSH key "${name}" loaded into agent') } @@ -112,28 +110,22 @@ fn sshkey_load(mut agent SSHAgent, name string) ! { // Check if SSH key is valid fn sshkey_check(mut agent SSHAgent, name string) ! { console.print_header('Checking SSH key: ${name}') - - mut key := agent.get(name: name) or { - return error('SSH key "${name}" not found') - } - + + mut key := agent.get(name: name) or { return error('SSH key "${name}" not found') } + // Check if key files exist - key_path := key.keypath() or { - return error('Private key file not found for "${name}"') - } - - key_pub_path := key.keypath_pub() or { - return error('Public key file not found for "${name}"') - } - + key_path := key.keypath() or { return error('Private key file not found for "${name}"') } + + key_pub_path := key.keypath_pub() or { return error('Public key file not found for "${name}"') } + if !key_path.exists() { return error('Private key file does not exist: ${key_path.path}') } - + if !key_pub_path.exists() { return error('Public key file does not exist: ${key_pub_path.path}') } - + // Verify key can be loaded (if not already loaded) if !key.loaded { // Test load without actually loading (since forget is disabled) @@ -142,70 +134,68 @@ fn sshkey_check(mut agent SSHAgent, name string) ! { return error('Invalid private key format in "${name}"') } } - + console.print_item('Key type: ${key.cat}') console.print_item('Loaded: ${key.loaded}') console.print_item('Email: ${key.email}') console.print_item('Private key: ${key_path.path}') console.print_item('Public key: ${key_pub_path.path}') - + console.print_green('✓ SSH key "${name}" is valid') } // Copy private key to remote node fn remote_copy(mut agent SSHAgent, node_addr string, key_name string) ! { console.print_header('Copying SSH key "${key_name}" to ${node_addr}') - + // Get the key - mut key := agent.get(name: key_name) or { - return error('SSH key "${key_name}" not found') - } - + mut key := agent.get(name: key_name) or { return error('SSH key "${key_name}" not found') } + // Create builder node mut b := builder.new()! mut node := b.node_new(ipaddr: node_addr)! - + // Get private key content key_path := key.keypath()! if !key_path.exists() { return error('Private key file not found: ${key_path.path}') } - + private_key_content := key_path.read()! - + // Get home directory on remote home_dir := node.environ_get()!['HOME'] or { return error('Could not determine HOME directory on remote node') } - + remote_ssh_dir := '${home_dir}/.ssh' remote_key_path := '${remote_ssh_dir}/${key_name}' - + // Ensure .ssh directory exists with correct permissions node.exec_silent('mkdir -p ${remote_ssh_dir}')! node.exec_silent('chmod 700 ${remote_ssh_dir}')! - + // Copy private key to remote node.file_write(remote_key_path, private_key_content)! node.exec_silent('chmod 600 ${remote_key_path}')! - + // Generate public key on remote node.exec_silent('ssh-keygen -y -f ${remote_key_path} > ${remote_key_path}.pub')! node.exec_silent('chmod 644 ${remote_key_path}.pub')! - + console.print_green('✓ SSH key "${key_name}" copied to ${node_addr}') } // Add public key to authorized_keys on remote node fn remote_auth(mut agent SSHAgent, node_addr string, key_name string) ! { console.print_header('Adding SSH key "${key_name}" to authorized_keys on ${node_addr}') - + // Create builder node mut b := builder.new()! mut node := b.node_new(ipaddr: node_addr)! - + // Use existing builder integration agent.push_key_to_node(mut node, key_name)! - + console.print_green('✓ SSH key "${key_name}" added to authorized_keys on ${node_addr}') -} \ No newline at end of file +} diff --git a/lib/osal/sshagent/builder_integration.v b/lib/osal/sshagent/builder_integration.v index f85bf3d9..7e38b10e 100644 --- a/lib/osal/sshagent/builder_integration.v +++ b/lib/osal/sshagent/builder_integration.v @@ -10,27 +10,27 @@ pub fn (mut agent SSHAgent) push_key_to_node(mut node builder.Node, key_name str if node_info['category'] != 'ssh' { return error('Can only push keys to SSH nodes, got: ${node_info['category']}') } - + // Find the key mut key := agent.get(name: key_name) or { return error('SSH key "${key_name}" not found in agent') } - + // Get public key content pubkey_content := key.keypub()! - + // Check if authorized_keys file exists on remote home_dir := node.environ_get()!['HOME'] or { return error('Could not determine HOME directory on remote node') } - + ssh_dir := '${home_dir}/.ssh' authorized_keys_path := '${ssh_dir}/authorized_keys' - + // Ensure .ssh directory exists with correct permissions node.exec_silent('mkdir -p ${ssh_dir}')! node.exec_silent('chmod 700 ${ssh_dir}')! - + // Check if key already exists if node.file_exists(authorized_keys_path) { existing_keys := node.file_read(authorized_keys_path)! @@ -39,11 +39,11 @@ pub fn (mut agent SSHAgent) push_key_to_node(mut node builder.Node, key_name str return } } - + // Add key to authorized_keys node.exec_silent('echo "${pubkey_content}" >> ${authorized_keys_path}')! node.exec_silent('chmod 600 ${authorized_keys_path}')! - + console.print_debug('SSH key "${key_name}" successfully pushed to node') } @@ -54,31 +54,31 @@ pub fn (mut agent SSHAgent) remove_key_from_node(mut node builder.Node, key_name if node_info['category'] != 'ssh' { return error('Can only remove keys from SSH nodes, got: ${node_info['category']}') } - + // Find the key mut key := agent.get(name: key_name) or { return error('SSH key "${key_name}" not found in agent') } - + // Get public key content pubkey_content := key.keypub()! - + // Get authorized_keys path home_dir := node.environ_get()!['HOME'] or { return error('Could not determine HOME directory on remote node') } - + authorized_keys_path := '${home_dir}/.ssh/authorized_keys' - + if !node.file_exists(authorized_keys_path) { console.print_debug('authorized_keys file does not exist on remote node') return } - + // Remove the key line from authorized_keys escaped_key := pubkey_content.replace('/', '\\/') node.exec_silent('sed -i "\\|${escaped_key}|d" ${authorized_keys_path}')! - + console.print_debug('SSH key "${key_name}" removed from remote node') } @@ -90,11 +90,9 @@ pub fn (mut agent SSHAgent) verify_key_access(mut node builder.Node, key_name st if node_info['category'] != 'ssh' { return error('Can only verify access to SSH nodes') } - + // Test basic connectivity - result := node.exec_silent('echo "SSH key verification successful"') or { - return false - } - + result := node.exec_silent('echo "SSH key verification successful"') or { return false } + return result.contains('SSH key verification successful') -} \ No newline at end of file +} diff --git a/lib/osal/sshagent/play.v b/lib/osal/sshagent/play.v index 1ef6f5cb..693adae9 100644 --- a/lib/osal/sshagent/play.v +++ b/lib/osal/sshagent/play.v @@ -25,7 +25,7 @@ pub fn play(mut plbook PlayBook) ! { mut p := action.params name := p.get('name')! passphrase := p.get_default('passphrase', '')! - + sshkey_create(mut agent, name, passphrase)! action.done = true } @@ -35,7 +35,7 @@ pub fn play(mut plbook PlayBook) ! { for mut action in delete_actions { mut p := action.params name := p.get('name')! - + sshkey_delete(mut agent, name)! action.done = true } @@ -45,7 +45,7 @@ pub fn play(mut plbook PlayBook) ! { for mut action in load_actions { mut p := action.params name := p.get('name')! - + sshkey_load(mut agent, name)! action.done = true } @@ -55,7 +55,7 @@ pub fn play(mut plbook PlayBook) ! { for mut action in check_key_actions { mut p := action.params name := p.get('name')! - + sshkey_check(mut agent, name)! action.done = true } @@ -66,7 +66,7 @@ pub fn play(mut plbook PlayBook) ! { mut p := action.params node_addr := p.get('node')! key_name := p.get('name')! - + remote_copy(mut agent, node_addr, key_name)! action.done = true } @@ -77,7 +77,7 @@ pub fn play(mut plbook PlayBook) ! { mut p := action.params node_addr := p.get('node')! key_name := p.get('name')! - + remote_auth(mut agent, node_addr, key_name)! action.done = true } diff --git a/lib/osal/sshagent/sshagent.v b/lib/osal/sshagent/sshagent.v index eb14178c..23388e70 100644 --- a/lib/osal/sshagent/sshagent.v +++ b/lib/osal/sshagent/sshagent.v @@ -16,19 +16,19 @@ pub mut: pub fn (mut agent SSHAgent) ensure_single_agent() ! { user := os.getenv('USER') socket_path := get_agent_socket_path(user) - + // Check if we have a valid agent already if agent.is_agent_responsive() { console.print_debug('SSH agent already running and responsive') return } - + // Kill any orphaned agents agent.cleanup_orphaned_agents()! - + // Start new agent with consistent socket agent.start_agent_with_socket(socket_path)! - + // Set environment variables os.setenv('SSH_AUTH_SOCK', socket_path, true) agent.active = true @@ -44,7 +44,7 @@ pub fn (mut agent SSHAgent) is_agent_responsive() bool { if os.getenv('SSH_AUTH_SOCK') == '' { return false } - + res := os.execute('ssh-add -l 2>/dev/null') return res.exit_code == 0 || res.exit_code == 1 // 1 means no keys, but agent is running } @@ -52,12 +52,12 @@ pub fn (mut agent SSHAgent) is_agent_responsive() bool { // cleanup orphaned ssh-agent processes pub fn (mut agent SSHAgent) cleanup_orphaned_agents() ! { user := os.getenv('USER') - + // Find ssh-agent processes for current user res := os.execute('pgrep -u ${user} ssh-agent') if res.exit_code == 0 && res.output.len > 0 { pids := res.output.trim_space().split('\n') - + for pid in pids { if pid.trim_space() != '' { // Check if this agent has a valid socket @@ -77,7 +77,7 @@ fn (mut agent SSHAgent) is_agent_pid_valid(pid int) bool { if res.exit_code != 0 { return false } - + for socket_path in res.output.split('\n') { if socket_path.trim_space() != '' { // Test if this socket responds @@ -85,7 +85,7 @@ fn (mut agent SSHAgent) is_agent_pid_valid(pid int) bool { os.setenv('SSH_AUTH_SOCK', socket_path, true) test_res := os.execute('ssh-add -l 2>/dev/null') os.setenv('SSH_AUTH_SOCK', old_sock, true) - + if test_res.exit_code == 0 || test_res.exit_code == 1 { return true } @@ -100,45 +100,45 @@ pub fn (mut agent SSHAgent) start_agent_with_socket(socket_path string) ! { if os.exists(socket_path) { os.rm(socket_path)! } - + // Start ssh-agent with specific socket cmd := 'ssh-agent -a ${socket_path}' res := os.execute(cmd) if res.exit_code != 0 { return error('Failed to start ssh-agent: ${res.output}') } - + // Verify socket was created if !os.exists(socket_path) { return error('SSH agent socket was not created at ${socket_path}') } - + // Set environment variable os.setenv('SSH_AUTH_SOCK', socket_path, true) - + // Verify agent is responsive if !agent.is_agent_responsive() { return error('SSH agent started but is not responsive') } - + console.print_debug('SSH agent started with socket: ${socket_path}') } // get agent status and diagnostics pub fn (mut agent SSHAgent) diagnostics() map[string]string { mut diag := map[string]string{} - + diag['socket_path'] = os.getenv('SSH_AUTH_SOCK') diag['socket_exists'] = os.exists(diag['socket_path']).str() diag['agent_responsive'] = agent.is_agent_responsive().str() diag['loaded_keys_count'] = agent.keys.filter(it.loaded).len.str() diag['total_keys_count'] = agent.keys.len.str() - + // Count running ssh-agent processes user := os.getenv('USER') res := os.execute('pgrep -u ${user} ssh-agent | wc -l') diag['agent_processes'] = if res.exit_code == 0 { res.output.trim_space() } else { '0' } - + return diag } diff --git a/lib/osal/tmux/play.v b/lib/osal/tmux/play.v index 0df2f197..79a4ed0e 100644 --- a/lib/osal/tmux/play.v +++ b/lib/osal/tmux/play.v @@ -11,7 +11,7 @@ pub fn play(mut plbook PlayBook) ! { // Create tmux instance mut tmux_instance := new()! - + // Start tmux if not running if !tmux_instance.is_running()! { tmux_instance.start()! @@ -44,7 +44,7 @@ fn parse_window_name(name string) !ParsedWindowName { } return ParsedWindowName{ session: texttools.name_fix(parts[0]) - window: texttools.name_fix(parts[1]) + window: texttools.name_fix(parts[1]) } } @@ -55,8 +55,8 @@ fn parse_pane_name(name string) !ParsedPaneName { } return ParsedPaneName{ session: texttools.name_fix(parts[0]) - window: texttools.name_fix(parts[1]) - pane: texttools.name_fix(parts[2]) + window: texttools.name_fix(parts[1]) + pane: texttools.name_fix(parts[2]) } } @@ -66,12 +66,12 @@ fn play_session_create(mut plbook PlayBook, mut tmux_instance Tmux) ! { mut p := action.params session_name := p.get('name')! reset := p.get_default_false('reset') - + tmux_instance.session_create( - name: session_name + name: session_name reset: reset )! - + action.done = true } } @@ -81,9 +81,9 @@ fn play_session_delete(mut plbook PlayBook, mut tmux_instance Tmux) ! { for mut action in actions { mut p := action.params session_name := p.get('name')! - + tmux_instance.session_delete(session_name)! - + action.done = true } } @@ -96,7 +96,7 @@ fn play_window_create(mut plbook PlayBook, mut tmux_instance Tmux) ! { parsed := parse_window_name(name)! cmd := p.get_default('cmd', '')! reset := p.get_default_false('reset') - + // Parse environment variables if provided mut env := map[string]string{} if env_str := p.get_default('env', '') { @@ -109,21 +109,21 @@ fn play_window_create(mut plbook PlayBook, mut tmux_instance Tmux) ! { } } } - + // Get or create session mut session := if tmux_instance.session_exist(parsed.session) { tmux_instance.session_get(parsed.session)! } else { tmux_instance.session_create(name: parsed.session)! } - + session.window_new( - name: parsed.window - cmd: cmd - env: env + name: parsed.window + cmd: cmd + env: env reset: reset )! - + action.done = true } } @@ -134,12 +134,12 @@ fn play_window_delete(mut plbook PlayBook, mut tmux_instance Tmux) ! { mut p := action.params name := p.get('name')! parsed := parse_window_name(name)! - + if tmux_instance.session_exist(parsed.session) { mut session := tmux_instance.session_get(parsed.session)! session.window_delete(name: parsed.window)! } - + action.done = true } } @@ -151,19 +151,19 @@ fn play_pane_execute(mut plbook PlayBook, mut tmux_instance Tmux) ! { name := p.get('name')! cmd := p.get('cmd')! parsed := parse_pane_name(name)! - + // Find the session and window if tmux_instance.session_exist(parsed.session) { mut session := tmux_instance.session_get(parsed.session)! if session.window_exist(name: parsed.window) { mut window := session.window_get(name: parsed.window)! - + // Send command to the window (goes to active pane by default) tmux_cmd := 'tmux send-keys -t ${session.name}:@${window.id} "${cmd}" Enter' osal.exec(cmd: tmux_cmd, stdout: false, name: 'tmux_pane_execute')! } } - + action.done = true } } @@ -174,21 +174,26 @@ fn play_pane_kill(mut plbook PlayBook, mut tmux_instance Tmux) ! { mut p := action.params name := p.get('name')! parsed := parse_pane_name(name)! - + // Find the session and window, then kill the active pane if tmux_instance.session_exist(parsed.session) { mut session := tmux_instance.session_get(parsed.session)! if session.window_exist(name: parsed.window) { mut window := session.window_get(name: parsed.window)! - + // Kill the active pane in the window if pane := window.pane_active() { tmux_cmd := 'tmux kill-pane -t ${session.name}:@${window.id}.%${pane.id}' - osal.exec(cmd: tmux_cmd, stdout: false, name: 'tmux_pane_kill', ignore_error: true)! + osal.exec( + cmd: tmux_cmd + stdout: false + name: 'tmux_pane_kill' + ignore_error: true + )! } } } - + action.done = true } -} \ No newline at end of file +} diff --git a/lib/osal/tmux/tmux.v b/lib/osal/tmux/tmux.v index 1d9b09d3..5060bf27 100644 --- a/lib/osal/tmux/tmux.v +++ b/lib/osal/tmux/tmux.v @@ -14,7 +14,6 @@ pub mut: sessionid string // unique link to job } - // get session (session has windows) . // returns none if not found pub fn (mut t Tmux) session_get(name_ string) !&Session { @@ -56,8 +55,6 @@ pub mut: reset bool } - - // create session, if reset will re-create pub fn (mut t Tmux) session_create(args SessionCreateArgs) !&Session { name := texttools.name_fix(args.name) @@ -83,7 +80,6 @@ pub fn (mut t Tmux) session_create(args SessionCreateArgs) !&Session { return s } - @[params] pub struct TmuxNewArgs { sessionid string @@ -116,17 +112,16 @@ pub fn (mut t Tmux) window_new(args WindowNewArgs) !&Window { } else { t.session_create(name: args.session_name)! } - + // Create window in session return session.window_new( - name: args.name - cmd: args.cmd - env: args.env + name: args.name + cmd: args.cmd + env: args.env reset: args.reset )! } - pub fn (mut t Tmux) stop() ! { $if debug { console.print_debug('Stopping tmux...') @@ -156,7 +151,6 @@ pub fn (mut t Tmux) start() ! { t.scan()! } - // print list of tmux sessions pub fn (mut t Tmux) list_print() { // os.log('TMUX - Start listing ....') diff --git a/lib/osal/tmux/tmux_pane.v b/lib/osal/tmux/tmux_pane.v index 1b786cc4..6ed0f150 100644 --- a/lib/osal/tmux/tmux_pane.v +++ b/lib/osal/tmux/tmux_pane.v @@ -10,141 +10,134 @@ import freeflowuniverse.herolib.ui.console @[heap] struct Pane { pub mut: - window &Window @[str: skip] - id int // pane id (e.g., %1, %2) - pid int // process id - active bool // is this the active pane - cmd string // command running in pane - env map[string]string - created_at time.Time - last_output_offset int // for tracking new logs + window &Window @[str: skip] + id int // pane id (e.g., %1, %2) + pid int // process id + active bool // is this the active pane + cmd string // command running in pane + env map[string]string + created_at time.Time + last_output_offset int // for tracking new logs } - pub fn (mut p Pane) stats() !ProcessStats { - if p.pid == 0 { - return ProcessStats{} - } + if p.pid == 0 { + return ProcessStats{} + } - // Use ps command to get CPU and memory stats - cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers' - result := osal.execute_silent(cmd) or { - return error('Cannot get stats for PID ${p.pid}: ${err}') - } + // Use ps command to get CPU and memory stats + cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers' + result := osal.execute_silent(cmd) or { + return error('Cannot get stats for PID ${p.pid}: ${err}') + } - if result.trim_space() == '' { - return error('Process ${p.pid} not found') - } + if result.trim_space() == '' { + return error('Process ${p.pid} not found') + } - parts := result.trim_space().split_any(' \t').filter(it != '') - if parts.len < 3 { - return error('Invalid ps output: ${result}') - } + parts := result.trim_space().split_any(' \t').filter(it != '') + if parts.len < 3 { + return error('Invalid ps output: ${result}') + } - return ProcessStats{ - cpu_percent: parts[0].f64() - memory_percent: parts[1].f64() - memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes - } + return ProcessStats{ + cpu_percent: parts[0].f64() + memory_percent: parts[1].f64() + memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes + } } - pub struct TMuxLogEntry { pub mut: - content string - timestamp time.Time - offset int + content string + timestamp time.Time + offset int } pub fn (mut p Pane) logs_get_new(reset bool) ![]TMuxLogEntry { + if reset { + p.last_output_offset = 0 + } + // Capture pane content with line numbers + cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p' + result := osal.execute_silent(cmd) or { return error('Cannot capture pane output: ${err}') } - if reset{ - p.last_output_offset = 0 - } - // Capture pane content with line numbers - cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p' - result := osal.execute_silent(cmd) or { - return error('Cannot capture pane output: ${err}') - } + lines := result.split_into_lines() + mut entries := []TMuxLogEntry{} - lines := result.split_into_lines() - mut entries := []TMuxLogEntry{} - - mut i:= 0 - for line in lines { - if line.trim_space() != '' { - entries << TMuxLogEntry{ - content: line - timestamp: time.now() - offset: p.last_output_offset + i + 1 - } - } - } - // Update offset to avoid duplicates next time - if entries.len > 0 { - p.last_output_offset = entries.last().offset - } - return entries + mut i := 0 + for line in lines { + if line.trim_space() != '' { + entries << TMuxLogEntry{ + content: line + timestamp: time.now() + offset: p.last_output_offset + i + 1 + } + } + } + // Update offset to avoid duplicates next time + if entries.len > 0 { + p.last_output_offset = entries.last().offset + } + return entries } pub fn (mut p Pane) exit_status() !ProcessStatus { - // Get the last few lines to see if there's an exit status - logs := p.logs_all()! - lines := logs.split_into_lines() + // Get the last few lines to see if there's an exit status + logs := p.logs_all()! + lines := logs.split_into_lines() - // Look for shell prompt indicating command finished - for line in lines.reverse() { - line_clean := line.trim_space() - if line_clean.contains('$') || line_clean.contains('#') || line_clean.contains('>') { - // Found shell prompt, command likely finished - // Could also check for specific exit codes in history - return .finished_ok - } - } - return .finished_error + // Look for shell prompt indicating command finished + for line in lines.reverse() { + line_clean := line.trim_space() + if line_clean.contains('$') || line_clean.contains('#') || line_clean.contains('>') { + // Found shell prompt, command likely finished + // Could also check for specific exit codes in history + return .finished_ok + } + } + return .finished_error } pub fn (mut p Pane) logs_all() !string { - cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S -2000 -p' - return osal.execute_silent(cmd) or { - error('Cannot capture pane output: ${err}') - } + cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S -2000 -p' + return osal.execute_silent(cmd) or { error('Cannot capture pane output: ${err}') } } // Fix the output_wait method to use correct method name pub fn (mut p Pane) output_wait(c_ string, timeoutsec int) ! { - mut t := ourtime.now() - start := t.unix() - c := c_.replace('\n', '') - for i in 0 .. 2000 { - entries := p.logs_get_new(reset: false)! - for entry in entries { - if entry.content.replace('\n', '').contains(c) { - return - } - } - mut t2 := ourtime.now() - if t2.unix() > start + timeoutsec { - return error('timeout on output wait for tmux.\n${p} .\nwaiting for:\n${c}') - } - time.sleep(100 * time.millisecond) - } + mut t := ourtime.now() + start := t.unix() + c := c_.replace('\n', '') + for i in 0 .. 2000 { + entries := p.logs_get_new(reset: false)! + for entry in entries { + if entry.content.replace('\n', '').contains(c) { + return + } + } + mut t2 := ourtime.now() + if t2.unix() > start + timeoutsec { + return error('timeout on output wait for tmux.\n${p} .\nwaiting for:\n${c}') + } + time.sleep(100 * time.millisecond) + } } // Get process information for this pane and all its children pub fn (mut p Pane) processinfo() !osal.ProcessMap { - if p.pid == 0 { - return error('Pane has no associated process (pid is 0)') - } - - return osal.processinfo_with_children(p.pid)! + if p.pid == 0 { + return error('Pane has no associated process (pid is 0)') + } + + return osal.processinfo_with_children(p.pid)! } // Get process information for just this pane's main process pub fn (mut p Pane) processinfo_main() !osal.ProcessInfo { - if p.pid == 0 { - return error('Pane has no associated process (pid is 0)') - } - - return osal.processinfo_get(p.pid)! + if p.pid == 0 { + return error('Pane has no associated process (pid is 0)') + } + + return osal.processinfo_get(p.pid)! } diff --git a/lib/osal/tmux/tmux_process.v b/lib/osal/tmux/tmux_process.v index 7153460f..122f9cac 100644 --- a/lib/osal/tmux/tmux_process.v +++ b/lib/osal/tmux/tmux_process.v @@ -1,21 +1,15 @@ module tmux - - pub struct ProcessStats { pub mut: - cpu_percent f64 - memory_bytes u64 - memory_percent f64 + cpu_percent f64 + memory_bytes u64 + memory_percent f64 } - - enum ProcessStatus { - running - finished_ok - finished_error - not_found - } - - + running + finished_ok + finished_error + not_found +} diff --git a/lib/osal/tmux/tmux_scan.v b/lib/osal/tmux/tmux_scan.v index 65274a6a..6af13d4e 100644 --- a/lib/osal/tmux/tmux_scan.v +++ b/lib/osal/tmux/tmux_scan.v @@ -6,66 +6,66 @@ import freeflowuniverse.herolib.ui.console import time fn (mut t Tmux) scan_add(line string) !&Pane { - // Parse the line to get session, window, and pane info - line_arr := line.split('|') - session_name := line_arr[0] - window_name := line_arr[1] - window_id := line_arr[2] - pane_active := line_arr[3] - pane_id := line_arr[4] - pane_pid := line_arr[5] - pane_start_command := line_arr[6] or { '' } + // Parse the line to get session, window, and pane info + line_arr := line.split('|') + session_name := line_arr[0] + window_name := line_arr[1] + window_id := line_arr[2] + pane_active := line_arr[3] + pane_id := line_arr[4] + pane_pid := line_arr[5] + pane_start_command := line_arr[6] or { '' } - wid := (window_id.replace('@', '')).int() - pid := (pane_id.replace('%', '')).int() + wid := (window_id.replace('@', '')).int() + pid := (pane_id.replace('%', '')).int() - mut s := t.session_get(session_name)! + mut s := t.session_get(session_name)! - // Get or create window - mut w := if s.window_exist(name: window_name, id: wid) { - s.window_get(name: window_name, id: wid)! - } else { - mut new_w := Window{ - session: s - name: texttools.name_fix(window_name) - id: wid - panes: []&Pane{} - } - s.windows << &new_w - &new_w - } + // Get or create window + mut w := if s.window_exist(name: window_name, id: wid) { + s.window_get(name: window_name, id: wid)! + } else { + mut new_w := Window{ + session: s + name: texttools.name_fix(window_name) + id: wid + panes: []&Pane{} + } + s.windows << &new_w + &new_w + } - // Create or update pane - mut p := Pane{ - window: w - id: pid - pid: pane_pid.int() - active: pane_active == '1' - cmd: pane_start_command - created_at: time.now() - } + // Create or update pane + mut p := Pane{ + window: w + id: pid + pid: pane_pid.int() + active: pane_active == '1' + cmd: pane_start_command + created_at: time.now() + } - // Check if pane already exists - mut found := false - for mut existing_pane in w.panes { - if existing_pane.id == pid { - existing_pane.pid = p.pid - existing_pane.active = p.active - existing_pane.cmd = p.cmd - found = true - break - } - } + // Check if pane already exists + mut found := false + for mut existing_pane in w.panes { + if existing_pane.id == pid { + existing_pane.pid = p.pid + existing_pane.active = p.active + existing_pane.cmd = p.cmd + found = true + break + } + } - if !found { - w.panes << &p - } + if !found { + w.panes << &p + } - return &p + return &p } // scan the system to detect sessions . -//TODO needs to be done differently, here only find the sessions, then per session call the scan() which will find the windows, call scan() there as well ... +// TODO needs to be done differently, here only find the sessions, then per session call the scan() which will find the windows, call scan() there as well ... pub fn (mut t Tmux) scan() ! { // os.log('TMUX - Scanning ....') diff --git a/lib/osal/tmux/tmux_session.v b/lib/osal/tmux/tmux_session.v index 76d73f73..5d39d80e 100644 --- a/lib/osal/tmux/tmux_session.v +++ b/lib/osal/tmux/tmux_session.v @@ -21,87 +21,86 @@ pub mut: env map[string]string reset bool } + @[params] pub struct WindowGetArgs { pub mut: - name string - id int + name string + id int } - pub fn (mut s Session) create() ! { - // Check if session already exists - cmd_check := "tmux has-session -t ${s.name}" - check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or { - // Session doesn't exist, this is expected - osal.Job{} - } - - if check_result.exit_code == 0 { - return error('duplicate session: ${s.name}') - } - - // Create new session - cmd := "tmux new-session -d -s ${s.name}" - osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or { - return error("Can't create session ${s.name}: ${err}") - } + // Check if session already exists + cmd_check := 'tmux has-session -t ${s.name}' + check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or { + // Session doesn't exist, this is expected + osal.Job{} + } + + if check_result.exit_code == 0 { + return error('duplicate session: ${s.name}') + } + + // Create new session + cmd := 'tmux new-session -d -s ${s.name}' + osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or { + return error("Can't create session ${s.name}: ${err}") + } } -//load info from reality +// load info from reality pub fn (mut s Session) scan() ! { - // Get current windows from tmux for this session - cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'" - result := osal.execute_silent(cmd) or { - if err.msg().contains('session not found') { - return // Session doesn't exist anymore - } - return error('Cannot list windows for session ${s.name}: ${err}') - } - - mut current_windows := map[string]bool{} - for line in result.split_into_lines() { - if line.contains('|') { - parts := line.split('|') - if parts.len >= 2 { - window_name := texttools.name_fix(parts[0]) - window_id := parts[1].replace('@', '').int() - window_active := parts[2] == '1' - - current_windows[window_name] = true - - // Update existing window or create new one - mut found := false - for mut w in s.windows { - if w.name == window_name { - w.id = window_id - w.active = window_active - w.scan()! // Scan panes for this window - found = true - break - } - } - - if !found { - mut new_window := Window{ - session: &s - name: window_name - id: window_id - active: window_active - panes: []&Pane{} - env: map[string]string{} - } - new_window.scan()! // Scan panes for new window - s.windows << &new_window - } - } - } - } - - // Remove windows that no longer exist in tmux - s.windows = s.windows.filter(current_windows[it.name] == true) -} + // Get current windows from tmux for this session + cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'" + result := osal.execute_silent(cmd) or { + if err.msg().contains('session not found') { + return + } + return error('Cannot list windows for session ${s.name}: ${err}') + } + mut current_windows := map[string]bool{} + for line in result.split_into_lines() { + if line.contains('|') { + parts := line.split('|') + if parts.len >= 2 { + window_name := texttools.name_fix(parts[0]) + window_id := parts[1].replace('@', '').int() + window_active := parts[2] == '1' + + current_windows[window_name] = true + + // Update existing window or create new one + mut found := false + for mut w in s.windows { + if w.name == window_name { + w.id = window_id + w.active = window_active + w.scan()! // Scan panes for this window + found = true + break + } + } + + if !found { + mut new_window := Window{ + session: &s + name: window_name + id: window_id + active: window_active + panes: []&Pane{} + env: map[string]string{} + } + new_window.scan()! // Scan panes for new window + s.windows << &new_window + } + } + } + } + + // Remove windows that no longer exist in tmux + s.windows = s.windows.filter(current_windows[it.name] == true) +} // window_name is the name of the window in session main (will always be called session main) // cmd to execute e.g. bash file @@ -139,14 +138,10 @@ pub fn (mut s Session) window_new(args WindowArgs) !Window { // Create the window with the specified command w.create(args.cmd)! s.scan()! - + return w } - - - - // get all windows as found in a session pub fn (mut s Session) windows_get() []&Window { mut res := []&Window{} @@ -179,14 +174,14 @@ pub fn (mut s Session) str() string { } pub fn (mut s Session) stats() !ProcessStats { - mut total := ProcessStats{} - for mut window in s.windows { - stats := window.stats() or { continue } - total.cpu_percent += stats.cpu_percent - total.memory_bytes += stats.memory_bytes - total.memory_percent += stats.memory_percent - } - return total + mut total := ProcessStats{} + for mut window in s.windows { + stats := window.stats() or { continue } + total.cpu_percent += stats.cpu_percent + total.memory_bytes += stats.memory_bytes + total.memory_percent += stats.memory_percent + } + return total } // pub fn (mut s Session) activate()! { @@ -208,8 +203,6 @@ pub fn (mut s Session) stats() !ProcessStats { // } // } - - fn (mut s Session) window_exist(args_ WindowGetArgs) bool { mut args := args_ s.window_get(args) or { return false } @@ -249,7 +242,6 @@ pub fn (mut s Session) window_delete(args_ WindowGetArgs) ! { s.windows.delete(i) // i is now the one in the list which needs to be removed } - pub fn (mut s Session) restart() ! { s.stop()! s.create()! @@ -259,4 +251,4 @@ pub fn (mut s Session) stop() ! { osal.execute_silent('tmux kill-session -t ${s.name}') or { return error("Can't delete session ${s.name} - This may happen when session is not found: ${err}") } -} \ No newline at end of file +} diff --git a/lib/osal/tmux/tmux_test.v b/lib/osal/tmux/tmux_test.v index 806588d3..03d61475 100644 --- a/lib/osal/tmux/tmux_test.v +++ b/lib/osal/tmux/tmux_test.v @@ -45,37 +45,37 @@ fn test_stop() ! { } fn test_windows_get() ! { - mut tmux := new()! - tmux.start()! - - // After start, scan to get the initial session - tmux.scan()! - - windows := tmux.windows_get() - assert windows.len >= 0 // At least the default session should exist - - tmux.stop()! + mut tmux := new()! + tmux.start()! + + // After start, scan to get the initial session + tmux.scan()! + + windows := tmux.windows_get() + assert windows.len >= 0 // At least the default session should exist + + tmux.stop()! } fn test_scan() ! { - console.print_debug('-----Testing scan------') - mut tmux := new()! - tmux.start()! + console.print_debug('-----Testing scan------') + mut tmux := new()! + tmux.start()! - // Test initial scan - tmux.scan()! - sessions_before := tmux.sessions.len - - // Create a test session - mut session := tmux.session_create(name: 'test_scan')! - - // Scan again - tmux.scan()! - sessions_after := tmux.sessions.len - - assert sessions_after >= sessions_before - - tmux.stop()! + // Test initial scan + tmux.scan()! + sessions_before := tmux.sessions.len + + // Create a test session + mut session := tmux.session_create(name: 'test_scan')! + + // Scan again + tmux.scan()! + sessions_after := tmux.sessions.len + + assert sessions_after >= sessions_before + + tmux.stop()! } // //TODO: fix test diff --git a/lib/osal/tmux/tmux_window.v b/lib/osal/tmux/tmux_window.v index 6412aac7..a108de49 100644 --- a/lib/osal/tmux/tmux_window.v +++ b/lib/osal/tmux/tmux_window.v @@ -13,7 +13,7 @@ pub mut: session &Session @[skip] name string id int - panes []&Pane // windows contain multiple panes + panes []&Pane // windows contain multiple panes active bool env map[string]string } @@ -22,105 +22,104 @@ pub mut: pub struct PaneNewArgs { pub mut: name string - reset bool //means we reset the pane if it already exists + reset bool // means we reset the pane if it already exists cmd string - env map[string]string + env map[string]string } - pub fn (mut w Window) scan() ! { - // Get current panes for this window - cmd := "tmux list-panes -t ${w.session.name}:@${w.id} -F '#{pane_id}|#{pane_pid}|#{pane_active}|#{pane_start_command}'" - result := osal.execute_silent(cmd) or { - // Window might not exist anymore - return - } - - mut current_panes := map[int]bool{} - for line in result.split_into_lines() { - if line.contains('|') { - parts := line.split('|') - if parts.len >= 3 { - pane_id := parts[0].replace('%', '').int() - pane_pid := parts[1].int() - pane_active := parts[2] == '1' - pane_cmd := if parts.len > 3 { parts[3] } else { '' } - - current_panes[pane_id] = true - - // Update existing pane or create new one - mut found := false - for mut p in w.panes { - if p.id == pane_id { - p.pid = pane_pid - p.active = pane_active - p.cmd = pane_cmd - found = true - break - } - } - - if !found { - mut new_pane := Pane{ - window: &w - id: pane_id - pid: pane_pid - active: pane_active - cmd: pane_cmd - env: map[string]string{} - created_at: time.now() - last_output_offset: 0 - } - w.panes << &new_pane - } - } - } - } - - // Remove panes that no longer exist - w.panes = w.panes.filter(current_panes[it.id] == true) -} + // Get current panes for this window + cmd := "tmux list-panes -t ${w.session.name}:@${w.id} -F '#{pane_id}|#{pane_pid}|#{pane_active}|#{pane_start_command}'" + result := osal.execute_silent(cmd) or { + // Window might not exist anymore + return + } + mut current_panes := map[int]bool{} + for line in result.split_into_lines() { + if line.contains('|') { + parts := line.split('|') + if parts.len >= 3 { + pane_id := parts[0].replace('%', '').int() + pane_pid := parts[1].int() + pane_active := parts[2] == '1' + pane_cmd := if parts.len > 3 { parts[3] } else { '' } + + current_panes[pane_id] = true + + // Update existing pane or create new one + mut found := false + for mut p in w.panes { + if p.id == pane_id { + p.pid = pane_pid + p.active = pane_active + p.cmd = pane_cmd + found = true + break + } + } + + if !found { + mut new_pane := Pane{ + window: &w + id: pane_id + pid: pane_pid + active: pane_active + cmd: pane_cmd + env: map[string]string{} + created_at: time.now() + last_output_offset: 0 + } + w.panes << &new_pane + } + } + } + } + + // Remove panes that no longer exist + w.panes = w.panes.filter(current_panes[it.id] == true) +} pub fn (mut w Window) stop() ! { - w.kill()! + w.kill()! } -//helper function -//TODO env variables are not inserted in pane + +// helper function +// TODO env variables are not inserted in pane pub fn (mut w Window) create(cmd_ string) ! { - mut final_cmd := cmd_ - if cmd_.contains('\n') { - os.mkdir_all('/tmp/tmux/${w.session.name}')! - // Fix: osal.exec_string doesn't exist, use file writing instead - script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh' - script_content := '#!/bin/bash\n' + cmd_ - os.write_file(script_path, script_content)! - os.chmod(script_path, 0o755)! - final_cmd = script_path - } + mut final_cmd := cmd_ + if cmd_.contains('\n') { + os.mkdir_all('/tmp/tmux/${w.session.name}')! + // Fix: osal.exec_string doesn't exist, use file writing instead + script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh' + script_content := '#!/bin/bash\n' + cmd_ + os.write_file(script_path, script_content)! + os.chmod(script_path, 0o755)! + final_cmd = script_path + } - mut newcmd := '/bin/bash -c "${final_cmd}"' - if cmd_ == "" { - newcmd = '/bin/bash' - } + mut newcmd := '/bin/bash -c "${final_cmd}"' + if cmd_ == '' { + newcmd = '/bin/bash' + } - // Build environment arguments - mut env_args := '' - for key, value in w.env { - env_args += ' -e ${key}="${value}"' - } + // Build environment arguments + mut env_args := '' + for key, value in w.env { + env_args += ' -e ${key}="${value}"' + } - res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'" - cmd := 'tmux new-window ${res_opt}${env_args} -t ${w.session.name} -n ${w.name} \'${newcmd}\'' - console.print_debug(cmd) - - res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or { - return error("Can't create new window ${w.name} \n${cmd}\n${err}") - } - - line_arr := res.output.split('|') - wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') } - w.id = wid.replace('@', '').int() + res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'" + cmd := 'tmux new-window ${res_opt}${env_args} -t ${w.session.name} -n ${w.name} \'${newcmd}\'' + console.print_debug(cmd) + + res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or { + return error("Can't create new window ${w.name} \n${cmd}\n${err}") + } + + line_arr := res.output.split('|') + wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') } + w.id = wid.replace('@', '').int() } // stop the window @@ -143,14 +142,14 @@ pub fn (window Window) str() string { } pub fn (mut w Window) stats() !ProcessStats { - mut total := ProcessStats{} - for mut pane in w.panes { - stats := pane.stats() or { continue } - total.cpu_percent += stats.cpu_percent - total.memory_bytes += stats.memory_bytes - total.memory_percent += stats.memory_percent - } - return total + mut total := ProcessStats{} + for mut pane in w.panes { + stats := pane.stats() or { continue } + total.cpu_percent += stats.cpu_percent + total.memory_bytes += stats.memory_bytes + total.memory_percent += stats.memory_percent + } + return total } // will select the current window so with tmux a we can go there . @@ -169,10 +168,10 @@ pub fn (mut w Window) pane_list() []&Pane { // Get active pane in window pub fn (mut w Window) pane_active() ?&Pane { - for pane in w.panes { - if pane.active { - return pane - } - } - return none + for pane in w.panes { + if pane.active { + return pane + } + } + return none } diff --git a/lib/osal/tmux/tmux_window_test.v b/lib/osal/tmux/tmux_window_test.v index 80a8f416..d4224b58 100644 --- a/lib/osal/tmux/tmux_window_test.v +++ b/lib/osal/tmux/tmux_window_test.v @@ -24,23 +24,23 @@ fn testsuite_end() { } fn test_window_new() ! { - mut tmux := new()! - tmux.start()! + mut tmux := new()! + tmux.start()! - // Create session first - mut session := tmux.session_create(name: 'main')! - - // Test window creation - mut window := session.window_new( - name: 'TestWindow' - cmd: 'bash' - reset: true - )! - - assert window.name == 'testwindow' // name_fix converts to lowercase - assert session.window_exist(name: 'testwindow') - - tmux.stop()! + // Create session first + mut session := tmux.session_create(name: 'main')! + + // Test window creation + mut window := session.window_new( + name: 'TestWindow' + cmd: 'bash' + reset: true + )! + + assert window.name == 'testwindow' // name_fix converts to lowercase + assert session.window_exist(name: 'testwindow') + + tmux.stop()! } // tests creating duplicate windows diff --git a/lib/threefold/grid4/datamodel/model_bid.v b/lib/threefold/grid4/datamodel/model_bid.v index 5ff8051f..7bf2a751 100644 --- a/lib/threefold/grid4/datamodel/model_bid.v +++ b/lib/threefold/grid4/datamodel/model_bid.v @@ -1,18 +1,18 @@ module datamodel +// I can bid for infra, and optionally get accepted @[heap] -//I can bid for infra, and optionally get accepted pub struct Bid { pub mut: - id u32 - customer_id u32 //links back to customer for this capacity (user on ledger) - compute_slices_nr int //nr of slices I need in 1 machine - compute_slice f64 //price per 1 GB slice I want to accept - storage_slices []u32 - status BidStatus - obligation bool //if obligation then will be charged and money needs to be in escrow, otherwise its an intent - start_date u32 //epoch - end_date u32 + id u32 + customer_id u32 // links back to customer for this capacity (user on ledger) + compute_slices_nr int // nr of slices I need in 1 machine + compute_slice f64 // price per 1 GB slice I want to accept + storage_slices []u32 + status BidStatus + obligation bool // if obligation then will be charged and money needs to be in escrow, otherwise its an intent + start_date u32 // epoch + end_date u32 } pub enum BidStatus { @@ -21,4 +21,4 @@ pub enum BidStatus { assigned cancelled done -} \ No newline at end of file +} diff --git a/lib/threefold/grid4/datamodel/model_reservation.v b/lib/threefold/grid4/datamodel/model_reservation.v index 139ecf2c..6ba6d5e1 100644 --- a/lib/threefold/grid4/datamodel/model_reservation.v +++ b/lib/threefold/grid4/datamodel/model_reservation.v @@ -3,13 +3,13 @@ module datamodel @[heap] pub struct Reservation { pub mut: - id u32 - customer_id u32 //links back to customer for this capacity + id u32 + customer_id u32 // links back to customer for this capacity compute_slices []u32 storage_slices []u32 - status ReservationStatus - start_date u32 //epoch - end_date u32 + status ReservationStatus + start_date u32 // epoch + end_date u32 } pub enum ReservationStatus { @@ -18,4 +18,4 @@ pub enum ReservationStatus { assigned cancelled done -} \ No newline at end of file +} diff --git a/lib/threefold/grid4/datamodelsimulator/model_simulations.v b/lib/threefold/grid4/datamodelsimulator/model_simulations.v index 8d057496..d3e77bc1 100644 --- a/lib/threefold/grid4/datamodelsimulator/model_simulations.v +++ b/lib/threefold/grid4/datamodelsimulator/model_simulations.v @@ -1,5 +1,7 @@ module datamodel + import freeflowuniverse.herolib.threefold.grid4.datamodel { Node } + pub struct NodeSim { Node pub mut: