windows installer fixes
This commit is contained in:
@@ -31,7 +31,7 @@ Filename: "{app}\nssm.exe"; Parameters: "install LOGAR_Client ""{app}\Win_Client
|
||||
Filename: "{app}\nssm.exe"; Parameters: "set LOGAR_Client AppDirectory ""{app}"""; Flags: runhidden
|
||||
Filename: "{app}\nssm.exe"; Parameters: "set LOGAR_Client AppStdout ""{code:GetDataDir}\client.log"""; Flags: runhidden
|
||||
Filename: "{app}\nssm.exe"; Parameters: "set LOGAR_Client AppStderr ""{code:GetDataDir}\client_err.log"""; Flags: runhidden
|
||||
Filename: "{app}\nssm.exe"; Parameters: "set LOGAR_Client AppParameters ""--config """"{code:GetDataDir}\client_config.json"""""""; Flags: runhidden
|
||||
Filename: "{app}\nssm.exe"; Parameters: "set LOGAR_Client AppParameters ""--config """"{code:GetDataDir}\client_config.json"""" --interval 30"""; Flags: runhidden
|
||||
Filename: "{app}\nssm.exe"; Parameters: "start LOGAR_Client"; Flags: runhidden
|
||||
|
||||
[UninstallRun]
|
||||
|
||||
@@ -124,7 +124,7 @@ Section "Install"
|
||||
ExecWait '"$INSTDIR\nssm.exe" set LOGAR_Client AppDirectory "$INSTDIR"'
|
||||
ExecWait '"$INSTDIR\nssm.exe" set LOGAR_Client AppStdout "$DATADIR\client.log"'
|
||||
ExecWait '"$INSTDIR\nssm.exe" set LOGAR_Client AppStderr "$DATADIR\client_err.log"'
|
||||
ExecWait '"$INSTDIR\nssm.exe" set LOGAR_Client AppParameters "--config \"$DATADIR\client_config.json\""'
|
||||
ExecWait '"$INSTDIR\nssm.exe" set LOGAR_Client AppParameters "--config \"$DATADIR\client_config.json\" --interval 30"'
|
||||
ExecWait '"$INSTDIR\nssm.exe" start LOGAR_Client'
|
||||
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
+58
-2
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import socket
|
||||
import ssl
|
||||
import struct
|
||||
@@ -168,7 +169,25 @@ def load_config(config_path: str = CONFIG_FILE_NAME):
|
||||
f"Generate one from the server using: python Server.py --create-client-config --client-out {config_path}"
|
||||
)
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
config = json.load(f)
|
||||
|
||||
cfg_dir = os.path.dirname(os.path.abspath(config_path))
|
||||
raw_cert_dir = config.get("cert_dir", "certs")
|
||||
if not os.path.isabs(raw_cert_dir):
|
||||
config["cert_dir"] = os.path.join(cfg_dir, raw_cert_dir)
|
||||
|
||||
ca_cert_pem = config.get("ca_cert")
|
||||
if ca_cert_pem and isinstance(ca_cert_pem, str):
|
||||
ca_file = os.path.join(config["cert_dir"], "ca.crt")
|
||||
if not os.path.exists(ca_file):
|
||||
try:
|
||||
os.makedirs(config["cert_dir"], exist_ok=True)
|
||||
with open(ca_file, "w", encoding="utf-8") as f:
|
||||
f.write(ca_cert_pem)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_machine_identifier() -> str:
|
||||
@@ -452,12 +471,33 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
|
||||
return response
|
||||
|
||||
|
||||
def ensure_enrolled(config: dict, machine_id: str) -> bool:
|
||||
"""Proactively ensures client certificate enrollment with the Hub if enrollment_secret is available."""
|
||||
cert_dir = config.get("cert_dir", "certs")
|
||||
enrollment_secret = config.get("enrollment_secret")
|
||||
if not enrollment_secret:
|
||||
return False
|
||||
|
||||
server_host = config.get("server_host", "127.0.0.1")
|
||||
hermes_host = config.get("hermes_host", server_host)
|
||||
if hermes_host in ("0.0.0.0", "::", None, ""):
|
||||
hermes_host = server_host if server_host not in ("0.0.0.0", "::", None, "") else "127.0.0.1"
|
||||
hermes_port = config.get("hermes_port", 8443)
|
||||
hub_url = f"http://{hermes_host}:{hermes_port}"
|
||||
try:
|
||||
return enroll_client_if_needed(hub_url, enrollment_secret, cert_dir, machine_id, machine_id, os_type="linux")
|
||||
except Exception as e:
|
||||
print(f"[!] Warning: Initial client enrollment bootstrap with Hub at {hub_url} failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="LOGAR Linux Edge Log Forwarder with State Tracking")
|
||||
parser.add_argument("--config", default=CONFIG_FILE_NAME, help="Path to client_config.json")
|
||||
parser.add_argument("--hours", type=int, default=24, help="Lookback window in hours for journalctl logs (default: 24)")
|
||||
parser.add_argument("--state-file", default=None, help="Path to state tracking file (default: client_state.json next to config)")
|
||||
parser.add_argument("--no-state", action="store_true", help="Disable state tracking and send all events matching lookback window")
|
||||
parser.add_argument("--interval", type=int, default=0, help="Continuous polling interval in seconds (0 = run once and exit, >0 = service loop)")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
@@ -469,13 +509,17 @@ def main():
|
||||
state_path = get_state_path(args.config, args.state_file)
|
||||
state = None if args.no_state else load_state(state_path)
|
||||
|
||||
machine_id = get_machine_identifier()
|
||||
machine_id = config.get("client_id") or get_machine_identifier()
|
||||
print(f"[*] Edge Forwarder Node: {machine_id}")
|
||||
if state and ("last_cursor" in state or "last_timestamp_us" in state):
|
||||
print(f"[*] State tracking active: resuming after previous cursor/timestamp (state file: {state_path})")
|
||||
elif not args.no_state:
|
||||
print(f"[*] State tracking initialized (state file: {state_path})")
|
||||
|
||||
# Proactively ensure dynamic PKI certificate enrollment with Server Hub
|
||||
ensure_enrolled(config, machine_id)
|
||||
|
||||
def run_forwarder_cycle():
|
||||
print(f"[*] Scanning Linux journalctl for unsent entries (last {args.hours} hours)...")
|
||||
candidate_logs = get_recent_linux_logs(hours=args.hours, state=state)
|
||||
print(f"[*] Found {len(candidate_logs)} unsent candidate entries (INFO to ERROR, entries > {args.hours}h and already-sent skipped).")
|
||||
@@ -493,8 +537,20 @@ def main():
|
||||
print(f"[+] State successfully committed to {state_path}")
|
||||
except Exception as e:
|
||||
print(f"[!] Failed to stream logs to server: {e}")
|
||||
if not args.interval:
|
||||
sys.exit(1)
|
||||
|
||||
if args.interval and args.interval > 0:
|
||||
print(f"[*] Starting continuous forwarder service loop (Interval: {args.interval}s)...")
|
||||
while True:
|
||||
try:
|
||||
run_forwarder_cycle()
|
||||
except Exception as e:
|
||||
print(f"[!] Error in forwarder cycle: {e}")
|
||||
time.sleep(args.interval)
|
||||
else:
|
||||
run_forwarder_cycle()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2082,7 +2082,6 @@ def serve_webui(request: Request = None):
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button onclick="downloadLicRequest()" class="btn btn-secondary" style="font-size: 12px; padding: 7px 14px;">📤 Export License Request</button>
|
||||
<button onclick="openImportLicModal()" class="btn btn-accent" style="font-size: 12px; padding: 7px 14px;">📥 Import License (.lic)</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2461,7 +2460,6 @@ def serve_webui(request: Request = None):
|
||||
<div style="font-weight: 600; font-size: 13px; color: var(--text-muted);">PKI & Seat Synchronization:</div>
|
||||
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<button class="btn btn-accent" onclick="openImportLicModal()">📥 Import License (.lic)</button>
|
||||
<button class="btn btn-secondary" onclick="downloadLicRequest()">📤 Export License Request (.lic)</button>
|
||||
<button class="btn btn-outline" onclick="copyCaCsr()">📋 Copy CA CSR</button>
|
||||
<button class="btn btn-secondary" onclick="triggerMtlsRenewal()">🔄 Sync & Renew via mTLS</button>
|
||||
</div>
|
||||
|
||||
+63
-3
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import socket
|
||||
import ssl
|
||||
import struct
|
||||
@@ -247,8 +248,30 @@ def load_config(config_path: str = CONFIG_FILE_NAME):
|
||||
with open(actual_config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
if "cert_dir" in config:
|
||||
config["cert_dir"] = resolve_writable_path(config["cert_dir"], "LOGAR", "certs")
|
||||
# Resolve cert_dir to a writable directory (defaulting next to the config file if relative)
|
||||
cfg_dir = os.path.dirname(os.path.abspath(actual_config_path))
|
||||
raw_cert_dir = config.get("cert_dir", "certs")
|
||||
if os.path.isabs(raw_cert_dir) and is_path_writable(raw_cert_dir):
|
||||
config["cert_dir"] = raw_cert_dir
|
||||
else:
|
||||
candidate = os.path.join(cfg_dir, os.path.basename(raw_cert_dir))
|
||||
if is_path_writable(candidate) or is_path_writable(cfg_dir):
|
||||
config["cert_dir"] = candidate
|
||||
else:
|
||||
config["cert_dir"] = resolve_writable_path("certs", "LOGAR", "certs")
|
||||
|
||||
# If ca_cert is present in config and ca.crt doesn't exist yet, save it
|
||||
ca_cert_pem = config.get("ca_cert")
|
||||
if ca_cert_pem and isinstance(ca_cert_pem, str):
|
||||
ca_file = os.path.join(config["cert_dir"], "ca.crt")
|
||||
if not os.path.exists(ca_file):
|
||||
try:
|
||||
os.makedirs(config["cert_dir"], exist_ok=True)
|
||||
with open(ca_file, "w", encoding="utf-8") as f:
|
||||
f.write(ca_cert_pem)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -500,12 +523,33 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
|
||||
return response
|
||||
|
||||
|
||||
def ensure_enrolled(config: dict, machine_id: str) -> bool:
|
||||
"""Proactively ensures client certificate enrollment with the Hub if enrollment_secret is available."""
|
||||
cert_dir = config.get("cert_dir", "certs")
|
||||
enrollment_secret = config.get("enrollment_secret")
|
||||
if not enrollment_secret:
|
||||
return False
|
||||
|
||||
server_host = config.get("server_host", "127.0.0.1")
|
||||
hermes_host = config.get("hermes_host", server_host)
|
||||
if hermes_host in ("0.0.0.0", "::", None, ""):
|
||||
hermes_host = server_host if server_host not in ("0.0.0.0", "::", None, "") else "127.0.0.1"
|
||||
hermes_port = config.get("hermes_port", 8443)
|
||||
hub_url = f"http://{hermes_host}:{hermes_port}"
|
||||
try:
|
||||
return enroll_client_if_needed(hub_url, enrollment_secret, cert_dir, machine_id, machine_id, os_type="windows")
|
||||
except Exception as e:
|
||||
print(f"[!] Warning: Initial client enrollment bootstrap with Hub at {hub_url} failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="LOGAR Windows Edge Log Forwarder with State Tracking")
|
||||
parser.add_argument("--config", default=CONFIG_FILE_NAME, help="Path to client_config.json")
|
||||
parser.add_argument("--hours", type=int, default=24, help="Lookback window in hours for event logs (default: 24)")
|
||||
parser.add_argument("--state-file", default=None, help="Path to state tracking file (default: client_state.json next to config)")
|
||||
parser.add_argument("--no-state", action="store_true", help="Disable state tracking and send all events matching lookback window")
|
||||
parser.add_argument("--interval", type=int, default=0, help="Continuous polling interval in seconds (0 = run once and exit, >0 = service loop)")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
@@ -517,13 +561,17 @@ def main():
|
||||
state_path = get_state_path(args.config, args.state_file)
|
||||
state = None if args.no_state else load_state(state_path)
|
||||
|
||||
machine_id = get_machine_identifier()
|
||||
machine_id = config.get("client_id") or get_machine_identifier()
|
||||
print(f"[*] Edge Forwarder Node: {machine_id}")
|
||||
if state and "last_record_number" in state:
|
||||
print(f"[*] State tracking active: resuming from record #{state['last_record_number']} (state file: {state_path})")
|
||||
elif not args.no_state:
|
||||
print(f"[*] State tracking initialized (state file: {state_path})")
|
||||
|
||||
# Proactively ensure dynamic PKI certificate enrollment with Server Hub
|
||||
ensure_enrolled(config, machine_id)
|
||||
|
||||
def run_forwarder_cycle():
|
||||
print(f"[*] Scanning Windows Application event log for unsent entries (last {args.hours} hours)...")
|
||||
candidate_logs = get_recent_windows_logs(hours=args.hours, state=state)
|
||||
print(f"[*] Found {len(candidate_logs)} unsent candidate entries (INFO to ERROR, entries > {args.hours}h and already-sent skipped).")
|
||||
@@ -541,8 +589,20 @@ def main():
|
||||
print(f"[+] State successfully committed to {state_path}")
|
||||
except Exception as e:
|
||||
print(f"[!] Failed to stream logs to server: {e}")
|
||||
if not args.interval:
|
||||
sys.exit(1)
|
||||
|
||||
if args.interval and args.interval > 0:
|
||||
print(f"[*] Starting continuous forwarder service loop (Interval: {args.interval}s)...")
|
||||
while True:
|
||||
try:
|
||||
run_forwarder_cycle()
|
||||
except Exception as e:
|
||||
print(f"[!] Error in forwarder cycle: {e}")
|
||||
time.sleep(args.interval)
|
||||
else:
|
||||
run_forwarder_cycle()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -31,8 +31,14 @@ HERMES_PORT = 8443
|
||||
|
||||
|
||||
def run_tests():
|
||||
print("=== [1] Verifying server_config.json & client_config.json ===")
|
||||
server_cfg_path = "server_config.json" if os.path.exists("server_config.json") else os.path.join(ROOT_DIR, "server_config.json")
|
||||
server_cfg_path = "server_config.json"
|
||||
if not os.path.exists(server_cfg_path):
|
||||
server_cfg_path = os.path.join(ROOT_DIR, "server_config.json")
|
||||
if not os.path.exists(server_cfg_path):
|
||||
progdata_path = os.path.join(os.environ.get("ProgramData", r"C:\ProgramData"), "LOGAR-Server", "server_config.json")
|
||||
if os.path.exists(progdata_path):
|
||||
server_cfg_path = progdata_path
|
||||
|
||||
client_cfg_path = "client_config.json" if os.path.exists("client_config.json") else os.path.join(ROOT_DIR, "client_config.json")
|
||||
|
||||
assert os.path.exists(server_cfg_path), f"{server_cfg_path} must exist"
|
||||
|
||||
Reference in New Issue
Block a user