ENGINE: Migrated Logs to Runtime Folders

This commit is contained in:
2025-10-29 15:19:29 -06:00
parent 98737fb737
commit 8fa7bd4fb0
11 changed files with 36 additions and 33 deletions

View File

@@ -4,9 +4,9 @@
- **Runtime Paths**: Do not edit `/Agent`; make changes in `Data/Agent` so the runtime copy stays ephemeral. Runtime folders are wiped regularly. - **Runtime Paths**: Do not edit `/Agent`; make changes in `Data/Agent` so the runtime copy stays ephemeral. Runtime folders are wiped regularly.
### Logging ### Logging
- General log: `Logs/Agent/agent.log`; rotate daily to `agent.log.YYYY-MM-DD` and never delete automatically. - General log: `Agent/Logs/agent.log`; rotate daily to `agent.log.YYYY-MM-DD` and never delete automatically.
- Subsystems (e.g., `ansible`, `webrtc`, `scheduler`) must log to `Logs/Agent/<service>.log` and follow the same rotation policy. - Subsystems (e.g., `ansible`, `webrtc`, `scheduler`) must log to `Agent/Logs/<service>.log` and follow the same rotation policy.
- Installation output writes to `Logs/Agent/install.log`. - Installation output writes to `Agent/Logs/install.log`; keep ad-hoc diagnostics (e.g., `system_last.ps1`, ansible traces) under `Agent/Logs/` so runtime state stays self-contained.
- When troubleshooting with operators, prepend each line with `<timestamp>-<service-name>-<log-data>` and confirm whether to keep or remove verbose logging after resolution. - When troubleshooting with operators, prepend each line with `<timestamp>-<service-name>-<log-data>` and confirm whether to keep or remove verbose logging after resolution.
### Security ### Security
@@ -15,7 +15,7 @@
- Uses a dedicated `ssl.SSLContext` seeded with the Engines TLS bundle for REST and Socket.IO traffic. - Uses a dedicated `ssl.SSLContext` seeded with the Engines TLS bundle for REST and Socket.IO traffic.
- Validates all script payloads with Ed25519 signatures issued by the backend before execution. - Validates all script payloads with Ed25519 signatures issued by the backend before execution.
- Enforces outbound-only communication; every API/WebSocket call flows through `AgentHttpClient.ensure_authenticated` to refresh tokens proactively. - Enforces outbound-only communication; every API/WebSocket call flows through `AgentHttpClient.ensure_authenticated` to refresh tokens proactively.
- Logs bootstrap, enrollment, token refresh, and signature events under `Logs/Agent/`. - Logs bootstrap, enrollment, token refresh, and signature events under `Agent/Logs/`.
### Execution Contexts & Roles ### Execution Contexts & Roles
- Roles auto-discover from `Data/Agent/Roles/` and require no loader changes. - Roles auto-discover from `Data/Agent/Roles/` and require no loader changes.
@@ -43,9 +43,9 @@
- Reference the migration tracker before making Engine changes to avoid jumping ahead of the approved stage. - Reference the migration tracker before making Engine changes to avoid jumping ahead of the approved stage.
### Logging ### Logging
- General log: `Logs/Engine/engine.log` with daily rotation (`engine.log.YYYY-MM-DD`); do not auto-delete rotated files. - General log: `Engine/Logs/engine.log` with daily rotation (`engine.log.YYYY-MM-DD`); do not auto-delete rotated files.
- Subsystems should log to `Logs/Engine/<service>.log`; installation output belongs in `Logs/Engine/install.log`. - Subsystems should log to `Engine/Logs/<service>.log`; installation output belongs in `Engine/Logs/install.log`.
- Adhere to the centralized logging policy and keep all log files inside the project root. - Adhere to the centralized logging policy and keep Engine-specific artifacts within `Engine/Logs/` to preserve the runtime boundary.
### Security & API Parity ### Security & API Parity
- Shares the mutual trust model with the legacy server: Ed25519 device identities, EdDSA-signed access tokens, pinned Borealis root CA, TLS 1.3-only serving, and Authorization headers plus service-context markers on every device API. - Shares the mutual trust model with the legacy server: Ed25519 device identities, EdDSA-signed access tokens, pinned Borealis root CA, TLS 1.3-only serving, and Authorization headers plus service-context markers on every device API.
@@ -78,4 +78,3 @@
### Platform Notes ### Platform Notes
- Exists primarily to document past behaviour and assist the Engine migration. Future platform parity work should target the Engine; the legacy server will be deprecated once feature parity is confirmed. - Exists primarily to document past behaviour and assist the Engine migration. Future platform parity work should target the Engine; the legacy server will be deprecated once feature parity is confirmed.

View File

@@ -165,8 +165,9 @@ function Request-AgentElevation {
# Ensure log directories # Ensure log directories
function Ensure-AgentLogDir { function Ensure-AgentLogDir {
$logRoot = Join-Path $scriptDir 'Logs' $agentRoot = Join-Path $scriptDir 'Agent'
$agentLogDir = Join-Path $logRoot 'Agent' if (-not (Test-Path $agentRoot)) { New-Item -ItemType Directory -Path $agentRoot -Force | Out-Null }
$agentLogDir = Join-Path $agentRoot 'Logs'
if (-not (Test-Path $agentLogDir)) { New-Item -ItemType Directory -Path $agentLogDir -Force | Out-Null } if (-not (Test-Path $agentLogDir)) { New-Item -ItemType Directory -Path $agentLogDir -Force | Out-Null }
return $agentLogDir return $agentLogDir
} }
@@ -1036,7 +1037,7 @@ function InstallOrUpdate-BorealisAgent {
} }
} catch { } catch {
Write-AgentLog -FileName 'Install.log' -Message ("[CONFIG] Failed to persist agent_settings.json: {0}" -f $_.Exception.Message) Write-AgentLog -FileName 'Install.log' -Message ("[CONFIG] Failed to persist agent_settings.json: {0}" -f $_.Exception.Message)
Write-Host "Failed to update agent_settings.json. Check Logs/Agent/install.log for details." -ForegroundColor Red Write-Host "Failed to update agent_settings.json. Check Agent/Logs/install.log for details." -ForegroundColor Red
} }
} }

View File

@@ -96,8 +96,8 @@ detect_distro() {
fi fi
} }
ensure_log_dir() { mkdir -p "${SCRIPT_DIR}/Logs/Agent"; } ensure_log_dir() { mkdir -p "${SCRIPT_DIR}/Agent/Logs"; }
log_agent() { ensure_log_dir; printf "[%s] %s\n" "$(date +%F\ %T)" "$1" >> "${SCRIPT_DIR}/Logs/Agent/$2"; } log_agent() { ensure_log_dir; printf "[%s] %s\n" "$(date +%F\ %T)" "$1" >> "${SCRIPT_DIR}/Agent/Logs/$2"; }
need_sudo() { [ "${EUID:-$(id -u)}" -ne 0 ]; } need_sudo() { [ "${EUID:-$(id -u)}" -ne 0 ]; }
@@ -231,7 +231,7 @@ set -o nounset
set -o pipefail set -o pipefail
ROOT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)" ROOT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
cd "$ROOT_DIR" cd "$ROOT_DIR"
LOG_DIR="$(cd -- "$ROOT_DIR/../../Logs/Agent" && pwd 2>/dev/null || echo "$ROOT_DIR/../../Logs/Agent")" LOG_DIR="$(cd -- "$ROOT_DIR/../Logs" && pwd 2>/dev/null || echo "$ROOT_DIR/../Logs")"
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
PY_BIN="${ROOT_DIR}/../bin/python3" PY_BIN="${ROOT_DIR}/../bin/python3"
exec "$PY_BIN" "$ROOT_DIR/agent.py" --system-service --config SYSTEM >>"$LOG_DIR/svc.out.log" 2>>"$LOG_DIR/svc.err.log" exec "$PY_BIN" "$ROOT_DIR/agent.py" --system-service --config SYSTEM >>"$LOG_DIR/svc.out.log" 2>>"$LOG_DIR/svc.err.log"

View File

@@ -242,7 +242,7 @@ class Role:
self._ansible_ready = False self._ansible_ready = False
self._ansible_bootstrap_lock = None self._ansible_bootstrap_lock = None
try: try:
base = os.path.join(_project_root(), 'Logs', 'Agent') base = os.path.join(_project_root(), 'Agent', 'Logs')
os.makedirs(base, exist_ok=True) os.makedirs(base, exist_ok=True)
self._ansible_log(f"[init] PlaybookExec role init agent_id={ctx.agent_id}") self._ansible_log(f"[init] PlaybookExec role init agent_id={ctx.agent_id}")
except Exception: except Exception:
@@ -580,7 +580,7 @@ class Role:
def _log_local(self, msg: str, error: bool = False): def _log_local(self, msg: str, error: bool = False):
try: try:
base = os.path.join(_project_root(), 'Logs', 'Agent') base = os.path.join(_project_root(), 'Agent', 'Logs')
os.makedirs(base, exist_ok=True) os.makedirs(base, exist_ok=True)
fn = 'agent.error.log' if error else 'agent.log' fn = 'agent.error.log' if error else 'agent.log'
ts = time.strftime('%Y-%m-%d %H:%M:%S') ts = time.strftime('%Y-%m-%d %H:%M:%S')
@@ -600,7 +600,7 @@ class Role:
def _ansible_log(self, msg: str, error: bool = False, run_id: str = None): def _ansible_log(self, msg: str, error: bool = False, run_id: str = None):
try: try:
d = os.path.join(_project_root(), 'Logs', 'Agent') d = os.path.join(_project_root(), 'Agent', 'Logs')
ts = time.strftime('%Y-%m-%d %H:%M:%S') ts = time.strftime('%Y-%m-%d %H:%M:%S')
path = os.path.join(d, 'ansible.log') path = os.path.join(d, 'ansible.log')
try: try:
@@ -716,7 +716,7 @@ class Role:
if os.name != 'nt': if os.name != 'nt':
return return
mod = self._ps_module_path() mod = self._ps_module_path()
log_dir = os.path.join(_project_root(), 'Logs', 'Agent') log_dir = os.path.join(_project_root(), 'Agent', 'Logs')
try: try:
os.makedirs(log_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True)
except Exception: except Exception:

View File

@@ -219,7 +219,7 @@ def _run_powershell_via_system_task(content: str, env_map: Dict[str, str], timeo
with os.fdopen(script_fd, 'w', encoding='utf-8', newline='\n') as f: with os.fdopen(script_fd, 'w', encoding='utf-8', newline='\n') as f:
f.write(final_content) f.write(final_content)
try: try:
log_dir = os.path.join(_project_root(), 'Logs', 'Agent') log_dir = os.path.join(_project_root(), 'Agent', 'Logs')
os.makedirs(log_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True)
with open(os.path.join(log_dir, 'system_last.ps1'), 'w', encoding='utf-8', newline='\n') as df: with open(os.path.join(log_dir, 'system_last.ps1'), 'w', encoding='utf-8', newline='\n') as df:
df.write(content or '') df.write(content or '')

View File

@@ -57,9 +57,10 @@ def _iter_exception_chain(exc: BaseException):
def _agent_logs_root() -> str: def _agent_logs_root() -> str:
try: try:
root = _find_project_root() root = _find_project_root()
return os.path.abspath(os.path.join(root, 'Logs', 'Agent')) return os.path.abspath(os.path.join(root, 'Agent', 'Logs'))
except Exception: except Exception:
return os.path.abspath(os.path.join(os.path.dirname(__file__), 'Logs', 'Agent')) base_dir = os.path.abspath(os.path.dirname(__file__))
return os.path.abspath(os.path.join(base_dir, 'Logs'))
def _rotate_daily(path: str): def _rotate_daily(path: str):
@@ -517,7 +518,7 @@ def _find_project_root():
# Heuristic fallback: two levels up from Agent/Borealis # Heuristic fallback: two levels up from Agent/Borealis
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
# Simple file logger under Logs/Agent # Simple file logger under Agent/Logs
def _log_agent(message: str, fname: str = 'agent.log', *, scope: Optional[str] = None): def _log_agent(message: str, fname: str = 'agent.log', *, scope: Optional[str] = None):
try: try:
log_dir = _agent_logs_root() log_dir = _agent_logs_root()
@@ -2856,7 +2857,7 @@ def _run_powershell_via_system_task(content: str):
with os.fdopen(fd, 'w', encoding='utf-8', newline='\n') as f: with os.fdopen(fd, 'w', encoding='utf-8', newline='\n') as f:
f.write(content or '') f.write(content or '')
try: try:
log_dir = os.path.join(_project_root_for_temp(), 'Logs', 'Agent') log_dir = os.path.join(_project_root_for_temp(), 'Agent', 'Logs')
os.makedirs(log_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True)
debug_copy = os.path.join(log_dir, 'system_last.ps1') debug_copy = os.path.join(log_dir, 'system_last.ps1')
with open(debug_copy, 'w', encoding='utf-8', newline='\n') as df: with open(debug_copy, 'w', encoding='utf-8', newline='\n') as df:
@@ -3260,7 +3261,7 @@ if __name__=='__main__':
return return
try: try:
# Save last SYSTEM script for debugging # Save last SYSTEM script for debugging
dbg_dir = os.path.join(_find_project_root(), 'Logs', 'Agent') dbg_dir = os.path.join(_find_project_root(), 'Agent', 'Logs')
os.makedirs(dbg_dir, exist_ok=True) os.makedirs(dbg_dir, exist_ok=True)
with open(os.path.join(dbg_dir, 'system_last.ps1'), 'w', encoding='utf-8', newline='\n') as df: with open(os.path.join(dbg_dir, 'system_last.ps1'), 'w', encoding='utf-8', newline='\n') as df:
df.write(content or '') df.write(content or '')

View File

@@ -14,7 +14,7 @@ def project_paths():
venv_root = os.path.abspath(os.path.join(venv_scripts, os.pardir)) venv_root = os.path.abspath(os.path.join(venv_scripts, os.pardir))
project_root = os.path.abspath(os.path.join(venv_root, os.pardir)) project_root = os.path.abspath(os.path.join(venv_root, os.pardir))
borealis_dir = os.path.join(venv_root, "Borealis") borealis_dir = os.path.join(venv_root, "Borealis")
logs_dir = os.path.join(project_root, "Logs", "Agent") logs_dir = os.path.join(project_root, "Agent", "Logs")
temp_dir = os.path.join(project_root, "Temp") temp_dir = os.path.join(project_root, "Temp")
return { return {
"project_root": project_root, "project_root": project_root,

View File

@@ -9,9 +9,11 @@ try {
$scriptDir = Split-Path -Path $PSCommandPath -Parent $scriptDir = Split-Path -Path $PSCommandPath -Parent
Set-Location -Path $scriptDir Set-Location -Path $scriptDir
# Centralized logs under <ProjectRoot>\Logs\Agent # Centralized logs under <ProjectRoot>\Agent\Logs
$projRoot = Resolve-Path (Join-Path $scriptDir '..\..') $projRoot = Resolve-Path (Join-Path $scriptDir '..\..')
$logsAgent = Join-Path $projRoot 'Logs\Agent' $agentRoot = Join-Path $projRoot 'Agent'
if (-not (Test-Path $agentRoot)) { New-Item -ItemType Directory -Path $agentRoot -Force | Out-Null }
$logsAgent = Join-Path $agentRoot 'Logs'
if (-not (Test-Path $logsAgent)) { New-Item -ItemType Directory -Path $logsAgent -Force | Out-Null } if (-not (Test-Path $logsAgent)) { New-Item -ItemType Directory -Path $logsAgent -Force | Out-Null }
$wrapperLog = Join-Path $logsAgent 'service_wrapper.log' $wrapperLog = Join-Path $logsAgent 'service_wrapper.log'

View File

@@ -34,8 +34,8 @@ defaults that mirror the legacy server runtime. Key environment variables are
When TLS values are not provided explicitly the Engine provisions certificates When TLS values are not provided explicitly the Engine provisions certificates
under ``Engine/Certificates`` (migrating any legacy material) so the runtime under ``Engine/Certificates`` (migrating any legacy material) so the runtime
remains self-contained. remains self-contained.
Logs are written to ``Logs/Engine/engine.log`` with daily rotation and Logs are written to ``Engine/Logs/engine.log`` with daily rotation and
errors are additionally duplicated to ``Logs/Engine/error.log`` so the errors are additionally duplicated to ``Engine/Logs/error.log`` so the
runtime integrates with the platform's logging policy. runtime integrates with the platform's logging policy.
""" """
@@ -73,7 +73,7 @@ def _discover_project_root() -> Path:
PROJECT_ROOT = _discover_project_root() PROJECT_ROOT = _discover_project_root()
DEFAULT_DATABASE_PATH = PROJECT_ROOT / "database.db" DEFAULT_DATABASE_PATH = PROJECT_ROOT / "database.db"
LOG_ROOT = PROJECT_ROOT / "Logs" / "Engine" LOG_ROOT = PROJECT_ROOT / "Engine" / "Logs"
LOG_FILE_PATH = LOG_ROOT / "engine.log" LOG_FILE_PATH = LOG_ROOT / "engine.log"
ERROR_LOG_FILE_PATH = LOG_ROOT / "error.log" ERROR_LOG_FILE_PATH = LOG_ROOT / "error.log"
API_LOG_FILE_PATH = LOG_ROOT / "api.log" API_LOG_FILE_PATH = LOG_ROOT / "api.log"

View File

@@ -11,7 +11,7 @@ Stage 1 introduced the structural skeleton for the Engine runtime. Stage 2
builds upon that foundation by centralising configuration handling and logging builds upon that foundation by centralising configuration handling and logging
initialisation so the Engine mirrors the legacy server's start-up behaviour. initialisation so the Engine mirrors the legacy server's start-up behaviour.
The factory delegates configuration resolution to :mod:`Data.Engine.config` The factory delegates configuration resolution to :mod:`Data.Engine.config`
and emits structured logs to ``Logs/Engine/engine.log`` (with an accompanying and emits structured logs to ``Engine/Logs/engine.log`` (with an accompanying
error log) to align with the project's operational practices. error log) to align with the project's operational practices.
""" """
from __future__ import annotations from __future__ import annotations

View File

@@ -90,7 +90,7 @@ The process that agents go through when authenticating securely with a Borealis
- Device enrollment is gated by enrollment/installer codes (*They have configurable expiration and usage limits*) and an operator approval queue; replay-resistant nonces plus rate limits (40req/min/IP, 12req/min/fingerprint) prevent brute force or code reuse. - Device enrollment is gated by enrollment/installer codes (*They have configurable expiration and usage limits*) and an operator approval queue; replay-resistant nonces plus rate limits (40req/min/IP, 12req/min/fingerprint) prevent brute force or code reuse.
- All device APIs now require Authorization: Bearer headers and a service-context (e.g. SYSTEM or CURRENTUSER) marker; missing, expired, mismatched, or revoked credentials are rejected before any business logic runs. Operator-driven revoking / device quarantining logic is not yet implemented. - All device APIs now require Authorization: Bearer headers and a service-context (e.g. SYSTEM or CURRENTUSER) marker; missing, expired, mismatched, or revoked credentials are rejected before any business logic runs. Operator-driven revoking / device quarantining logic is not yet implemented.
- Replay and credential theft defenses layer in DPoP proof validation (thumbprint binding) on the server side and short-lived access tokens (15min) with 30-day refresh tokens hashed via SHA-256. - Replay and credential theft defenses layer in DPoP proof validation (thumbprint binding) on the server side and short-lived access tokens (15min) with 30-day refresh tokens hashed via SHA-256.
- Centralized logging under Logs/Server and Logs/Agent captures enrollment approvals, rate-limit hits, signature failures, and auth anomalies for post-incident review. - Centralized logging under Logs/Server and Agent/Logs captures enrollment approvals, rate-limit hits, signature failures, and auth anomalies for post-incident review.
#### Server Security #### Server Security
- Auto-manages PKI: a persistent Borealis root CA (ECDSA SECP384R1) signs leaf certificates that include localhost SANs, tightened filesystem permissions, and a combined bundle for agent identity / cert pinning. - Auto-manages PKI: a persistent Borealis root CA (ECDSA SECP384R1) signs leaf certificates that include localhost SANs, tightened filesystem permissions, and a combined bundle for agent identity / cert pinning.
- Script delivery is code-signed with an Ed25519 key stored under Certificates/Server/Code-Signing; agents refuse any payload whose signature or hash does not match the pinned public key. - Script delivery is code-signed with an Ed25519 key stored under Certificates/Server/Code-Signing; agents refuse any payload whose signature or hash does not match the pinned public key.
@@ -104,7 +104,7 @@ The process that agents go through when authenticating securely with a Borealis
- Imports the servers TLS bundle into a dedicated ssl.SSLContext, reuses it for the REST session, and injects it into the Socket.IO engine so WebSockets enjoy the same pinning and hostname checks. - Imports the servers TLS bundle into a dedicated ssl.SSLContext, reuses it for the REST session, and injects it into the Socket.IO engine so WebSockets enjoy the same pinning and hostname checks.
- Treats every script payload as hostile until verified: only Ed25519 signatures from the server are accepted, missing/invalid signatures are logged and dropped, and the trusted signing key is updated only after successful verification between the agent and the server. - Treats every script payload as hostile until verified: only Ed25519 signatures from the server are accepted, missing/invalid signatures are logged and dropped, and the trusted signing key is updated only after successful verification between the agent and the server.
- Operates outbound-only; there are no listener ports, and every API/WebSocket call flows through AgentHttpClient.ensure_authenticated, forcing token refresh logic before retrying. - Operates outbound-only; there are no listener ports, and every API/WebSocket call flows through AgentHttpClient.ensure_authenticated, forcing token refresh logic before retrying.
- Logs bootstrap, enrollment, token refresh, and signature events to daily-rotated files under Logs/Agent, giving operators visibility without leaking secrets outside the project root. - Logs bootstrap, enrollment, token refresh, and signature events to daily-rotated files under Agent/Logs, giving operators visibility without leaking secrets outside the project root.
### Agent/Server Enrollment ### Agent/Server Enrollment
```mermaid ```mermaid