From 9259506fcbcc68c164de1d69b7b89208bcd059ce Mon Sep 17 00:00:00 2001 From: nasr Date: Wed, 7 Jan 2026 14:00:22 +0100 Subject: [PATCH 01/55] checkpoint --- internal/dbusservices/manager.go | 79 ++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 9931950..eff1e78 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -76,18 +76,67 @@ func RunRetrieval( units := make([]*v2.LoadedUnit, 0, len(entries)) for _, e := range entries { - units = append(units, &v2.LoadedUnit{ - Name: e.Name, - Description: "", - LoadState: e.State, - SubState: "", - ActiveState: "", - DepUnit: "", - ObjectPath: "", - QueuedJob: 0, - JobType: "", - JobPath: "", - }) + for _, i := range entries { + + /** + + TODO(nasr): replace this with the proper enum + */ + + ch := make(chan []svctypes.LoadedUnit) + parse := make(chan []svctypes.LoadedUnit) + + go svc.GetLoadedUnits(obj, ch) + go dh.ParseLoadedUnits(ch, parse) + + loaded := <-parse + + if i.State == "enabled" { + + svc.GetLoadedUnits(obj, out) + + var luIdx int16 + var alIdx int16 + + for luIdx, lu := range <-out { + + } + + } + + units = append(units, &v2.LoadedUnit{ + Name: e.Name, + Description: "", + LoadState: e.State, + SubState: "", + ActiveState: "", + DepUnit: "", + ObjectPath: "", + QueuedJob: 0, + JobType: "", + JobPath: "", + }) + } + + loaded := <-parse + + units := make([]*v2.LoadedUnit, 0, len(loaded)) + for _, u := range loaded { + units = append(units, &v2.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + /*oops typo in queued job :)*/ + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + }) + } + } return units, nil @@ -112,9 +161,9 @@ func RunRetrieval( DepUnit: u.DepUnit, ObjectPath: string(u.ObjectPath), /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), }) } From f4e0d6fd13ab042c40c92c71d36253122d978585 Mon Sep 17 00:00:00 2001 From: nasr Date: Wed, 7 Jan 2026 15:06:30 +0100 Subject: [PATCH 02/55] feature: the total list of returned unit lists now return a complete list of units where the enabled units have complete properties --- internal/dbusservices/dbus/parse.go | 6 +- internal/dbusservices/manager.go | 139 +++++++++++----------------- 2 files changed, 56 insertions(+), 89 deletions(-) diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index fd31e91..5f77fe9 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -19,13 +19,11 @@ func ParseUnitFileEntries(in chan []svctypes.UnitFileEntry, out chan []svctypes. for i := range input { if input[i].State == "enabled" { - fmt.Println(input[i].Name) + continue } else { - - fmt.Println(input[i].Name) + out <- input } } - out <- input } diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index eff1e78..2a86ec0 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -1,3 +1,4 @@ + package dbus_services import ( @@ -57,6 +58,41 @@ func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime b return nil } +/** +Helper function to retrieve all loaded units + +*/ +func getLoadedUnits(conn *dbus.Conn) []*v2.LoadedUnit { + + obj := dh.CreateSystemdObject(conn) + ch := make(chan []svctypes.LoadedUnit) + parse := make(chan []svctypes.LoadedUnit) + + go svc.GetLoadedUnits(obj, ch) + go dh.ParseLoadedUnits(ch, parse) + + loaded := <-parse + + units := make([]*v2.LoadedUnit, 0, len(loaded)) + for _, u := range loaded { + units = append(units, &v2.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + /*oops typo in queued job :)*/ + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + }) + } + + return units +} + // @param, true for all on disk, false for loaded units func RunRetrieval( conn *dbus.Conn, @@ -64,7 +100,6 @@ func RunRetrieval( ) ([]*v2.LoadedUnit, error) { obj := dh.CreateSystemdObject(conn) - if all { ch := make(chan []svctypes.UnitFileEntry) parse := make(chan []svctypes.UnitFileEntry) @@ -76,96 +111,30 @@ func RunRetrieval( units := make([]*v2.LoadedUnit, 0, len(entries)) for _, e := range entries { - for _, i := range entries { - - /** - - TODO(nasr): replace this with the proper enum - */ - - ch := make(chan []svctypes.LoadedUnit) - parse := make(chan []svctypes.LoadedUnit) - - go svc.GetLoadedUnits(obj, ch) - go dh.ParseLoadedUnits(ch, parse) - - loaded := <-parse - - if i.State == "enabled" { - - svc.GetLoadedUnits(obj, out) - - var luIdx int16 - var alIdx int16 - - for luIdx, lu := range <-out { - - } - - } - - units = append(units, &v2.LoadedUnit{ - Name: e.Name, - Description: "", - LoadState: e.State, - SubState: "", - ActiveState: "", - DepUnit: "", - ObjectPath: "", - QueuedJob: 0, - JobType: "", - JobPath: "", - }) - } - - loaded := <-parse - - units := make([]*v2.LoadedUnit, 0, len(loaded)) - for _, u := range loaded { - units = append(units, &v2.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), - }) - } + units = append(units, &v2.LoadedUnit{ + Name: e.Name, + Description: "", + LoadState: e.State, + SubState: "", + ActiveState: "", + DepUnit: "", + ObjectPath: "", + QueuedJob: 0, + JobType: "", + JobPath: "", + }) + } + loadedUnits := getLoadedUnits(conn) + for _, i := range loadedUnits { + + units = append(units, i) } return units, nil } - ch := make(chan []svctypes.LoadedUnit) - parse := make(chan []svctypes.LoadedUnit) - - go svc.GetLoadedUnits(obj, ch) - go dh.ParseLoadedUnits(ch, parse) - - loaded := <-parse - - units := make([]*v2.LoadedUnit, 0, len(loaded)) - for _, u := range loaded { - units = append(units, &v2.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), - }) - } + units := getLoadedUnits(conn) return units, nil } From f4f7dc37b3cfc7509f114d45ac54ba5b353da688 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 13:26:53 +0100 Subject: [PATCH 03/55] feature: killing process and retrieving processes states protobuf --- ..._resources.proto => deviceresources.proto} | 0 proto/resources/v2/deviceresources.proto | 116 ++++++++++++++++++ 2 files changed, 116 insertions(+) rename proto/resources/v1/{device_resources.proto => deviceresources.proto} (100%) create mode 100644 proto/resources/v2/deviceresources.proto diff --git a/proto/resources/v1/device_resources.proto b/proto/resources/v1/deviceresources.proto similarity index 100% rename from proto/resources/v1/device_resources.proto rename to proto/resources/v1/deviceresources.proto diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto new file mode 100644 index 0000000..0272feb --- /dev/null +++ b/proto/resources/v2/deviceresources.proto @@ -0,0 +1,116 @@ +syntax = "proto3"; + +package resources.v2; + +option go_package = "paradigm-ehb/agent/gen/resources/v2;resourcespb"; + +service ResourcesService { + rpc GetSystemResources(GetSystemResourcesRequest) + returns (GetSystemResourcesResponse); +} + +message GetSystemResourcesRequest {} + +message GetSystemResourcesResponse { + SystemResources resources = 1; +} + +/** + * + * Get a complete system snapshots + * @return Cpu, Memory, Device, Disk ( partitions ) and processes + * + * */ +message SystemResources { + Cpu cpu = 1; + Memory memory = 2; + Device device = 3; + repeated Disk disks = 4; + repeated Process processes = 5; +} + +/** + * Cpu information + * + * @return vendor, model, frequency, cores + * */ +message Cpu { + string vendor = 1; + string model = 2; + string frequency = 3; + uint32 max_core = 4; +} + +/** + * Memory + * @return total memory on system, available memory on system + * */ +message Memory { + string total = 1; + string free = 2; +} + +/** + * Device specific information + * @return os version ( distro information ), and uptime in string format + * + * */ + +message Device { + string os_version = 1; + string uptime = 2; +} + +/** + * + * Disk information + * @disk a list of all available partitions + * + * */ + +message Disk { + repeated DiskPartition partitions = 1; +} + +/** + * Partition + * @return partition name, major, minor, blocks + * */ +message DiskPartition { + string name = 1; + uint32 major = 2; + uint32 minor = 3; + uint64 blocks = 4; +} + +enum ProcessState { + PROCESS_STATE_UNSPECIFIED = 0; + PROCESS_STATE_RUNNING = 1; + PROCESS_STATE_SLEEPING = 2; + PROCESS_STATE_DISK_SLEEPING = 3; + PROCESS_STATE_STOPPED = 4; + PROCESS_STATE_TRACING_STOPPED = 5; + PROCESS_STATE_ZOMBIE = 6; + PROCESS_STATE_DEAD = 7; +} + +/** + * Process information + * @return pid ( process identifier ), process name, the state, todo :) + * */ +message Process { + uint32 pid = 1; + string name = 2; + ProcessState state = 3; + uint64 utime = 4; + uint32 num_threads = 5; +} + +message KillProcessRequest { + uint32 pid = 1; +} + +message KillProcessReply { + + string succes = 1; +} From 8dfd98fd21a8fef3886e06b1bd2691b473cb11b6 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:40:57 +0100 Subject: [PATCH 04/55] refactor: added the new services and resources version --- test/setup_test.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/setup_test.go b/test/setup_test.go index e14c012..2b90e4d 100644 --- a/test/setup_test.go +++ b/test/setup_test.go @@ -1,17 +1,23 @@ package server_test +/** + +TODO(nasr): update tests for the new grpc handler services version +*/ import ( "context" "net" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" - "paradigm-ehb/agent/pkg/grpc_handler" - respb "paradigm-ehb/agent/gen/resources/v1" + "paradigm-ehb/agent/pkg/grpchandler" + servicesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/services/v1" + servicesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/services/v2" + // respb "paradigm-ehb/agent/gen/resources/v1" + serpb_v1 "paradigm-ehb/agent/gen/services/v1" serpb_v2 "paradigm-ehb/agent/gen/services/v2" - greetpb "paradigm-ehb/agent/gen/greet" journalpb "paradigm-ehb/agent/gen/journal/v1" @@ -37,9 +43,9 @@ func init() { grpc_health_v1.RegisterHealthServer(server, healthServer) - respb.RegisterResourcesServiceServer(server, &grpc_handler.ResourcesService{}) - serpb_v1.RegisterHandlerServiceServer(server, &grpc_handler.HandlerService{}) - serpb_v2.RegisterHandlerServiceServer(server, &grpc_handler.HandlerServiceV2{}) + // respb.RegisterResourcesServiceServer(server, &grpc_handler.ResourcesService{}) + serpb_v1.RegisterHandlerServiceServer(server, &servicesHandlerV1.HandlerService{}) + serpb_v2.RegisterHandlerServiceServer(server, &servicesHandlerV2.HandlerServiceV2{}) greetpb.RegisterGreeterServer(server, &grpc_handler.GreeterServer{}) journalpb.RegisterJournalServiceServer(server, &grpc_handler.JournalService{}) From fe524d1184199817e2fa604cfb9ef5481957398c Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:41:26 +0100 Subject: [PATCH 05/55] feature: new grpcurl tests --- test/grpc_res_v2.sh | 114 +++++++++++++++++++++++++++++++++++++++ test/grpc_resources.sh | 27 ++++++++++ test/grpc_services.sh | 106 ++++++++++++++++++++++++++++++++++++ test/grpc_test_script.sh | 106 ------------------------------------ 4 files changed, 247 insertions(+), 106 deletions(-) create mode 100755 test/grpc_res_v2.sh create mode 100755 test/grpc_resources.sh create mode 100755 test/grpc_services.sh delete mode 100755 test/grpc_test_script.sh diff --git a/test/grpc_res_v2.sh b/test/grpc_res_v2.sh new file mode 100755 index 0000000..b372ae6 --- /dev/null +++ b/test/grpc_res_v2.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +# gRPCurl test script for ResourcesServiceV2 +# Make sure your gRPC server is running before executing these commands + +# Color codes for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default server address +SERVER="localhost:5000" + +# Check if custom server address provided +if [ ! -z "$1" ]; then + SERVER="$1" +fi + +echo -e "${BLUE}Testing ResourcesServiceV2 on ${SERVER}${NC}\n" + +# List available services +echo -e "${GREEN}=== Listing available services ===${NC}" +grpcurl -plaintext ${SERVER} list +echo "" + +# List methods for ResourcesServiceV2 +echo -e "${GREEN}=== Listing methods for resources.v2.ResourcesService ===${NC}" +grpcurl -plaintext ${SERVER} list resources.v2.ResourcesService +echo "" + +# Describe the service +echo -e "${GREEN}=== Describing ResourcesService ===${NC}" +grpcurl -plaintext ${SERVER} describe resources.v2.ResourcesService +echo "" + +# Test GetSystemResources +echo -e "${GREEN}=== Getting System Resources ===${NC}" +grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources +echo "" + +# Test KillProcess (example with PID 12345) +# WARNING: Uncomment and modify PID carefully - this will kill a process! +# echo -e "${GREEN}=== Killing Process (PID: 12345) ===${NC}" +# grpcurl -plaintext \ +# -d '{"pid": 12345}' \ +# ${SERVER} \ +# resources.v2.ResourcesService/KillProcess +# echo "" + +# Get system resources with formatted output (using jq if available) +echo -e "${GREEN}=== Getting System Resources (formatted) ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.' +else + echo "jq not installed - skipping formatted output" + echo "Install jq for pretty JSON: sudo apt-get install jq" +fi +echo "" + +# Show CPU information only +echo -e "${GREEN}=== CPU Information ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.cpu' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +# Show Memory information only +echo -e "${GREEN}=== Memory Information ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.memory' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +# Show first 5 processes +echo -e "${GREEN}=== First 5 Processes ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.processes[:5]' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +# Count total processes +echo -e "${GREEN}=== Total Process Count ===${NC}" +if command -v jq &> /dev/null; then + grpcurl -plaintext \ + -d '{}' \ + ${SERVER} \ + resources.v2.ResourcesService/GetSystemResources | jq '.resources.processes | length' +else + echo "jq not installed - install it to filter output" +fi +echo "" + +echo -e "${BLUE}Tests completed!${NC}" diff --git a/test/grpc_resources.sh b/test/grpc_resources.sh new file mode 100755 index 0000000..5ea9c84 --- /dev/null +++ b/test/grpc_resources.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env sh +set -eu + +ADDR="${ADDR:-localhost:5000}" + +echo "== ResourcesService integration test ==" +echo "Target: $ADDR" +echo + +command -v grpcurl >/dev/null 2>&1 || { + echo "ERROR: grpcurl not found" + exit 1 +} + +echo "== Checking service availability ==" +grpcurl -plaintext "$ADDR" list resources.v1.ResourcesService >/dev/null +echo "OK" +echo + +echo "== Calling GetSystemResources ==" +grpcurl -plaintext \ + -format text \ + "$ADDR" \ + resources.v1.ResourcesService/GetSystemResources +echo + +echo "== Test completed ==" diff --git a/test/grpc_services.sh b/test/grpc_services.sh new file mode 100755 index 0000000..c2c13d3 --- /dev/null +++ b/test/grpc_services.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env sh +set -euo pipefail + +HOST="${HOST:-localhost}" +PORT="${PORT:-5000}" +ADDR="${HOST}:${PORT}" +SERVICE="services.v2.HandlerService" + +echo "== gRPC integration test ==" +echo "Target: ${ADDR}" +echo + +command -v grpcurl >/dev/null 2>&1 || { + echo "ERROR: grpcurl not found" + exit 1 +} + +echo "== Checking server availability ==" +grpcurl -plaintext "${ADDR}" list >/dev/null +echo "OK" +echo + +echo "== Listing all services ==" +grpcurl -plaintext "${ADDR}" list +echo + +echo "== Checking ${SERVICE} existence ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" >/dev/null +echo "OK" +echo + + +echo "== Listing ${SERVICE} methods ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" +echo + +echo "== GetAllUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetAllUnits" +echo + +echo "== GetLoadedUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetLoadedUnits" +echo + + echo "== GetUnitStatus (tailscaled.service) ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service" + }' \ + "${ADDR}" \ + "${SERVICE}/GetUnitStatus" + echo + + echo "== PerformUnitAction: START tailscaled.service ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_START" + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" + echo + + echo "== PerformUnitFileAction: ENABLE tailscaled.service (runtime=true, force=true) ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_ENABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" + echo + + echo "== PerformUnitAction: STOP tailscaled.service ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_STOP", + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" + echo + + + echo "== PerformUnitFileAction: DISABLE tailscaled.service ==" + grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_DISABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" + echo + +echo "== All gRPC v2 tests passed ==" diff --git a/test/grpc_test_script.sh b/test/grpc_test_script.sh deleted file mode 100755 index 07f19b8..0000000 --- a/test/grpc_test_script.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env sh -set -euo pipefail - -HOST="${HOST:-localhost}" -PORT="${PORT:-5000}" -ADDR="${HOST}:${PORT}" - -SERVICE="services.v2.HandlerService" - -echo "== gRPC integration test ==" -echo "Target: ${ADDR}" -echo - -command -v grpcurl >/dev/null 2>&1 || { - echo "ERROR: grpcurl not found" - exit 1 -} - -echo "== Checking server availability ==" -grpcurl -plaintext "${ADDR}" list >/dev/null -echo "OK" -echo - -echo "== Listing all services ==" -grpcurl -plaintext "${ADDR}" list -echo - -echo "== Checking ${SERVICE} existence ==" -grpcurl -plaintext "${ADDR}" list "${SERVICE}" >/dev/null -echo "OK" -echo - -echo "== Listing ${SERVICE} methods ==" -grpcurl -plaintext "${ADDR}" list "${SERVICE}" -echo - -echo "== GetAllUnits ==" -grpcurl -plaintext \ - -d '{}' \ - "${ADDR}" \ - "${SERVICE}/GetAllUnits" -echo - -echo "== GetLoadedUnits ==" -grpcurl -plaintext \ - -d '{}' \ - "${ADDR}" \ - "${SERVICE}/GetLoadedUnits" -echo - -echo "== GetUnitStatus (tailscaled.service) ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service" - }' \ - "${ADDR}" \ - "${SERVICE}/GetUnitStatus" -echo - -echo "== PerformUnitAction: START tailscaled.service ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_ACTION_START" - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitAction" -echo - -echo "== PerformUnitFileAction: ENABLE tailscaled.service (runtime=true, force=true) ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_FILE_ACTION_ENABLE", - "runtime": true, - "force": true - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitFileAction" -echo - -echo "== PerformUnitAction: STOP tailscaled.service ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_ACTION_STOP", - "force": true - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitAction" -echo - - -echo "== PerformUnitFileAction: DISABLE tailscaled.service ==" -grpcurl -plaintext \ - -d '{ - "unitName": "tailscaled.service", - "action": "UNIT_FILE_ACTION_DISABLE", - "runtime": true, - "force": true - }' \ - "${ADDR}" \ - "${SERVICE}/PerformUnitFileAction" -echo - -echo "== All gRPC v2 tests passed ==" From e42d0b3629bf6ee461b9d64f373534b853195aa0 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:41:34 +0100 Subject: [PATCH 06/55] feature: v3 services --- proto/services/v3/services.proto | 124 +++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 proto/services/v3/services.proto diff --git a/proto/services/v3/services.proto b/proto/services/v3/services.proto new file mode 100644 index 0000000..1319324 --- /dev/null +++ b/proto/services/v3/services.proto @@ -0,0 +1,124 @@ +syntax = "proto3"; + +package services.v3; + +option go_package = "paradigm-ehb/agent/proto/services/v3"; + + +service HandlerService { + rpc PerformUnitAction (UnitActionRequest) returns (UnitActionReply); + rpc PerformUnitFileAction (UnitFileActionRequest) returns (UnitFileActionReply); + rpc GetAllUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetLoadedUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetFilteredUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetUnitStatus (GetUnitStatusRequest) returns (GetUnitStatusReply); +} + + +message UnitActionRequest { + string unit_name = 1; + + enum UnitAction { + UNIT_ACTION_UNSPECIFIED = 0; + UNIT_ACTION_START = 1; + UNIT_ACTION_STOP = 2; + UNIT_ACTION_RESTART = 3; + } + + UnitAction action = 2; + bool force = 3; +} + +message UnitActionReply { + bytes status = 1; + bool success = 2; + string error_message = 3; +} + + +message UnitFileActionRequest { + string unit_name = 1; + + enum UnitFileAction { + UNIT_FILE_ACTION_UNSPECIFIED = 0; + UNIT_FILE_ACTION_ENABLE = 1; + UNIT_FILE_ACTION_DISABLE = 2; + } + + UnitFileAction action = 2; + bool runtime = 3; + bool force = 4; +} + +message UnitFileActionReply { + bytes status = 1; + bool success = 2; + string error_message = 3; +} + + +message LoadedUnit { + string name = 1; + string description = 2; + string load_state = 3; + string sub_state = 4; + string active_state = 5; + string dep_unit = 6; + + string object_path = 7; + + uint32 queued_job = 8; + string job_type = 9; + string job_path = 10; +} + +message Unit { + string name = 1; + string state = 2; +} + + +message GetUnitsRequest { + + enum UnitState { + UNIT_STATE_UNSPECIFIED = 0; + ENABLED = 1; + DISABLED = 2; + } + + string name = 1; + UnitState state = 2; +} + +message GetUnitsFilteredRequest { + + enum UnitFileState { + + UNIT_FILE_STATE_UNSPECIFIED = 0; + LOADED = 1; + NOT_FOUND = 2; + BAD_SETTING = 3; + ERROR = 4; + MASKED = 5; + } + + UnitFileState state = 1; + +} + +message GetUnitsReply { + repeated LoadedUnit units = 1; + bool success = 2; + string error_message = 3; +} + + +message GetUnitStatusRequest { + string unit_name = 1; +} + +message GetUnitStatusReply { + string state = 1; + bool success = 2; + string error_message = 3; +} From 16a3aba5280d4db961dcbd8236126efc5068b108 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:41:47 +0100 Subject: [PATCH 07/55] feature: resources services v2 and file restructure --- proto/resources/v1/deviceresources.proto | 1 + proto/resources/v2/deviceresources.proto | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/proto/resources/v1/deviceresources.proto b/proto/resources/v1/deviceresources.proto index 667c92a..63e6087 100644 --- a/proto/resources/v1/deviceresources.proto +++ b/proto/resources/v1/deviceresources.proto @@ -92,6 +92,7 @@ enum ProcessState { PROCESS_STATE_TRACING_STOPPED = 5; PROCESS_STATE_ZOMBIE = 6; PROCESS_STATE_DEAD = 7; + PROCESS_STATE_IDLE = 8; } /** diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 0272feb..55beeec 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -92,6 +92,8 @@ enum ProcessState { PROCESS_STATE_TRACING_STOPPED = 5; PROCESS_STATE_ZOMBIE = 6; PROCESS_STATE_DEAD = 7; + PROCESS_STATE_IDLE = 8; + PROCESS_STATE_UNDEFINED = 9; } /** @@ -99,7 +101,7 @@ enum ProcessState { * @return pid ( process identifier ), process name, the state, todo :) * */ message Process { - uint32 pid = 1; + int32 pid = 1; string name = 2; ProcessState state = 3; uint64 utime = 4; From 431c11376d449dd8cbb3401ef47b5401fad0c9d5 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:42:27 +0100 Subject: [PATCH 08/55] feature: adjusted the dbus manager for filtered services and better login objects handling still a lot of todos to handle --- internal/dbusservices/dbus/call.go | 33 +++++- internal/dbusservices/dbus/parse.go | 40 +++++-- internal/dbusservices/manager.go | 165 +++++++++++++++----------- internal/dbusservices/systemd/list.go | 40 +++++-- internal/dbusservices/types/types.go | 2 +- 5 files changed, 191 insertions(+), 89 deletions(-) diff --git a/internal/dbusservices/dbus/call.go b/internal/dbusservices/dbus/call.go index 9f9ac0b..5b0391a 100644 --- a/internal/dbusservices/dbus/call.go +++ b/internal/dbusservices/dbus/call.go @@ -1,6 +1,7 @@ package dbushandler import ( + "fmt" "github.com/godbus/dbus" ) @@ -9,7 +10,35 @@ import ( * used to handle services * @return BusObjcet */ -func CreateSystemdObject(conn *dbus.Conn) dbus.BusObject { +func CreateSystemdObject(conn *dbus.Conn) (dbus.BusObject, error) { + + obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") + if obj == nil { + + return nil, fmt.Errorf("failed to create a systemd object") + } + + return obj, nil +} + +/** + +* CreateLoginObject +* used to handle services +* @return BusObject + +*/ +func CreateLoginObject(conn *dbus.Conn) (dbus.BusObject, error) { + obj := conn.Object( + "org.freedesktop.login1", + "/org/freedesktop/login1", + ) + + if obj == nil { + + return nil, fmt.Errorf("failed to create a login object") + } + + return obj, nil - return conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") } diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index 5f77fe9..b8bf23f 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -2,40 +2,54 @@ package dbushandler import ( - "fmt" - svctypes "paradigm-ehb/agent/internal/dbusservices/types" + types "paradigm-ehb/agent/internal/dbusservices/types" + "strings" ) -// TODO: implement interfaces maybe +// TODO(nasr): implement interfaces maybe // Method // @param chan a(ss), chan a(ss) // @param chan UnitFileEntry, chan UnitFileEntry // @return nil -func ParseUnitFileEntries(in chan []svctypes.UnitFileEntry, out chan []svctypes.UnitFileEntry) { +func ParseUnits(in chan []types.Unit, out chan []types.Unit) { input := <-in - for i := range input { + /** + filter the units on services and remove devices etc + */ + buffer := make([]types.Unit, 0, len(input)) + for _, value := range input { - if input[i].State == "enabled" { - continue - } else { - out <- input + if strings.HasSuffix(".service", value.Name) { + buffer = append(buffer, value) } } + out <- buffer + + return + } -func ParseLoadedUnits(in chan []svctypes.LoadedUnit, out chan []svctypes.LoadedUnit) { +func ParseLoadedUnits(in chan []types.LoadedUnit, out chan []types.LoadedUnit) { input := <-in - for i := range input { + /** + filter the units on services and remove devices etc + */ + buffer := make([]types.LoadedUnit, 0, len(input)) + for _, value := range input { - fmt.Println(input[i]) + if strings.HasSuffix(".service", string(value.Name)) { + buffer = append(buffer, value) + } } - out <- input + out <- buffer + + return } diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 2a86ec0..e4eb8e3 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -1,21 +1,23 @@ - package dbus_services import ( "fmt" + "log" - v2 "paradigm-ehb/agent/gen/services/v2" - dh "paradigm-ehb/agent/internal/dbusservices/dbus" - svc "paradigm-ehb/agent/internal/dbusservices/systemd" - svctypes "paradigm-ehb/agent/internal/dbusservices/types" + dbushelper "paradigm-ehb/agent/internal/dbusservices/dbus" + systemd "paradigm-ehb/agent/internal/dbusservices/systemd" + types "paradigm-ehb/agent/internal/dbusservices/types" "github.com/godbus/dbus" ) // @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" -func RunAction(conn *dbus.Conn, ac svc.UnitAction, service string) error { +func RunAction( + conn *dbus.Conn, + ac systemd.UnitAction, + service string) error { - obj := dh.CreateSystemdObject(conn) + obj, _ := dbushelper.CreateSystemdObject(conn) if !obj.Path().IsValid() { return fmt.Errorf("object path is invalid") } @@ -30,9 +32,14 @@ func RunAction(conn *dbus.Conn, ac svc.UnitAction, service string) error { } // @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" -func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime bool, enableForce bool, service []string) error { +func RunSymlinkAction( + conn *dbus.Conn, + sc systemd.UnitFileAction, + enableForRunTime bool, + enableForce bool, + service []string) error { - obj := dh.CreateSystemdObject(conn) + obj, _ := dbushelper.CreateSystemdObject(conn) if !obj.Path().IsValid() { fmt.Println("invalid systemd path") @@ -43,12 +50,12 @@ func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime b switch sc { - case svc.UnitFileActionEnable: + case systemd.UnitFileActionEnable: call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime, enableForce) if call.Err != nil { return fmt.Errorf("error %v", call.Err) } - case svc.UnitFileActionDisable: + case systemd.UnitFileActionDisable: call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime) if call.Err != nil { return fmt.Errorf("something happened here %v", call.Err) @@ -58,85 +65,113 @@ func RunSymlinkAction(conn *dbus.Conn, sc svc.UnitFileAction, enableForRunTime b return nil } -/** -Helper function to retrieve all loaded units - -*/ -func getLoadedUnits(conn *dbus.Conn) []*v2.LoadedUnit { +// MapLoadedUnits /* +func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { - obj := dh.CreateSystemdObject(conn) - ch := make(chan []svctypes.LoadedUnit) - parse := make(chan []svctypes.LoadedUnit) + obj, _ := dbushelper.CreateSystemdObject(conn) + ch := make(chan []types.LoadedUnit) + parse := make(chan []types.LoadedUnit) - go svc.GetLoadedUnits(obj, ch) - go dh.ParseLoadedUnits(ch, parse) + go systemd.GetLoadedUnits(obj, ch) + go dbushelper.ParseLoadedUnits(ch, parse) loaded := <-parse - units := make([]*v2.LoadedUnit, 0, len(loaded)) + units := make([]*types.LoadedUnit, 0, len(loaded)) + for _, u := range loaded { - units = append(units, &v2.LoadedUnit{ + units = append(units, &types.LoadedUnit{ Name: u.Name, Description: u.Description, LoadState: u.LoadState, SubState: u.SubState, ActiveState: u.ActiveState, DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), + ObjectPath: u.ObjectPath, /*oops typo in queued job :)*/ - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), + QueudJob: u.QueudJob, + JobType: u.JobType, + JobPath: u.JobPath, + }) + } + + return units +} + +func MapFilteredUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + + /* + + dbushelper.CreateSystemdObject(conn) + TODO(nasr): map the filtered units to their appropriate object and pass + them to the grpc handler + **/ + + return nil, nil +} + +func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { + + /** + TODO(nasr): fix the error handling + */ + obj, _ := dbushelper.CreateSystemdObject(conn) + + in := make(chan []types.Unit) + out := make(chan []types.Unit) + + go systemd.GetUnits(obj, in) + go dbushelper.ParseUnits(in, out) + + var entries []types.Unit + + units := make([]*types.LoadedUnit, 0, len(entries)) + + entries = <-out + + for _, e := range entries { + + units = append(units, &types.LoadedUnit{ + Name: e.Name, + Description: "Not available", + LoadState: e.State, + SubState: "Not Available", + ActiveState: "Not Available", + DepUnit: "Not Available", + ObjectPath: "Not Available", + QueudJob: 0, + JobType: "Not Available", + JobPath: "Not Available", }) } return units } -// @param, true for all on disk, false for loaded units +/* +* +* @param, true for all on disk, false for loaded units +* a loaded unit is a unit that has been activated before +* and is available in memoery for the server to start up +* or something like that +* @return []*types.LoadedUnit, error + */ func RunRetrieval( conn *dbus.Conn, - all bool, -) ([]*v2.LoadedUnit, error) { - - obj := dh.CreateSystemdObject(conn) - if all { - ch := make(chan []svctypes.UnitFileEntry) - parse := make(chan []svctypes.UnitFileEntry) - - go svc.GetAllUnits(obj, ch) - go dh.ParseUnitFileEntries(ch, parse) - - entries := <-parse - - units := make([]*v2.LoadedUnit, 0, len(entries)) - for _, e := range entries { - units = append(units, &v2.LoadedUnit{ - Name: e.Name, - Description: "", - LoadState: e.State, - SubState: "", - ActiveState: "", - DepUnit: "", - ObjectPath: "", - QueuedJob: 0, - JobType: "", - JobPath: "", - }) - } + requestAllUnitsOnDisk bool, +) ([]*types.LoadedUnit, error) { - loadedUnits := getLoadedUnits(conn) - for _, i := range loadedUnits { - - units = append(units, i) - } + conn, err := dbushelper.CreateSystemBus() - return units, nil + if err != nil { + log.Printf("failed to create a system bus connection for retrieving units %v", err) } - units := getLoadedUnits(conn) + if requestAllUnitsOnDisk { + return MapUnits(conn), nil + } - return units, nil + return MapLoadedUnits(conn), nil } func GetStatus(obj dbus.BusObject, name string) (string, error) { @@ -156,7 +191,5 @@ func GetStatus(obj dbus.BusObject, name string) (string, error) { return "call store: ", err } - fmt.Println("status:", result) - return result, nil } diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index bbf46dd..3361b88 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -2,7 +2,7 @@ package servicecontrol import ( "fmt" - svctypes "paradigm-ehb/agent/internal/dbusservices/types" + types "paradigm-ehb/agent/internal/dbusservices/types" "github.com/godbus/dbus" ) @@ -14,10 +14,11 @@ import ( // files (templates) cannot directly be loaded as units but need to be instantiated. // --------------------------------------------------------------------------------------- // Method returns an array of all currently loaded units, -// -func GetLoadedUnits(obj dbus.BusObject, out chan []svctypes.LoadedUnit) { +func GetLoadedUnits( + obj dbus.BusObject, + out chan []types.LoadedUnit) { - var result []svctypes.LoadedUnit + var result []types.LoadedUnit call := obj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0) if call.Err != nil { @@ -25,7 +26,6 @@ func GetLoadedUnits(obj dbus.BusObject, out chan []svctypes.LoadedUnit) { return } - err := call.Store(&result) if err != nil { @@ -36,12 +36,14 @@ func GetLoadedUnits(obj dbus.BusObject, out chan []svctypes.LoadedUnit) { } -func GetAllUnits(obj dbus.BusObject, out chan []svctypes.UnitFileEntry) { +func GetUnits( + obj dbus.BusObject, + out chan []types.Unit) { // ListUnitFiles(out a(ss) files); // an array of struct string string - var result []svctypes.UnitFileEntry + var result []types.Unit call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0) @@ -54,5 +56,29 @@ func GetAllUnits(obj dbus.BusObject, out chan []svctypes.UnitFileEntry) { if err != nil { return } + + out <- result +} + +func GetUnitsFiltered( + obj dbus.BusObject, + out chan []types.LoadedUnit, + states []string) { + + var result []types.LoadedUnit + + call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states) + + if call.Err != nil { + fmt.Println("failed to call filtered list of units") + return + } + + err := call.Store(&result) + if err != nil { + return + } + out <- result + } diff --git a/internal/dbusservices/types/types.go b/internal/dbusservices/types/types.go index 4f01975..83c2df1 100644 --- a/internal/dbusservices/types/types.go +++ b/internal/dbusservices/types/types.go @@ -27,7 +27,7 @@ type Service struct { } // type a(ss) -type UnitFileEntry struct { +type Unit struct { Name string State string } From 826b9934f806fc4ad6bd5c63529434cc9c790004 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:42:41 +0100 Subject: [PATCH 09/55] feature: v2 resources grpc handler --- pkg/grpchandler/resources/v1/resources.go | 192 ++++++++++++++++ pkg/grpchandler/resources/v2/resources.go | 264 ++++++++++++++++++++++ 2 files changed, 456 insertions(+) create mode 100644 pkg/grpchandler/resources/v1/resources.go create mode 100644 pkg/grpchandler/resources/v2/resources.go diff --git a/pkg/grpchandler/resources/v1/resources.go b/pkg/grpchandler/resources/v1/resources.go new file mode 100644 index 0000000..c9f9e01 --- /dev/null +++ b/pkg/grpchandler/resources/v1/resources.go @@ -0,0 +1,192 @@ +package grpc_handler + +import ( + "context" + + proto "paradigm-ehb/agent/gen/resources/v1" + res "paradigm-ehb/agent/internal/resources" + wr "paradigm-ehb/agent/pkg/cgowrap" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +/** + * ResourcesService implements the gRPC ResourcesServiceServer. + * + * It exposes endpoints that provide a snapshot of system-level resources + * such as CPU, memory, disks, and running processes. All data collection + * is delegated to the internal resources package and then mapped to + * protobuf-defined response types. + */ +type ResourcesService struct { + proto.UnimplementedResourcesServiceServer +} + +/** + * GetSystemResources returns a full snapshot of system resources. + * + * The method: + * - Respects context cancellation to avoid unnecessary work + * - Collects system information via the internal resources layer + * - Maps internal domain structures to protobuf response messages + * + * Errors: + * - Returns codes.Canceled if the request context is canceled + * - Returns codes.Internal if resource collection fails + */ +func (s *ResourcesService) GetSystemResources( + ctx context.Context, + req *proto.GetSystemResourcesRequest, +) (*proto.GetSystemResourcesResponse, error) { + + select { + case <-ctx.Done(): + return nil, status.Error(codes.Canceled, "request canceled") + default: + } + + snap, err := res.GetCompleteSystemResources() + if err != nil { + return nil, status.Errorf( + codes.Internal, + "failed to collect system resources: %v", + err, + ) + } + + return &proto.GetSystemResourcesResponse{ + Resources: mapSystemResources(snap), + }, nil +} + +/** + * mapSystemResources converts an internal SystemResources snapshot + * into its protobuf representation. + * + * This acts as the top-level aggregation mapper, delegating + * to more specific mapping functions per subsystem. + */ +func mapSystemResources(s *res.SystemResources) *proto.SystemResources { + return &proto.SystemResources{ + Cpu: mapCPU(s.CPU), + Memory: mapMemory(s.Memory), + Device: mapDevice(s.Device), + Disks: mapDisks(s.Disks), + Processes: mapProcesses(s.Procs), + } +} + +/** + * mapCPU maps CPU metadata from the cgo wrapper type + * into the protobuf Cpu message. + */ +func mapCPU(c wr.Cpu) *proto.Cpu { + return &proto.Cpu{ + Vendor: c.Vendor, + Model: c.Model, + Frequency: c.Frequency, + MaxCore: c.MaxCore, + } +} + +/** + * mapMemory maps RAM usage information into the protobuf Memory message. + * + * Values are expected to be raw byte counts as reported by the system. + */ +func mapMemory(m wr.Ram) *proto.Memory { + return &proto.Memory{ + Total: m.Total, + Free: m.Free, + } +} + +/** + * mapDevice maps general device and OS-level metadata + * into the protobuf Device message. + */ +func mapDevice(d wr.Device) *proto.Device { + return &proto.Device{ + OsVersion: d.OsVersion, + Uptime: d.Uptime, + } +} + +/** + * mapDisks maps a slice of disk descriptors into protobuf Disk messages. + * + * Each disk contains a list of partitions, which are also converted + * field-by-field into their protobuf equivalents. + */ +func mapDisks(disks []wr.Disk) []*proto.Disk { + out := make([]*proto.Disk, 0, len(disks)) + + for _, d := range disks { + parts := make([]*proto.DiskPartition, 0, len(d.Partitions)) + for _, p := range d.Partitions { + parts = append(parts, &proto.DiskPartition{ + Name: p.Name, + Major: p.Major, + Minor: p.Minor, + Blocks: p.Blocks, + }) + } + + out = append(out, &proto.Disk{ + Partitions: parts, + }) + } + + return out +} + +/** + * mapProcesses maps a slice of process descriptors into protobuf Process messages. + * + * Each process includes basic scheduling and accounting information + * such as PID, state, CPU time, and thread count. + */ +func mapProcesses(ps []wr.Process) []*proto.Process { + out := make([]*proto.Process, 0, len(ps)) + + for _, p := range ps { + out = append(out, &proto.Process{ + Pid: uint32(p.PID), + Name: p.Name, + State: mapProcessState(p.State), + Utime: p.UTime, + NumThreads: p.NumThreads, + }) + } + + return out +} + +/** + * mapProcessState converts an internal ProcessState enum + * into the corresponding protobuf ProcessState value. + * + * Unknown or unmapped states are converted to PROCESS_STATE_UNSPECIFIED + * to preserve forward compatibility. + */ +func mapProcessState(s wr.ProcessState) proto.ProcessState { + switch s { + case wr.ProcessRunning: + return proto.ProcessState_PROCESS_STATE_RUNNING + case wr.ProcessSleeping: + return proto.ProcessState_PROCESS_STATE_SLEEPING + case wr.ProcessDiskSleep: + return proto.ProcessState_PROCESS_STATE_DISK_SLEEPING + case wr.ProcessStopped: + return proto.ProcessState_PROCESS_STATE_STOPPED + case wr.ProcessTracingStopped: + return proto.ProcessState_PROCESS_STATE_TRACING_STOPPED + case wr.ProcessZombie: + return proto.ProcessState_PROCESS_STATE_ZOMBIE + case wr.ProcessDead: + return proto.ProcessState_PROCESS_STATE_DEAD + default: + return proto.ProcessState_PROCESS_STATE_UNSPECIFIED + } +} diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go new file mode 100644 index 0000000..e385559 --- /dev/null +++ b/pkg/grpchandler/resources/v2/resources.go @@ -0,0 +1,264 @@ +package grpc_handler + +import ( + "context" + + proto "paradigm-ehb/agent/gen/resources/v2" + "paradigm-ehb/agent/internal/resources" + cgo "paradigm-ehb/agent/pkg/cgowrap" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +/** + * ResourcesServiceV2 implements the gRPC ResourcesServiceServer. + * + * It exposes endpoints that provide a snapshot of system-level resources + * such as CPU, memory, disks, and running processes. All data collection + * is delegated to the internal resources package and then mapped to + * protobuf-defined response types. + */ +type ResourcesServiceV2 struct { + proto.UnimplementedResourcesServiceServer +} + +/** + * GetSystemResources returns a full snapshot of system resources. + * + * The method: + * - Respects context cancellation to avoid unnecessary work + * - Collects system information via the internal resources layer + * - Maps internal domain structures to protobuf response messages + * + * Errors: + * - Returns codes.Canceled if the request context is canceled + * - Returns codes.Internal if resource collection fails + */ +func (s *ResourcesServiceV2) GetSystemResources( + ctx context.Context, + req *proto.GetSystemResourcesRequest, +) (*proto.GetSystemResourcesResponse, error) { + + select { + case <-ctx.Done(): + return nil, status.Error(codes.Canceled, "request canceled") + default: + } + + snap, err := resources.GetCompleteSystemResources() + if err != nil { + return nil, status.Errorf( + codes.Internal, + "failed to collect system resources: %v", + err, + ) + } + + return &proto.GetSystemResourcesResponse{ + Resources: mapSystemResources(snap), + }, nil +} + +/** +* TODO(nasr): update individual resources + */ + +func (s *ResourcesServiceV2) KillProcess( + ctx context.Context, + req *proto.KillProcessRequest, +) (*proto.KillProcessReply, error) { + + err := cgo.KillProcess(int(req.Pid)) + if err != nil { + + return &proto.KillProcessReply{ + Succes: "false", + }, nil + } + + return &proto.KillProcessReply{ + Succes: "true", + }, nil +} + +/** + * mapSystemResources converts an internal SystemResources snapshot + * into its protobuf representation. + * + * This acts as the top-level aggregation mapper, delegating + * to more specific mapping functions per subsystem. + */ +func mapSystemResources(s *resources.SystemResources) *proto.SystemResources { + + + return &proto.SystemResources{ + Cpu: mapCPU(s.CPU), + Memory: mapMemory(s.Memory), + Device: mapDevice(s.Device), + Disks: mapDisks(s.Disks), + Processes: mapProcesses(s.Procs), + } +} + +/** + * mapCPU maps CPU metadata from the cgo wrapper type + * into the protobuf Cpu message. + */ +func mapCPU(c cgo.Cpu) *proto.Cpu { + + return &proto.Cpu{ + Vendor: c.Vendor, + Model: c.Model, + Frequency: c.Frequency, + MaxCore: c.MaxCore, + } + +} + +/** + * mapMemory maps RAM usage information into the protobuf Memory message. + * + * Values are expected to be raw byte counts as reported by the system. + */ +func mapMemory(m cgo.Ram) *proto.Memory { + + return &proto.Memory{ + Total: m.Total, + Free: m.Free, + } + +} + +/** + * mapDevice maps general device and OS-level metadata + * into the protobuf Device message. + */ +func mapDevice(d cgo.Device) *proto.Device { + + return &proto.Device{ + OsVersion: d.OsVersion, + Uptime: d.Uptime, + } + +} + +/** + * mapDisks maps a slice of disk descriptors into protobuf Disk messages. + * + * Each disk contains a list of partitions, which are also converted + * field-by-field into their protobuf equivalents. + */ +func mapDisks(disks []cgo.Disk) []*proto.Disk { + + out := make([]*proto.Disk, 0, len(disks)) + + for _, d := range disks { + + parts := make([]*proto.DiskPartition, 0, len(d.Partitions)) + + for _, p := range d.Partitions { + + parts = append(parts, &proto.DiskPartition{ + + Name: p.Name, + Major: p.Major, + Minor: p.Minor, + Blocks: p.Blocks, + }) + } + + out = append(out, &proto.Disk{ + Partitions: parts, + }) + } + + return out +} + +/** + * mapProcessState converts an internal ProcessState enum + * into the corresponding protobuf ProcessState value. + * + * Unknown or unmapped states are converted to PROCESS_STATE_UNSPECIFIED + * to preserve forward compatibility. + */ +func mapProcessState(s cgo.ProcessState) proto.ProcessState { + + switch s { + + + case cgo.ProcessRunning: + { + + return proto.ProcessState_PROCESS_STATE_RUNNING + } + case cgo.ProcessSleeping: + { + + return proto.ProcessState_PROCESS_STATE_SLEEPING + } + case cgo.ProcessDiskSleep: + { + + return proto.ProcessState_PROCESS_STATE_DISK_SLEEPING + } + case cgo.ProcessStopped: + { + + return proto.ProcessState_PROCESS_STATE_STOPPED + } + case cgo.ProcessTracingStopped: + { + + return proto.ProcessState_PROCESS_STATE_TRACING_STOPPED + } + case cgo.ProcessZombie: + { + + return proto.ProcessState_PROCESS_STATE_ZOMBIE + } + case cgo.ProcessDead: + { + + return proto.ProcessState_PROCESS_STATE_DEAD + } + case cgo.ProcessIdle: + { + return proto.ProcessState_PROCESS_STATE_IDLE + } + + case cgo.ProcessUndefined: + { + return proto.ProcessState_PROCESS_STATE_UNSPECIFIED + + } + default: + + return proto.ProcessState_PROCESS_STATE_UNSPECIFIED + } +} + +/** + * mapProcesses maps a slice of process descriptors into protobuf Process messages. + * + * Each process includes basic scheduling and accounting information + * such as PID, state, CPU time, and thread count. + */ +func mapProcesses(ps []cgo.Process) []*proto.Process { + + out := make([]*proto.Process, 0, len(ps)) + + for _, p := range ps { + out = append(out, &proto.Process{ + Pid: int32(p.PID), + Name: p.Name, + State: mapProcessState(p.State), + Utime: p.UTime, + NumThreads: p.NumThreads, + }) + } + + + return out +} From f3f4a12af5be76fccc3c1de61819a70ca6bc3511 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:42:54 +0100 Subject: [PATCH 10/55] feature: v3 grpc handler --- pkg/grpchandler/services/v1/services.go | 101 ++++++++++ pkg/grpchandler/services/v2/services.go | 211 +++++++++++++++++++++ pkg/grpchandler/services/v3/services.go | 240 ++++++++++++++++++++++++ 3 files changed, 552 insertions(+) create mode 100644 pkg/grpchandler/services/v1/services.go create mode 100644 pkg/grpchandler/services/v2/services.go create mode 100644 pkg/grpchandler/services/v3/services.go diff --git a/pkg/grpchandler/services/v1/services.go b/pkg/grpchandler/services/v1/services.go new file mode 100644 index 0000000..719e0ed --- /dev/null +++ b/pkg/grpchandler/services/v1/services.go @@ -0,0 +1,101 @@ +package grpc_handler + +import ( + "context" + "log" + + manager "paradigm-ehb/agent/internal/dbusservices" + + "paradigm-ehb/agent/gen/services/v1" + dh "paradigm-ehb/agent/internal/dbusservices/dbus" + "paradigm-ehb/agent/internal/dbusservices/systemd" + + "github.com/godbus/dbus" +) + +type HandlerService struct { + v1.UnimplementedHandlerServiceServer +} + +func (s *HandlerService) UnitAction(_ context.Context, in *v1.ServiceActionRequest) (*v1.ServiceActionReply, error) { + + var out string + conn, err := dh.CreateSystemBus() + if err != nil { + + log.Println("failed to create systembus") + out = "failed system bus" + } + + defer func(conn *dbus.Conn) { + if err != nil { + + } + }(conn) + + if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_ENABLE { + + err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionEnable, true, true, []string{in.ServiceName}) + if err != nil { + + log.Println("failed to enable service") + out = "failed unit file action" + } + + } else if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_DISABLE { + + err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionDisable, true, true, []string{in.ServiceName}) + if err != nil { + + log.Println("failed to disable service") + out = "failed unit file action" + } + + } else { + + log.Println("Bad input") + out = "Bad input" + + } + + if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_START { + + err = manager.RunAction(conn, servicecontrol.UnitActionStart, in.ServiceName) + if err != nil { + + log.Println("failed to run action") + out = "failed unit action" + } + + } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_STOP { + + err = manager.RunAction(conn, servicecontrol.UnitActionStop, in.ServiceName) + if err != nil { + + log.Println("failed to run action") + out = "failed unit action" + } + + } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_RESTART { + + err = manager.RunAction(conn, servicecontrol.UnitActionRestart, in.ServiceName) + if err != nil { + + log.Println("failed to run action") + out = "failed unit action" + } + + } else { + + log.Println("external bad input") + out = "external bad input" + } + + _, err = manager.RunRetrieval(conn, true) + if err != nil { + log.Println("failed to do everything") + out = "failed even more" + } + + return &v1.ServiceActionReply{Status: out}, nil +} diff --git a/pkg/grpchandler/services/v2/services.go b/pkg/grpchandler/services/v2/services.go new file mode 100644 index 0000000..3ded666 --- /dev/null +++ b/pkg/grpchandler/services/v2/services.go @@ -0,0 +1,211 @@ +package grpc_handler + +import ( + "context" + "fmt" + + v2 "paradigm-ehb/agent/gen/services/v2" + manager "paradigm-ehb/agent/internal/dbusservices" + dh "paradigm-ehb/agent/internal/dbusservices/dbus" + servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" + +) + +type HandlerServiceV2 struct { + v2.UnimplementedHandlerServiceServer +} + + +func (s *HandlerServiceV2) PerformUnitAction( + _ context.Context, + in *v2.UnitActionRequest, +) (*v2.UnitActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.UnitActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitAction + var actionName string + + switch in.Action { + case v2.UnitActionRequest_UNIT_ACTION_START: + action = servicecontrol.UnitActionStart + actionName = "start" + case v2.UnitActionRequest_UNIT_ACTION_STOP: + action = servicecontrol.UnitActionStop + actionName = "stop" + case v2.UnitActionRequest_UNIT_ACTION_RESTART: + action = servicecontrol.UnitActionRestart + actionName = "restart" + default: + return &v2.UnitActionReply{ + Success: false, + ErrorMessage: "unspecified unit action", + }, nil + } + + err = manager.RunAction(conn, action, in.UnitName) + if err != nil { + return &v2.UnitActionReply{ + Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.UnitActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + + +func (s *HandlerServiceV2) PerformUnitFileAction( + _ context.Context, + in *v2.UnitFileActionRequest, +) (*v2.UnitFileActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.UnitFileActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitFileAction + + switch in.Action { + case v2.UnitFileActionRequest_UNIT_FILE_ACTION_ENABLE: + action = servicecontrol.UnitFileActionEnable + case v2.UnitFileActionRequest_UNIT_FILE_ACTION_DISABLE: + action = servicecontrol.UnitFileActionDisable + default: + return &v2.UnitFileActionReply{ + Success: false, + ErrorMessage: "unspecified unit file action", + }, nil + } + + err = manager.RunSymlinkAction( + conn, + action, + in.Runtime, + in.Force, + []string{in.UnitName}, + ) + + if err != nil { + return &v2.UnitFileActionReply{ + Status: []byte("unit file action failed"), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.UnitFileActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + + +func (s *HandlerServiceV2) GetAllUnits( + _ context.Context, + _ *v2.GetUnitsRequest, +) (*v2.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + + TODO(nasr): fix the design issue and return the correct mapped types + from the correct namespace + + */ + _, err = manager.RunRetrieval(conn, true) + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + +func (s *HandlerServiceV2) GetLoadedUnits( + _ context.Context, + _ *v2.GetUnitsRequest, +) (*v2.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + /** + + TODO(nasr): fix the design issue and return the correct mapped types + from the correct namespace + + */ + _, err = manager.RunRetrieval(conn, false) + if err != nil { + return &v2.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + + +func (s *HandlerServiceV2) GetUnitStatus( + _ context.Context, + in *v2.GetUnitStatusRequest, +) (*v2.GetUnitStatusReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v2.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + obj, _ := dh.CreateSystemdObject(conn) + + state, err := manager.GetStatus(obj, in.UnitName) + if err != nil { + return &v2.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v2.GetUnitStatusReply{ + State: state, + Success: true, + }, nil +} diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go new file mode 100644 index 0000000..ae67b55 --- /dev/null +++ b/pkg/grpchandler/services/v3/services.go @@ -0,0 +1,240 @@ +package grpc_handler + +import ( + "context" + "fmt" + + v3 "paradigm-ehb/agent/gen/services/v3" + manager "paradigm-ehb/agent/internal/dbusservices" + dh "paradigm-ehb/agent/internal/dbusservices/dbus" + servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" +) + +type HandlerServicev3 struct { + v3.UnimplementedHandlerServiceServer +} + +func (s *HandlerServicev3) PerformUnitAction( + _ context.Context, + in *v3.UnitActionRequest, +) (*v3.UnitActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.UnitActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitAction + var actionName string + + switch in.Action { + case v3.UnitActionRequest_UNIT_ACTION_START: + action = servicecontrol.UnitActionStart + actionName = "start" + case v3.UnitActionRequest_UNIT_ACTION_STOP: + action = servicecontrol.UnitActionStop + actionName = "stop" + case v3.UnitActionRequest_UNIT_ACTION_RESTART: + action = servicecontrol.UnitActionRestart + actionName = "restart" + default: + return &v3.UnitActionReply{ + Success: false, + ErrorMessage: "unspecified unit action", + }, nil + } + + err = manager.RunAction(conn, action, in.UnitName) + if err != nil { + return &v3.UnitActionReply{ + Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.UnitActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + +func (s *HandlerServicev3) PerformUnitFileAction( + _ context.Context, + in *v3.UnitFileActionRequest, +) (*v3.UnitFileActionReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.UnitFileActionReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + var action servicecontrol.UnitFileAction + + switch in.Action { + case v3.UnitFileActionRequest_UNIT_FILE_ACTION_ENABLE: + action = servicecontrol.UnitFileActionEnable + case v3.UnitFileActionRequest_UNIT_FILE_ACTION_DISABLE: + action = servicecontrol.UnitFileActionDisable + default: + return &v3.UnitFileActionReply{ + Success: false, + ErrorMessage: "unspecified unit file action", + }, nil + } + + err = manager.RunSymlinkAction( + conn, + action, + in.Runtime, + in.Force, + []string{in.UnitName}, + ) + + if err != nil { + return &v3.UnitFileActionReply{ + Status: []byte("unit file action failed"), + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.UnitFileActionReply{ + Status: []byte("success"), + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetAllUnits( + _ context.Context, + _ *v3.GetUnitsRequest, +) (*v3.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + + Handle error hanlding + */ + _, err = manager.RunRetrieval(conn, true) + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + // mappedUnits := make(*v3.GetUnitsReply, 0, len(units)) + // mappedUnits = &units{} + + return &v3.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetLoadedUnits( + _ context.Context, + _ *v3.GetUnitsRequest, +) (*v3.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + TODO(nasr): handle error + */ + _, err = manager.RunRetrieval(conn, false) + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.GetUnitsReply{ + Units: nil, + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetUnitStatus( + _ context.Context, + in *v3.GetUnitStatusRequest, +) (*v3.GetUnitStatusReply, error) { + + conn, err := dh.CreateSystemBus() + if err != nil { + return &v3.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + TODO(nasr): todo handle error + */ + obj, _ := dh.CreateSystemdObject(conn) + + state, err := manager.GetStatus(obj, in.UnitName) + if err != nil { + return &v3.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + return &v3.GetUnitStatusReply{ + State: state, + Success: true, + }, nil +} + +func (s *HandlerServicev3) GetFilteredUnits( + _ context.Context, + in *v3.GetUnitsFilteredRequest, +) (*v3.GetUnitsReply, error) { + + conn, err := dh.CreateSystemBus() + + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + + /** + + TODO(nasr): handle the correct types + + */ + _, err = manager.MapFilteredUnits(conn) + if err != nil { + + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, fmt.Errorf("failed to map filtered units %v: ", err) + } + + return &v3.GetUnitsReply{}, nil +} From f1b5c6bb135654f153fc4bf2037d55a214616491 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:43:05 +0100 Subject: [PATCH 11/55] refacor: folder restructure --- pkg/grpc_handler/resources.go | 192 ----------------- pkg/grpc_handler/services.go | 101 --------- pkg/grpc_handler/services_v2.go | 200 ------------------ .../deviceactions.go | 9 +- pkg/{grpc_handler => grpchandler}/greeter.go | 0 pkg/{grpc_handler => grpchandler}/journal.go | 0 6 files changed, 2 insertions(+), 500 deletions(-) delete mode 100644 pkg/grpc_handler/resources.go delete mode 100644 pkg/grpc_handler/services.go delete mode 100644 pkg/grpc_handler/services_v2.go rename pkg/{grpc_handler => grpchandler}/deviceactions.go (91%) rename pkg/{grpc_handler => grpchandler}/greeter.go (100%) rename pkg/{grpc_handler => grpchandler}/journal.go (100%) diff --git a/pkg/grpc_handler/resources.go b/pkg/grpc_handler/resources.go deleted file mode 100644 index 25f2117..0000000 --- a/pkg/grpc_handler/resources.go +++ /dev/null @@ -1,192 +0,0 @@ -package grpc_handler - -import ( - "context" - - proto "paradigm-ehb/agent/gen/resources/v1" - res "paradigm-ehb/agent/internal/resources" - wr "paradigm-ehb/agent/pkg/cgowrap" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -/** - * ResourcesService implements the gRPC ResourcesServiceServer. - * - * It exposes endpoints that provide a snapshot of system-level resources - * such as CPU, memory, disks, and running processes. All data collection - * is delegated to the internal resources package and then mapped to - * protobuf-defined response types. - */ -type ResourcesService struct { - proto.UnimplementedResourcesServiceServer -} - -/** - * GetSystemResources returns a full snapshot of system resources. - * - * The method: - * - Respects context cancellation to avoid unnecessary work - * - Collects system information via the internal resources layer - * - Maps internal domain structures to protobuf response messages - * - * Errors: - * - Returns codes.Canceled if the request context is canceled - * - Returns codes.Internal if resource collection fails - */ -func (s *ResourcesService) GetSystemResources( - ctx context.Context, - req *proto.GetSystemResourcesRequest, -) (*proto.GetSystemResourcesResponse, error) { - - select { - case <-ctx.Done(): - return nil, status.Error(codes.Canceled, "request canceled") - default: - } - - snap, err := res.GetCompleteSystemResources() - if err != nil { - return nil, status.Errorf( - codes.Internal, - "failed to collect system resources: %v", - err, - ) - } - - return &proto.GetSystemResourcesResponse{ - Resources: mapSystemResources(snap), - }, nil -} - -/** - * mapSystemResources converts an internal SystemResources snapshot - * into its protobuf representation. - * - * This acts as the top-level aggregation mapper, delegating - * to more specific mapping functions per subsystem. - */ -func mapSystemResources(s *res.SystemResources) *proto.SystemResources { - return &proto.SystemResources{ - Cpu: mapCPU(s.CPU), - Memory: mapMemory(s.Memory), - Device: mapDevice(s.Device), - Disks: mapDisks(s.Disks), - Processes: mapProcesses(s.Procs), - } -} - -/** - * mapCPU maps CPU metadata from the cgo wrapper type - * into the protobuf Cpu message. - */ -func mapCPU(c wr.Cpu) *proto.Cpu { - return &proto.Cpu{ - Vendor: c.Vendor, - Model: c.Model, - Frequency: c.Frequency, - MaxCore: c.MaxCore, - } -} - -/** - * mapMemory maps RAM usage information into the protobuf Memory message. - * - * Values are expected to be raw byte counts as reported by the system. - */ -func mapMemory(m wr.Ram) *proto.Memory { - return &proto.Memory{ - Total: m.Total, - Free: m.Free, - } -} - -/** - * mapDevice maps general device and OS-level metadata - * into the protobuf Device message. - */ -func mapDevice(d wr.Device) *proto.Device { - return &proto.Device{ - OsVersion: d.OsVersion, - Uptime: d.Uptime, - } -} - -/** - * mapDisks maps a slice of disk descriptors into protobuf Disk messages. - * - * Each disk contains a list of partitions, which are also converted - * field-by-field into their protobuf equivalents. - */ -func mapDisks(disks []wr.Disk) []*proto.Disk { - out := make([]*proto.Disk, 0, len(disks)) - - for _, d := range disks { - parts := make([]*proto.DiskPartition, 0, len(d.Partitions)) - for _, p := range d.Partitions { - parts = append(parts, &proto.DiskPartition{ - Name: p.Name, - Major: p.Major, - Minor: p.Minor, - Blocks: p.Blocks, - }) - } - - out = append(out, &proto.Disk{ - Partitions: parts, - }) - } - - return out -} - -/** - * mapProcesses maps a slice of process descriptors into protobuf Process messages. - * - * Each process includes basic scheduling and accounting information - * such as PID, state, CPU time, and thread count. - */ -func mapProcesses(ps []wr.Process) []*proto.Process { - out := make([]*proto.Process, 0, len(ps)) - - for _, p := range ps { - out = append(out, &proto.Process{ - Pid: p.PID, - Name: p.Name, - State: mapProcessState(p.State), - Utime: p.UTime, - NumThreads: p.NumThreads, - }) - } - - return out -} - -/** - * mapProcessState converts an internal ProcessState enum - * into the corresponding protobuf ProcessState value. - * - * Unknown or unmapped states are converted to PROCESS_STATE_UNSPECIFIED - * to preserve forward compatibility. - */ -func mapProcessState(s wr.ProcessState) proto.ProcessState { - switch s { - case wr.ProcessRunning: - return proto.ProcessState_PROCESS_STATE_RUNNING - case wr.ProcessSleeping: - return proto.ProcessState_PROCESS_STATE_SLEEPING - case wr.ProcessDiskSleep: - return proto.ProcessState_PROCESS_STATE_DISK_SLEEPING - case wr.ProcessStopped: - return proto.ProcessState_PROCESS_STATE_STOPPED - case wr.ProcessTracingStopped: - return proto.ProcessState_PROCESS_STATE_TRACING_STOPPED - case wr.ProcessZombie: - return proto.ProcessState_PROCESS_STATE_ZOMBIE - case wr.ProcessDead: - return proto.ProcessState_PROCESS_STATE_DEAD - default: - return proto.ProcessState_PROCESS_STATE_UNSPECIFIED - } -} diff --git a/pkg/grpc_handler/services.go b/pkg/grpc_handler/services.go deleted file mode 100644 index 719e0ed..0000000 --- a/pkg/grpc_handler/services.go +++ /dev/null @@ -1,101 +0,0 @@ -package grpc_handler - -import ( - "context" - "log" - - manager "paradigm-ehb/agent/internal/dbusservices" - - "paradigm-ehb/agent/gen/services/v1" - dh "paradigm-ehb/agent/internal/dbusservices/dbus" - "paradigm-ehb/agent/internal/dbusservices/systemd" - - "github.com/godbus/dbus" -) - -type HandlerService struct { - v1.UnimplementedHandlerServiceServer -} - -func (s *HandlerService) UnitAction(_ context.Context, in *v1.ServiceActionRequest) (*v1.ServiceActionReply, error) { - - var out string - conn, err := dh.CreateSystemBus() - if err != nil { - - log.Println("failed to create systembus") - out = "failed system bus" - } - - defer func(conn *dbus.Conn) { - if err != nil { - - } - }(conn) - - if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_ENABLE { - - err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionEnable, true, true, []string{in.ServiceName}) - if err != nil { - - log.Println("failed to enable service") - out = "failed unit file action" - } - - } else if in.GetUnitFileAction() == v1.ServiceActionRequest_UNIT_FILE_ACTION_DISABLE { - - err = manager.RunSymlinkAction(conn, servicecontrol.UnitFileActionDisable, true, true, []string{in.ServiceName}) - if err != nil { - - log.Println("failed to disable service") - out = "failed unit file action" - } - - } else { - - log.Println("Bad input") - out = "Bad input" - - } - - if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_START { - - err = manager.RunAction(conn, servicecontrol.UnitActionStart, in.ServiceName) - if err != nil { - - log.Println("failed to run action") - out = "failed unit action" - } - - } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_STOP { - - err = manager.RunAction(conn, servicecontrol.UnitActionStop, in.ServiceName) - if err != nil { - - log.Println("failed to run action") - out = "failed unit action" - } - - } else if in.GetUnitAction() == v1.ServiceActionRequest_UNIT_ACTION_RESTART { - - err = manager.RunAction(conn, servicecontrol.UnitActionRestart, in.ServiceName) - if err != nil { - - log.Println("failed to run action") - out = "failed unit action" - } - - } else { - - log.Println("external bad input") - out = "external bad input" - } - - _, err = manager.RunRetrieval(conn, true) - if err != nil { - log.Println("failed to do everything") - out = "failed even more" - } - - return &v1.ServiceActionReply{Status: out}, nil -} diff --git a/pkg/grpc_handler/services_v2.go b/pkg/grpc_handler/services_v2.go deleted file mode 100644 index a7549a7..0000000 --- a/pkg/grpc_handler/services_v2.go +++ /dev/null @@ -1,200 +0,0 @@ -package grpc_handler - -import ( - "context" - "fmt" - - v2 "paradigm-ehb/agent/gen/services/v2" - manager "paradigm-ehb/agent/internal/dbusservices" - dh "paradigm-ehb/agent/internal/dbusservices/dbus" - servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" - -) - -type HandlerServiceV2 struct { - v2.UnimplementedHandlerServiceServer -} - - -func (s *HandlerServiceV2) PerformUnitAction( - _ context.Context, - in *v2.UnitActionRequest, -) (*v2.UnitActionReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.UnitActionReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - var action servicecontrol.UnitAction - var actionName string - - switch in.Action { - case v2.UnitActionRequest_UNIT_ACTION_START: - action = servicecontrol.UnitActionStart - actionName = "start" - case v2.UnitActionRequest_UNIT_ACTION_STOP: - action = servicecontrol.UnitActionStop - actionName = "stop" - case v2.UnitActionRequest_UNIT_ACTION_RESTART: - action = servicecontrol.UnitActionRestart - actionName = "restart" - default: - return &v2.UnitActionReply{ - Success: false, - ErrorMessage: "unspecified unit action", - }, nil - } - - err = manager.RunAction(conn, action, in.UnitName) - if err != nil { - return &v2.UnitActionReply{ - Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.UnitActionReply{ - Status: []byte("success"), - Success: true, - }, nil -} - - -func (s *HandlerServiceV2) PerformUnitFileAction( - _ context.Context, - in *v2.UnitFileActionRequest, -) (*v2.UnitFileActionReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.UnitFileActionReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - var action servicecontrol.UnitFileAction - - switch in.Action { - case v2.UnitFileActionRequest_UNIT_FILE_ACTION_ENABLE: - action = servicecontrol.UnitFileActionEnable - case v2.UnitFileActionRequest_UNIT_FILE_ACTION_DISABLE: - action = servicecontrol.UnitFileActionDisable - default: - return &v2.UnitFileActionReply{ - Success: false, - ErrorMessage: "unspecified unit file action", - }, nil - } - - err = manager.RunSymlinkAction( - conn, - action, - in.Runtime, - in.Force, - []string{in.UnitName}, - ) - - if err != nil { - return &v2.UnitFileActionReply{ - Status: []byte("unit file action failed"), - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.UnitFileActionReply{ - Status: []byte("success"), - Success: true, - }, nil -} - - -func (s *HandlerServiceV2) GetAllUnits( - _ context.Context, - _ *v2.GetUnitsRequest, -) (*v2.GetUnitsReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - units, err := manager.RunRetrieval(conn, true) - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.GetUnitsReply{ - Units: units, - Success: true, - }, nil -} - -func (s *HandlerServiceV2) GetLoadedUnits( - _ context.Context, - _ *v2.GetUnitsRequest, -) (*v2.GetUnitsReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - units, err := manager.RunRetrieval(conn, false) - if err != nil { - return &v2.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.GetUnitsReply{ - Units: units, - Success: true, - }, nil -} - - -func (s *HandlerServiceV2) GetUnitStatus( - _ context.Context, - in *v2.GetUnitStatusRequest, -) (*v2.GetUnitStatusReply, error) { - - conn, err := dh.CreateSystemBus() - if err != nil { - return &v2.GetUnitStatusReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - obj := dh.CreateSystemdObject(conn) - - state, err := manager.GetStatus(obj, in.UnitName) - if err != nil { - return &v2.GetUnitStatusReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - return &v2.GetUnitStatusReply{ - State: state, - Success: true, - }, nil -} diff --git a/pkg/grpc_handler/deviceactions.go b/pkg/grpchandler/deviceactions.go similarity index 91% rename from pkg/grpc_handler/deviceactions.go rename to pkg/grpchandler/deviceactions.go index baf9b58..334b2eb 100644 --- a/pkg/grpc_handler/deviceactions.go +++ b/pkg/grpchandler/deviceactions.go @@ -25,18 +25,13 @@ func (s *DeviceActionsService) Action(ctx context.Context, req *actions.ActionRe } var out string - bus, err := dh.CreateSystemBus() + conn, err := dh.CreateSystemBus() if err != nil { log.Println("failed to create systembus") out = "failed system bus" } - - obj := bus.Object( - "org.freedesktop.login1", - "/org/freedesktop/login1", - ) - + obj, _ := dh.CreateLoginObject(conn) switch req.GetDeviceAction() { case actions.DeviceAction_DEVICE_ACTION_SHUTDOWN: out = string(da.PerformDeviceAction(obj, da.DeviceActionShutdown)) diff --git a/pkg/grpc_handler/greeter.go b/pkg/grpchandler/greeter.go similarity index 100% rename from pkg/grpc_handler/greeter.go rename to pkg/grpchandler/greeter.go diff --git a/pkg/grpc_handler/journal.go b/pkg/grpchandler/journal.go similarity index 100% rename from pkg/grpc_handler/journal.go rename to pkg/grpchandler/journal.go From d9de1dc1bddb62203af7306f5c3727828b8cb43d Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:43:17 +0100 Subject: [PATCH 12/55] debug: checkpoint fixing the process status --- pkg/cgowrap/types.go | 44 ---------------- pkg/cgowrap/wrapper.go | 113 ++++++++++++++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 62 deletions(-) diff --git a/pkg/cgowrap/types.go b/pkg/cgowrap/types.go index 0b6a8e2..1d42440 100644 --- a/pkg/cgowrap/types.go +++ b/pkg/cgowrap/types.go @@ -33,48 +33,4 @@ type Device struct { Uptime string } -// ProcessState represents the state of a process. -type ProcessState uint32 -const ( - ProcessUndefined ProcessState = 0 - ProcessRunning ProcessState = 1 - ProcessSleeping ProcessState = 2 - ProcessDiskSleep ProcessState = 3 - ProcessStopped ProcessState = 4 - ProcessTracingStopped ProcessState = 5 - ProcessZombie ProcessState = 6 - ProcessDead ProcessState = 7 -) - -// String returns a human-readable representation of the process state. -func (ps ProcessState) String() string { - switch ps { - case ProcessRunning: - return "Running" - case ProcessSleeping: - return "Sleeping" - case ProcessDiskSleep: - return "Disk Sleep" - case ProcessStopped: - return "Stopped" - case ProcessTracingStopped: - return "Tracing Stopped" - case ProcessZombie: - return "Zombie" - case ProcessDead: - return "Dead" - default: - return "Undefined" - } -} - -// Process represents a single process with its attributes. -type Process struct { - PID uint32 - Name string - State ProcessState - UTime uint64 - STime uint64 - NumThreads uint32 -} diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index 4283da8..49b95a2 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -13,6 +13,56 @@ import ( "golang.org/x/sys/unix" ) +// ProcessState represents the state of a process. + +type ProcessState C.int32_t + +const ( + ProcessUndefined ProcessState = C.PROCESS_UNDEFINED + ProcessRunning ProcessState = C.PROCESS_RUNNING + ProcessSleeping ProcessState = C.PROCESS_SLEEPING + ProcessDiskSleep ProcessState = C.PROCESS_DISK_SLEEP + ProcessStopped ProcessState = C.PROCESS_STOPPED + ProcessTracingStopped ProcessState = C.PROCESS_TRACING_STOPPED + ProcessZombie ProcessState = C.PROCESS_ZOMBIE + ProcessDead ProcessState = C.PROCESS_DEAD + ProcessIdle ProcessState = C.PROCESS_IDLE +) + +// Process represents a single process with its attributes. +type Process struct { + PID int32 + State ProcessState + UTime uint64 + STime uint64 + NumThreads uint32 + Name string +} + +func (s ProcessState) String() string { + + switch s { + case ProcessRunning: + return "Running" + case ProcessSleeping: + return "Sleeping" + case ProcessDiskSleep: + return "Disk Sleep" + case ProcessStopped: + return "Stopped" + case ProcessTracingStopped: + return "Tracing Stopped" + case ProcessZombie: + return "Zombie" + case ProcessDead: + return "Dead" + case ProcessIdle: + return "Idle" + default: + return "Undefined" + } +} + // TODO(nasr): research this, interesting, alias vs true aliasing type Arena = C.mem_arena @@ -59,6 +109,14 @@ func AllocateArena(size uint64) (*C.mem_arena, error) { return arena, nil } +func PushArena(arena *C.mem_arena, size uint64) { + + if arena != nil { + C.arena_push(arena, C.ulong(size), 1) + } + +} + /* * DestroyArena unmaps and destroys the memory arena. @@ -334,37 +392,55 @@ Returns: - error: Error if device pointer is nil */ func ReadProcesses(device *C.Device) ([]Process, error) { + if device == nil { - return nil, fmt.Errorf("nil Device pointer") + return nil, fmt.Errorf("dvice null pointer") } - procs := make([]Process, 0, device.processes.count) + count := int(device.processes.count) items := device.processes.items - for i := C.size_t(0); i < device.processes.count; i++ { - p := (*C.Process)( - unsafe.Pointer( - uintptr(unsafe.Pointer(items)) + - uintptr(i)*unsafe.Sizeof(*items), - ), - ) + // slice := unsafe.Slice(items, count) - /** - Read detailed process information - Skip processes that can't be read - */ - if C.process_read(p.pid, p) != C.OK { - continue + procs := make([]Process, 0, count) + + // p := unsafe.Pointer(&items[i]) + + // p := (*C.Process)(unsafe.Pointer(items), uintptr(i)*unsafe.Sizeof(*items)) + + for i := 0; i < count; i++ { + // p := &slice[i] + + p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) + + + err := C.process_read(p.pid, p) + if err != C.OK { + fmt.Errorf("failed reading processes") + } + + if p.state == C.PROCESS_UNDEFINED { + + C.process_read(p.pid, p) } + + fmt.Println(unsafe.Sizeof(C.Process{})) + + + fmt.Println("pid offset ", unsafe.Offsetof(p.pid)) + fmt.Println("state offset", unsafe.Offsetof(p.state)) + fmt.Println("utime offset", unsafe.Offsetof(p.utime)) + procs = append(procs, Process{ - PID: uint32(p.pid), - Name: C.GoString(&p.name[0]), - State: ProcessState(p.state), + PID: int32(p.pid), + State: ProcessState(p.state), //BREAKPOINT UTime: uint64(p.utime), STime: uint64(p.stime), NumThreads: uint32(p.num_threads), + Name: C.GoString(&p.name[0]), }) + } return procs, nil @@ -381,6 +457,7 @@ Returns: - error: Error if the process cannot be killed or doesn't exist */ func KillProcess(pid int) error { + if pid <= 0 { return fmt.Errorf("invalid PID: %d", pid) } From 94e33dfafbd8ceb93283fe3fc719c49193974072 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:43:25 +0100 Subject: [PATCH 13/55] feature: updated entry poitns for v2 and v3 --- cmd/agent/agent.go | 61 ++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 0ee0202..8e7bd6a 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -22,9 +22,12 @@ import ( devacpb "paradigm-ehb/agent/gen/actions/v1" "paradigm-ehb/agent/gen/greet" "paradigm-ehb/agent/gen/journal/v1" - "paradigm-ehb/agent/gen/resources/v1" - services_v1 "paradigm-ehb/agent/gen/services/v1" - services_v2 "paradigm-ehb/agent/gen/services/v2" + + servicesV1 "paradigm-ehb/agent/gen/services/v1" + servicesV2 "paradigm-ehb/agent/gen/services/v2" + + resourcesv1 "paradigm-ehb/agent/gen/resources/v1" + resourcesv2 "paradigm-ehb/agent/gen/resources/v2" "google.golang.org/grpc" "google.golang.org/grpc/health" @@ -33,7 +36,14 @@ import ( "paradigm-ehb/agent/internal/platform" - "paradigm-ehb/agent/pkg/grpc_handler" + "paradigm-ehb/agent/pkg/grpchandler" + + resourcesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/resources/v1" + resourcesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/resources/v2" + + servicesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/services/v1" + servicesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/services/v2" + "syscall" "time" ) @@ -132,29 +142,33 @@ func main() { */ - greet.RegisterGreeterServer(server, &grpc_handler.GreeterServer{}) - services_v1.RegisterHandlerServiceServer(server, &grpc_handler.HandlerService{}) - services_v2.RegisterHandlerServiceServer(server, &grpc_handler.HandlerServiceV2{}) - journal.RegisterJournalServiceServer(server, &grpc_handler.JournalService{}) - resourcespb.RegisterResourcesServiceServer(server, &grpc_handler.ResourcesService{}) - devacpb.RegisterActionServiceServer(server, &grpc_handler.DeviceActionsService{}) + greet.RegisterGreeterServer( + server, + &grpc_handler.GreeterServer{}) - /* - Diagnostics mode (disabled for now) + servicesV1.RegisterHandlerServiceServer( + server, + &servicesHandlerV1.HandlerService{}) - Problem: - - Running diagnostics synchronously introduces an infinite loop - that blocks gRPC reflection and request handling. + servicesV2.RegisterHandlerServiceServer( + server, + &servicesHandlerV2.HandlerServiceV2{}) - Important invariant: - - Diagnostics must run asynchronously and must never interfere - with server startup, reflection, or request processing. + journal.RegisterJournalServiceServer( + server, + &grpc_handler.JournalService{}) - TODO(nasr): - - Move diagnostics into a separate goroutine - - Introduce a proper shutdown context - - Ensure diagnostics respect server lifecycle - */ + resourcesv1.RegisterResourcesServiceServer( + server, + &resourcesHandlerV1.ResourcesService{}) + + resourcesv2.RegisterResourcesServiceServer( + server, + &resourcesHandlerV2.ResourcesServiceV2{}) + + devacpb.RegisterActionServiceServer( + server, + &grpc_handler.DeviceActionsService{}) /** Enable gRPC reflection unconditionally. @@ -171,7 +185,6 @@ func main() { } - /** Start serving requests. This call blocks until the server is stopped or encounters a fatal error. From 99ea9adb14f95ca90126459d86f7a941eabd46a1 Mon Sep 17 00:00:00 2001 From: nasr Date: Thu, 8 Jan 2026 23:44:34 +0100 Subject: [PATCH 14/55] checkpoint --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 5dc30a0..404f56e 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 5dc30a052160c8234393641db3521b59e26f4fb9 +Subproject commit 404f56e54c5e3373f529f0f5b59d6fed2fa241d8 From fa7c69777ba363152fb0337dd84b014136a0148d Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 13:35:44 +0100 Subject: [PATCH 15/55] feature: updated process kill from string to boolean --- pkg/grpchandler/resources/v2/resources.go | 4 ++-- proto/resources/v2/deviceresources.proto | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index e385559..51dc7a8 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -73,12 +73,12 @@ func (s *ResourcesServiceV2) KillProcess( if err != nil { return &proto.KillProcessReply{ - Succes: "false", + Succes: false, }, nil } return &proto.KillProcessReply{ - Succes: "true", + Succes: true, }, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 55beeec..7404d50 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -114,5 +114,5 @@ message KillProcessRequest { message KillProcessReply { - string succes = 1; + bool succes = 1; } From d1d454c234ed69cc9f6a993e372128950e6be7f2 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 13:41:11 +0100 Subject: [PATCH 16/55] bugfix: fixed process state --- pkg/agent-resources | 2 +- pkg/cgowrap/wrapper.go | 166 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 154 insertions(+), 14 deletions(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 404f56e..4358ac6 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 404f56e54c5e3373f529f0f5b59d6fed2fa241d8 +Subproject commit 4358ac69903d0897f09b19f87b78e2aa1cc1e0b7 diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index 49b95a2..b4c14ea 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -3,7 +3,138 @@ package wrapper /* #cgo CFLAGS: -I${SRCDIR}/../agent-resources #cgo LDFLAGS: -L${SRCDIR}/../agent-resources/build -lagent_resources + +#include +#include +#include #include "resources.h" + +int +process_read2(i32 pid, Process *out) +{ + char path[PATH_MAX_LEN]; + snprintf(path, sizeof(path), "/proc/%d/status", pid); + + FILE *fp = fopen(path, "r"); + if (!fp) + { + return ERR_IO; + } + + char buf[BUFFER_SIZE_LARGE]; + + out->pid = pid; + + while (fgets( + buf, + sizeof(buf), + fp)) + { + char *colon = strchr(buf, ':'); + if (!colon) + { + continue; + } + + char *val = colon + 1; + while (*val == ' ' || *val == '\t') + { + ++val; + } + + size_t len = strcspn(val, "\n"); + + printf("lenght: %lu", len); + + if (!strncmp(buf, "Name:", 5)) + { + + memcpy(out->name, val, len); + printf("\nout name %s\n", out->name); + } + printf("name: %s", out->name); + if (!strncmp(buf, "State:", 6)) + { + char state_char = 0; + for (char *p = val; *p; ++p) + { + if (*p >= 'A' && *p <= 'Z' || *p == 't') + { + state_char = *p; + break; + } + } + + + printf("\nstate char: %d\n", out->pid); + printf("\nstate char: %c\n", state_char); + switch (state_char) + { + case 'R': + { + out->state = PROCESS_RUNNING; + break; + } + case 'S': + { + out->state = PROCESS_SLEEPING; + break; + } + case 'D': + { + out->state = PROCESS_DISK_SLEEP; + break; + } + case 'T': + { + out->state = PROCESS_STOPPED; + break; + } + case 't': + { + out->state = PROCESS_TRACING_STOPPED; + break; + } + case 'Z': + { + out->state = PROCESS_ZOMBIE; + break; + } + case 'X': + { + out->state = PROCESS_DEAD; + break; + } + case 'I': + { + out->state = PROCESS_IDLE; + break; + } + default: + { + out->state = PROCESS_UNDEFINED; + break; + } + } + } + + if (!strncmp(buf, "Threads:", 8)) + { + out->num_threads = (u32)strtoul(val, 0, 10); + } + } + + printf("sizeof(Process) = %zu\n", sizeof(Process)); + + int error = fclose(fp); + if (error != 0) + { + return ERR_IO; + } + + printf("\n\n\n\nout state: %d\n", out->state); + return OK; +} */ import "C" import ( @@ -18,15 +149,15 @@ import ( type ProcessState C.int32_t const ( - ProcessUndefined ProcessState = C.PROCESS_UNDEFINED - ProcessRunning ProcessState = C.PROCESS_RUNNING - ProcessSleeping ProcessState = C.PROCESS_SLEEPING - ProcessDiskSleep ProcessState = C.PROCESS_DISK_SLEEP - ProcessStopped ProcessState = C.PROCESS_STOPPED - ProcessTracingStopped ProcessState = C.PROCESS_TRACING_STOPPED - ProcessZombie ProcessState = C.PROCESS_ZOMBIE - ProcessDead ProcessState = C.PROCESS_DEAD - ProcessIdle ProcessState = C.PROCESS_IDLE + ProcessUndefined ProcessState = ProcessState(C.PROCESS_UNDEFINED) + ProcessRunning ProcessState = ProcessState(C.PROCESS_RUNNING) + ProcessSleeping ProcessState = ProcessState(C.PROCESS_SLEEPING) + ProcessDiskSleep ProcessState = ProcessState(C.PROCESS_DISK_SLEEP) + ProcessStopped ProcessState = ProcessState(C.PROCESS_STOPPED) + ProcessTracingStopped ProcessState = ProcessState(C.PROCESS_TRACING_STOPPED) + ProcessZombie ProcessState = ProcessState(C.PROCESS_ZOMBIE) + ProcessDead ProcessState = ProcessState(C.PROCESS_DEAD) + ProcessIdle ProcessState = ProcessState(C.PROCESS_IDLE) ) // Process represents a single process with its attributes. @@ -393,6 +524,7 @@ Returns: */ func ReadProcesses(device *C.Device) ([]Process, error) { + if device == nil { return nil, fmt.Errorf("dvice null pointer") } @@ -411,20 +543,28 @@ func ReadProcesses(device *C.Device) ([]Process, error) { for i := 0; i < count; i++ { // p := &slice[i] + p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) - err := C.process_read(p.pid, p) + err := C.process_read2(p.pid, p) if err != C.OK { fmt.Errorf("failed reading processes") } - if p.state == C.PROCESS_UNDEFINED { + fmt.Println("Reading processes...", device.processes) - C.process_read(p.pid, p) - } + fmt.Println("pid:",p.pid) + fmt.Println("state:", p.state) + + // if p.state == C.PROCESS_UNDEFINED { + // + // C.process_read(p.pid, p) + // } + // + fmt.Println("\t\t\t\tstate: ", C.GoString(&p.name[0])) fmt.Println(unsafe.Sizeof(C.Process{})) From 69bd5d83eaa7fed1bae4188c5fc99586fde484db Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 13:54:18 +0100 Subject: [PATCH 17/55] test: studyreflect --- pkg/grpchandler/services/v3/services.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index ae67b55..1814d57 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -225,6 +225,7 @@ func (s *HandlerServicev3) GetFilteredUnits( /** TODO(nasr): handle the correct types + doing now */ _, err = manager.MapFilteredUnits(conn) From b3f946f6ca19dddcdac0d589c7bed5b2373c5d97 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 15:11:02 +0100 Subject: [PATCH 18/55] feature: wrapper cleanup + process signal handling --- pkg/agent-resources | 2 +- pkg/cgowrap/wrapper.go | 42 +++-------------------- pkg/grpchandler/resources/v2/resources.go | 25 +++++++------- proto/resources/v2/deviceresources.proto | 7 ++-- 4 files changed, 22 insertions(+), 54 deletions(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 4358ac6..79d1ea9 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 4358ac69903d0897f09b19f87b78e2aa1cc1e0b7 +Subproject commit 79d1ea93aef1cabe101fd51f9d49587918b625a9 diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index b4c14ea..ff0c9cf 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -524,7 +524,6 @@ Returns: */ func ReadProcesses(device *C.Device) ([]Process, error) { - if device == nil { return nil, fmt.Errorf("dvice null pointer") } @@ -532,57 +531,26 @@ func ReadProcesses(device *C.Device) ([]Process, error) { count := int(device.processes.count) items := device.processes.items - // slice := unsafe.Slice(items, count) - procs := make([]Process, 0, count) - // p := unsafe.Pointer(&items[i]) - - // p := (*C.Process)(unsafe.Pointer(items), uintptr(i)*unsafe.Sizeof(*items)) - for i := 0; i < count; i++ { - // p := &slice[i] - - - p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) + p := (*C.Process)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*unsafe.Sizeof(*items))) err := C.process_read2(p.pid, p) if err != C.OK { - fmt.Errorf("failed reading processes") + fmt.Printf("failed reading processes") } - fmt.Println("Reading processes...", device.processes) - - - fmt.Println("pid:",p.pid) - fmt.Println("state:", p.state) - - // if p.state == C.PROCESS_UNDEFINED { - // - // C.process_read(p.pid, p) - // } - // - - fmt.Println("\t\t\t\tstate: ", C.GoString(&p.name[0])) - fmt.Println(unsafe.Sizeof(C.Process{})) - - - fmt.Println("pid offset ", unsafe.Offsetof(p.pid)) - fmt.Println("state offset", unsafe.Offsetof(p.state)) - fmt.Println("utime offset", unsafe.Offsetof(p.utime)) - procs = append(procs, Process{ PID: int32(p.pid), - State: ProcessState(p.state), //BREAKPOINT + State: ProcessState(p.state), UTime: uint64(p.utime), STime: uint64(p.stime), NumThreads: uint32(p.num_threads), Name: C.GoString(&p.name[0]), }) - } - return procs, nil } @@ -596,13 +564,13 @@ Parameters: Returns: - error: Error if the process cannot be killed or doesn't exist */ -func KillProcess(pid int) error { +func ProcessAction(pid int, action unix.Signal) error { if pid <= 0 { return fmt.Errorf("invalid PID: %d", pid) } - if C.process_kill(C.int(pid), C.int(unix.SIGTERM)) != C.OK { + if C.process_kill(C.int(pid), C.int(action)) != C.OK { return fmt.Errorf("failed to kill process %d", pid) } return nil diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index 51dc7a8..c59d02f 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -7,6 +7,8 @@ import ( "paradigm-ehb/agent/internal/resources" cgo "paradigm-ehb/agent/pkg/cgowrap" + "golang.org/x/sys/unix" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -66,18 +68,18 @@ func (s *ResourcesServiceV2) GetSystemResources( func (s *ResourcesServiceV2) KillProcess( ctx context.Context, - req *proto.KillProcessRequest, -) (*proto.KillProcessReply, error) { + req *proto.ProcessActionRequest, +) (*proto.ProcessActionReply, error) { - err := cgo.KillProcess(int(req.Pid)) + err := cgo.ProcessAction(int(req.Pid), unix.Signal(req.Signal)) if err != nil { - return &proto.KillProcessReply{ + return &proto.ProcessActionReply{ Succes: false, }, nil } - return &proto.KillProcessReply{ + return &proto.ProcessActionReply{ Succes: true, }, nil } @@ -91,12 +93,11 @@ func (s *ResourcesServiceV2) KillProcess( */ func mapSystemResources(s *resources.SystemResources) *proto.SystemResources { - return &proto.SystemResources{ - Cpu: mapCPU(s.CPU), - Memory: mapMemory(s.Memory), - Device: mapDevice(s.Device), - Disks: mapDisks(s.Disks), + Cpu: mapCPU(s.CPU), + Memory: mapMemory(s.Memory), + Device: mapDevice(s.Device), + Disks: mapDisks(s.Disks), Processes: mapProcesses(s.Procs), } } @@ -187,7 +188,6 @@ func mapProcessState(s cgo.ProcessState) proto.ProcessState { switch s { - case cgo.ProcessRunning: { @@ -231,7 +231,7 @@ func mapProcessState(s cgo.ProcessState) proto.ProcessState { case cgo.ProcessUndefined: { return proto.ProcessState_PROCESS_STATE_UNSPECIFIED - + } default: @@ -259,6 +259,5 @@ func mapProcesses(ps []cgo.Process) []*proto.Process { }) } - return out } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 7404d50..35dbd02 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -108,11 +108,12 @@ message Process { uint32 num_threads = 5; } -message KillProcessRequest { - uint32 pid = 1; +message ProcessActionRequest { + int32 pid = 1; + int32 signal = 2; } -message KillProcessReply { +message ProcessActionReply { bool succes = 1; } From 2e179f4215d50594e9190b9362b5ec6e0710293a Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 15:12:29 +0100 Subject: [PATCH 19/55] feature: refactored services grpc handler to better handle filtered and loaded units --- pkg/grpchandler/services/v3/services.go | 136 +++++++++++++++--------- 1 file changed, 88 insertions(+), 48 deletions(-) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index 1814d57..3ca3f3c 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -5,15 +5,48 @@ import ( "fmt" v3 "paradigm-ehb/agent/gen/services/v3" + manager "paradigm-ehb/agent/internal/dbusservices" dh "paradigm-ehb/agent/internal/dbusservices/dbus" servicecontrol "paradigm-ehb/agent/internal/dbusservices/systemd" + types "paradigm-ehb/agent/internal/dbusservices/types" ) type HandlerServicev3 struct { v3.UnimplementedHandlerServiceServer } + +func mapLoadedUnit(u *types.LoadedUnit) *v3.LoadedUnit { + if u == nil { + return nil + } + + return &v3.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + } +} + +func mapLoadedUnits(units []*types.LoadedUnit) []*v3.LoadedUnit { + out := make([]*v3.LoadedUnit, 0, len(units)) + for _, u := range units { + if pu := mapLoadedUnit(u); pu != nil { + out = append(out, pu) + } + } + return out +} + + func (s *HandlerServicev3) PerformUnitAction( _ context.Context, in *v3.UnitActionRequest, @@ -47,8 +80,7 @@ func (s *HandlerServicev3) PerformUnitAction( }, nil } - err = manager.RunAction(conn, action, in.UnitName) - if err != nil { + if err := manager.RunAction(conn, action, in.UnitName); err != nil { return &v3.UnitActionReply{ Status: []byte(fmt.Sprintf("failed to %s unit", actionName)), Success: false, @@ -89,15 +121,13 @@ func (s *HandlerServicev3) PerformUnitFileAction( }, nil } - err = manager.RunSymlinkAction( + if err := manager.RunSymlinkAction( conn, action, in.Runtime, in.Force, []string{in.UnitName}, - ) - - if err != nil { + ); err != nil { return &v3.UnitFileActionReply{ Status: []byte("unit file action failed"), Success: false, @@ -111,13 +141,13 @@ func (s *HandlerServicev3) PerformUnitFileAction( }, nil } + func (s *HandlerServicev3) GetAllUnits( _ context.Context, _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { conn, err := dh.CreateSystemBus() - if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -125,11 +155,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } - /** - - Handle error hanlding - */ - _, err = manager.RunRetrieval(conn, true) + units, err := manager.RunRetrieval(conn, true) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -137,11 +163,8 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } - // mappedUnits := make(*v3.GetUnitsReply, 0, len(units)) - // mappedUnits = &units{} - return &v3.GetUnitsReply{ - Units: nil, + Units: mapLoadedUnits(units), Success: true, }, nil } @@ -159,10 +182,7 @@ func (s *HandlerServicev3) GetLoadedUnits( }, nil } - /** - TODO(nasr): handle error - */ - _, err = manager.RunRetrieval(conn, false) + units, err := manager.RunRetrieval(conn, false) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -171,71 +191,91 @@ func (s *HandlerServicev3) GetLoadedUnits( } return &v3.GetUnitsReply{ - Units: nil, + Units: mapLoadedUnits(units), Success: true, }, nil } -func (s *HandlerServicev3) GetUnitStatus( +func (s *HandlerServicev3) GetFilteredUnits( _ context.Context, - in *v3.GetUnitStatusRequest, -) (*v3.GetUnitStatusReply, error) { + in *v3.GetUnitsFilteredRequest, +) (*v3.GetUnitsReply, error) { conn, err := dh.CreateSystemBus() if err != nil { - return &v3.GetUnitStatusReply{ + return &v3.GetUnitsReply{ Success: false, ErrorMessage: err.Error(), }, nil } - /** - TODO(nasr): todo handle error - */ - obj, _ := dh.CreateSystemdObject(conn) + var filters []string + + switch in.State { + case v3.GetUnitsFilteredRequest_LOADED: + filters = []string{"loaded"} + case v3.GetUnitsFilteredRequest_NOT_FOUND: + filters = []string{"not-found"} + case v3.GetUnitsFilteredRequest_BAD_SETTING: + filters = []string{"bad-setting"} + case v3.GetUnitsFilteredRequest_ERROR: + filters = []string{"error"} + case v3.GetUnitsFilteredRequest_MASKED: + filters = []string{"masked"} + default: + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: "unspecified filter state", + }, nil + } - state, err := manager.GetStatus(obj, in.UnitName) + units, err := manager.MapFilteredUnits(conn, filters) if err != nil { - return &v3.GetUnitStatusReply{ + return &v3.GetUnitsReply{ Success: false, ErrorMessage: err.Error(), }, nil } - return &v3.GetUnitStatusReply{ - State: state, + return &v3.GetUnitsReply{ + Units: mapLoadedUnits(units), Success: true, }, nil } -func (s *HandlerServicev3) GetFilteredUnits( + +func (s *HandlerServicev3) GetUnitStatus( _ context.Context, - in *v3.GetUnitsFilteredRequest, -) (*v3.GetUnitsReply, error) { + in *v3.GetUnitStatusRequest, +) (*v3.GetUnitStatusReply, error) { conn, err := dh.CreateSystemBus() - if err != nil { - return &v3.GetUnitsReply{ + return &v3.GetUnitStatusReply{ Success: false, ErrorMessage: err.Error(), }, nil } - /** - - TODO(nasr): handle the correct types - doing now - - */ - _, err = manager.MapFilteredUnits(conn) + obj, err := dh.CreateSystemdObject(conn) if err != nil { + return &v3.GetUnitStatusReply{ + Success: false, + ErrorMessage: err.Error(), + }, fmt.Errorf("failed to create systemd object") + } - return &v3.GetUnitsReply{ + state, err := manager.GetStatus(obj, in.UnitName) + if err != nil { + return &v3.GetUnitStatusReply{ Success: false, ErrorMessage: err.Error(), - }, fmt.Errorf("failed to map filtered units %v: ", err) + }, nil } - return &v3.GetUnitsReply{}, nil + return &v3.GetUnitStatusReply{ + State: state, + Success: true, + }, nil } +``` From d0384039e5577537c249e9145c62601e6fdcae10 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 15:13:44 +0100 Subject: [PATCH 20/55] debug: moved from ssh to https for the git submodule --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 3631d7d..3d20071 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "internal/resmanager/agent-resources"] path = pkg/agent-resources - url = git@github.com:paradigm-ehb/agent-resources.git + url = https://www.github.com/paradigm-ehb/agent-resources.git From 2a48c726711d2f54da13233ef74cbc2cd7219112 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 17:16:25 +0100 Subject: [PATCH 21/55] feature: v3 test services --- test/gprc_services_v3.sh | 114 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100755 test/gprc_services_v3.sh diff --git a/test/gprc_services_v3.sh b/test/gprc_services_v3.sh new file mode 100755 index 0000000..eab5361 --- /dev/null +++ b/test/gprc_services_v3.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env sh +set -euo pipefail + +HOST="${HOST:-localhost}" +PORT="${PORT:-5000}" +ADDR="${HOST}:${PORT}" +SERVICE="services.v3.HandlerService" + +echo "== gRPC v3 integration test ==" +echo "Target: ${ADDR}" +echo + +command -v grpcurl >/dev/null 2>&1 || { + echo "ERROR: grpcurl not found" + exit 1 +} + +echo "== Checking server availability ==" +grpcurl -plaintext "${ADDR}" list >/dev/null +echo "OK" +echo + +echo "== Listing all services ==" +grpcurl -plaintext "${ADDR}" list +echo + +echo "== Checking ${SERVICE} existence ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" >/dev/null +echo "OK" +echo + +echo "== Listing ${SERVICE} methods ==" +grpcurl -plaintext "${ADDR}" list "${SERVICE}" +echo + +echo "== GetAllUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetAllUnits" +echo + +echo "== GetLoadedUnits ==" +grpcurl -plaintext \ + -d '{}' \ + "${ADDR}" \ + "${SERVICE}/GetLoadedUnits" +echo + +echo "== GetFilteredUnits (loaded, masked) ==" + +grpcurl -plaintext \ + -d '{ + "filters": ["LOADED", "MASKED"] + }' \ + localhost:5000 \ + services.v3.HandlerService/GetFilteredUnits + +echo + +echo "== GetUnitStatus (tailscaled.service) ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service" + }' \ + "${ADDR}" \ + "${SERVICE}/GetUnitStatus" +echo + +echo "== PerformUnitAction: START tailscaled.service ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_START" + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" +echo + +echo "== PerformUnitFileAction: ENABLE tailscaled.service (runtime=true, force=true) ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_ENABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" +echo + +echo "== PerformUnitAction: STOP tailscaled.service ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_ACTION_STOP" + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitAction" +echo + +echo "== PerformUnitFileAction: DISABLE tailscaled.service ==" +grpcurl -plaintext \ + -d '{ + "unitName": "tailscaled.service", + "action": "UNIT_FILE_ACTION_DISABLE", + "runtime": true, + "force": true + }' \ + "${ADDR}" \ + "${SERVICE}/PerformUnitFileAction" +echo + +echo "== All gRPC v3 tests passed ==" From 109573f4daf18f3cd443125581e34a31ecc7123b Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 17:16:51 +0100 Subject: [PATCH 22/55] feature: v3 services grpc and protobufs, still one issue but thats for tomorrow checkpoitn --- cmd/agent/agent.go | 9 ++++ internal/dbusservices/manager.go | 71 ++++++++++++++++--------- internal/dbusservices/systemd/list.go | 23 ++++++++ pkg/grpchandler/services/v2/services.go | 7 ++- pkg/grpchandler/services/v3/services.go | 64 +++++++++++----------- 5 files changed, 114 insertions(+), 60 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 8e7bd6a..87a2651 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -25,6 +25,7 @@ import ( servicesV1 "paradigm-ehb/agent/gen/services/v1" servicesV2 "paradigm-ehb/agent/gen/services/v2" + servicesV3 "paradigm-ehb/agent/gen/services/v3" resourcesv1 "paradigm-ehb/agent/gen/resources/v1" resourcesv2 "paradigm-ehb/agent/gen/resources/v2" @@ -43,6 +44,7 @@ import ( servicesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/services/v1" servicesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/services/v2" + servicesHandlerV3 "paradigm-ehb/agent/pkg/grpchandler/services/v3" "syscall" "time" @@ -154,6 +156,12 @@ func main() { server, &servicesHandlerV2.HandlerServiceV2{}) + servicesV3.RegisterHandlerServiceServer( + server, + &servicesHandlerV3.HandlerServicev3{}) + + + journal.RegisterJournalServiceServer( server, &grpc_handler.JournalService{}) @@ -170,6 +178,7 @@ func main() { server, &grpc_handler.DeviceActionsService{}) + /** Enable gRPC reflection unconditionally. diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index e4eb8e3..539be6c 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -98,24 +98,50 @@ func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { return units } -func MapFilteredUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { +func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, error) { - /* + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + fmt.Errorf("failed to create systemd object") + } + + in := make(chan []types.LoadedUnit) + out := make(chan []types.LoadedUnit) + + go systemd.GetUnitsFiltered(obj, in, filters) + go dbushelper.ParseLoadedUnits(in, out) + + var entries []types.LoadedUnit + + units := make([]*types.LoadedUnit, 0, len(entries)) + + entries = <-out - dbushelper.CreateSystemdObject(conn) - TODO(nasr): map the filtered units to their appropriate object and pass - them to the grpc handler - **/ + for _, e := range entries { - return nil, nil + units = append(units, &types.LoadedUnit{ + Name: e.Name, + Description: "Not available", + LoadState: e.LoadState, + SubState: "Not Available", + ActiveState: "Not Available", + DepUnit: "Not Available", + ObjectPath: "Not Available", + QueudJob: 0, + JobType: "Not Available", + JobPath: "Not Available", + }) + } + + return units, nil } func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { - /** - TODO(nasr): fix the error handling - */ - obj, _ := dbushelper.CreateSystemdObject(conn) + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + fmt.Errorf("failed to create systemd object") + } in := make(chan []types.Unit) out := make(chan []types.Unit) @@ -174,22 +200,15 @@ func RunRetrieval( return MapLoadedUnits(conn), nil } -func GetStatus(obj dbus.BusObject, name string) (string, error) { - var result string +func UnitStatus( + obj dbus.BusObject, + name string) (string, error) { - call := obj.Call( - "org.freedesktop.systemd1.Manager.GetUnitFileState", - 0, - name, - ) - - if call.Err != nil { - return "call error: ", call.Err - } + out, err := systemd.GetStatusCall(obj, name) + if err != nil { + return "Failed", fmt.Errorf("failed to execute status call ", err) + } - if err := call.Store(&result); err != nil { - return "call store: ", err - } + return out, nil - return result, nil } diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index 3361b88..0e6def2 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -82,3 +82,26 @@ func GetUnitsFiltered( out <- result } + + +func GetStatusCall(obj dbus.BusObject, name string) (string, error) { + var result string + + call := obj.Call( + "org.freedesktop.systemd1.Manager.GetUnitFileState", + 0, + name, + ) + + if call.Err != nil { + return "call error: ", call.Err + } + + if err := call.Store(&result); err != nil { + return "call store: ", err + } + + return result, nil +} + + diff --git a/pkg/grpchandler/services/v2/services.go b/pkg/grpchandler/services/v2/services.go index 3ded666..4a57da7 100644 --- a/pkg/grpchandler/services/v2/services.go +++ b/pkg/grpchandler/services/v2/services.go @@ -194,9 +194,12 @@ func (s *HandlerServiceV2) GetUnitStatus( }, nil } - obj, _ := dh.CreateSystemdObject(conn) + obj, err := dh.CreateSystemdObject(conn) + if err != nil { + return nil, fmt.Errorf("failed to create system object") + } - state, err := manager.GetStatus(obj, in.UnitName) + state, err := manager.UnitStatus(obj, in.UnitName) if err != nil { return &v2.GetUnitStatusReply{ Success: false, diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index 3ca3f3c..f9245bd 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -23,16 +23,16 @@ func mapLoadedUnit(u *types.LoadedUnit) *v3.LoadedUnit { } return &v3.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), } } @@ -209,24 +209,26 @@ func (s *HandlerServicev3) GetFilteredUnits( }, nil } - var filters []string - - switch in.State { - case v3.GetUnitsFilteredRequest_LOADED: - filters = []string{"loaded"} - case v3.GetUnitsFilteredRequest_NOT_FOUND: - filters = []string{"not-found"} - case v3.GetUnitsFilteredRequest_BAD_SETTING: - filters = []string{"bad-setting"} - case v3.GetUnitsFilteredRequest_ERROR: - filters = []string{"error"} - case v3.GetUnitsFilteredRequest_MASKED: - filters = []string{"masked"} - default: - return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: "unspecified filter state", - }, nil + filters := make([]string, 0, len(in.Filters)) + + for _, f := range in.Filters { + switch f { + case v3.GetUnitsFilteredRequest_LOADED: + filters = append(filters, "loaded") + case v3.GetUnitsFilteredRequest_NOT_FOUND: + filters = append(filters, "not-found") + case v3.GetUnitsFilteredRequest_BAD_SETTING: + filters = append(filters, "bad-setting") + case v3.GetUnitsFilteredRequest_ERROR: + filters = append(filters, "error") + case v3.GetUnitsFilteredRequest_MASKED: + filters = append(filters, "masked") + default: + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: "unspecified filter state", + }, nil + } } units, err := manager.MapFilteredUnits(conn, filters) @@ -243,7 +245,6 @@ func (s *HandlerServicev3) GetFilteredUnits( }, nil } - func (s *HandlerServicev3) GetUnitStatus( _ context.Context, in *v3.GetUnitStatusRequest, @@ -262,10 +263,10 @@ func (s *HandlerServicev3) GetUnitStatus( return &v3.GetUnitStatusReply{ Success: false, ErrorMessage: err.Error(), - }, fmt.Errorf("failed to create systemd object") + }, nil } - state, err := manager.GetStatus(obj, in.UnitName) + state, err := manager.UnitStatus(obj, in.UnitName) if err != nil { return &v3.GetUnitStatusReply{ Success: false, @@ -278,4 +279,3 @@ func (s *HandlerServicev3) GetUnitStatus( Success: true, }, nil } -``` From 65f137f5ae267351f8136902c321cc96afa325e8 Mon Sep 17 00:00:00 2001 From: nasr Date: Fri, 9 Jan 2026 17:17:00 +0100 Subject: [PATCH 23/55] debug: array for file states --- proto/services/v3/services.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proto/services/v3/services.proto b/proto/services/v3/services.proto index 1319324..01e9755 100644 --- a/proto/services/v3/services.proto +++ b/proto/services/v3/services.proto @@ -10,7 +10,7 @@ service HandlerService { rpc PerformUnitFileAction (UnitFileActionRequest) returns (UnitFileActionReply); rpc GetAllUnits (GetUnitsRequest) returns (GetUnitsReply); rpc GetLoadedUnits (GetUnitsRequest) returns (GetUnitsReply); - rpc GetFilteredUnits (GetUnitsRequest) returns (GetUnitsReply); + rpc GetFilteredUnits (GetUnitsFilteredRequest) returns (GetUnitsReply); rpc GetUnitStatus (GetUnitStatusRequest) returns (GetUnitStatusReply); } @@ -102,7 +102,7 @@ message GetUnitsFilteredRequest { MASKED = 5; } - UnitFileState state = 1; + repeated UnitFileState filters = 1; } From f0c6eab7680fadfa083802e80550be0eed4b3b42 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:06:42 +0100 Subject: [PATCH 24/55] feature: refactor mapLoadedUnits to return error, update GetAllUnits and related handlers --- pkg/grpchandler/services/v3/services.go | 68 +++++++++++++++---------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index f9245bd..a2abed3 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -16,37 +16,39 @@ type HandlerServicev3 struct { v3.UnimplementedHandlerServiceServer } +func mapLoadedUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { -func mapLoadedUnit(u *types.LoadedUnit) *v3.LoadedUnit { - if u == nil { - return nil + if units == nil { + return nil, fmt.Errorf("passed input is nil") } - return &v3.LoadedUnit{ - Name: u.Name, - Description: u.Description, - LoadState: u.LoadState, - SubState: u.SubState, - ActiveState: u.ActiveState, - DepUnit: u.DepUnit, - ObjectPath: string(u.ObjectPath), - QueuedJob: u.QueudJob, - JobType: u.JobType, - JobPath: string(u.JobPath), - } -} + fmt.Println("input :", units) -func mapLoadedUnits(units []*types.LoadedUnit) []*v3.LoadedUnit { out := make([]*v3.LoadedUnit, 0, len(units)) + for _, u := range units { - if pu := mapLoadedUnit(u); pu != nil { - out = append(out, pu) + if u == nil { + continue } + + out = append(out, &v3.LoadedUnit{ + Name: u.Name, + Description: u.Description, + LoadState: u.LoadState, + SubState: u.SubState, + ActiveState: u.ActiveState, + DepUnit: u.DepUnit, + ObjectPath: string(u.ObjectPath), + QueuedJob: u.QueudJob, + JobType: u.JobType, + JobPath: string(u.JobPath), + }) } - return out -} + fmt.Println("out -> ", out) + return out, nil +} func (s *HandlerServicev3) PerformUnitAction( _ context.Context, in *v3.UnitActionRequest, @@ -141,7 +143,6 @@ func (s *HandlerServicev3) PerformUnitFileAction( }, nil } - func (s *HandlerServicev3) GetAllUnits( _ context.Context, _ *v3.GetUnitsRequest, @@ -163,8 +164,19 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } + /*** + units is empty here! + */ + mapped, err := mapLoadedUnits(units) + if err != nil { + return &v3.GetUnitsReply{ + Success: false, + ErrorMessage: err.Error(), + }, nil + } + return &v3.GetUnitsReply{ - Units: mapLoadedUnits(units), + Units: mapped, Success: true, }, nil } @@ -174,7 +186,7 @@ func (s *HandlerServicev3) GetLoadedUnits( _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { - conn, err := dh.CreateSystemBus() + _, err := dh.CreateSystemBus() if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -182,7 +194,7 @@ func (s *HandlerServicev3) GetLoadedUnits( }, nil } - units, err := manager.RunRetrieval(conn, false) + // units, err := manager.RunRetrieval(conn, false) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -191,7 +203,7 @@ func (s *HandlerServicev3) GetLoadedUnits( } return &v3.GetUnitsReply{ - Units: mapLoadedUnits(units), + Units: nil, Success: true, }, nil } @@ -231,7 +243,7 @@ func (s *HandlerServicev3) GetFilteredUnits( } } - units, err := manager.MapFilteredUnits(conn, filters) + _, err = manager.MapFilteredUnits(conn, filters) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -240,7 +252,7 @@ func (s *HandlerServicev3) GetFilteredUnits( } return &v3.GetUnitsReply{ - Units: mapLoadedUnits(units), + Units: nil, Success: true, }, nil } From c351a4114d0d2b616638b6fd76afb9f7ccbfc3ab Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:06:52 +0100 Subject: [PATCH 25/55] refactor: update ParseUnits to accept slice input and return filtered slice with error --- internal/dbusservices/dbus/parse.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index b8bf23f..8610d27 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -12,24 +12,20 @@ import ( // @param chan a(ss), chan a(ss) // @param chan UnitFileEntry, chan UnitFileEntry // @return nil -func ParseUnits(in chan []types.Unit, out chan []types.Unit) { - - input := <-in - +func ParseUnits(input []types.Unit) ([]types.Unit, error) { /** filter the units on services and remove devices etc */ + buffer := make([]types.Unit, 0, len(input)) for _, value := range input { - if strings.HasSuffix(".service", value.Name) { + if strings.HasSuffix(value.Name, ".service") { buffer = append(buffer, value) } } - out <- buffer - - return + return buffer, nil } From d5cd34c788b12cb38cfdaa06481a43962938b0fd Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:06:58 +0100 Subject: [PATCH 26/55] refactor: update MapUnits to return error, remove channels, improve error handling in GetAllUnits --- internal/dbusservices/manager.go | 54 +++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 539be6c..facf24e 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -2,7 +2,6 @@ package dbus_services import ( "fmt" - "log" dbushelper "paradigm-ehb/agent/internal/dbusservices/dbus" systemd "paradigm-ehb/agent/internal/dbusservices/systemd" @@ -136,26 +135,40 @@ func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, e return units, nil } -func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { +func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { - fmt.Errorf("failed to create systemd object") + return nil, fmt.Errorf("failed to create systemd object") } - in := make(chan []types.Unit) - out := make(chan []types.Unit) + // in := make(chan []types.Unit) + // out := make(chan []types.Unit) - go systemd.GetUnits(obj, in) - go dbushelper.ParseUnits(in, out) + var result []types.Unit - var entries []types.Unit + result, err = systemd.GetUnits(obj) - units := make([]*types.LoadedUnit, 0, len(entries)) + if err != nil { + return nil, fmt.Errorf("failed in the Map Units function") + } - entries = <-out + /** + * TODO(nasr): remove channel impelmentetation + */ - for _, e := range entries { + parsedUnits, err := dbushelper.ParseUnits(result) + if err != nil { + return nil, fmt.Errorf("failed to parse units") + } + + // var entries []types.Unit + + units := make([]*types.LoadedUnit, 0, len(parsedUnits)) + + for _, e := range parsedUnits { + + fmt.Println("unit: -> ", e) units = append(units, &types.LoadedUnit{ Name: e.Name, @@ -171,7 +184,7 @@ func MapUnits(conn *dbus.Conn) []*types.LoadedUnit { }) } - return units + return units, nil } /* @@ -190,11 +203,14 @@ func RunRetrieval( conn, err := dbushelper.CreateSystemBus() if err != nil { - log.Printf("failed to create a system bus connection for retrieving units %v", err) + return nil, fmt.Errorf("failed to create a system bus connection for retrieving units %v", err) } if requestAllUnitsOnDisk { - return MapUnits(conn), nil + result, err := MapUnits(conn) + if err != nil { + return result, fmt.Errorf("failed to request stuff") + } } return MapLoadedUnits(conn), nil @@ -204,11 +220,11 @@ func UnitStatus( obj dbus.BusObject, name string) (string, error) { - out, err := systemd.GetStatusCall(obj, name) - if err != nil { - return "Failed", fmt.Errorf("failed to execute status call ", err) - } + out, err := systemd.GetStatusCall(obj, name) + if err != nil { + return "Failed", fmt.Errorf("failed to execute status call %v", err) + } - return out, nil + return out, nil } From c6c89cad355783362243304df9555a4f0de94574 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:07:06 +0100 Subject: [PATCH 27/55] refactor: update GetUnits to return slice and error instead of using channel --- internal/dbusservices/systemd/list.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index 0e6def2..e4f22ef 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -38,7 +38,7 @@ func GetLoadedUnits( func GetUnits( obj dbus.BusObject, - out chan []types.Unit) { +) ([]types.Unit, error) { // ListUnitFiles(out a(ss) files); // an array of struct string string @@ -48,16 +48,26 @@ func GetUnits( call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0) if call.Err != nil { - fmt.Println("failed to call all units loaded on disk") - return + + return nil, fmt.Errorf("failed to call all units loaded on disk") } err := call.Store(&result) if err != nil { - return + return nil, fmt.Errorf("failed to store services ") } - out <- result + /** + * THIS IS WORKING + * fmt.Println("channel result: \n", result) + */ + + /** + * THIS ISNT + * out <- result + */ + + return result, nil } func GetUnitsFiltered( @@ -83,7 +93,6 @@ func GetUnitsFiltered( } - func GetStatusCall(obj dbus.BusObject, name string) (string, error) { var result string @@ -103,5 +112,3 @@ func GetStatusCall(obj dbus.BusObject, name string) (string, error) { return result, nil } - - From 3a7efeed44f2191c621718d007e1a11a601e647e Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:07:11 +0100 Subject: [PATCH 28/55] chore: update agent-resources submodule URL to use SSH --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 3d20071..3631d7d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "internal/resmanager/agent-resources"] path = pkg/agent-resources - url = https://www.github.com/paradigm-ehb/agent-resources.git + url = git@github.com:paradigm-ehb/agent-resources.git From c36504670a33a16913aa4ad0accc9138d97e4684 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:07:20 +0100 Subject: [PATCH 29/55] chore: set diagnostics flag default to false --- cmd/agent/agent.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 87a2651..47d1a6b 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -57,7 +57,7 @@ var ( NOTE: When enabled, diagnostics must never block the gRPC server. **/ - diagnostics = flag.Bool("diagnostics", true, "run runtime diagnostics") + diagnostics = flag.Bool("diagnostics", false, "run runtime diagnostics") /** portFlag is the preferred TCP port to bind the gRPC server to. @@ -159,8 +159,6 @@ func main() { servicesV3.RegisterHandlerServiceServer( server, &servicesHandlerV3.HandlerServicev3{}) - - journal.RegisterJournalServiceServer( server, @@ -178,7 +176,6 @@ func main() { server, &grpc_handler.DeviceActionsService{}) - /** Enable gRPC reflection unconditionally. From 06364f5d89a7fa9542904d738f2e4d2b9f6dcceb Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 13:22:49 +0100 Subject: [PATCH 30/55] checkpoint? --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 79d1ea9..f9e91ef 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 79d1ea93aef1cabe101fd51f9d49587918b625a9 +Subproject commit f9e91ef837924ba8ece0d2ed626f23747bbc9788 From d239cd3514b5558eb8e084bb7340c8aef531284a Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:26 +0100 Subject: [PATCH 31/55] refactor: update ParseUnits and ParseLoadedUnits to return slices and errors instead of using channels --- internal/dbusservices/dbus/parse.go | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/internal/dbusservices/dbus/parse.go b/internal/dbusservices/dbus/parse.go index 8610d27..6f9fce0 100644 --- a/internal/dbusservices/dbus/parse.go +++ b/internal/dbusservices/dbus/parse.go @@ -8,44 +8,34 @@ import ( // TODO(nasr): implement interfaces maybe -// Method -// @param chan a(ss), chan a(ss) -// @param chan UnitFileEntry, chan UnitFileEntry -// @return nil +// ParseUnits filters units to only include services +// @param input []types.Unit +// @return []types.Unit, error func ParseUnits(input []types.Unit) ([]types.Unit, error) { /** filter the units on services and remove devices etc */ - buffer := make([]types.Unit, 0, len(input)) for _, value := range input { - if strings.HasSuffix(value.Name, ".service") { buffer = append(buffer, value) } } - return buffer, nil - } -func ParseLoadedUnits(in chan []types.LoadedUnit, out chan []types.LoadedUnit) { - - input := <-in - +// ParseLoadedUnits filters loaded units to only include services +// @param input []types.LoadedUnit +// @return []types.LoadedUnit, error +func ParseLoadedUnits(input []types.LoadedUnit) ([]types.LoadedUnit, error) { /** filter the units on services and remove devices etc */ buffer := make([]types.LoadedUnit, 0, len(input)) for _, value := range input { - - if strings.HasSuffix(".service", string(value.Name)) { + if strings.HasSuffix(value.Name, ".service") { buffer = append(buffer, value) } } - - out <- buffer - - return - + return buffer, nil } From 40443b9aa480e723729f8b1a2e2c6d67212390f5 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:32 +0100 Subject: [PATCH 32/55] refactor: streamline unit retrieval and status functions, replacing channels with direct returns --- internal/dbusservices/manager.go | 167 ++++++++++++++----------------- 1 file changed, 76 insertions(+), 91 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index facf24e..27fdebc 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -64,21 +64,40 @@ func RunSymlinkAction( return nil } -// MapLoadedUnits /* -func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { - - obj, _ := dbushelper.CreateSystemdObject(conn) - ch := make(chan []types.LoadedUnit) - parse := make(chan []types.LoadedUnit) +func UnitStatus( + obj dbus.BusObject, + name string) (string, error) { - go systemd.GetLoadedUnits(obj, ch) - go dbushelper.ParseLoadedUnits(ch, parse) + out, err := systemd.GetStatusCall(obj, name) + if err != nil { + return "Failed", fmt.Errorf("failed to execute status call %v", err) + } - loaded := <-parse + return out, nil - units := make([]*types.LoadedUnit, 0, len(loaded)) +} - for _, u := range loaded { +/** + * Map to the correct types + */ +func MapLoadedUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + return nil, fmt.Errorf("failed to create systemd object: %w", err) + } + + loaded, err := systemd.GetLoadedUnits(obj) + if err != nil { + return nil, fmt.Errorf("failed to get loaded units: %w", err) + } + + parsed, err := dbushelper.ParseLoadedUnits(loaded) + if err != nil { + return nil, fmt.Errorf("failed to parse loaded units: %w", err) + } + + units := make([]*types.LoadedUnit, 0, len(parsed)) + for _, u := range parsed { units = append(units, &types.LoadedUnit{ Name: u.Name, Description: u.Description, @@ -87,37 +106,32 @@ func MapLoadedUnits(conn *dbus.Conn) []*types.LoadedUnit { ActiveState: u.ActiveState, DepUnit: u.DepUnit, ObjectPath: u.ObjectPath, - /*oops typo in queued job :)*/ - QueudJob: u.QueudJob, - JobType: u.JobType, - JobPath: u.JobPath, + QueudJob: u.QueudJob, // Keep the typo for consistency + JobType: u.JobType, + JobPath: u.JobPath, }) } - - return units + return units, nil } func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, error) { - obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { - fmt.Errorf("failed to create systemd object") + return nil, fmt.Errorf("failed to create systemd object: %w", err) } - - in := make(chan []types.LoadedUnit) - out := make(chan []types.LoadedUnit) - - go systemd.GetUnitsFiltered(obj, in, filters) - go dbushelper.ParseLoadedUnits(in, out) - - var entries []types.LoadedUnit - - units := make([]*types.LoadedUnit, 0, len(entries)) - - entries = <-out - - for _, e := range entries { - + + entries, err := systemd.GetUnitsFiltered(obj, filters) + if err != nil { + return nil, fmt.Errorf("failed to get filtered units: %w", err) + } + + parsed, err := dbushelper.ParseLoadedUnits(entries) + if err != nil { + return nil, fmt.Errorf("failed to parse filtered units: %w", err) + } + + units := make([]*types.LoadedUnit, 0, len(parsed)) + for _, e := range parsed { units = append(units, &types.LoadedUnit{ Name: e.Name, Description: "Not available", @@ -131,45 +145,30 @@ func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, e JobPath: "Not Available", }) } - return units, nil } +/** + * Fill in proper unit types + */ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { - obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { - return nil, fmt.Errorf("failed to create systemd object") + return nil, fmt.Errorf("failed to create systemd object: %w", err) } - - // in := make(chan []types.Unit) - // out := make(chan []types.Unit) - - var result []types.Unit - - result, err = systemd.GetUnits(obj) - + + result, err := systemd.GetUnits(obj) if err != nil { - return nil, fmt.Errorf("failed in the Map Units function") + return nil, fmt.Errorf("failed to get units: %w", err) } - - /** - * TODO(nasr): remove channel impelmentetation - */ - + parsedUnits, err := dbushelper.ParseUnits(result) if err != nil { - return nil, fmt.Errorf("failed to parse units") + return nil, fmt.Errorf("failed to parse units: %w", err) } - - // var entries []types.Unit - + units := make([]*types.LoadedUnit, 0, len(parsedUnits)) - for _, e := range parsedUnits { - - fmt.Println("unit: -> ", e) - units = append(units, &types.LoadedUnit{ Name: e.Name, Description: "Not available", @@ -183,48 +182,34 @@ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { JobPath: "Not Available", }) } - return units, nil } -/* -* -* @param, true for all on disk, false for loaded units -* a loaded unit is a unit that has been activated before -* and is available in memoery for the server to start up -* or something like that -* @return []*types.LoadedUnit, error +/** + * @param requestAllUnitsOnDisk: true for all units on disk, false for loaded units + * A loaded unit is a unit that has been activated before and is available in memory + * for the server to start up + * @return []*types.LoadedUnit, error */ -func RunRetrieval( - conn *dbus.Conn, - requestAllUnitsOnDisk bool, -) ([]*types.LoadedUnit, error) { - +func RunRetrieval(requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { conn, err := dbushelper.CreateSystemBus() - if err != nil { - return nil, fmt.Errorf("failed to create a system bus connection for retrieving units %v", err) + return nil, fmt.Errorf("failed to create system bus connection: %w", err) } - + defer conn.Close() + if requestAllUnitsOnDisk { - result, err := MapUnits(conn) - if err != nil { - return result, fmt.Errorf("failed to request stuff") - } + return MapUnits(conn) } - - return MapLoadedUnits(conn), nil + + return MapLoadedUnits(conn) } -func UnitStatus( - obj dbus.BusObject, - name string) (string, error) { - - out, err := systemd.GetStatusCall(obj, name) - if err != nil { - return "Failed", fmt.Errorf("failed to execute status call %v", err) +func RunRetrievalDeprecated(conn *dbus.Conn, requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { + + if requestAllUnitsOnDisk { + return MapUnits(conn) } - - return out, nil - + + return MapLoadedUnits(conn) } From 6b16881dafb2a5a1dc4eb228f63e9d32258a0794 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:36 +0100 Subject: [PATCH 33/55] refactor: update unit retrieval functions to return slices and errors instead of using channels --- internal/dbusservices/systemd/list.go | 65 +++++++-------------------- 1 file changed, 17 insertions(+), 48 deletions(-) diff --git a/internal/dbusservices/systemd/list.go b/internal/dbusservices/systemd/list.go index e4f22ef..f509f56 100644 --- a/internal/dbusservices/systemd/list.go +++ b/internal/dbusservices/systemd/list.go @@ -14,83 +14,52 @@ import ( // files (templates) cannot directly be loaded as units but need to be instantiated. // --------------------------------------------------------------------------------------- // Method returns an array of all currently loaded units, -func GetLoadedUnits( - obj dbus.BusObject, - out chan []types.LoadedUnit) { +func GetLoadedUnits(obj dbus.BusObject) ([]types.LoadedUnit, error) { var result []types.LoadedUnit - call := obj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0) if call.Err != nil { - fmt.Printf("failed to list unit files that are loaded in memory %v", call.Err) - return + return nil, fmt.Errorf("failed to list unit files that are loaded in memory: %w", call.Err) } - + err := call.Store(&result) - if err != nil { - return + return nil, fmt.Errorf("failed to store loaded units: %w", err) } - - out <- result - + + return result, nil } -func GetUnits( - obj dbus.BusObject, -) ([]types.Unit, error) { - +func GetUnits(obj dbus.BusObject) ([]types.Unit, error) { // ListUnitFiles(out a(ss) files); // an array of struct string string - var result []types.Unit - call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0) - if call.Err != nil { - - return nil, fmt.Errorf("failed to call all units loaded on disk") + return nil, fmt.Errorf("failed to call all units loaded on disk: %w", call.Err) } - + err := call.Store(&result) if err != nil { - return nil, fmt.Errorf("failed to store services ") + return nil, fmt.Errorf("failed to store services: %w", err) } - - /** - * THIS IS WORKING - * fmt.Println("channel result: \n", result) - */ - - /** - * THIS ISNT - * out <- result - */ - + return result, nil } -func GetUnitsFiltered( - obj dbus.BusObject, - out chan []types.LoadedUnit, - states []string) { - +func GetUnitsFiltered(obj dbus.BusObject, states []string) ([]types.LoadedUnit, error) { var result []types.LoadedUnit - call := obj.Call("org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states) - if call.Err != nil { - fmt.Println("failed to call filtered list of units") - return + return nil, fmt.Errorf("failed to call filtered list of units: %w", call.Err) } - + err := call.Store(&result) if err != nil { - return + return nil, fmt.Errorf("failed to store filtered units: %w", err) } - - out <- result - + + return result, nil } func GetStatusCall(obj dbus.BusObject, name string) (string, error) { From bee5856646ebc298e6b1626c155960a41812b5ef Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:41 +0100 Subject: [PATCH 34/55] refactor: implement runtime diagnostics using bubbletea for improved UI and interactivity --- internal/platform/platform.go | 148 +++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 45 deletions(-) diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 9323a61..68748f4 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -6,6 +6,9 @@ import ( "runtime" "runtime/debug" "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) func AssertLinux() error { @@ -15,65 +18,120 @@ func AssertLinux() error { return nil } -func RunRuntimeDiagnostics(interval time.Duration, ip string, port int) { - ticker := time.NewTicker(interval) - defer ticker.Stop() +type tickMsg time.Time - for range ticker.C { +type model struct { + ip string + port int + interval time.Duration +} - // clear screen - fmt.Print("\033[2J") - fmt.Print("\033[H") +func (m model) Init() tea.Cmd { + return tick(m.interval) +} - var m runtime.MemStats - runtime.ReadMemStats(&m) +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl+c": + return m, tea.Quit + } + case tickMsg: + return m, tick(m.interval) + } + return m, nil +} - fmt.Println("runtime diagnostics") - fmt.Println("-------------------") +func (m model) View() string { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) - fmt.Printf("%-20s : %s\n", "go version", runtime.Version()) - fmt.Printf("%-20s : %s\n", "os", runtime.GOOS) - fmt.Printf("%-20s : %s\n", "architecture", runtime.GOARCH) - fmt.Printf("%-20s : %s\n", "compiler", runtime.Compiler) + // Styles + titleStyle := lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("39")). + MarginBottom(1) - fmt.Printf("%-20s : %d\n", "cpu cores", runtime.NumCPU()) - fmt.Printf("%-20s : %d\n", "gomaxprocs", runtime.GOMAXPROCS(0)) - fmt.Printf("%-20s : %d\n", "goroutines", runtime.NumGoroutine()) + sectionStyle := lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("86")). + MarginTop(1) - fmt.Println() - fmt.Println("memory") - fmt.Println("-----------------") + labelStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("241")) - fmt.Printf("%-20s : %d KB\n", "heap alloc", m.HeapAlloc/1024) - fmt.Printf("%-20s : %d KB\n", "heap sys", m.HeapSys/1024) - fmt.Printf("%-20s : %d KB\n", "heap in use", m.HeapInuse/1024) - fmt.Printf("%-20s : %d KB\n", "heap idle", m.HeapIdle/1024) - fmt.Printf("%-20s : %d KB\n", "stack in use", m.StackInuse/1024) - fmt.Printf("%-20s : %d KB\n", "stack sys", m.StackSys/1024) + valueStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("white")) - fmt.Printf("%-20s : %d\n", "gc cycles", m.NumGC) - fmt.Printf("%-20s : %d ms\n", "gc pause total", m.PauseTotalNs/1e6) + helpStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("241")). + MarginTop(1) - fmt.Printf("%-20s : %d\n", "gc next", m.NextGC/1024) + // Helper function to format rows + row := func(label, value string) string { + return fmt.Sprintf("%s %s", + labelStyle.Width(20).Render(label+":"), + valueStyle.Render(value)) + } - fmt.Println() - fmt.Println("server info") - fmt.Println("-----------------") - fmt.Printf("port listening on : %d\n", port) - fmt.Printf("ip address : %s\n", ip) + // Build the view + var s string + + s += titleStyle.Render("Runtime Diagnostics") + "\n" + + s += sectionStyle.Render("System") + "\n" + s += row("Go Version", runtime.Version()) + "\n" + s += row("OS", runtime.GOOS) + "\n" + s += row("Architecture", runtime.GOARCH) + "\n" + s += row("Compiler", runtime.Compiler) + "\n" + s += row("CPU Cores", fmt.Sprintf("%d", runtime.NumCPU())) + "\n" + s += row("GOMAXPROCS", fmt.Sprintf("%d", runtime.GOMAXPROCS(0))) + "\n" + s += row("Goroutines", fmt.Sprintf("%d", runtime.NumGoroutine())) + "\n" + + s += sectionStyle.Render("Memory") + "\n" + s += row("Heap Alloc", fmt.Sprintf("%d KB", mem.HeapAlloc/1024)) + "\n" + s += row("Heap Sys", fmt.Sprintf("%d KB", mem.HeapSys/1024)) + "\n" + s += row("Heap In Use", fmt.Sprintf("%d KB", mem.HeapInuse/1024)) + "\n" + s += row("Heap Idle", fmt.Sprintf("%d KB", mem.HeapIdle/1024)) + "\n" + s += row("Stack In Use", fmt.Sprintf("%d KB", mem.StackInuse/1024)) + "\n" + s += row("Stack Sys", fmt.Sprintf("%d KB", mem.StackSys/1024)) + "\n" + s += row("GC Cycles", fmt.Sprintf("%d", mem.NumGC)) + "\n" + s += row("GC Pause Total", fmt.Sprintf("%d ms", mem.PauseTotalNs/1e6)) + "\n" + s += row("GC Next", fmt.Sprintf("%d KB", mem.NextGC/1024)) + "\n" + + s += sectionStyle.Render("Server Info") + "\n" + s += row("IP Address", m.ip) + "\n" + s += row("Port", fmt.Sprintf("%d", m.port)) + "\n" + + if info, ok := debug.ReadBuildInfo(); ok { + s += sectionStyle.Render("Build Info") + "\n" + s += row("Module", info.Path) + "\n" + s += row("Go Version", info.GoVersion) + "\n" + if info.Main.Version != "(devel)" { + s += row("Version", info.Main.Version) + "\n" + } + } - if info, ok := debug.ReadBuildInfo(); ok { - fmt.Println() - fmt.Println("information") - fmt.Println("-----------------") + s += helpStyle.Render("\nPress 'q' or 'ctrl+c' to quit") - fmt.Printf("%-20s : %s\n", "module", info.Path) - fmt.Printf("%-20s : %s\n", "go version", info.GoVersion) + return s +} - if info.Main.Version != "(devel)" { - fmt.Printf("%-20s : %s\n", "version", info.Main.Version) - } +func tick(interval time.Duration) tea.Cmd { + return tea.Tick(interval, func(t time.Time) tea.Msg { + return tickMsg(t) + }) +} - } +func RunRuntimeDiagnostics(interval time.Duration, ip string, port int) { + p := tea.NewProgram(model{ + ip: ip, + port: port, + interval: interval, + }) + + if _, err := p.Run(); err != nil { + fmt.Printf("Error running diagnostics: %v\n", err) } } From 120e7d0fda255c77c65b59e9c31cffe03b702349 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:47 +0100 Subject: [PATCH 35/55] refactor: update go.mod and go.sum to include additional indirect dependencies for enhanced functionality --- go.mod | 20 ++++++++++++++++++++ go.sum | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/go.mod b/go.mod index 8fb194a..34322c6 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,26 @@ require ( google.golang.org/protobuf v1.36.10 ) +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect +) + require ( github.com/godbus/dbus v4.1.0+incompatible golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect diff --git a/go.sum b/go.sum index 2391d25..c15576e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,21 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -12,6 +28,25 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -26,6 +61,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= From 86ea77be63ad5308fc01f97e6e7e5573ae371146 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:54:53 +0100 Subject: [PATCH 36/55] refactor: update UnitAction to use RunRetrievalDeprecated for improved compatibility --- pkg/grpchandler/services/v1/services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/grpchandler/services/v1/services.go b/pkg/grpchandler/services/v1/services.go index 719e0ed..ef1e423 100644 --- a/pkg/grpchandler/services/v1/services.go +++ b/pkg/grpchandler/services/v1/services.go @@ -91,7 +91,7 @@ func (s *HandlerService) UnitAction(_ context.Context, in *v1.ServiceActionReque out = "external bad input" } - _, err = manager.RunRetrieval(conn, true) + _, err = manager.RunRetrievalDeprecated(conn, true) if err != nil { log.Println("failed to do everything") out = "failed even more" From 5889aabfe8f714233b5453922f02650ce3699e65 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 14:55:04 +0100 Subject: [PATCH 37/55] refactor: update service methods to use deprecated retrieval and improve unit handling --- pkg/grpchandler/services/v2/services.go | 4 +- pkg/grpchandler/services/v3/services.go | 52 ++--- proto/resources/v2/deviceresources.proto | 3 + test/test-all.sh | 254 +++++++++++++++++++++++ 4 files changed, 286 insertions(+), 27 deletions(-) create mode 100755 test/test-all.sh diff --git a/pkg/grpchandler/services/v2/services.go b/pkg/grpchandler/services/v2/services.go index 4a57da7..6ede873 100644 --- a/pkg/grpchandler/services/v2/services.go +++ b/pkg/grpchandler/services/v2/services.go @@ -134,7 +134,7 @@ func (s *HandlerServiceV2) GetAllUnits( from the correct namespace */ - _, err = manager.RunRetrieval(conn, true) + _, err = manager.RunRetrievalDeprecated(conn, true) if err != nil { return &v2.GetUnitsReply{ Success: false, @@ -166,7 +166,7 @@ func (s *HandlerServiceV2) GetLoadedUnits( from the correct namespace */ - _, err = manager.RunRetrieval(conn, false) + _, err = manager.RunRetrievalDeprecated(conn, false) if err != nil { return &v2.GetUnitsReply{ Success: false, diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index a2abed3..48f0c63 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -16,7 +16,7 @@ type HandlerServicev3 struct { v3.UnimplementedHandlerServiceServer } -func mapLoadedUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { +func toGrpcUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { if units == nil { return nil, fmt.Errorf("passed input is nil") @@ -148,15 +148,7 @@ func (s *HandlerServicev3) GetAllUnits( _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { - conn, err := dh.CreateSystemBus() - if err != nil { - return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil - } - - units, err := manager.RunRetrieval(conn, true) + units, err := manager.RunRetrieval(true) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -164,10 +156,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } - /*** - units is empty here! - */ - mapped, err := mapLoadedUnits(units) + mapped, err := toGrpcUnits(units) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -186,24 +175,28 @@ func (s *HandlerServicev3) GetLoadedUnits( _ *v3.GetUnitsRequest, ) (*v3.GetUnitsReply, error) { - _, err := dh.CreateSystemBus() + in, err := manager.RunRetrieval(false) + if err != nil { + return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil + Units: nil, + Success: true, + }, fmt.Errorf("failed to run retrieval") } - // units, err := manager.RunRetrieval(conn, false) + out, err := toGrpcUnits(in) + if err != nil { + return &v3.GetUnitsReply{ - Success: false, - ErrorMessage: err.Error(), - }, nil + Units: nil, + Success: true, + }, fmt.Errorf("failed to map to grpc types") } return &v3.GetUnitsReply{ - Units: nil, + Units: out, Success: true, }, nil } @@ -243,7 +236,7 @@ func (s *HandlerServicev3) GetFilteredUnits( } } - _, err = manager.MapFilteredUnits(conn, filters) + units, err := manager.MapFilteredUnits(conn, filters) if err != nil { return &v3.GetUnitsReply{ Success: false, @@ -251,8 +244,17 @@ func (s *HandlerServicev3) GetFilteredUnits( }, nil } + result, err := toGrpcUnits(units) + + if err != nil { + return &v3.GetUnitsReply{ + Units: nil, + Success: false, + }, fmt.Errorf("failed to parse to grpc units") + } + return &v3.GetUnitsReply{ - Units: nil, + Units: result, Success: true, }, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 35dbd02..6175536 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -7,6 +7,9 @@ option go_package = "paradigm-ehb/agent/gen/resources/v2;resourcespb"; service ResourcesService { rpc GetSystemResources(GetSystemResourcesRequest) returns (GetSystemResourcesResponse); + rpc ProcessAction(ProcessActionRequest) + returns (ProcessActionReply); + } message GetSystemResourcesRequest {} diff --git a/test/test-all.sh b/test/test-all.sh new file mode 100755 index 0000000..c20a1fe --- /dev/null +++ b/test/test-all.sh @@ -0,0 +1,254 @@ +#!/bin/bash +# gRPCurl test commands for all services +# Make sure your gRPC server is running on localhost:5000 (adjust port as needed) +# Install grpcurl: go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest + +SERVER="localhost:5000" + +echo "======================================" +echo "ResourcesServiceV2 Tests" +echo "======================================" + +# Get System Resources +echo -e "\n[1] GetSystemResources" +grpcurl -plaintext \ + -d '{}' \ + $SERVER \ + resources.v2.ResourcesService/GetSystemResources + +# Kill Process (replace with actual PID) +echo -e "\n[2] KillProcess (SIGTERM - signal 15)" +grpcurl -plaintext \ + -d '{ + "pid": 12345, + "signal": 15 + }' \ + $SERVER \ + resources.v2.ResourcesService/KillProcess + +echo -e "\n[3] KillProcess (SIGKILL - signal 9)" +grpcurl -plaintext \ + -d '{ + "pid": 12345, + "signal": 9 + }' \ + $SERVER \ + resources.v2.ResourcesService/KillProcess + +echo "======================================" +echo "HandlerServicev3 Tests (Systemd)" +echo "======================================" + +# Get All Units +echo -e "\n[4] GetAllUnits" +grpcurl -plaintext \ + -d '{}' \ + $SERVER \ + services.v3.HandlerService/GetAllUnits + +# Get Loaded Units +echo -e "\n[5] GetLoadedUnits" +grpcurl -plaintext \ + -d '{}' \ + $SERVER \ + services.v3.HandlerService/GetLoadedUnits + +# Get Filtered Units +echo -e "\n[6] GetFilteredUnits - LOADED" +grpcurl -plaintext \ + -d '{ + "filters": ["LOADED"] + }' \ + $SERVER \ + services.v3.HandlerService/GetFilteredUnits + +echo -e "\n[7] GetFilteredUnits - ERROR and MASKED" +grpcurl -plaintext \ + -d '{ + "filters": ["ERROR", "MASKED"] + }' \ + $SERVER \ + services.v3.HandlerService/GetFilteredUnits + +# Get Unit Status +echo -e "\n[8] GetUnitStatus - nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service" + }' \ + $SERVER \ + services.v3.HandlerService/GetUnitStatus + +echo -e "\n[9] GetUnitStatus - sshd.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "sshd.service" + }' \ + $SERVER \ + services.v3.HandlerService/GetUnitStatus + +# Perform Unit Action - Start +echo -e "\n[10] PerformUnitAction - START nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_ACTION_START" + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitAction + +# Perform Unit Action - Stop +echo -e "\n[11] PerformUnitAction - STOP nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_ACTION_STOP" + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitAction + +# Perform Unit Action - Restart +echo -e "\n[12] PerformUnitAction - RESTART nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_ACTION_RESTART" + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitAction + +# Perform Unit File Action - Enable +echo -e "\n[13] PerformUnitFileAction - ENABLE nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_FILE_ACTION_ENABLE", + "runtime": false, + "force": false + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitFileAction + +# Perform Unit File Action - Disable +echo -e "\n[14] PerformUnitFileAction - DISABLE nginx.service" +grpcurl -plaintext \ + -d '{ + "unit_name": "nginx.service", + "action": "UNIT_FILE_ACTION_DISABLE", + "runtime": false, + "force": true + }' \ + $SERVER \ + services.v3.HandlerService/PerformUnitFileAction + +echo "======================================" +echo "DeviceActionsService Tests" +echo "======================================" + +# Shutdown (WARNING: Will shutdown the system!) +echo -e "\n[15] Action - SHUTDOWN (commented out for safety)" +# grpcurl -plaintext \ +# -d '{ +# "device_action": "DEVICE_ACTION_SHUTDOWN" +# }' \ +# $SERVER \ +# actions.v1.ActionService/Action + +# Reboot (WARNING: Will reboot the system!) +echo -e "\n[16] Action - REBOOT (commented out for safety)" +# grpcurl -plaintext \ +# -d '{ +# "device_action": "DEVICE_ACTION_REBOOT" +# }' \ +# $SERVER \ +# actions.v1.ActionService/Action + +# Suspend +echo -e "\n[17] Action - SUSPEND" +grpcurl -plaintext \ + -d '{ + "device_action": "DEVICE_ACTION_SUSPEND" + }' \ + $SERVER \ + actions.v1.ActionService/Action + +# Hibernate +echo -e "\n[18] Action - HIBERNATE" +grpcurl -plaintext \ + -d '{ + "device_action": "DEVICE_ACTION_HIBERNATE" + }' \ + $SERVER \ + actions.v1.ActionService/Action + +echo "======================================" +echo "JournalService Tests (Server Streaming)" +echo "======================================" + +# Journal by Systemd Unit +echo -e "\n[19] Action - Get journal for nginx.service" +grpcurl -plaintext \ + -d '{ + "field": 0, + "value": "nginx.service", + "num_from_tail": 100, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +# Journal by PID +echo -e "\n[20] Action - Get journal for PID 1" +grpcurl -plaintext \ + -d '{ + "field": 1, + "value": "1", + "num_from_tail": 50, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +# Journal by UID +echo -e "\n[21] Action - Get journal for UID 0 (root)" +grpcurl -plaintext \ + -d '{ + "field": 2, + "value": "0", + "num_from_tail": 100, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +# Journal by GID +echo -e "\n[22] Action - Get journal for GID 0 (root)" +grpcurl -plaintext \ + -d '{ + "field": 3, + "value": "0", + "num_from_tail": 100, + "cursor": "", + "path": "" + }' \ + $SERVER \ + journal.v1.JournalService/Action + +echo -e "\n======================================" +echo "List Available Services" +echo "======================================" + +grpcurl -plaintext $SERVER list + +echo -e "\n======================================" +echo "Describe a Service" +echo "======================================" + +grpcurl -plaintext $SERVER describe resources.v2.ResourcesService + +echo -e "\n======================================" +echo "Tests Complete!" +echo "======================================" From ae8dc06d846630fccf11f95b76059311124ee144 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 18:42:19 +0100 Subject: [PATCH 38/55] debug: connection closed errror --- internal/dbusservices/manager.go | 2 +- pkg/grpchandler/services/v3/services.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 27fdebc..8d0d27f 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -193,10 +193,10 @@ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { */ func RunRetrieval(requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { conn, err := dbushelper.CreateSystemBus() + if err != nil { return nil, fmt.Errorf("failed to create system bus connection: %w", err) } - defer conn.Close() if requestAllUnitsOnDisk { return MapUnits(conn) diff --git a/pkg/grpchandler/services/v3/services.go b/pkg/grpchandler/services/v3/services.go index 48f0c63..d569141 100644 --- a/pkg/grpchandler/services/v3/services.go +++ b/pkg/grpchandler/services/v3/services.go @@ -22,7 +22,6 @@ func toGrpcUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { return nil, fmt.Errorf("passed input is nil") } - fmt.Println("input :", units) out := make([]*v3.LoadedUnit, 0, len(units)) @@ -45,7 +44,6 @@ func toGrpcUnits(units []*types.LoadedUnit) ([]*v3.LoadedUnit, error) { }) } - fmt.Println("out -> ", out) return out, nil } @@ -156,6 +154,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } + mapped, err := toGrpcUnits(units) if err != nil { return &v3.GetUnitsReply{ @@ -164,6 +163,7 @@ func (s *HandlerServicev3) GetAllUnits( }, nil } + return &v3.GetUnitsReply{ Units: mapped, Success: true, @@ -246,6 +246,8 @@ func (s *HandlerServicev3) GetFilteredUnits( result, err := toGrpcUnits(units) + fmt.Print("result: ", result) + if err != nil { return &v3.GetUnitsReply{ Units: nil, From e7913de35773bb1e11fe25dfa84353eb859e3143 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 19:57:06 +0100 Subject: [PATCH 39/55] checkpoint: played around with build scripts --- compile.sh | 132 +++-------------------------------------------------- 1 file changed, 6 insertions(+), 126 deletions(-) diff --git a/compile.sh b/compile.sh index d19a4f5..456bd75 100755 --- a/compile.sh +++ b/compile.sh @@ -1,129 +1,9 @@ #!/bin/sh -# echo ------------------------------------------------------------------------- -# echo Description: build go binary with flags -# echo ------------------------------------------------------------------------- -VERSION=1.0 -prod() { - go build \ - -ldflags="-s -w -X main.version=${VERSION}" \ - -o agent \ - ./cmd/agent/agent.go -} +go clean -cache -debug() { - go build \ - -race \ - -gcflags="all=-N -l" \ - -o debug \ - ./cmd/agent/agent.go -} - -valgrind() { - echo "Building with debug symbols for valgrind..." - CGO_CFLAGS="-g -O0" \ - CGO_LDFLAGS="-g" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_valgrind \ - ./cmd/agent/agent.go - - echo "Running valgrind..." - valgrind \ - --leak-check=full \ - --show-leak-kinds=all \ - --track-origins=yes \ - --verbose \ - --log-file=valgrind-out.txt \ - ./debug_valgrind - - echo "Valgrind output saved to valgrind-out.txt" -} - -gdb() { - echo "Building with debug symbols for gdb..." - CGO_CFLAGS="-g -O0" \ - CGO_LDFLAGS="-g" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_gdb \ - ./cmd/agent/agent.go - - echo "Starting gdb..." - gdb ./debug_gdb -} - -core() { - echo "Building with debug symbols..." - CGO_CFLAGS="-g -O0" \ - CGO_LDFLAGS="-g" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_core \ - ./cmd/agent/agent.go - - echo "Enabling core dumps..." - ulimit -c unlimited - - echo "Running program (will generate core dump on crash)..." - ./debug_core - - # Check if core dump was created - if [ -f core ]; then - echo "Core dump generated. Starting gdb..." - gdb ./debug_core core - elif [ -f core.* ]; then - CORE_FILE=$(ls -t core.* | head -n1) - echo "Core dump generated: $CORE_FILE. Starting gdb..." - gdb ./debug_core "$CORE_FILE" - else - echo "No core dump found. Program may have exited normally." - echo "Core dumps might be in: /var/lib/systemd/coredump/ or /var/crash/" - echo "Check with: coredumpctl list" - fi -} - -sanitize() { - echo "Building with address sanitizer..." - CGO_CFLAGS="-fsanitize=address -g -O0" \ - CGO_LDFLAGS="-fsanitize=address" \ - go build \ - -gcflags="all=-N -l" \ - -o debug_asan \ - ./cmd/agent/agent.go - - echo "Running with address sanitizer..." - ASAN_OPTIONS=detect_leaks=1:halt_on_error=0 ./debug_asan -} - -case "$1" in - prod) - prod - ;; - debug) - debug - ;; - valgrind) - valgrind - ;; - gdb) - gdb - ;; - core) - core - ;; - sanitize|asan) - sanitize - ;; - *) - echo "Usage: $0 [prod|debug|valgrind|gdb|core|sanitize]" - echo "" - echo " prod - Production build (stripped, optimized)" - echo " debug - Debug build with race detector" - echo " valgrind - Build and run with valgrind memory checker" - echo " gdb - Build and start gdb debugger" - echo " core - Build, enable core dumps, run and analyze crash" - echo " sanitize - Build and run with address sanitizer (ASAN)" - exit 1 - ;; -esac +if CGO_ENABLED=1 go build -race -gcflags="all=-N -l" -o debug ./cmd/agent/agent.go; then + exec ./debug +else + exit 1 +fi From 439a70063b32324f4f356b25973cd19d9b4fe026 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 19:58:06 +0100 Subject: [PATCH 40/55] feature: implemented total time and idle time in protobbufs and cgo wrapper --- cmd/agent/agent.go | 1 + pkg/agent-resources | 2 +- pkg/cgowrap/types.go | 2 ++ pkg/cgowrap/wrapper.go | 3 +++ proto/resources/v2/deviceresources.proto | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 47d1a6b..1c8df7a 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -39,6 +39,7 @@ import ( "paradigm-ehb/agent/pkg/grpchandler" + resourcesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/resources/v1" resourcesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/resources/v2" diff --git a/pkg/agent-resources b/pkg/agent-resources index f9e91ef..f9aa0f4 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit f9e91ef837924ba8ece0d2ed626f23747bbc9788 +Subproject commit f9aa0f4c68b0ed5771077c308107a731f43f2da8 diff --git a/pkg/cgowrap/types.go b/pkg/cgowrap/types.go index 1d42440..2e2d769 100644 --- a/pkg/cgowrap/types.go +++ b/pkg/cgowrap/types.go @@ -6,6 +6,8 @@ type Cpu struct { Model string Frequency string MaxCore uint32 + TotalTime uint64 + IdleTime uint64 } // Ram represents RAM information including total and free memory. diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index ff0c9cf..d4d1147 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -321,6 +321,9 @@ func CpuRead(c *C.Cpu) (Cpu, error) { Model: C.GoString(&c.model[0]), Frequency: C.GoString(&c.frequency[0]), MaxCore: uint32(c.cores), + TotalTime: uint64(c.total_time), + IdleTime: uint64(c.idle_time), + } return cpu, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index 6175536..f811a98 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -42,6 +42,8 @@ message Cpu { string model = 2; string frequency = 3; uint32 max_core = 4; + uint64 total_time = 5; + uint64 idle_time = 6; } /** From a8d8ebc7f1158556c42ddccc16ccad6ffa6da544 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 21:47:00 +0100 Subject: [PATCH 41/55] feature: updated scripts for correct versiona and correct namings --- test/grpc_resources.sh | 4 ++-- test/test-all.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/grpc_resources.sh b/test/grpc_resources.sh index 5ea9c84..4e7b93b 100755 --- a/test/grpc_resources.sh +++ b/test/grpc_resources.sh @@ -13,7 +13,7 @@ command -v grpcurl >/dev/null 2>&1 || { } echo "== Checking service availability ==" -grpcurl -plaintext "$ADDR" list resources.v1.ResourcesService >/dev/null +grpcurl -plaintext "$ADDR" list resources.v2.ResourcesService >/dev/null echo "OK" echo @@ -21,7 +21,7 @@ echo "== Calling GetSystemResources ==" grpcurl -plaintext \ -format text \ "$ADDR" \ - resources.v1.ResourcesService/GetSystemResources + resources.v2.ResourcesService/GetSystemResources echo echo "== Test completed ==" diff --git a/test/test-all.sh b/test/test-all.sh index c20a1fe..a787032 100755 --- a/test/test-all.sh +++ b/test/test-all.sh @@ -191,7 +191,7 @@ grpcurl -plaintext \ -d '{ "field": 0, "value": "nginx.service", - "num_from_tail": 100, + "numFromTail": 100, "cursor": "", "path": "" }' \ From 9095621492f8bff823eff374dab470e4bb0f28f5 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 21:47:39 +0100 Subject: [PATCH 42/55] bugfix: fixed memory issue --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index f9aa0f4..f654d6e 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit f9aa0f4c68b0ed5771077c308107a731f43f2da8 +Subproject commit f654d6e475b562ced341ba57108c55e1ca37f81b From 83541a1f30ca10df09b269435a56c3559c0dd82d Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 21:48:03 +0100 Subject: [PATCH 43/55] feature: added idle time and total time to cpu resources --- cmd/agent/agent.go | 4 +++- internal/dbusservices/manager.go | 7 +++++-- pkg/cgowrap/wrapper.go | 7 +++++-- pkg/grpchandler/resources/v2/resources.go | 2 ++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 1c8df7a..f42057f 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -23,6 +23,8 @@ import ( "paradigm-ehb/agent/gen/greet" "paradigm-ehb/agent/gen/journal/v1" + "log" + servicesV1 "paradigm-ehb/agent/gen/services/v1" servicesV2 "paradigm-ehb/agent/gen/services/v2" servicesV3 "paradigm-ehb/agent/gen/services/v3" @@ -116,7 +118,7 @@ func main() { /** Any error other than "address already in use" is fatal. */ - fmt.Println("failed to listen:", err) + log.Printf("failed to listen:", err) return } break diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 8d0d27f..48cee6a 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -38,10 +38,13 @@ func RunSymlinkAction( enableForce bool, service []string) error { - obj, _ := dbushelper.CreateSystemdObject(conn) + obj, err := dbushelper.CreateSystemdObject(conn) + if err != nil { + return fmt.Errorf("failed to create systemd object %v", err) + } if !obj.Path().IsValid() { - fmt.Println("invalid systemd path") + return fmt.Errorf("invalid systemd path") } /** EnableUnitFiles(in as files, in b runtime, in b force, out b carries_install_info, out a(sss) changes); */ diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index d4d1147..ff7e648 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -316,14 +316,17 @@ func CpuRead(c *C.Cpu) (Cpu, error) { return Cpu{}, fmt.Errorf("failed to read CPU information") } + if C.cpu_read_usage(c) != C.OK { + return Cpu{}, fmt.Errorf("failed to read CPU information") + } + cpu := Cpu{ Vendor: C.GoString(&c.vendor[0]), Model: C.GoString(&c.model[0]), Frequency: C.GoString(&c.frequency[0]), MaxCore: uint32(c.cores), TotalTime: uint64(c.total_time), - IdleTime: uint64(c.idle_time), - + IdleTime: uint64(c.idle_time), } return cpu, nil } diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index c59d02f..69e3c10 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -113,6 +113,8 @@ func mapCPU(c cgo.Cpu) *proto.Cpu { Model: c.Model, Frequency: c.Frequency, MaxCore: c.MaxCore, + TotalTime: c.TotalTime, + IdleTime: c.IdleTime, } } From 057326279f474d428b75092763630168935aafae Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:17:04 +0100 Subject: [PATCH 44/55] refactore: improved documentation and todos in certain features --- cmd/agent/agent.go | 170 ++++++++++++++++++--------- internal/dbusservices/manager.go | 195 ++++++++++++++++++++++++------- internal/journal/journal.go | 158 ++++++++++++++++--------- pkg/cgowrap/wrapper.go | 9 +- pkg/grpchandler/journal.go | 57 +++++++-- 5 files changed, 421 insertions(+), 168 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index f42057f..a72b10b 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -1,16 +1,18 @@ -// Package main boots the Paradigm Agent gRPC server. -// -// Responsibilities: -// - Parse runtime flags (IP, port, diagnostics) -// - Bind a TCP listener with automatic port fallback -// - Initialize and register all gRPC services -// - Expose health and reflection endpoints -// -// Design notes: -// - The server must be able to start even if the preferred port is occupied. -// - Reflection is enabled by default for debugging and introspection. -// - Diagnostics are intentionally decoupled from server startup to avoid -// blocking gRPC reflection and request handling. +/* +Package main boots the Paradigm Agent gRPC server. + +Responsibilities: +- Parse runtime flags (IP, port, diagnostics) +- Bind a TCP listener with automatic port fallback +- Initialize and register all gRPC services +- Expose health and reflection endpoints + +Design notes: +- The server must be able to start even if the preferred port is occupied. +- Reflection is enabled by default for debugging and introspection. +- Diagnostics are intentionally decoupled from server startup to avoid + blocking gRPC reflection and request handling. +*/ package main import ( @@ -41,7 +43,6 @@ import ( "paradigm-ehb/agent/pkg/grpchandler" - resourcesHandlerV1 "paradigm-ehb/agent/pkg/grpchandler/resources/v1" resourcesHandlerV2 "paradigm-ehb/agent/pkg/grpchandler/resources/v2" @@ -54,48 +55,68 @@ import ( ) var ( - /** - diagnostics enables periodic runtime diagnostics such as - resource usage, process health, and connectivity checks. - NOTE: When enabled, diagnostics must never block the gRPC server. - **/ + /* + diagnostics enables periodic runtime diagnostics such as + resource usage, process health, and connectivity checks. - diagnostics = flag.Bool("diagnostics", false, "run runtime diagnostics") + TODO: + - Add structured configuration for diagnostics intervals. + - Allow diagnostics to be toggled or reconfigured at runtime. + - Ensure diagnostics respect context cancellation on shutdown. - /** - portFlag is the preferred TCP port to bind the gRPC server to. - If unavailable, the server will increment the port until a free - one is found. + NOTE: + When enabled, diagnostics must never block the gRPC server. */ + diagnostics = flag.Bool("diagnostics", true, "run runtime diagnostics") + + /* + portFlag is the preferred TCP port to bind the gRPC server to. + + If unavailable: + - The server increments the port until a free one is found. + TODO: + - Log the final selected port explicitly. + - Optionally expose the selected port via diagnostics or metadata. + */ portFlag = flag.Int("port", 5000, "port to listen on") - /** - ipFlag defines the IP address diagnostics may use when reporting - or exposing runtime information. + /* + ipFlag defines the IP address diagnostics may use when reporting + or exposing runtime information. + + TODO: + - Validate IP format early. + - Clarify distinction between bind address vs diagnostics address. */ ipFlag = flag.String("ip", "0.0.0.0", "ip addr") ) func main() { - /** + /* Parse command-line flags before any runtime behavior. - */ + TODO: + - Add validation for flag combinations. + - Print effective configuration at startup. + */ flag.Parse() - /** + /* Attempt to bind a TCP listener. Strategy: - - Start with the requested port - - If EADDRINUSE is encountered, increment the port and retry - - Fail hard on any other error + - Start with the requested port + - If EADDRINUSE is encountered, increment the port and retry + - Fail hard on any other error This guarantees the agent can always start, even in constrained or multi-agent environments. + TODO: + - Add an upper bound to port scanning. + - Support IPv6 or configurable network protocols. */ var lis net.Listener var err error @@ -115,90 +136,125 @@ func main() { } } - /** + /* Any error other than "address already in use" is fatal. + + TODO: + - Emit structured logs. + - Exit with non-zero status code. */ - log.Printf("failed to listen:", err) + log.Printf("failed to listen: %v", err) return } break } - /** + /* Create the gRPC server instance. + + TODO: + - Configure server options (timeouts, interceptors, limits). + - Add graceful shutdown handling. */ server := grpc.NewServer() - /** + /* Health server is used by orchestration systems (systemd, Kubernetes, external monitors) to determine liveness and readiness. + + TODO: + - Set explicit serving statuses per service. */ healthServer := health.NewServer() grpc_health_v1.RegisterHealthServer(server, healthServer) - /** - Register all application services. + /* + Register all application services. - Each service implements a distinct responsibility: - - Greeter: connectivity / handshake testing - - HandlerService: service lifecycle and orchestration - - JournalService: event and state journaling - - ResourcesService: system resource inspection and reporting + Each service implements a distinct responsibility: + - Greeter: connectivity / handshake testing + - HandlerService: service lifecycle and orchestration + - JournalService: event and state journaling + - ResourcesService: system resource inspection and reporting + TODO: + - Centralize service registration. + - Version-gate deprecated service versions. */ greet.RegisterGreeterServer( server, - &grpc_handler.GreeterServer{}) + &grpc_handler.GreeterServer{}, + ) servicesV1.RegisterHandlerServiceServer( server, - &servicesHandlerV1.HandlerService{}) + &servicesHandlerV1.HandlerService{}, + ) servicesV2.RegisterHandlerServiceServer( server, - &servicesHandlerV2.HandlerServiceV2{}) + &servicesHandlerV2.HandlerServiceV2{}, + ) servicesV3.RegisterHandlerServiceServer( server, - &servicesHandlerV3.HandlerServicev3{}) + &servicesHandlerV3.HandlerServicev3{}, + ) journal.RegisterJournalServiceServer( server, - &grpc_handler.JournalService{}) + &grpc_handler.JournalService{}, + ) resourcesv1.RegisterResourcesServiceServer( server, - &resourcesHandlerV1.ResourcesService{}) + &resourcesHandlerV1.ResourcesService{}, + ) resourcesv2.RegisterResourcesServiceServer( server, - &resourcesHandlerV2.ResourcesServiceV2{}) + &resourcesHandlerV2.ResourcesServiceV2{}, + ) devacpb.RegisterActionServiceServer( server, - &grpc_handler.DeviceActionsService{}) + &grpc_handler.DeviceActionsService{}, + ) - /** + /* Enable gRPC reflection unconditionally. This allows tools such as grpcurl and gRPC UI to inspect services and message schemas at runtime. + + TODO: + - Make reflection configurable for production environments. */ reflection.Register(server) - fmt.Printf("\nserver listening at %v\n", lis.Addr()) + + log.Printf("\nserver listening at %v\n", lis.Addr()) if *diagnostics { + /* + TODO: + - Tie diagnostics lifecycle to server context. + - Ensure diagnostics terminate on server shutdown. + */ go platform.RunRuntimeDiagnostics(time.Second*2, *ipFlag, *portFlag) - } - /** + /* Start serving requests. + This call blocks until the server is stopped or encounters a fatal error. + + TODO: + - Implement graceful shutdown (signals, context). + - Flush diagnostics and logs before exit. */ if err := server.Serve(lis); err != nil { - fmt.Println("failed to serve:", err) + log.Printf("failed to serve: %v", err) } } diff --git a/internal/dbusservices/manager.go b/internal/dbusservices/manager.go index 48cee6a..c801519 100644 --- a/internal/dbusservices/manager.go +++ b/internal/dbusservices/manager.go @@ -10,13 +10,30 @@ import ( "github.com/godbus/dbus" ) -// @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" +/* +RunAction executes a systemd unit action (start, stop, restart). + +Parameters: +- conn: + Active D-Bus connection. +- ac: + Unit action to execute (start, stop, restart). +- service: + Unit name in systemd format (e.g. "example.service"). + +TODO: +- Validate service name format before invoking D-Bus. +- Propagate context / cancellation support. +- Replace raw string conversion with strongly typed D-Bus method mapping. +*/ func RunAction( conn *dbus.Conn, ac systemd.UnitAction, - service string) error { + service string, +) error { obj, _ := dbushelper.CreateSystemdObject(conn) + if !obj.Path().IsValid() { return fmt.Errorf("object path is invalid") } @@ -24,19 +41,39 @@ func RunAction( call := obj.Call(string(ac), 0, service, "replace") if call.Err != nil { - return fmt.Errorf("failed to execute object on in unit action, %v", call.Err) + return fmt.Errorf("failed to execute object on unit action, %v", call.Err) } return nil } -// @param, action [start, stop, restart], symLinkAction [enable, disable], service name format "example.service" +/* +RunSymlinkAction executes systemd unit file actions (enable / disable). + +Parameters: +- conn: + Active D-Bus connection. +- sc: + Unit file action (enable or disable). +- enableForRunTime: + Whether the action applies only at runtime. +- enableForce: + Whether to force-enable units (only relevant for enable). +- service: + Slice of unit names. + +TODO: +- Validate service slice is non-empty. +- Clarify runtime vs persistent semantics in API naming. +- Normalize error messages. +*/ func RunSymlinkAction( conn *dbus.Conn, sc systemd.UnitFileAction, enableForRunTime bool, enableForce bool, - service []string) error { + service []string, +) error { obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { @@ -47,18 +84,43 @@ func RunSymlinkAction( return fmt.Errorf("invalid systemd path") } - /** EnableUnitFiles(in as files, in b runtime, in b force, out b carries_install_info, out a(sss) changes); */ - /** DisableUnitFiles(in as files, in b runtime, out a(sss) changes); */ + /* + EnableUnitFiles( + in as files, + in b runtime, + in b force, + out b carries_install_info, + out a(sss) changes + ) + + DisableUnitFiles( + in as files, + in b runtime, + out a(sss) changes + ) + */ switch sc { case systemd.UnitFileActionEnable: - call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime, enableForce) + call := obj.Call( + string(sc), + dbus.FlagAllowInteractiveAuthorization, + service, + enableForRunTime, + enableForce, + ) if call.Err != nil { return fmt.Errorf("error %v", call.Err) } + case systemd.UnitFileActionDisable: - call := obj.Call(string(sc), dbus.FlagAllowInteractiveAuthorization, service, enableForRunTime) + call := obj.Call( + string(sc), + dbus.FlagAllowInteractiveAuthorization, + service, + enableForRunTime, + ) if call.Err != nil { return fmt.Errorf("something happened here %v", call.Err) } @@ -67,9 +129,23 @@ func RunSymlinkAction( return nil } +/* +UnitStatus retrieves the status of a single systemd unit. + +Parameters: +- obj: + Systemd D-Bus object. +- name: + Unit name. + +TODO: +- Replace string status with structured state representation. +- Normalize error return values. +*/ func UnitStatus( obj dbus.BusObject, - name string) (string, error) { + name string, +) (string, error) { out, err := systemd.GetStatusCall(obj, name) if err != nil { @@ -77,28 +153,32 @@ func UnitStatus( } return out, nil - } -/** - * Map to the correct types - */ +/* +MapLoadedUnits maps loaded systemd units to internal LoadedUnit types. + +TODO: +- Remove duplicated mapping logic across unit retrieval functions. +- Fix QueudJob typo once wire format compatibility is resolved. +*/ func MapLoadedUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { return nil, fmt.Errorf("failed to create systemd object: %w", err) } - + loaded, err := systemd.GetLoadedUnits(obj) if err != nil { return nil, fmt.Errorf("failed to get loaded units: %w", err) } - + parsed, err := dbushelper.ParseLoadedUnits(loaded) if err != nil { return nil, fmt.Errorf("failed to parse loaded units: %w", err) } - + units := make([]*types.LoadedUnit, 0, len(parsed)) for _, u := range parsed { units = append(units, &types.LoadedUnit{ @@ -109,30 +189,42 @@ func MapLoadedUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { ActiveState: u.ActiveState, DepUnit: u.DepUnit, ObjectPath: u.ObjectPath, - QueudJob: u.QueudJob, // Keep the typo for consistency + QueudJob: u.QueudJob, /* keep typo for consistency */ JobType: u.JobType, JobPath: u.JobPath, }) } + return units, nil } -func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, error) { +/* +MapFilteredUnits retrieves and maps filtered systemd units. + +TODO: +- Replace placeholder "Not Available" strings with optional fields. +- Clarify which fields are guaranteed by GetUnitsFiltered. +*/ +func MapFilteredUnits( + conn *dbus.Conn, + filters []string, +) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { return nil, fmt.Errorf("failed to create systemd object: %w", err) } - + entries, err := systemd.GetUnitsFiltered(obj, filters) if err != nil { return nil, fmt.Errorf("failed to get filtered units: %w", err) } - + parsed, err := dbushelper.ParseLoadedUnits(entries) if err != nil { return nil, fmt.Errorf("failed to parse filtered units: %w", err) } - + units := make([]*types.LoadedUnit, 0, len(parsed)) for _, e := range parsed { units = append(units, &types.LoadedUnit{ @@ -148,28 +240,34 @@ func MapFilteredUnits(conn *dbus.Conn, filters []string) ([]*types.LoadedUnit, e JobPath: "Not Available", }) } + return units, nil } -/** - * Fill in proper unit types - */ +/* +MapUnits retrieves and maps all systemd units. + +TODO: +- Distinguish unit-on-disk vs loaded semantics at the type level. +- Avoid repeating placeholder field values. +*/ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { + obj, err := dbushelper.CreateSystemdObject(conn) if err != nil { return nil, fmt.Errorf("failed to create systemd object: %w", err) } - + result, err := systemd.GetUnits(obj) if err != nil { return nil, fmt.Errorf("failed to get units: %w", err) } - + parsedUnits, err := dbushelper.ParseUnits(result) if err != nil { return nil, fmt.Errorf("failed to parse units: %w", err) } - + units := make([]*types.LoadedUnit, 0, len(parsedUnits)) for _, e := range parsedUnits { units = append(units, &types.LoadedUnit{ @@ -185,34 +283,51 @@ func MapUnits(conn *dbus.Conn) ([]*types.LoadedUnit, error) { JobPath: "Not Available", }) } + return units, nil } -/** - * @param requestAllUnitsOnDisk: true for all units on disk, false for loaded units - * A loaded unit is a unit that has been activated before and is available in memory - * for the server to start up - * @return []*types.LoadedUnit, error - */ +/* +RunRetrieval retrieves units using an internally created system bus connection. + +Parameters: +- requestAllUnitsOnDisk: + true -> retrieve all units on disk + false -> retrieve only loaded units + +TODO: +- Close system bus connection explicitly. +- Propagate context. +*/ func RunRetrieval(requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { - conn, err := dbushelper.CreateSystemBus() + conn, err := dbushelper.CreateSystemBus() if err != nil { return nil, fmt.Errorf("failed to create system bus connection: %w", err) } - + if requestAllUnitsOnDisk { return MapUnits(conn) } - + return MapLoadedUnits(conn) } -func RunRetrievalDeprecated(conn *dbus.Conn, requestAllUnitsOnDisk bool) ([]*types.LoadedUnit, error) { - +/* +RunRetrievalDeprecated performs unit retrieval using a caller-provided connection. + +TODO: +- Remove once all call sites migrate to RunRetrieval. +- Clearly document ownership of conn lifecycle. +*/ +func RunRetrievalDeprecated( + conn *dbus.Conn, + requestAllUnitsOnDisk bool, +) ([]*types.LoadedUnit, error) { + if requestAllUnitsOnDisk { return MapUnits(conn) } - + return MapLoadedUnits(conn) } diff --git a/internal/journal/journal.go b/internal/journal/journal.go index e963cf2..dcbb9b0 100644 --- a/internal/journal/journal.go +++ b/internal/journal/journal.go @@ -4,34 +4,38 @@ import ( "io" "time" - "fmt" + "log" - jrnl "github.com/coreos/go-systemd/v22/journal" - sdj "github.com/coreos/go-systemd/v22/sdjournal" + "github.com/coreos/go-systemd/v22/journal" + "github.com/coreos/go-systemd/v22/sdjournal" ) -// checkJournal reports whether systemd’s journal is available and enabled -// on the current system. -// -// It is a lightweight capability check and does not open or read the journal. -// Internally, this relies on libsystemd to detect whether journald is usable -// (for example, not present on non-systemd systems). +/* +checkJournal reports whether systemd’s journal is available and enabled +on the current system. + +It is a lightweight capability check and does not open or read the journal. +Internally, this relies on libsystemd to detect whether journald is usable +(for example, not present on non-systemd systems). +*/ func checkJournal() bool { - return jrnl.Enabled() + return journal.Enabled() } -// systemdID returns the boot ID associated with the currently running system. -// -// The boot ID uniquely identifies the current boot session and is useful for -// correlating journal entries to a specific system start. The function opens -// the journal, queries the boot ID, and then closes the journal handle. -// -// If the journal cannot be opened or the boot ID cannot be retrieved, the -// returned string may be empty. +/* +systemdID returns the boot ID associated with the currently running system. + +The boot ID uniquely identifies the current boot session and is useful for +correlating journal entries to a specific system start. The function opens +the journal, queries the boot ID, and then closes the journal handle. + +If the journal cannot be opened or the boot ID cannot be retrieved, the +returned string may be empty. +*/ func systemdID() (string, error) { - j, err := sdj.NewJournal() + j, err := sdjournal.NewJournal() if err != nil { return "not available", err } @@ -43,35 +47,68 @@ func systemdID() (string, error) { } return bid, nil - } -// TODO(nasr): checkout formatters - -// GetJournalInformation GetJournaldInformation reads entries from the systemd journal and returns -// them as a single concatenated string. -// -// The journal reader is configured through the provided parameters: -// - since: limits entries to those newer than the given duration -// relative now -// - numFromTail: limits the number of entries read from the end of the journal -// - cursor: reserved for future cursor-based positioning (currently unused) -// - matches: filters entries using systemd journal match rules -// - path: optionally specifies a custom journal path -// -// Internally, this function uses a JournalReader and performs sequential reads -// into a fixed-size buffer until no more data is available or an error occurs. -// The caller receives raw journal output as text, without further parsing or -// field-level decoding. -// -// Example Matches: []sdj.Match{{Field: "_SYSTEMD_UNIT", Value: "ssh.service"}}} -func GetJournalInformation(since time.Duration, numFromTail uint64, cursor string, matches []sdj.Match, path string, out chan []byte) { - +/* +TODO(nasr): +- Evaluate and implement journal output formatters. +- Decide whether formatting should be: + - raw text (current behavior), + - structured (map / proto), + - or selectable via configuration. +*/ + +/* +GetJournalInformation reads entries from the systemd journal and streams them +through the provided output channel. + +Parameters: +- since: + Duration relative to "now" used to limit journal entries. + (Currently not implemented.) +- numFromTail: + Limits the number of entries read from the end of the journal. +- cursor: + Reserved for cursor-based positioning (currently unused). +- matches: + Journal match filters (field/value pairs). +- path: + Optional custom journal path. +- out: + Output channel receiving raw journal byte slices. + +Behavior: +- Opens a JournalReader with the provided configuration. +- Reads sequentially into a fixed-size buffer. +- Streams raw journal output without parsing or decoding fields. +- Closes the output channel before returning. + +Example: + []sdj.Match{{Field: "_SYSTEMD_UNIT", Value: "ssh.service"}} +*/ +func GetJournalInformation( + since time.Duration, + numFromTail uint64, + cursor string, + matches []sdjournal.Match, + path string, + out chan []byte, +) { + + /* + TODO(nasr): + - Define ownership and lifecycle rules for `out`. + - Document that this function is responsible for closing the channel. + */ defer close(out) - config := sdj.JournalReaderConfig{ - // TODO(nasr): fix time imlementation - //Since: since, + config := sdjournal.JournalReaderConfig{ + /* + TODO(nasr): + - Implement time-based filtering using `since`. + - Decide whether `since` should override cursor semantics. + */ + // Since: since, NumFromTail: numFromTail, Cursor: cursor, Matches: matches, @@ -79,10 +116,14 @@ func GetJournalInformation(since time.Duration, numFromTail uint64, cursor strin Formatter: nil, } - reader, err := sdj.NewJournalReader(config) - + reader, err := sdjournal.NewJournalReader(config) if err != nil { - fmt.Println("failed to open the journal reader") + /* + TODO(nasr): + - Replace stdout logging with structured error propagation. + - Decide whether to terminate early or send error markers via channel. + */ + } defer reader.Close() @@ -93,23 +134,36 @@ func GetJournalInformation(since time.Duration, numFromTail uint64, cursor strin c, err := reader.Read(b) if err == io.EOF { - fmt.Println("End of journal") + /* + TODO(nasr): + - Decide whether EOF should be silent. + - Avoid stdout logging in library code. + */ + log.Printf("end of journal %v, ", err) break } if c == 0 { - fmt.Println(" data") + /* + TODO(nasr): + - Clarify whether zero-length reads are expected. + - Remove noisy logging or replace with debug-level tracing. + */ continue } if err != nil { + /* + TODO(nasr): + - Avoid sending placeholder data on error. + - Define a structured error signaling mechanism. + - Consider context cancellation or error channels. + */ out <- []byte("nothing in here") - fmt.Println("failed to read from the journal reader", err) + log.Printf("no more data to read %v, ", err) break } - out <- b[:c] - } } diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index ff7e648..b6e84be 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -44,15 +44,12 @@ process_read2(i32 pid, Process *out) size_t len = strcspn(val, "\n"); - printf("lenght: %lu", len); if (!strncmp(buf, "Name:", 5)) { memcpy(out->name, val, len); - printf("\nout name %s\n", out->name); } - printf("name: %s", out->name); if (!strncmp(buf, "State:", 6)) { char state_char = 0; @@ -66,8 +63,6 @@ process_read2(i32 pid, Process *out) } - printf("\nstate char: %d\n", out->pid); - printf("\nstate char: %c\n", state_char); switch (state_char) { case 'R': @@ -124,7 +119,6 @@ process_read2(i32 pid, Process *out) } } - printf("sizeof(Process) = %zu\n", sizeof(Process)); int error = fclose(fp); if (error != 0) @@ -132,7 +126,6 @@ process_read2(i32 pid, Process *out) return ERR_IO; } - printf("\n\n\n\nout state: %d\n", out->state); return OK; } */ @@ -545,7 +538,7 @@ func ReadProcesses(device *C.Device) ([]Process, error) { err := C.process_read2(p.pid, p) if err != C.OK { - fmt.Printf("failed reading processes") + return nil, fmt.Errorf("failed reading processes %v", err) } procs = append(procs, Process{ diff --git a/pkg/grpchandler/journal.go b/pkg/grpchandler/journal.go index 8f60f58..7bf9801 100644 --- a/pkg/grpchandler/journal.go +++ b/pkg/grpchandler/journal.go @@ -16,7 +16,15 @@ type JournalService struct { func (s *JournalService) Action(in *journal.JournalRequest, srv journal.JournalService_ActionServer) error { var val string - // TODO(nasr): remove the magic number enums, horrible code practice + /* + TODO(nasr): + - Remove magic-number–based enum handling. + - Replace `switch in.Field` with: + - a typed protobuf enum, or + - a map[JournalField]string lookup, or + - explicit constants with semantic names. + - Ensure invalid enum values are handled (default case + error). + */ switch in.Field { case 0: @@ -31,30 +39,51 @@ func (s *JournalService) Action(in *journal.JournalRequest, srv journal.JournalS m := []sdjournal.Match{{Field: val, Value: in.Value}} - // Generated time. testing issue - // TODO(nasr): fix the time - //sinceTime, err := time.Parse(time.RFC3339, "0") - // - //if err != nil { - // fmt.Println("error parsing time:", err) - // return nil, nil - //} - // - //duration := time.Since(sinceTime) + /* + TODO(nasr): + - Implement proper time filtering support. + - Decide on API semantics: + - absolute timestamp vs relative duration + - server-side vs client-provided time window + - Validate time parsing errors and propagate them via gRPC status. + - Remove dead/commented-out code once design is finalized. + */ var wg sync.WaitGroup ch := make(chan []byte) + /* + TODO(nasr): + - Define clear channel ownership: + - who closes `ch` and when. + - Consider making channel buffered to avoid producer/consumer blocking. + - Document lifetime guarantees between goroutines. + */ + wg.Add(2) go func() { defer wg.Done() + /* + TODO(nasr): + - Propagate context cancellation into GetJournalInformation. + - Return errors instead of silent failure. + - Clarify meaning of leading `0` argument. + */ j.GetJournalInformation(0, in.NumFromTail, in.Cursor, m, in.Path, ch) }() go func() { defer wg.Done() + /* + TODO(nasr): + - Fix channel consumption logic: + current code ranges over `ch` but also performs `<-ch` again. + - Avoid double reads from the same channel. + - Handle channel close explicitly. + - Propagate send errors via context cancellation or status return. + */ for range ch { resp := journal.JournalChunk{Reply: <-ch} if err := srv.Send(&resp); err != nil { @@ -63,6 +92,12 @@ func (s *JournalService) Action(in *journal.JournalRequest, srv journal.JournalS } }() + /* + TODO(nasr): + - Consider early exit on client disconnect. + - Avoid waiting indefinitely if goroutines deadlock. + - Evaluate replacing WaitGroup + channels with errgroup + context. + */ wg.Wait() return nil } From 96ef9bc73d05c9cb258e49dff1d3c4cf9b73c4ad Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:24:50 +0100 Subject: [PATCH 45/55] refactor: test names --- test/{grpc_resources.sh => grpc_resources_test.sh} | 0 test/{grpc_res_v2.sh => grpc_resources_v2_test.sh} | 0 test/{grpc_services.sh => grpc_services_test.sh} | 0 test/{gprc_services_v3.sh => grpc_services_v3_test.sh} | 0 test/{setup_test.go => integration_setup_test.go} | 0 test/{action_test.go => journal_action_test.go} | 0 test/{journal_test.go => journal_service_test.go} | 0 test/{resources_test.go => resources_service_test.go} | 0 test/{test-all.sh => run_all_tests.sh} | 5 ++--- .../{services_v1_test.go => services_v1_integration_test.go} | 0 .../{services_v2_test.go => services_v2_integration_test.go} | 0 11 files changed, 2 insertions(+), 3 deletions(-) rename test/{grpc_resources.sh => grpc_resources_test.sh} (100%) rename test/{grpc_res_v2.sh => grpc_resources_v2_test.sh} (100%) rename test/{grpc_services.sh => grpc_services_test.sh} (100%) rename test/{gprc_services_v3.sh => grpc_services_v3_test.sh} (100%) rename test/{setup_test.go => integration_setup_test.go} (100%) rename test/{action_test.go => journal_action_test.go} (100%) rename test/{journal_test.go => journal_service_test.go} (100%) rename test/{resources_test.go => resources_service_test.go} (100%) rename test/{test-all.sh => run_all_tests.sh} (98%) rename test/{services_v1_test.go => services_v1_integration_test.go} (100%) rename test/{services_v2_test.go => services_v2_integration_test.go} (100%) diff --git a/test/grpc_resources.sh b/test/grpc_resources_test.sh similarity index 100% rename from test/grpc_resources.sh rename to test/grpc_resources_test.sh diff --git a/test/grpc_res_v2.sh b/test/grpc_resources_v2_test.sh similarity index 100% rename from test/grpc_res_v2.sh rename to test/grpc_resources_v2_test.sh diff --git a/test/grpc_services.sh b/test/grpc_services_test.sh similarity index 100% rename from test/grpc_services.sh rename to test/grpc_services_test.sh diff --git a/test/gprc_services_v3.sh b/test/grpc_services_v3_test.sh similarity index 100% rename from test/gprc_services_v3.sh rename to test/grpc_services_v3_test.sh diff --git a/test/setup_test.go b/test/integration_setup_test.go similarity index 100% rename from test/setup_test.go rename to test/integration_setup_test.go diff --git a/test/action_test.go b/test/journal_action_test.go similarity index 100% rename from test/action_test.go rename to test/journal_action_test.go diff --git a/test/journal_test.go b/test/journal_service_test.go similarity index 100% rename from test/journal_test.go rename to test/journal_service_test.go diff --git a/test/resources_test.go b/test/resources_service_test.go similarity index 100% rename from test/resources_test.go rename to test/resources_service_test.go diff --git a/test/test-all.sh b/test/run_all_tests.sh similarity index 98% rename from test/test-all.sh rename to test/run_all_tests.sh index a787032..25ad4ce 100755 --- a/test/test-all.sh +++ b/test/run_all_tests.sh @@ -204,7 +204,6 @@ grpcurl -plaintext \ -d '{ "field": 1, "value": "1", - "num_from_tail": 50, "cursor": "", "path": "" }' \ @@ -217,7 +216,7 @@ grpcurl -plaintext \ -d '{ "field": 2, "value": "0", - "num_from_tail": 100, + "numFromTail": 100, "cursor": "", "path": "" }' \ @@ -230,7 +229,7 @@ grpcurl -plaintext \ -d '{ "field": 3, "value": "0", - "num_from_tail": 100, + "numFromTail": 100, "cursor": "", "path": "" }' \ diff --git a/test/services_v1_test.go b/test/services_v1_integration_test.go similarity index 100% rename from test/services_v1_test.go rename to test/services_v1_integration_test.go diff --git a/test/services_v2_test.go b/test/services_v2_integration_test.go similarity index 100% rename from test/services_v2_test.go rename to test/services_v2_integration_test.go From 0270ac973f8e719369a1b3aec6581cfd1b8aa5a4 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:42:50 +0100 Subject: [PATCH 46/55] checkpoint: refactor --- internal/platform/platform.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 68748f4..54fb879 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -47,7 +47,6 @@ func (m model) View() string { var mem runtime.MemStats runtime.ReadMemStats(&mem) - // Styles titleStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("39")). @@ -68,14 +67,12 @@ func (m model) View() string { Foreground(lipgloss.Color("241")). MarginTop(1) - // Helper function to format rows row := func(label, value string) string { return fmt.Sprintf("%s %s", labelStyle.Width(20).Render(label+":"), valueStyle.Render(value)) } - // Build the view var s string s += titleStyle.Render("Runtime Diagnostics") + "\n" From 88c8d730ccada4160ae2836a2ab7bf69086c236c Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:50:39 +0100 Subject: [PATCH 47/55] debug: removed deprecated test --- test/services_v1_integration_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/services_v1_integration_test.go b/test/services_v1_integration_test.go index 69dc9c8..844c030 100644 --- a/test/services_v1_integration_test.go +++ b/test/services_v1_integration_test.go @@ -1,5 +1,6 @@ package server_test +/* import ( "context" "testing" @@ -49,3 +50,4 @@ func TestService_Test(t *testing.T) { t.Fatalf("unexpected response: %q", resp) } } +*/ From 1751bb5f446ebe42be88383db1e1b7c184befb34 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:54:22 +0100 Subject: [PATCH 48/55] debug: removed deprecated test --- test/journal_action_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/journal_action_test.go b/test/journal_action_test.go index 635c423..302b846 100644 --- a/test/journal_action_test.go +++ b/test/journal_action_test.go @@ -1,5 +1,6 @@ package server_test +/* import ( "context" "testing" @@ -13,9 +14,8 @@ import ( "google.golang.org/grpc/credentials/insecure" ) -/** TODO(nasr): write test for reboot and shutdown, what would be a proper way of doing this -*/ + func TestActions_all(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -45,3 +45,4 @@ func TestActions_all(t *testing.T) { t.Fatalf("unexpected response: %q", resp) } } +*/ From 96ed346a4d7879b2866b28d7e0fe592869fba176 Mon Sep 17 00:00:00 2001 From: nasr Date: Sat, 10 Jan 2026 22:58:03 +0100 Subject: [PATCH 49/55] debug: removed deprecated tests --- test/journal_service_test.go | 86 +++++++++++++++++----------------- test/resources_service_test.go | 86 +++++++++++++++++----------------- 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/test/journal_service_test.go b/test/journal_service_test.go index 5db71ba..3dcd0c8 100644 --- a/test/journal_service_test.go +++ b/test/journal_service_test.go @@ -1,44 +1,44 @@ package server_test - -import ( - "context" - "testing" - "time" - - pb "paradigm-ehb/agent/gen/journal/v1" - - "google.golang.org/grpc/resolver" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - - -func TestJournal_all(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - resolver.SetDefaultScheme("passthrough") - - clientConn, err := grpc.NewClient( - "bufnet", - grpc.WithContextDialer(BufDialer), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - - if err != nil { - t.Fatalf("failed to create client: %v", err) - } - defer clientConn.Close() - - client := pb.NewJournalServiceClient(clientConn) - - resp, err := client.Action(ctx, &pb.JournalRequest{}) - if err != nil { - t.Fatalf("rpc failed: %v", err) - } - - if resp == nil { - t.Fatalf("unexpected response: %q", resp) - } -} +// +// import ( +// "context" +// "testing" +// "time" +// +// pb "paradigm-ehb/agent/gen/journal/v1" +// +// "google.golang.org/grpc/resolver" +// +// "google.golang.org/grpc" +// "google.golang.org/grpc/credentials/insecure" +// ) +// +// +// func TestJournal_all(t *testing.T) { +// ctx, cancel := context.WithTimeout(context.Background(), time.Second) +// defer cancel() +// +// resolver.SetDefaultScheme("passthrough") +// +// clientConn, err := grpc.NewClient( +// "bufnet", +// grpc.WithContextDialer(BufDialer), +// grpc.WithTransportCredentials(insecure.NewCredentials()), +// ) +// +// if err != nil { +// t.Fatalf("failed to create client: %v", err) +// } +// defer clientConn.Close() +// +// client := pb.NewJournalServiceClient(clientConn) +// +// resp, err := client.Action(ctx, &pb.JournalRequest{}) +// if err != nil { +// t.Fatalf("rpc failed: %v", err) +// } +// +// if resp == nil { +// t.Fatalf("unexpected response: %q", resp) +// } +// } diff --git a/test/resources_service_test.go b/test/resources_service_test.go index 9fc92fe..6bf5cf3 100644 --- a/test/resources_service_test.go +++ b/test/resources_service_test.go @@ -1,44 +1,44 @@ package server_test - -import ( - "context" - "testing" - "time" - - pb "paradigm-ehb/agent/gen/resources/v1" - - "google.golang.org/grpc/resolver" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - - -func TestResources_All(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - resolver.SetDefaultScheme("passthrough") - - clientConn, err := grpc.NewClient( - "bufnet", - grpc.WithContextDialer(BufDialer), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - - if err != nil { - t.Fatalf("failed to create client: %v", err) - } - defer clientConn.Close() - - client := pb.NewResourcesServiceClient(clientConn) - - resp, err := client.GetSystemResources(ctx, &pb.GetSystemResourcesRequest{}) - if err != nil { - t.Fatalf("rpc failed: %v", err) - } - - if resp == nil { - t.Fatalf("unexpected response: %q", resp) - } -} +// +// import ( +// "context" +// "testing" +// "time" +// +// pb "paradigm-ehb/agent/gen/resources/v1" +// +// "google.golang.org/grpc/resolver" +// +// "google.golang.org/grpc" +// "google.golang.org/grpc/credentials/insecure" +// ) +// +// +// func TestResources_All(t *testing.T) { +// ctx, cancel := context.WithTimeout(context.Background(), time.Second) +// defer cancel() +// +// resolver.SetDefaultScheme("passthrough") +// +// clientConn, err := grpc.NewClient( +// "bufnet", +// grpc.WithContextDialer(BufDialer), +// grpc.WithTransportCredentials(insecure.NewCredentials()), +// ) +// +// if err != nil { +// t.Fatalf("failed to create client: %v", err) +// } +// defer clientConn.Close() +// +// client := pb.NewResourcesServiceClient(clientConn) +// +// resp, err := client.GetSystemResources(ctx, &pb.GetSystemResourcesRequest{}) +// if err != nil { +// t.Fatalf("rpc failed: %v", err) +// } +// +// if resp == nil { +// t.Fatalf("unexpected response: %q", resp) +// } +// } From 82065666afaa6edab2b65b7b0070bc7f6104de1f Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 12:03:08 +0100 Subject: [PATCH 50/55] bugfix: grpc process action call was wrongly named --- pkg/grpchandler/resources/v2/resources.go | 2 +- test/process_action.sh | 11 +++++++++++ test/run_all_tests.sh | 8 ++++---- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100755 test/process_action.sh diff --git a/pkg/grpchandler/resources/v2/resources.go b/pkg/grpchandler/resources/v2/resources.go index 69e3c10..5da3509 100644 --- a/pkg/grpchandler/resources/v2/resources.go +++ b/pkg/grpchandler/resources/v2/resources.go @@ -66,7 +66,7 @@ func (s *ResourcesServiceV2) GetSystemResources( * TODO(nasr): update individual resources */ -func (s *ResourcesServiceV2) KillProcess( +func (s *ResourcesServiceV2) ProcessAction( ctx context.Context, req *proto.ProcessActionRequest, ) (*proto.ProcessActionReply, error) { diff --git a/test/process_action.sh b/test/process_action.sh new file mode 100755 index 0000000..569d500 --- /dev/null +++ b/test/process_action.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -eu + +grpcurl -plaintext \ + -d '{ + "pid": 2497, + "signal": 15 + }' \ + localhost:5000 \ + resources.v2.ResourcesService/ProcessAction diff --git a/test/run_all_tests.sh b/test/run_all_tests.sh index 25ad4ce..d35c7db 100755 --- a/test/run_all_tests.sh +++ b/test/run_all_tests.sh @@ -17,23 +17,23 @@ grpcurl -plaintext \ resources.v2.ResourcesService/GetSystemResources # Kill Process (replace with actual PID) -echo -e "\n[2] KillProcess (SIGTERM - signal 15)" +echo -e "\n[2] ProcessAction (SIGTERM - signal 15)" grpcurl -plaintext \ -d '{ "pid": 12345, "signal": 15 }' \ $SERVER \ - resources.v2.ResourcesService/KillProcess + resources.v2.ResourcesService/ProcessAction -echo -e "\n[3] KillProcess (SIGKILL - signal 9)" +echo -e "\n[3] ProcessAction (SIGKILL - signal 9)" grpcurl -plaintext \ -d '{ "pid": 12345, "signal": 9 }' \ $SERVER \ - resources.v2.ResourcesService/KillProcess + resources.v2.ResourcesService/ProcessAction echo "======================================" echo "HandlerServicev3 Tests (Systemd)" From 0fad65c718b1072ef62c149a575de9536ad95e6a Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 12:04:22 +0100 Subject: [PATCH 51/55] checkpoint: agent-resources update --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index f654d6e..a69ede2 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit f654d6e475b562ced341ba57108c55e1ca37f81b +Subproject commit a69ede2a36da7d7d198ffe17f68c61d9995552cf From 5dc145313d5ef7471bcb442ca8f8fc9c956d8ca1 Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 14:01:08 +0100 Subject: [PATCH 52/55] checkpoint: package manager --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index a69ede2..4c63c83 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit a69ede2a36da7d7d198ffe17f68c61d9995552cf +Subproject commit 4c63c830b566512af8b35715479ba6afcc836c13 From 0cc80985728715cad438c4ebadc49ae0942b9378 Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 15:37:09 +0100 Subject: [PATCH 53/55] feature: disk resources --- pkg/agent-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent-resources b/pkg/agent-resources index 4c63c83..01f4420 160000 --- a/pkg/agent-resources +++ b/pkg/agent-resources @@ -1 +1 @@ -Subproject commit 4c63c830b566512af8b35715479ba6afcc836c13 +Subproject commit 01f44207854e794f162bb23953bb4dcf45f1b154 From aa168433c35554a6f20e466620612d2b7aa2210a Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 15:08:41 +0100 Subject: [PATCH 54/55] feature: converted ram strings to int --- cmd/agent/agent.go | 2 +- pkg/cgowrap/types.go | 4 ++-- pkg/cgowrap/wrapper.go | 5 +++-- proto/resources/v2/deviceresources.proto | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index a72b10b..0bf0755 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -67,7 +67,7 @@ var ( NOTE: When enabled, diagnostics must never block the gRPC server. */ - diagnostics = flag.Bool("diagnostics", true, "run runtime diagnostics") + diagnostics = flag.Bool("diagnostics", false, "run runtime diagnostics") /* portFlag is the preferred TCP port to bind the gRPC server to. diff --git a/pkg/cgowrap/types.go b/pkg/cgowrap/types.go index 2e2d769..30d8492 100644 --- a/pkg/cgowrap/types.go +++ b/pkg/cgowrap/types.go @@ -12,8 +12,8 @@ type Cpu struct { // Ram represents RAM information including total and free memory. type Ram struct { - Total string - Free string + Total uint64 + Free uint64 } // DiskPartition represents a single disk partition with device identifiers and block count. diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index b6e84be..f0b9993 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -364,9 +364,10 @@ func RamRead(ram *C.Ram) (Ram, error) { } r := Ram{ - Total: C.GoString(&ram.total[0]), - Free: C.GoString(&ram.free[0]), + Total: uint64(ram.total), + Free: uint64(ram.free), } + return r, nil } diff --git a/proto/resources/v2/deviceresources.proto b/proto/resources/v2/deviceresources.proto index f811a98..13d2712 100644 --- a/proto/resources/v2/deviceresources.proto +++ b/proto/resources/v2/deviceresources.proto @@ -51,8 +51,8 @@ message Cpu { * @return total memory on system, available memory on system * */ message Memory { - string total = 1; - string free = 2; + uint64 total = 1; + uint64 free = 2; } /** From ccdcdb46a5f9481df6bb9d370d936175277860e7 Mon Sep 17 00:00:00 2001 From: nasr Date: Mon, 12 Jan 2026 15:36:46 +0100 Subject: [PATCH 55/55] feature: fixed compile script to handle parsing function --- build.sh | 7 ++++++- pkg/cgowrap/wrapper.go | 4 ++-- pkg/grpchandler/resources/v1/resources.go | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/build.sh b/build.sh index 0eabe9e..dcc7473 100755 --- a/build.sh +++ b/build.sh @@ -92,9 +92,11 @@ OUT_DIR="$AGENT_RES_DIR/build" SRC_RES="$AGENT_RES_DIR/resources.c" SRC_ARENA="$AGENT_RES_DIR/arena.c" +SRC_BASE="$AGENT_RES_DIR/base.c" OUT_OBJ_RES="$OUT_DIR/resources.o" OUT_OBJ_ARENA="$OUT_DIR/arena.o" +OUT_OBJ_BASE="$OUT_DIR/base.o" OUT_LIB="$OUT_DIR/libagent_resources.a" @@ -133,10 +135,13 @@ $CC $CFLAGS -c "$SRC_RES" -o "$OUT_OBJ_RES" echo "Compiling arena.c..." $CC $CFLAGS -c "$SRC_ARENA" -o "$OUT_OBJ_ARENA" +echo "Compiling base.c..." +$CC $CFLAGS -c "$SRC_BASE" -o "$OUT_OBJ_BASE" + echo "Creating static library..." # $AR rcs "$OUT_LIB" "$OUT_OBJ" -$AR rcs "$OUT_LIB" "$OUT_OBJ_RES" "$OUT_OBJ_ARENA" +$AR rcs "$OUT_LIB" "$OUT_OBJ_RES" "$OUT_OBJ_ARENA" "$OUT_OBJ_BASE" echo "Done: $OUT_LIB" diff --git a/pkg/cgowrap/wrapper.go b/pkg/cgowrap/wrapper.go index f0b9993..034d9a0 100644 --- a/pkg/cgowrap/wrapper.go +++ b/pkg/cgowrap/wrapper.go @@ -413,10 +413,10 @@ func DiskRead(disk *C.Disk) (Disk, error) { } d := Disk{ - Partitions: make([]DiskPartition, 0, disk.count), + Partitions: make([]DiskPartition, 0, disk.part_count), } - for i := C.size_t(0); i < disk.count; i++ { + for i := C.size_t(0); i < disk.part_count; i++ { part := (*C.Partition)( unsafe.Pointer( uintptr(unsafe.Pointer(disk.partitions)) + diff --git a/pkg/grpchandler/resources/v1/resources.go b/pkg/grpchandler/resources/v1/resources.go index c9f9e01..0a60456 100644 --- a/pkg/grpchandler/resources/v1/resources.go +++ b/pkg/grpchandler/resources/v1/resources.go @@ -97,8 +97,8 @@ func mapCPU(c wr.Cpu) *proto.Cpu { */ func mapMemory(m wr.Ram) *proto.Memory { return &proto.Memory{ - Total: m.Total, - Free: m.Free, + Total: string(m.Total), + Free: string(m.Free), } }