24 Commits
Author SHA1 Message Date
me0nline 9f999fc6c3 feat(release): v0.3.4.2 - host binding locking and manual reassignment controls
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m54s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 4m21s
2026-09-30 15:40:16 +02:00
me0nline 3399c6d57b feat(licensing): add host-binding locking and reset-binding button for license reassignment
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m49s
2026-09-30 15:37:20 +02:00
me0nline c6d04f8896 feat(release): v0.3.4.1 - fix installer UI text overlap and branding
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m42s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 5m40s
2026-09-27 19:30:52 +02:00
me0nline 0a67b43619 fix(licensing): keep only Manage button in ACTIONS table column
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m48s
2026-09-27 19:29:40 +02:00
me0nline 3234acf435 feat(release): v0.3.4 - in-place installer upgrades, mutual exclusion guards, 2FA, and telemetry cap
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Canceled after 0s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Canceled after 1m13s
2026-09-27 19:13:22 +02:00
me0nline 702ca985d2 Update LOGAR-LICENSING submodule pointer for clickable capabilities and implementation overview
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m38s
2026-09-27 19:07:05 +02:00
me0nline 97d92d1bee Update LOGAR-LICENSING submodule with delete and reinstate buttons for revoked licenses
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m49s
2026-09-27 18:53:46 +02:00
me0nline a8cd2cd83c Add TOTP 2FA to Server Fleet Hub and limit verified telemetry stream to latest 50 events
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m15s
2026-09-27 18:48:40 +02:00
me0nline 6843b22ac1 Update LOGAR-LICENSING submodule pointer with container pruner
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m1s
2026-09-27 18:35:33 +02:00
me0nline bfd576bfdd Update LOGAR-LICENSING submodule pointer with working revoke button
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m54s
2026-09-27 18:32:05 +02:00
me0nline 9810220cb7 Use raw repository URL for banner image in README
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m16s
2026-09-27 18:06:54 +02:00
me0nline 13ca1cacda Align installer header text cleanly to the left with right-positioned header image
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m53s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 5m13s
2026-09-27 18:04:11 +02:00
me0nline 4d607d0a9e Add vector color swatches and update asset generator
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Canceled after 0s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Canceled after 0s
2026-09-27 18:01:25 +02:00
me0nline ad80620e41 Update regenerated branding assets across main repo
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m8s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 6m6s
2026-09-27 17:57:45 +02:00
me0nline 6fb631f908 Fix XML entity in logo_banner.svg and asset generator
CI Test Suite / Run Component Tests & Pipeline Verification (push) Canceled after 1s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Canceled after 0s
2026-09-27 17:57:24 +02:00
me0nline 65ec1b3b1b Update LOGAR-LICENSING submodule pointer with centered navbar and 2x2 grid
CI Test Suite / Run Component Tests & Pipeline Verification (push) Canceled after 0s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Canceled after 0s
2026-09-27 17:55:38 +02:00
me0nline 7b3b822974 Update LOGAR-LICENSING submodule with navbar, minimalist footer, and impressum
CI Test Suite / Run Component Tests & Pipeline Verification (push) Canceled after 0s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Canceled after 0s
2026-09-27 17:41:42 +02:00
me0nline 5b97ccc1f2 remove legacy out, docs, and wiki folders from repo
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Canceled after 0s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m26s
2026-09-27 17:37:00 +02:00
me0nline d6e5ef7107 update LOGAR-LICENSING pointer with dynamic health status indicator
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m23s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 4m56s
2026-09-27 17:32:24 +02:00
me0nline d689713234 add Impressum page and wiki documentation
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m19s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 4m29s
2026-09-27 17:31:00 +02:00
me0nline 61825571b1 update LOGAR-LICENSING submodule pointer with UI adjustments
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m20s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 5m21s
2026-09-27 17:28:54 +02:00
me0nline 06db6672ce ignore master_ca_certs test output
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m54s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 5m25s
2026-09-27 17:24:49 +02:00
me0nline a6eddc207c update LOGAR-LICENSING submodule with test fix
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m56s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 5m54s
2026-09-27 17:24:20 +02:00
me0nline a70ce9162d brand asset library, wiki branding guide, and v0.3.3 release
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m30s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 5m11s
2026-09-27 17:17:57 +02:00
70 changed files with 1303 additions and 738 deletions
+2
View File
@@ -9,6 +9,7 @@ __pycache__/
*$py.class *$py.class
build/ build/
dist/ dist/
out/
*.spec *.spec
*.exe *.exe
!compilation/nssm.exe !compilation/nssm.exe
@@ -29,6 +30,7 @@ server_config.json
client_config.json client_config.json
test_*.json test_*.json
test_*.db test_*.db
master_ca_certs/
.env .env
*.env *.env
runner/.env runner/.env
+18 -5
View File
@@ -1,13 +1,18 @@
<p align="center">
<img src="https://gitea.eibl.tech/me0nline/LOGAR/raw/branch/main/assets/logo_banner.svg" alt="LOGAR Banner" width="750"/>
</p>
# LOGAR: Fleet Log Aggregation & Centralized Provisioning System # LOGAR: Fleet Log Aggregation & Centralized Provisioning System
### *Secure Log Aggregation, PKI Fleet Licensing & Centralized Delivery for Autonomous AI Analysis* ### *Secure Log Aggregation, PKI Fleet Licensing & Centralized Delivery for Autonomous AI Analysis*
[![Version](https://img.shields.io/badge/version-0.3.2-blue.svg)](RELEASE_NOTES.md) [![Version](https://img.shields.io/badge/version-0.3.4.2-blue.svg)](RELEASE_NOTES.md)
[![Tests](https://img.shields.io/badge/tests-35%20passed-success.svg)](tests/) [![Tests](https://img.shields.io/badge/tests-42%20passed-success.svg)](tests/)
[![Security](https://img.shields.io/badge/mTLS-TLS%201.3-green.svg)](https://gitea.eibl.tech/me0nline/LOGAR/wiki/PKI-and-Licensing) [![Security](https://img.shields.io/badge/mTLS-TLS%201.3-green.svg)](https://gitea.eibl.tech/me0nline/LOGAR/wiki/PKI-and-Licensing)
[![Branding](https://img.shields.io/badge/branding-guide-cyan.svg)](https://gitea.eibl.tech/me0nline/LOGAR/wiki/Branding-Guide)
**LOGAR** is an enterprise-grade log aggregation, normalization, and centralized provisioning system built for heterogeneous server fleets (Windows & Linux). It pairs ultra-lightweight, zero-state edge forwarders with a secure centralized server hub that enforces mutual TLS 1.3 (**mTLS**) authentication, dynamic PKI fleet licensing with cryptographic seat leasing, 12-hour temporal persistence tracking, and direct anomaly pass-through—delivering clean, structured, verified log telemetry directly to **Hermes AI** for autonomous deep reasoning, contextual root-cause analysis, and operational decision-making. **LOGAR** is an enterprise-grade log aggregation, normalization, and centralized provisioning system built for heterogeneous server fleets (Windows & Linux). It pairs ultra-lightweight, zero-state edge forwarders with a secure centralized server hub that enforces mutual TLS 1.3 (**mTLS**) authentication, dynamic PKI fleet licensing with cryptographic seat leasing, 12-hour temporal persistence tracking, and direct anomaly pass-through—delivering clean, structured, verified log telemetry directly to **Hermes AI** for autonomous deep reasoning, contextual root-cause analysis, and operational decision-making.
> 📚 **Comprehensive Technical Documentation**: Full architectural specifications, state machines, PKI X.509 OID definitions, and complete REST API references are maintained on the **[LOGAR Gitea Wiki](https://gitea.eibl.tech/me0nline/LOGAR/wiki)**. > 📚 **Comprehensive Technical Documentation**: Full architectural specifications, state machines, PKI X.509 OID definitions, branding assets, and complete REST API references are maintained on the **[LOGAR Gitea Wiki](https://gitea.eibl.tech/me0nline/LOGAR/wiki)**.
--- ---
@@ -107,8 +112,15 @@ Once installed or executed, access the web console:
``` ```
LOGAR/ LOGAR/
├── assets/ # Official vector SVGs, PNGs, ICOs, and branding bitmaps
│ ├── logo.svg # Master vector emblem
│ ├── logo_transparent.svg # Transparent WebUI emblem
│ ├── logo_banner.svg # Full brand banner
│ ├── logo.ico / favicon.ico # Application & web icons
│ └── wizard_*.bmp # Installer dialog bitmaps
├── .gitea/workflows/ # CI/CD pipelines (ci.yml, release.yml) ├── .gitea/workflows/ # CI/CD pipelines (ci.yml, release.yml)
├── compilation/ # Build scripts, Linux installers & Inno Setup scripts ├── compilation/ # Build scripts, Linux installers & Inno Setup scripts
│ ├── generate_branding_assets.py # Asset & bitmap generator
│ ├── install_linux_client.sh # Linux client systemd auto-installer │ ├── install_linux_client.sh # Linux client systemd auto-installer
│ ├── install_linux_server.sh # Linux server systemd auto-installer │ ├── install_linux_server.sh # Linux server systemd auto-installer
│ ├── package_dist.py # Standalone binary cross-compiler │ ├── package_dist.py # Standalone binary cross-compiler
@@ -118,7 +130,7 @@ LOGAR/
│ ├── server_enrollment.py # Sub-CA PKI and certificate generator │ ├── server_enrollment.py # Sub-CA PKI and certificate generator
│ ├── Win_Client.py # Windows edge forwarder │ ├── Win_Client.py # Windows edge forwarder
│ └── Linux_Client.py # Linux edge forwarder │ └── Linux_Client.py # Linux edge forwarder
├── tests/ # Automated test suites (35 unit tests + pipeline test) ├── tests/ # Automated test suites (42 unit & integration tests)
├── LOGAR-LICENSING/ # Standalone Master License Authority (Git submodule) ├── LOGAR-LICENSING/ # Standalone Master License Authority (Git submodule)
├── RELEASE_NOTES.md # Version history and security changelog ├── RELEASE_NOTES.md # Version history and security changelog
└── README.md # Repository overview └── README.md # Repository overview
@@ -146,7 +158,7 @@ LOGAR/
## Testing & Cross-Compilation ## Testing & Cross-Compilation
```bash ```bash
# Run full unit test suite (35 tests) # Run full unit test suite (42 tests)
python -m unittest discover -s tests -v python -m unittest discover -s tests -v
# Run end-to-end mTLS pipeline integration test # Run end-to-end mTLS pipeline integration test
@@ -162,6 +174,7 @@ python compilation/package_dist.py --target all
For in-depth operational guides, lifecycle state machines, and PKI specifications, consult the **[LOGAR Gitea Wiki](https://gitea.eibl.tech/me0nline/LOGAR/wiki)**: For in-depth operational guides, lifecycle state machines, and PKI specifications, consult the **[LOGAR Gitea Wiki](https://gitea.eibl.tech/me0nline/LOGAR/wiki)**:
* [Branding & Design System Guide](https://gitea.eibl.tech/me0nline/LOGAR/wiki/Branding-Guide)
* [Architecture & Design Details](https://gitea.eibl.tech/me0nline/LOGAR/wiki/Architecture-and-Design) * [Architecture & Design Details](https://gitea.eibl.tech/me0nline/LOGAR/wiki/Architecture-and-Design)
* [System State Machines & Subsystem Lifecycles](https://gitea.eibl.tech/me0nline/LOGAR/wiki/State-Machines-and-Lifecycles) * [System State Machines & Subsystem Lifecycles](https://gitea.eibl.tech/me0nline/LOGAR/wiki/State-Machines-and-Lifecycles)
* [PKI Architecture & Custom X.509 Seat OIDs](https://gitea.eibl.tech/me0nline/LOGAR/wiki/PKI-and-Licensing) * [PKI Architecture & Custom X.509 Seat OIDs](https://gitea.eibl.tech/me0nline/LOGAR/wiki/PKI-and-Licensing)
+51 -2
View File
@@ -1,6 +1,55 @@
# LOGAR Release v0.3.2 # LOGAR Release Notes
### 🪟 Windows Installer Fixes ## [v0.3.4.2] - 2026-09-30
### 🔒 Exclusive Hardware Host-Binding & Reassignment Controls
- **Automated Host Binding on First Activation**: Licenses now automatically lock to the specific server hardware fingerprint (`server_fingerprint`) and hostname upon first activation/enrollment, preventing multi-host cloning or license hijacking.
- **Manual Host Reassignment Control**: Added `🔓 Reset Host Binding` button to the Master License Authority dashboard (`licenseDetailsModal`), allowing administrators to unbind a license so it can be cleanly transferred or reassigned to a new server instance.
- **REST & CLI Management**: Added `POST /api/license/reset-binding` and CLI argument `--reset-binding LIC_ID` for automated provisioning pipelines.
---
## [v0.3.4.1] - 2026-09-27
### 🎨 Installer Dialog UI Spacing & Branding Enhancements
- **NSIS Client Config Profile Dialog Fix**: Adjusted vertical coordinates and label boundaries in the Windows Client NSIS installer (`installer_client.nsi`) to prevent input field overlap and ensure multi-line instructional text renders without visual clipping.
- **Installer Footer Branding Clean-Up**: Escaped ampersands in Windows installer branding labels (`BrandingText`) to avoid character truncation in native win32 footer panels.
- **License Authority UI Clean-Up**: Simplified table ACTIONS column in the Licensing Authority portal to exclusively display `⚙️ Manage`, keeping secondary actions (`Reinstate`, `Delete`) inside the dedicated management modal.
---
## [v0.3.4] - 2026-09-27
### 🔄 In-Place Installer Upgrades & Mutual Exclusion Guards
- **Seamless In-Place Upgrades**: Windows (Inno Setup & NSIS) and Linux installers now detect existing installations automatically. In upgrade mode, installers stop background services, update core binaries and runtime in-place, and restart services without overwriting configuration files (`server_config.json`, `client_config.json`), database state (`logar_state.db`), state tracking (`client_state.json`), or mTLS certificates.
- **Host Mutual Exclusion Validation**: Added pre-installation validation across all installers to prevent accidental co-installation of LOGAR Server and LOGAR Client on the same host, showing descriptive guidance to administrators.
### 🛡️ WebUI 2FA & Telemetry Stability
- **Server Hub TOTP Two-Factor Authentication**: Implemented RFC 6238 TOTP 2FA for the LOGAR Server Hub web portal (`:8443`), featuring interactive QR code setup, secret generation, and secure session validation.
- **Telemetry Stream UI Buffer Capping**: Limited the live verified telemetry stream view to the latest 50 events in the Server Hub dashboard for enhanced browser performance and UI stability.
### 🔑 Licensing Authority Management & Registry Tooling
- **Revocation, Reinstatement & License Deletion**: Enhanced the Master Licensing Authority (`logar.eibl.tech`) with instant license revocation, reinstatement of revoked licenses, and conditional permanent deletion of revoked records.
- **Interactive Capability Explorer**: Added interactive implementation breakdown modals across core capabilities on the public project landing page.
- **Automated Gitea Container Pruning**: Added `prune_images.py` utility to maintain clean container registries by keeping only the latest active image versions.
---
## [v0.3.3] - 2026-09-27
### 🎨 Brand Identity, Centralized Asset Library & Design System
- **Centralized `/assets` Repository**: Added official vector graphics (`logo.svg`, `logo_transparent.svg`, `logo_dark.svg`, `logo_banner.svg`), high-resolution master PNGs (16px to 512px), multi-resolution Windows ICOs (`logo.ico`, `favicon.ico`), and installer dialog bitmaps (`wizard_header.bmp`, `wizard_small.bmp`, `wizard_sidebar.bmp`).
- **Official Wiki Branding Guide**: Published comprehensive visual identity standards on the Gitea Wiki at `https://gitea.eibl.tech/me0nline/LOGAR/wiki/Branding-Guide` detailing logo anatomy, color palettes (Cyber Cyan `#00E5FF`, Emerald `#00FFA3`, Dark Navy `#0E1A2E`), typography, clear-space rules, dos and don'ts, and strict non-SIEM positioning.
- **Unified Interface & Tab Branding**: Implemented transparent vector logos on all web navigation bars and browser tabs across both the LOGAR Server Hub (`:8443`) and Master Licensing Authority (`logar.eibl.tech`).
- **Clean Installer Bitmaps**: Regenerated 24-bit RGB header bitmaps with seamless white backgrounds (`#FFFFFF`) matching native Windows Inno Setup wizard dialogs.
- **Automated Asset Pipeline**: Enhanced `compilation/generate_branding_assets.py` to deterministically compile and distribute assets across main repo, compilation subdirectories, and the licensing submodule without third-party graphics dependencies.
---
## [v0.3.2] - 2026-09-27
### 🪟 Windows Installer & Web Portal Enhancements
- **Project Showcase Landing Page**: Deployed high-impact public homepage at `GET /` on the licensing authority with architecture diagrams, quickstart commands, and direct navigation to Gitea, Wiki, and the Licensing Portal (`/licensing`).
- **Standalone Windows Binaries & Python Runtime**: Resolved issues with standalone Windows executable execution and bundled runtime paths. - **Standalone Windows Binaries & Python Runtime**: Resolved issues with standalone Windows executable execution and bundled runtime paths.
- **Data Directory Selection & Permissions**: Added custom data directory selection during setup and fixed permissions for background Windows services. - **Data Directory Selection & Permissions**: Added custom data directory selection during setup and fixed permissions for background Windows services.
- **Service Lifecycle & Clean Uninstallation**: Resolved service startup issues and ensured complete cleanup of state files upon uninstallation. - **Service Lifecycle & Clean Uninstallation**: Resolved service startup issues and ensured complete cleanup of state files upon uninstallation.
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+43
View File
@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="50%" stop-color="#08101E"/>
<stop offset="100%" stop-color="#040810"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Base Hexagon Shield -->
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="url(#bgGrad)" stroke="url(#cyanGrad)" stroke-width="8" filter="url(#glow)"/>
<!-- Inner Telemetry Grid / Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="3" stroke-dasharray="8 6"/>
<!-- Stylized L and Log Streams -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Nodes / Spark Points -->
<circle cx="238" cy="190" r="7" fill="#00FFA3" filter="url(#glow)"/>
<circle cx="304" cy="130" r="7" fill="#00E5FF" filter="url(#glow)"/>
<circle cx="380" cy="349" r="8" fill="#00E5FF" filter="url(#glow)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+54
View File
@@ -0,0 +1,54 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 240" width="100%" height="100%">
<defs>
<linearGradient id="bannerBg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="100%" stop-color="#070C18"/>
</linearGradient>
<linearGradient id="textCyan" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#00FFA3"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="bannerGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Background Card -->
<rect width="960" height="240" rx="16" fill="url(#bannerBg)" stroke="#162A45" stroke-width="2"/>
<!-- Left Emblem Group (scaled & positioned) -->
<g transform="translate(40, 20) scale(0.39)">
<!-- Hexagon Shield -->
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="#0E1A2E" stroke="url(#cyanGrad)" stroke-width="12" filter="url(#bannerGlow)"/>
<!-- Inner Telemetry Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="4" stroke-dasharray="10 8"/>
<!-- Stylized L & Telemetry Bars -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Points -->
<circle cx="238" cy="190" r="9" fill="#00FFA3"/>
<circle cx="304" cy="130" r="9" fill="#00E5FF"/>
<circle cx="380" cy="349" r="10" fill="#00E5FF"/>
</g>
<!-- Typography & Tagline -->
<text x="270" y="98" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" font-size="56" font-weight="900" letter-spacing="4" fill="url(#textCyan)" filter="url(#bannerGlow)">LOGAR</text>
<text x="272" y="142" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" font-size="20" font-weight="600" fill="#E2E8F0" letter-spacing="0.5">Fleet Log Aggregation &amp; Telemetry Hub</text>
<text x="272" y="174" font-family="'JetBrains Mono', 'Fira Code', monospace" font-size="13" font-weight="400" fill="#00E5FF" letter-spacing="1">ZERO-STATE FORWARDERS &#8226; MUTUAL TLS 1.3 &#8226; TEMPORAL PERSISTENCE</text>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+43
View File
@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="50%" stop-color="#08101E"/>
<stop offset="100%" stop-color="#040810"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Base Hexagon Shield -->
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="url(#bgGrad)" stroke="url(#cyanGrad)" stroke-width="8" filter="url(#glow)"/>
<!-- Inner Telemetry Grid / Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="3" stroke-dasharray="8 6"/>
<!-- Stylized L and Log Streams -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Nodes / Spark Points -->
<circle cx="238" cy="190" r="7" fill="#00FFA3" filter="url(#glow)"/>
<circle cx="304" cy="130" r="7" fill="#00E5FF" filter="url(#glow)"/>
<circle cx="380" cy="349" r="8" fill="#00E5FF" filter="url(#glow)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+41
View File
@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="50%" stop-color="#08101E"/>
<stop offset="100%" stop-color="#040810"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Inner Telemetry Grid / Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="3" stroke-dasharray="8 6"/>
<!-- Stylized L and Log Streams -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Nodes / Spark Points -->
<circle cx="238" cy="190" r="7" fill="#00FFA3" filter="url(#glow)"/>
<circle cx="304" cy="130" r="7" fill="#00E5FF" filter="url(#glow)"/>
<circle cx="380" cy="349" r="8" fill="#00E5FF" filter="url(#glow)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#0072FF" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#00E5FF" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#00FFA3" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#070C18" stroke="#1e293b" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#0E1A2E" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#94A3B8" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#EF4444" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#F59E0B" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#FFFFFF" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+54
View File
@@ -0,0 +1,54 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 240" width="100%" height="100%">
<defs>
<linearGradient id="bannerBg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="100%" stop-color="#070C18"/>
</linearGradient>
<linearGradient id="textCyan" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#00FFA3"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="bannerGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Background Card -->
<rect width="960" height="240" rx="16" fill="url(#bannerBg)" stroke="#162A45" stroke-width="2"/>
<!-- Left Emblem Group (scaled & positioned) -->
<g transform="translate(40, 20) scale(0.39)">
<!-- Hexagon Shield -->
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="#0E1A2E" stroke="url(#cyanGrad)" stroke-width="12" filter="url(#bannerGlow)"/>
<!-- Inner Telemetry Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="4" stroke-dasharray="10 8"/>
<!-- Stylized L & Telemetry Bars -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Points -->
<circle cx="238" cy="190" r="9" fill="#00FFA3"/>
<circle cx="304" cy="130" r="9" fill="#00E5FF"/>
<circle cx="380" cy="349" r="10" fill="#00E5FF"/>
</g>
<!-- Typography & Tagline -->
<text x="270" y="98" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" font-size="56" font-weight="900" letter-spacing="4" fill="url(#textCyan)" filter="url(#bannerGlow)">LOGAR</text>
<text x="272" y="142" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" font-size="20" font-weight="600" fill="#E2E8F0" letter-spacing="0.5">Fleet Log Aggregation &amp; Telemetry Hub</text>
<text x="272" y="174" font-family="'JetBrains Mono', 'Fira Code', monospace" font-size="13" font-weight="400" fill="#00E5FF" letter-spacing="1">ZERO-STATE FORWARDERS &#8226; MUTUAL TLS 1.3 &#8226; TEMPORAL PERSISTENCE</text>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+43
View File
@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="50%" stop-color="#08101E"/>
<stop offset="100%" stop-color="#040810"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Base Hexagon Shield -->
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="url(#bgGrad)" stroke="url(#cyanGrad)" stroke-width="8" filter="url(#glow)"/>
<!-- Inner Telemetry Grid / Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="3" stroke-dasharray="8 6"/>
<!-- Stylized L and Log Streams -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Nodes / Spark Points -->
<circle cx="238" cy="190" r="7" fill="#00FFA3" filter="url(#glow)"/>
<circle cx="304" cy="130" r="7" fill="#00E5FF" filter="url(#glow)"/>
<circle cx="380" cy="349" r="8" fill="#00E5FF" filter="url(#glow)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+41
View File
@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="50%" stop-color="#08101E"/>
<stop offset="100%" stop-color="#040810"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Inner Telemetry Grid / Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="3" stroke-dasharray="8 6"/>
<!-- Stylized L and Log Streams -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)" filter="url(#glow)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Nodes / Spark Points -->
<circle cx="238" cy="190" r="7" fill="#00FFA3" filter="url(#glow)"/>
<circle cx="304" cy="130" r="7" fill="#00E5FF" filter="url(#glow)"/>
<circle cx="380" cy="349" r="8" fill="#00E5FF" filter="url(#glow)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#0072FF" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#00E5FF" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#00FFA3" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#070C18" stroke="#1e293b" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#0E1A2E" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#94A3B8" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#EF4444" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#F59E0B" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="#FFFFFF" stroke="#334155" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

+152 -49
View File
@@ -1,7 +1,7 @@
""" """
LOGAR Branding & Installer Graphics Generator LOGAR Branding & Installer Graphics Generator
Generates clean, professional SVG, PNG, ICO, and BMP visual assets for Generates clean, professional SVG, PNG, ICO, and BMP visual assets for
LOGAR installers (Inno Setup & NSIS), application icons, and WebUI branding. LOGAR installers (Inno Setup & NSIS), application icons, root /assets library, and WebUI branding.
Zero external dependencies required (uses standard library zlib and struct). Zero external dependencies required (uses standard library zlib and struct).
""" """
@@ -9,9 +9,15 @@ import os
import math import math
import zlib import zlib
import struct import struct
import shutil
ASSETS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.makedirs(ASSETS_DIR, exist_ok=True) ROOT_ASSETS_DIR = os.path.join(ROOT_DIR, "assets")
COMPILATION_ASSETS_DIR = os.path.join(ROOT_DIR, "compilation", "assets")
LICENSING_ASSETS_DIR = os.path.join(ROOT_DIR, "LOGAR-LICENSING", "assets")
for d in (ROOT_ASSETS_DIR, COMPILATION_ASSETS_DIR, LICENSING_ASSETS_DIR):
os.makedirs(d, exist_ok=True)
def create_bmp_24bit(width: int, height: int, pixel_func) -> bytes: def create_bmp_24bit(width: int, height: int, pixel_func) -> bytes:
@@ -80,7 +86,7 @@ def create_png_rgba(width: int, height: int, pixel_func) -> bytes:
def draw_logo_rgba(x: int, y: int, w: int, h: int) -> tuple: def draw_logo_rgba(x: int, y: int, w: int, h: int) -> tuple:
"""Renders a sleek modern cyberpunk hexagon shield and pulse log streams.""" """Renders a sleek modern hexagon shield and pulse log streams."""
nx = (x - w / 2) / (w / 2) nx = (x - w / 2) / (w / 2)
ny = (y - h / 2) / (h / 2) ny = (y - h / 2) / (h / 2)
dist = math.sqrt(nx * nx + ny * ny) dist = math.sqrt(nx * nx + ny * ny)
@@ -144,12 +150,13 @@ def draw_wizard_header_white_bgr(x: int, y: int, w: int, h: int) -> tuple:
""" """
Renders header BMP graphic with a pure white background (255, 255, 255) Renders header BMP graphic with a pure white background (255, 255, 255)
matching the installer wizard dialog background seamlessly. matching the installer wizard dialog background seamlessly.
The logo is positioned on the left with clean alpha-blended edges. The logo is positioned on the right (icon_center_x = w - 32, center y = h / 2)
allowing the header title and subtext to align cleanly to the far left.
""" """
bg_r, bg_g, bg_b = 255, 255, 255 bg_r, bg_g, bg_b = 255, 255, 255
# Position emblem cleanly on the left (center x = 30, center y = h / 2) # Position emblem cleanly on the right
icon_center_x = 28 icon_center_x = w - 32
icon_center_y = h / 2 icon_center_y = h / 2
icon_radius = 22 icon_radius = 22
dx = (x - icon_center_x) / icon_radius dx = (x - icon_center_x) / icon_radius
@@ -192,7 +199,7 @@ def draw_wizard_sidebar_bgr(x: int, y: int, w: int, h: int) -> tuple:
"""Renders 164x314 BMP sidebar graphic for NSIS / Inno Setup.""" """Renders 164x314 BMP sidebar graphic for NSIS / Inno Setup."""
t_y = y / h t_y = y / h
# Deep cyber navy to midnight blue vertical gradient # Deep navy to midnight blue vertical gradient
r = int(10 * (1 - t_y) + 4 * t_y) r = int(10 * (1 - t_y) + 4 * t_y)
g = int(22 * (1 - t_y) + 12 * t_y) g = int(22 * (1 - t_y) + 12 * t_y)
b = int(48 * (1 - t_y) + 26 * t_y) b = int(48 * (1 - t_y) + 26 * t_y)
@@ -262,9 +269,13 @@ def generate_ico_file(png_images: list) -> bytes:
return header + bytes(entries) + b"".join(image_data_blocks) return header + bytes(entries) + b"".join(image_data_blocks)
def generate_svg_logo() -> str: def generate_svg_logo(transparent_bg: bool = False) -> str:
"""Generates a high-quality SVG vector graphic for LOGAR.""" """Generates a high-quality SVG vector graphic for LOGAR."""
return """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%"> bg_polygon = "" if transparent_bg else """ <!-- Base Hexagon Shield -->
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="url(#bgGrad)" stroke="url(#cyanGrad)" stroke-width="8" filter="url(#glow)"/>"""
return f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs> <defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%"> <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/> <stop offset="0%" stop-color="#0E1A2E"/>
@@ -288,9 +299,7 @@ def generate_svg_logo() -> str:
</filter> </filter>
</defs> </defs>
<!-- Base Hexagon Shield --> {bg_polygon}
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="url(#bgGrad)" stroke="url(#cyanGrad)" stroke-width="8" filter="url(#glow)"/>
<!-- Inner Telemetry Grid / Ring --> <!-- Inner Telemetry Grid / Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156" <polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
@@ -309,56 +318,150 @@ def generate_svg_logo() -> str:
</svg>""" </svg>"""
def generate_svg_banner() -> str:
"""Generates a wide SVG banner with emblem and modern typography."""
return """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 240" width="100%" height="100%">
<defs>
<linearGradient id="bannerBg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0E1A2E"/>
<stop offset="100%" stop-color="#070C18"/>
</linearGradient>
<linearGradient id="textCyan" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#00FFA3"/>
</linearGradient>
<linearGradient id="cyanGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#0072FF"/>
</linearGradient>
<linearGradient id="emeraldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00FFA3"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
<filter id="bannerGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Background Card -->
<rect width="960" height="240" rx="16" fill="url(#bannerBg)" stroke="#162A45" stroke-width="2"/>
<!-- Left Emblem Group (scaled & positioned) -->
<g transform="translate(40, 20) scale(0.39)">
<!-- Hexagon Shield -->
<polygon points="256,24 456,139 456,373 256,488 56,373 56,139"
fill="#0E1A2E" stroke="url(#cyanGrad)" stroke-width="12" filter="url(#bannerGlow)"/>
<!-- Inner Telemetry Ring -->
<polygon points="256,58 426,156 426,356 256,454 86,356 86,156"
fill="none" stroke="#162A45" stroke-width="4" stroke-dasharray="10 8"/>
<!-- Stylized L & Telemetry Bars -->
<rect x="140" y="140" width="46" height="232" rx="10" fill="url(#cyanGrad)"/>
<rect x="140" y="326" width="220" height="46" rx="10" fill="url(#cyanGrad)"/>
<rect x="220" y="210" width="36" height="80" rx="8" fill="url(#emeraldGrad)"/>
<rect x="286" y="150" width="36" height="140" rx="8" fill="url(#cyanGrad)"/>
<!-- Connection Points -->
<circle cx="238" cy="190" r="9" fill="#00FFA3"/>
<circle cx="304" cy="130" r="9" fill="#00E5FF"/>
<circle cx="380" cy="349" r="10" fill="#00E5FF"/>
</g>
<!-- Typography & Tagline -->
<text x="270" y="98" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" font-size="56" font-weight="900" letter-spacing="4" fill="url(#textCyan)" filter="url(#bannerGlow)">LOGAR</text>
<text x="272" y="142" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" font-size="20" font-weight="600" fill="#E2E8F0" letter-spacing="0.5">Fleet Log Aggregation &amp; Telemetry Hub</text>
<text x="272" y="174" font-family="'JetBrains Mono', 'Fira Code', monospace" font-size="13" font-weight="400" fill="#00E5FF" letter-spacing="1">ZERO-STATE FORWARDERS &#8226; MUTUAL TLS 1.3 &#8226; TEMPORAL PERSISTENCE</text>
</svg>"""
def main(): def main():
print("[*] Generating high-resolution branding assets for LOGAR...") print("[*] Generating comprehensive branding assets for LOGAR...")
# 1. Generate SVG Logo # 1. Generate SVG Logos
svg_path = os.path.join(ASSETS_DIR, "logo.svg") svg_logo_dark = generate_svg_logo(transparent_bg=False)
with open(svg_path, "w", encoding="utf-8") as f: svg_logo_trans = generate_svg_logo(transparent_bg=True)
f.write(generate_svg_logo()) svg_banner = generate_svg_banner()
print(f"[+] Saved SVG logo: {svg_path}")
# 2. Generate PNG images of various sizes # 2. Generate PNG images across multiple dimensions
png_sizes = [16, 32, 48, 64, 128, 256, 512] png_sizes = [16, 32, 48, 64, 128, 256, 512]
png_buffers = [] png_buffers = []
png_dict = {}
for s in png_sizes: for s in png_sizes:
png_data = create_png_rgba(s, s, draw_logo_rgba) png_data = create_png_rgba(s, s, draw_logo_rgba)
png_buffers.append((s, s, png_data)) png_buffers.append((s, s, png_data))
if s in [256, 512]: png_dict[s] = png_data
out_p = os.path.join(ASSETS_DIR, f"logo_{s}x{s}.png" if s != 512 else "logo.png")
with open(out_p, "wb") as f:
f.write(png_data)
print(f"[+] Saved PNG logo: {out_p}")
# 3. Generate Windows ICO File # 3. Generate Windows ICO File (multi-res 16..256)
ico_path = os.path.join(ASSETS_DIR, "logo.ico") ico_data = generate_ico_file(png_buffers[:-1])
ico_data = generate_ico_file(png_buffers[:-1]) # Up to 256x256
with open(ico_path, "wb") as f:
f.write(ico_data)
print(f"[+] Saved Windows Icon (.ico): {ico_path}")
# 4. Generate NSIS / Inno Setup Wizard Header Bitmap (150x57) with seamless white background # 4. Generate BMP files for installers
header_bmp_path = os.path.join(ASSETS_DIR, "wizard_header.bmp")
header_bmp_data = create_bmp_24bit(150, 57, draw_wizard_header_white_bgr) header_bmp_data = create_bmp_24bit(150, 57, draw_wizard_header_white_bgr)
with open(header_bmp_path, "wb") as f:
f.write(header_bmp_data)
print(f"[+] Saved Wizard Header Bitmap (White Background): {header_bmp_path}")
# 5. Generate Inno Setup Small Wizard Bitmap (55x55) with seamless white background
small_bmp_path = os.path.join(ASSETS_DIR, "wizard_small.bmp")
small_bmp_data = create_bmp_24bit(55, 55, draw_wizard_small_bgr) small_bmp_data = create_bmp_24bit(55, 55, draw_wizard_small_bgr)
with open(small_bmp_path, "wb") as f:
f.write(small_bmp_data)
print(f"[+] Saved Small Wizard Bitmap (55x55): {small_bmp_path}")
# 6. Generate NSIS / Inno Setup Wizard Sidebar Bitmap (164x314)
sidebar_bmp_path = os.path.join(ASSETS_DIR, "wizard_sidebar.bmp")
sidebar_bmp_data = create_bmp_24bit(164, 314, draw_wizard_sidebar_bgr) sidebar_bmp_data = create_bmp_24bit(164, 314, draw_wizard_sidebar_bgr)
with open(sidebar_bmp_path, "wb") as f:
f.write(sidebar_bmp_data)
print(f"[+] Saved Wizard Sidebar Bitmap: {sidebar_bmp_path}")
print("[+] All LOGAR branding and installer graphics generated successfully.") # 5. Generate Palette Swatch SVGs
swatches = {
"00e5ff": "#00E5FF",
"00ffa3": "#00FFA3",
"0072ff": "#0072FF",
"0e1a2e": "#0E1A2E",
"070c18": "#070C18",
"ef4444": "#EF4444",
"f59e0b": "#F59E0B",
"94a3b8": "#94A3B8",
"ffffff": "#FFFFFF",
}
swatch_svgs = {}
for name, hex_code in swatches.items():
border = "#334155" if hex_code.upper() != "#070C18" else "#1e293b"
swatch_svgs[name] = f'<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="16" height="16" rx="4" fill="{hex_code}" stroke="{border}" stroke-width="1.5"/></svg>'
# 6. Write to target directories
for target_dir in (ROOT_ASSETS_DIR, COMPILATION_ASSETS_DIR, LICENSING_ASSETS_DIR):
swatch_dir = os.path.join(target_dir, "swatches")
os.makedirs(swatch_dir, exist_ok=True)
# Swatches
for name, svg_content in swatch_svgs.items():
with open(os.path.join(swatch_dir, f"swatch_{name}.svg"), "w", encoding="utf-8") as f:
f.write(svg_content)
# SVGs
with open(os.path.join(target_dir, "logo.svg"), "w", encoding="utf-8") as f:
f.write(svg_logo_dark)
with open(os.path.join(target_dir, "logo_dark.svg"), "w", encoding="utf-8") as f:
f.write(svg_logo_dark)
with open(os.path.join(target_dir, "logo_transparent.svg"), "w", encoding="utf-8") as f:
f.write(svg_logo_trans)
with open(os.path.join(target_dir, "logo_banner.svg"), "w", encoding="utf-8") as f:
f.write(svg_banner)
# PNGs
with open(os.path.join(target_dir, "logo.png"), "wb") as f:
f.write(png_dict[512])
for s in png_sizes:
with open(os.path.join(target_dir, f"logo_{s}x{s}.png"), "wb") as f:
f.write(png_dict[s])
# ICOs
with open(os.path.join(target_dir, "logo.ico"), "wb") as f:
f.write(ico_data)
with open(os.path.join(target_dir, "favicon.ico"), "wb") as f:
f.write(ico_data)
# BMPs
with open(os.path.join(target_dir, "wizard_header.bmp"), "wb") as f:
f.write(header_bmp_data)
with open(os.path.join(target_dir, "wizard_small.bmp"), "wb") as f:
f.write(small_bmp_data)
with open(os.path.join(target_dir, "wizard_sidebar.bmp"), "wb") as f:
f.write(sidebar_bmp_data)
print(f"[+] Populated asset repository: {target_dir}")
print("[+] All LOGAR branding assets, icons, and banners generated successfully!")
if __name__ == "__main__": if __name__ == "__main__":
+20
View File
@@ -7,6 +7,26 @@ ENROLL_SECRET="${2:-}"
INSTALL_DIR="/opt/logar-client" INSTALL_DIR="/opt/logar-client"
CONFIG_DIR="/etc/logar" CONFIG_DIR="/etc/logar"
# 1. Mutual Exclusion Check: Deny installation if LOGAR Server is present
if [ -f "/opt/logar-server/Server" ] || [ -f "/etc/systemd/system/logar-server.service" ]; then
echo "[-] Error: LOGAR Server is already installed on this machine." >&2
echo "[-] LOGAR Client cannot be installed on a host where LOGAR Server is present (LOGAR Server includes built-in log collection)." >&2
echo "[-] Please uninstall LOGAR Server first before installing LOGAR Client." >&2
exit 1
fi
# 2. Existing LOGAR Client Detection for In-Place Update Mode
IS_UPDATE=0
if [ -f "${INSTALL_DIR}/Linux_Client" ] || [ -f "/etc/systemd/system/logar-client.service" ]; then
IS_UPDATE=1
echo "[*] Existing LOGAR Client installation detected. Performing in-place upgrade..."
fi
if [ "${IS_UPDATE}" -eq 1 ] && command -v systemctl >/dev/null 2>&1; then
echo "[*] Stopping LOGAR Client service for upgrade..."
systemctl stop logar-client.service || true
fi
echo "[+] Installing LOGAR Client..." echo "[+] Installing LOGAR Client..."
mkdir -p "${INSTALL_DIR}" "${CONFIG_DIR}/certs" mkdir -p "${INSTALL_DIR}" "${CONFIG_DIR}/certs"
chmod 700 "${CONFIG_DIR}/certs" chmod 700 "${CONFIG_DIR}/certs"
+22 -2
View File
@@ -21,14 +21,34 @@ echo "============================================================"
echo " LOGAR Server Enterprise Linux Installer" echo " LOGAR Server Enterprise Linux Installer"
echo "============================================================" echo "============================================================"
# Interactive configuration if running in a terminal without CLI arguments # 1. Mutual Exclusion Check: Deny installation if LOGAR Client is present
if [ -t 0 ]; then if [ -f "/opt/logar-client/Linux_Client" ] || [ -f "/etc/systemd/system/logar-client.service" ]; then
echo "[-] Error: LOGAR Client is already installed on this machine." >&2
echo "[-] LOGAR Server cannot be installed on a host where LOGAR Client is present (LOGAR Server includes built-in log collection)." >&2
echo "[-] Please uninstall LOGAR Client first before installing LOGAR Server." >&2
exit 1
fi
# 2. Existing LOGAR Server Detection for In-Place Update Mode
IS_UPDATE=0
if [ -f "${INSTALL_DIR}/Server" ] || [ -f "/etc/systemd/system/logar-server.service" ]; then
IS_UPDATE=1
echo "[*] Existing LOGAR Server installation detected. Performing in-place upgrade..."
fi
# Interactive configuration if running in a terminal without CLI arguments (only for fresh install)
if [ "${IS_UPDATE}" -eq 0 ] && [ -t 0 ]; then
read -r -p "Enter Data and Configuration Directory [${CONFIG_DIR}]: " USER_CFG read -r -p "Enter Data and Configuration Directory [${CONFIG_DIR}]: " USER_CFG
if [ -n "${USER_CFG}" ]; then if [ -n "${USER_CFG}" ]; then
CONFIG_DIR="${USER_CFG}" CONFIG_DIR="${USER_CFG}"
fi fi
fi fi
if [ "${IS_UPDATE}" -eq 1 ] && command -v systemctl >/dev/null 2>&1; then
echo "[*] Stopping LOGAR Server service for upgrade..."
systemctl stop logar-server.service || true
fi
mkdir -p "${INSTALL_DIR}" "${CONFIG_DIR}" "/var/log/logar" mkdir -p "${INSTALL_DIR}" "${CONFIG_DIR}" "/var/log/logar"
if [ -f "dist/Server.bin" ]; then if [ -f "dist/Server.bin" ]; then
+86 -10
View File
@@ -1,11 +1,13 @@
[Setup] [Setup]
AppId={{9C4A6F1D-8975-476E-8512-4217DE029A88}}
AppName=LOGAR Client AppName=LOGAR Client
AppVersion=0.3.2 AppVersion=0.3.4.2
AppPublisher=LOGAR Security Systems AppPublisher=LOGAR
AppPublisherURL=https://logar.eibl.tech AppPublisherURL=https://logar.eibl.tech
AppSupportURL=https://logar.eibl.tech AppSupportURL=https://logar.eibl.tech
AppUpdatesURL=https://logar.eibl.tech AppUpdatesURL=https://logar.eibl.tech
DefaultDirName={autopf}\LOGAR DefaultDirName={autopf}\LOGAR
UsePreviousAppDir=yes
OutputDir=..\dist OutputDir=..\dist
OutputBaseFilename=LOGAR-Client-Setup OutputBaseFilename=LOGAR-Client-Setup
PrivilegesRequired=admin PrivilegesRequired=admin
@@ -15,11 +17,11 @@ WizardStyle=modern
SetupIconFile=assets\logo.ico SetupIconFile=assets\logo.ico
WizardImageFile=assets\wizard_sidebar.bmp WizardImageFile=assets\wizard_sidebar.bmp
WizardSmallImageFile=assets\wizard_small.bmp WizardSmallImageFile=assets\wizard_small.bmp
VersionInfoCompany=LOGAR Security Systems VersionInfoCompany=LOGAR
VersionInfoDescription=LOGAR Windows Edge Log Forwarder Setup VersionInfoDescription=LOGAR Windows Edge Log Forwarder Setup
VersionInfoVersion=0.3.2.0 VersionInfoVersion=0.3.4.2
VersionInfoProductName=LOGAR Client VersionInfoProductName=LOGAR Client
VersionInfoProductVersion=0.3.2 VersionInfoProductVersion=0.3.4.2
[Files] [Files]
Source: "..\dist\Win_Client.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\dist\Win_Client.exe"; DestDir: "{app}"; Flags: ignoreversion
@@ -54,6 +56,58 @@ Type: filesandordirs; Name: "{commonappdata}\LOGAR"
var var
DataDirPage: TInputDirWizardPage; DataDirPage: TInputDirWizardPage;
ConfigFilePage: TInputFileWizardPage; ConfigFilePage: TInputFileWizardPage;
IsUpdateMode: Boolean;
ExistingDataDir: String;
function InitializeSetup(): Boolean;
var
ServerFound: Boolean;
Val: String;
begin
Result := True;
IsUpdateMode := False;
ExistingDataDir := '';
ServerFound := False;
// 1. Mutual Exclusion Check: Deny installation if LOGAR Server is already present
if RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Server') or
RegValueExists(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Server', 'DataDir') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{4F3D675E-1E4E-43B6-8C84-B47DE290BE4A}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{{4F3D675E-1E4E-43B6-8C84-B47DE290BE4A}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Server_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Server_is1') or
FileExists(ExpandConstant('{autopf}\LOGAR-Server\Server.exe')) then
begin
ServerFound := True;
end;
if ServerFound then
begin
MsgBox('LOGAR Server is already installed on this machine.'#13#10#13#10 +
'LOGAR Client cannot be installed on a LOGAR Server host (LOGAR Server already provides built-in log collection).'#13#10#13#10 +
'Please uninstall LOGAR Server first if you wish to run a standalone forwarder client.',
mbCriticalError, MB_OK);
Result := False;
Exit;
end;
// 2. Existing LOGAR Client Detection for In-Place Update Mode
if RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Client') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{9C4A6F1D-8975-476E-8512-4217DE029A88}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{{9C4A6F1D-8975-476E-8512-4217DE029A88}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Client_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Client_is1') or
FileExists(ExpandConstant('{autopf}\LOGAR\Win_Client.exe')) then
begin
IsUpdateMode := True;
if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Client', 'DataDir', Val) then
ExistingDataDir := Val;
MsgBox('An existing installation of LOGAR Client was detected.'#13#10#13#10 +
'Setup will perform an in-place update: core binaries will be updated, while your existing configuration (client_config.json), state tracking, and certificates will be preserved.',
mbInformation, MB_OK);
end;
end;
procedure InitializeWizard; procedure InitializeWizard;
begin begin
@@ -67,7 +121,10 @@ begin
'' ''
); );
DataDirPage.Add('Data Directory:'); DataDirPage.Add('Data Directory:');
DataDirPage.Values[0] := ExpandConstant('{commonappdata}\LOGAR'); if (ExistingDataDir <> '') then
DataDirPage.Values[0] := ExistingDataDir
else
DataDirPage.Values[0] := ExpandConstant('{commonappdata}\LOGAR');
ConfigFilePage := CreateInputFilePage( ConfigFilePage := CreateInputFilePage(
DataDirPage.ID, DataDirPage.ID,
@@ -80,8 +137,23 @@ begin
ConfigFilePage.Add('Client Configuration (client_config.json):', 'JSON Files (*.json)|*.json|All Files (*.*)|*.*', '.json'); ConfigFilePage.Add('Client Configuration (client_config.json):', 'JSON Files (*.json)|*.json|All Files (*.*)|*.*', '.json');
end; end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False;
if IsUpdateMode then
begin
if (PageID = wpSelectDir) or (PageID = DataDirPage.ID) or (PageID = ConfigFilePage.ID) then
Result := True;
end;
end;
function GetDataDir(Param: String): String; function GetDataDir(Param: String): String;
begin begin
if IsUpdateMode and (ExistingDataDir <> '') then
begin
Result := ExistingDataDir;
Exit;
end;
Result := DataDirPage.Values[0]; Result := DataDirPage.Values[0];
if Result = '' then if Result = '' then
Result := ExpandConstant('{commonappdata}\LOGAR'); Result := ExpandConstant('{commonappdata}\LOGAR');
@@ -107,13 +179,17 @@ begin
end end
else if CurStep = ssPostInstall then else if CurStep = ssPostInstall then
begin begin
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Client', 'InstallDir', ExpandConstant('{app}'));
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Client', 'DataDir', GetDataDir('')); RegWriteStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Client', 'DataDir', GetDataDir(''));
CfgPath := GetConfigFile(''); if not IsUpdateMode then
if (CfgPath <> '') and FileExists(CfgPath) then
begin begin
DestCfg := GetDataDir('') + '\client_config.json'; CfgPath := GetConfigFile('');
FileCopy(CfgPath, DestCfg, False); if (CfgPath <> '') and FileExists(CfgPath) then
begin
DestCfg := GetDataDir('') + '\client_config.json';
FileCopy(CfgPath, DestCfg, False);
end;
end; end;
end; end;
end; end;
+55 -11
View File
@@ -7,26 +7,28 @@ OutFile "../dist/LOGAR-Client-Setup.exe"
InstallDir "$PROGRAMFILES64\LOGAR" InstallDir "$PROGRAMFILES64\LOGAR"
RequestExecutionLevel admin RequestExecutionLevel admin
BrandingText "LOGAR Centralized Log Aggregator & Forwarder" BrandingText "LOGAR Centralized Log Aggregator && Forwarder"
VIProductVersion "0.3.2.0" VIProductVersion "0.3.4.2"
VIAddVersionKey "ProductName" "LOGAR Client" VIAddVersionKey "ProductName" "LOGAR Client"
VIAddVersionKey "CompanyName" "LOGAR Security Systems" VIAddVersionKey "CompanyName" "LOGAR"
VIAddVersionKey "LegalCopyright" "© 2026 LOGAR Security Systems" VIAddVersionKey "LegalCopyright" "© 2026 LOGAR"
VIAddVersionKey "FileDescription" "LOGAR Windows Edge Log Forwarder Setup" VIAddVersionKey "FileDescription" "LOGAR Windows Edge Log Forwarder Setup"
VIAddVersionKey "FileVersion" "0.3.2.0" VIAddVersionKey "FileVersion" "0.3.4.2"
VIAddVersionKey "ProductVersion" "0.3.2.0" VIAddVersionKey "ProductVersion" "0.3.4.2"
# --- Visual Graphics & Branding --- # --- Visual Graphics & Branding ---
!define MUI_ICON "assets/logo.ico" !define MUI_ICON "assets/logo.ico"
!define MUI_UNICON "assets/logo.ico" !define MUI_UNICON "assets/logo.ico"
!define MUI_HEADERIMAGE !define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_RIGHT
!define MUI_HEADERIMAGE_BITMAP "assets/wizard_header.bmp" !define MUI_HEADERIMAGE_BITMAP "assets/wizard_header.bmp"
!define MUI_WELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp" !define MUI_WELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp" !define MUI_UNWELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp"
Var DATADIR Var DATADIR
Var CFGFILE Var CFGFILE
Var IS_UPDATE
Var CfgDialog Var CfgDialog
Var CfgFileInput Var CfgFileInput
Var CfgBrowseBtn Var CfgBrowseBtn
@@ -42,6 +44,7 @@ Var CfgBrowseBtn
# Directory Page 1: Installation Directory # Directory Page 1: Installation Directory
!define MUI_PAGE_HEADER_TEXT "Choose Installation Location" !define MUI_PAGE_HEADER_TEXT "Choose Installation Location"
!define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install program binaries." !define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install program binaries."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfUpdate
!define MUI_DIRECTORYPAGE_VARIABLE $INSTDIR !define MUI_DIRECTORYPAGE_VARIABLE $INSTDIR
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
@@ -50,6 +53,7 @@ Var CfgBrowseBtn
!define MUI_PAGE_HEADER_SUBTEXT "Choose where state tracking, certificates, and configuration files should be stored." !define MUI_PAGE_HEADER_SUBTEXT "Choose where state tracking, certificates, and configuration files should be stored."
!define MUI_DIRECTORYPAGE_TEXT_TOP "Choose the folder where LOGAR Client should store state tracking (client_state.json), configuration (client_config.json), and mTLS certificates.$\r$\n$\r$\nDefault is recommended for automatic background service access." !define MUI_DIRECTORYPAGE_TEXT_TOP "Choose the folder where LOGAR Client should store state tracking (client_state.json), configuration (client_config.json), and mTLS certificates.$\r$\n$\r$\nDefault is recommended for automatic background service access."
!define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Data & Storage Folder" !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Data & Storage Folder"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfUpdate
!define MUI_DIRECTORYPAGE_VARIABLE $DATADIR !define MUI_DIRECTORYPAGE_VARIABLE $DATADIR
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
@@ -70,13 +74,51 @@ Page custom ConfigPageCreate ConfigPageLeave
!insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "English"
Function SkipIfUpdate
${If} $IS_UPDATE == "1"
Abort
${EndIf}
FunctionEnd
Function .onInit Function .onInit
SetShellVarContext all SetShellVarContext all
StrCpy $DATADIR "$APPDATA\LOGAR" StrCpy $DATADIR "$APPDATA\LOGAR"
StrCpy $CFGFILE "" StrCpy $CFGFILE ""
StrCpy $IS_UPDATE "0"
# 1. Mutual Exclusion Check: Deny installation if LOGAR Server is present
ReadRegStr $0 HKLM "Software\LOGAR\LOGAR-Server" "DataDir"
ReadRegStr $1 HKLM "Software\LOGAR\LOGAR-Server" "InstallDir"
${If} $0 != ""
${OrIf} $1 != ""
${OrIf} ${FileExists} "$PROGRAMFILES64\LOGAR-Server\Server.exe"
${OrIf} ${FileExists} "$PROGRAMFILES\LOGAR-Server\Server.exe"
MessageBox MB_ICONSTOP|MB_OK "LOGAR Server is already installed on this machine.$\r$\n$\r$\nLOGAR Client cannot be co-installed on a LOGAR Server host (LOGAR Server already provides built-in log collection).$\r$\n$\r$\nPlease uninstall LOGAR Server first if you wish to run a standalone forwarder client."
Abort
${EndIf}
# 2. Existing LOGAR Client Detection for In-Place Update Mode
ReadRegStr $0 HKLM "Software\LOGAR\LOGAR-Client" "DataDir"
ReadRegStr $1 HKLM "Software\LOGAR\LOGAR-Client" "InstallDir"
${If} $0 != ""
${OrIf} $1 != ""
${OrIf} ${FileExists} "$INSTDIR\Win_Client.exe"
StrCpy $IS_UPDATE "1"
${If} $0 != ""
StrCpy $DATADIR $0
${EndIf}
${If} $1 != ""
StrCpy $INSTDIR $1
${EndIf}
MessageBox MB_ICONINFORMATION|MB_OK "An existing installation of LOGAR Client was detected.$\r$\n$\r$\nSetup will perform an in-place update: program files will be updated while your existing configuration (client_config.json), state tracking, and certificates are preserved."
${EndIf}
FunctionEnd FunctionEnd
Function ConfigPageCreate Function ConfigPageCreate
${If} $IS_UPDATE == "1"
Abort
${EndIf}
nsDialogs::Create 1018 nsDialogs::Create 1018
Pop $CfgDialog Pop $CfgDialog
${If} $CfgDialog == error ${If} $CfgDialog == error
@@ -85,13 +127,13 @@ Function ConfigPageCreate
!insertmacro MUI_HEADER_TEXT "Import Client Configuration (Optional)" "Choose a client_config.json profile exported from LOGAR Server." !insertmacro MUI_HEADER_TEXT "Import Client Configuration (Optional)" "Choose a client_config.json profile exported from LOGAR Server."
${NSD_CreateLabel} 0 0 100% 30u "If you have exported a client_config.json profile bundle from your LOGAR Server dashboard, select it below to configure the forwarder immediately.$\r$\n$\r$\nLeave this field blank to configure later." ${NSD_CreateLabel} 0 0 100% 48u "If you have exported a client_config.json profile bundle from your LOGAR Server dashboard, select it below to configure the forwarder immediately.$\r$\n$\r$\nLeave this field blank to configure later."
Pop $0 Pop $0
${NSD_CreateText} 0 38u 75% 12u "$CFGFILE" ${NSD_CreateText} 0 54u 75% 14u "$CFGFILE"
Pop $CfgFileInput Pop $CfgFileInput
${NSD_CreateButton} 78% 37u 22% 14u "Browse..." ${NSD_CreateButton} 78% 53u 22% 16u "Browse..."
Pop $CfgBrowseBtn Pop $CfgBrowseBtn
${NSD_OnClick} $CfgBrowseBtn OnBrowseCfg ${NSD_OnClick} $CfgBrowseBtn OnBrowseCfg
@@ -129,8 +171,9 @@ Section "Install"
CreateDirectory "$DATADIR" CreateDirectory "$DATADIR"
ExecWait 'icacls "$DATADIR" /grant "*S-1-5-32-545:(OI)(CI)M" /T /Q' ExecWait 'icacls "$DATADIR" /grant "*S-1-5-32-545:(OI)(CI)M" /T /Q'
# If client config file was selected, copy to data directory # If client config file was selected and not updating existing config, copy to data directory
${If} $CFGFILE != "" ${If} $IS_UPDATE != "1"
${AndIf} $CFGFILE != ""
${AndIf} ${FileExists} "$CFGFILE" ${AndIf} ${FileExists} "$CFGFILE"
CopyFiles /SILENT "$CFGFILE" "$DATADIR\client_config.json" CopyFiles /SILENT "$CFGFILE" "$DATADIR\client_config.json"
${EndIf} ${EndIf}
@@ -144,6 +187,7 @@ Section "Install"
ExecWait '"$INSTDIR\nssm.exe" start LOGAR_Client' ExecWait '"$INSTDIR\nssm.exe" start LOGAR_Client'
WriteUninstaller "$INSTDIR\uninstall.exe" WriteUninstaller "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\LOGAR\LOGAR-Client" "InstallDir" "$INSTDIR"
WriteRegStr HKLM "Software\LOGAR\LOGAR-Client" "DataDir" "$DATADIR" WriteRegStr HKLM "Software\LOGAR\LOGAR-Client" "DataDir" "$DATADIR"
SectionEnd SectionEnd
+79 -6
View File
@@ -1,11 +1,13 @@
[Setup] [Setup]
AppId={{4F3D675E-1E4E-43B6-8C84-B47DE290BE4A}}
AppName=LOGAR Server AppName=LOGAR Server
AppVersion=0.3.2 AppVersion=0.3.4.2
AppPublisher=LOGAR Security Systems AppPublisher=LOGAR
AppPublisherURL=https://logar.eibl.tech AppPublisherURL=https://logar.eibl.tech
AppSupportURL=https://logar.eibl.tech AppSupportURL=https://logar.eibl.tech
AppUpdatesURL=https://logar.eibl.tech AppUpdatesURL=https://logar.eibl.tech
DefaultDirName={autopf}\LOGAR-Server DefaultDirName={autopf}\LOGAR-Server
UsePreviousAppDir=yes
OutputDir=..\dist OutputDir=..\dist
OutputBaseFilename=LOGAR-Server-Setup OutputBaseFilename=LOGAR-Server-Setup
PrivilegesRequired=admin PrivilegesRequired=admin
@@ -15,11 +17,11 @@ WizardStyle=modern
SetupIconFile=assets\logo.ico SetupIconFile=assets\logo.ico
WizardImageFile=assets\wizard_sidebar.bmp WizardImageFile=assets\wizard_sidebar.bmp
WizardSmallImageFile=assets\wizard_small.bmp WizardSmallImageFile=assets\wizard_small.bmp
VersionInfoCompany=LOGAR Security Systems VersionInfoCompany=LOGAR
VersionInfoDescription=LOGAR Cloud Hub & TCP Ingestion Server Setup VersionInfoDescription=LOGAR Cloud Hub & TCP Ingestion Server Setup
VersionInfoVersion=0.3.2.0 VersionInfoVersion=0.3.4.2
VersionInfoProductName=LOGAR Server VersionInfoProductName=LOGAR Server
VersionInfoProductVersion=0.3.2 VersionInfoProductVersion=0.3.4.2
[Files] [Files]
Source: "..\dist\Server.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\dist\Server.exe"; DestDir: "{app}"; Flags: ignoreversion
@@ -54,6 +56,58 @@ Type: filesandordirs; Name: "{commonappdata}\LOGAR-Server"
[Code] [Code]
var var
DataDirPage: TInputDirWizardPage; DataDirPage: TInputDirWizardPage;
IsUpdateMode: Boolean;
ExistingDataDir: String;
function InitializeSetup(): Boolean;
var
ClientFound: Boolean;
Val: String;
begin
Result := True;
IsUpdateMode := False;
ExistingDataDir := '';
ClientFound := False;
// 1. Mutual Exclusion Check: Deny installation if LOGAR Client is already present
if RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Client') or
RegValueExists(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Client', 'DataDir') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{9C4A6F1D-8975-476E-8512-4217DE029A88}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{{9C4A6F1D-8975-476E-8512-4217DE029A88}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Client_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Client_is1') or
FileExists(ExpandConstant('{autopf}\LOGAR\Win_Client.exe')) then
begin
ClientFound := True;
end;
if ClientFound then
begin
MsgBox('LOGAR Client is already installed on this machine.'#13#10#13#10 +
'LOGAR Server cannot be installed on a host where LOGAR Client is present (LOGAR Server already includes built-in local log collection).'#13#10#13#10 +
'Please uninstall LOGAR Client before installing LOGAR Server.',
mbCriticalError, MB_OK);
Result := False;
Exit;
end;
// 2. Existing LOGAR Server Detection for In-Place Update Mode
if RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Server') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{4F3D675E-1E4E-43B6-8C84-B47DE290BE4A}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{{4F3D675E-1E4E-43B6-8C84-B47DE290BE4A}}_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Server_is1') or
RegKeyExists(HKEY_LOCAL_MACHINE, 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\LOGAR Server_is1') or
FileExists(ExpandConstant('{autopf}\LOGAR-Server\Server.exe')) then
begin
IsUpdateMode := True;
if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Server', 'DataDir', Val) then
ExistingDataDir := Val;
MsgBox('An existing installation of LOGAR Server was detected.'#13#10#13#10 +
'Setup will perform an in-place update: core binaries will be updated, while your existing configuration (server_config.json), database (logar_state.db), and certificates will be preserved.',
mbInformation, MB_OK);
end;
end;
procedure InitializeWizard; procedure InitializeWizard;
begin begin
@@ -67,11 +121,29 @@ begin
'' ''
); );
DataDirPage.Add('Data Directory:'); DataDirPage.Add('Data Directory:');
DataDirPage.Values[0] := ExpandConstant('{commonappdata}\LOGAR-Server'); if (ExistingDataDir <> '') then
DataDirPage.Values[0] := ExistingDataDir
else
DataDirPage.Values[0] := ExpandConstant('{commonappdata}\LOGAR-Server');
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False;
if IsUpdateMode then
begin
if (PageID = wpSelectDir) or (PageID = DataDirPage.ID) then
Result := True;
end;
end; end;
function GetDataDir(Param: String): String; function GetDataDir(Param: String): String;
begin begin
if IsUpdateMode and (ExistingDataDir <> '') then
begin
Result := ExistingDataDir;
Exit;
end;
Result := DataDirPage.Values[0]; Result := DataDirPage.Values[0];
if Result = '' then if Result = '' then
Result := ExpandConstant('{commonappdata}\LOGAR-Server'); Result := ExpandConstant('{commonappdata}\LOGAR-Server');
@@ -91,6 +163,7 @@ begin
end end
else if CurStep = ssPostInstall then else if CurStep = ssPostInstall then
begin begin
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Server', 'InstallDir', ExpandConstant('{app}'));
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Server', 'DataDir', GetDataDir('')); RegWriteStringValue(HKEY_LOCAL_MACHINE, 'Software\LOGAR\LOGAR-Server', 'DataDir', GetDataDir(''));
end; end;
end; end;
+45 -6
View File
@@ -6,25 +6,27 @@ OutFile "../dist/LOGAR-Server-Setup.exe"
InstallDir "$PROGRAMFILES64\LOGAR-Server" InstallDir "$PROGRAMFILES64\LOGAR-Server"
RequestExecutionLevel admin RequestExecutionLevel admin
BrandingText "LOGAR Centralized Log Aggregator & Ingestion Hub" BrandingText "LOGAR Centralized Log Aggregator && Ingestion Hub"
VIProductVersion "0.3.2.0" VIProductVersion "0.3.4.2"
VIAddVersionKey "ProductName" "LOGAR Server" VIAddVersionKey "ProductName" "LOGAR Server"
VIAddVersionKey "CompanyName" "LOGAR Security Systems" VIAddVersionKey "CompanyName" "LOGAR"
VIAddVersionKey "LegalCopyright" "© 2026 LOGAR Security Systems" VIAddVersionKey "LegalCopyright" "© 2026 LOGAR"
VIAddVersionKey "FileDescription" "LOGAR Cloud Hub & TCP Ingestion Server Setup" VIAddVersionKey "FileDescription" "LOGAR Cloud Hub & TCP Ingestion Server Setup"
VIAddVersionKey "FileVersion" "0.3.2.0" VIAddVersionKey "FileVersion" "0.3.4.2"
VIAddVersionKey "ProductVersion" "0.3.2.0" VIAddVersionKey "ProductVersion" "0.3.4.2"
# --- Visual Graphics & Branding --- # --- Visual Graphics & Branding ---
!define MUI_ICON "assets/logo.ico" !define MUI_ICON "assets/logo.ico"
!define MUI_UNICON "assets/logo.ico" !define MUI_UNICON "assets/logo.ico"
!define MUI_HEADERIMAGE !define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_RIGHT
!define MUI_HEADERIMAGE_BITMAP "assets/wizard_header.bmp" !define MUI_HEADERIMAGE_BITMAP "assets/wizard_header.bmp"
!define MUI_WELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp" !define MUI_WELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp" !define MUI_UNWELCOMEFINISHPAGE_BITMAP "assets/wizard_sidebar.bmp"
Var DATADIR Var DATADIR
Var IS_UPDATE
# --- UI Pages --- # --- UI Pages ---
!define MUI_ABORTWARNING !define MUI_ABORTWARNING
@@ -37,6 +39,7 @@ Var DATADIR
# Directory Page 1: Installation Directory # Directory Page 1: Installation Directory
!define MUI_PAGE_HEADER_TEXT "Choose Installation Location" !define MUI_PAGE_HEADER_TEXT "Choose Installation Location"
!define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install program binaries." !define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install program binaries."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfUpdate
!define MUI_DIRECTORYPAGE_VARIABLE $INSTDIR !define MUI_DIRECTORYPAGE_VARIABLE $INSTDIR
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
@@ -45,6 +48,7 @@ Var DATADIR
!define MUI_PAGE_HEADER_SUBTEXT "Choose where database, certificates, and configuration files should be stored." !define MUI_PAGE_HEADER_SUBTEXT "Choose where database, certificates, and configuration files should be stored."
!define MUI_DIRECTORYPAGE_TEXT_TOP "Choose the folder where LOGAR Server should store its database (logar_state.db), configuration (server_config.json), and mTLS certificates.$\r$\n$\r$\nDefault is recommended for automatic background service access." !define MUI_DIRECTORYPAGE_TEXT_TOP "Choose the folder where LOGAR Server should store its database (logar_state.db), configuration (server_config.json), and mTLS certificates.$\r$\n$\r$\nDefault is recommended for automatic background service access."
!define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Data & Storage Folder" !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Data & Storage Folder"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfUpdate
!define MUI_DIRECTORYPAGE_VARIABLE $DATADIR !define MUI_DIRECTORYPAGE_VARIABLE $DATADIR
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
@@ -62,9 +66,43 @@ Var DATADIR
!insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "English"
Function SkipIfUpdate
${If} $IS_UPDATE == "1"
Abort
${EndIf}
FunctionEnd
Function .onInit Function .onInit
SetShellVarContext all SetShellVarContext all
StrCpy $DATADIR "$APPDATA\LOGAR-Server" StrCpy $DATADIR "$APPDATA\LOGAR-Server"
StrCpy $IS_UPDATE "0"
# 1. Mutual Exclusion Check: Deny installation if LOGAR Client is present
ReadRegStr $0 HKLM "Software\LOGAR\LOGAR-Client" "DataDir"
ReadRegStr $1 HKLM "Software\LOGAR\LOGAR-Client" "InstallDir"
${If} $0 != ""
${OrIf} $1 != ""
${OrIf} ${FileExists} "$PROGRAMFILES64\LOGAR\Win_Client.exe"
${OrIf} ${FileExists} "$PROGRAMFILES\LOGAR\Win_Client.exe"
MessageBox MB_ICONSTOP|MB_OK "LOGAR Client is already installed on this machine.$\r$\n$\r$\nLOGAR Server cannot be installed on a host where LOGAR Client is present (LOGAR Server already includes built-in local log collection).$\r$\n$\r$\nPlease uninstall LOGAR Client before installing LOGAR Server."
Abort
${EndIf}
# 2. Existing LOGAR Server Detection for In-Place Update Mode
ReadRegStr $0 HKLM "Software\LOGAR\LOGAR-Server" "DataDir"
ReadRegStr $1 HKLM "Software\LOGAR\LOGAR-Server" "InstallDir"
${If} $0 != ""
${OrIf} $1 != ""
${OrIf} ${FileExists} "$INSTDIR\Server.exe"
StrCpy $IS_UPDATE "1"
${If} $0 != ""
StrCpy $DATADIR $0
${EndIf}
${If} $1 != ""
StrCpy $INSTDIR $1
${EndIf}
MessageBox MB_ICONINFORMATION|MB_OK "An existing installation of LOGAR Server was detected.$\r$\n$\r$\nSetup will perform an in-place update: program files will be updated while your existing database, configuration (server_config.json), and certificates are preserved."
${EndIf}
FunctionEnd FunctionEnd
Section "Install" Section "Install"
@@ -94,6 +132,7 @@ Section "Install"
ExecWait '"$INSTDIR\nssm.exe" start LOGAR_Server' ExecWait '"$INSTDIR\nssm.exe" start LOGAR_Server'
WriteUninstaller "$INSTDIR\uninstall.exe" WriteUninstaller "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\LOGAR\LOGAR-Server" "InstallDir" "$INSTDIR"
WriteRegStr HKLM "Software\LOGAR\LOGAR-Server" "DataDir" "$DATADIR" WriteRegStr HKLM "Software\LOGAR\LOGAR-Server" "DataDir" "$DATADIR"
SectionEnd SectionEnd
-139
View File
@@ -1,139 +0,0 @@
# LOGAR Linux Edge Forwarder
Standalone compiled binary and automated systemd service distribution for Linux edge servers.
---
## Overview
`Linux_Client.bin` is a self-contained, pre-compiled executable that queries `systemd-journald` via `journalctl`, filters logs directly at the source, auto-enrolls with the central LOGAR hub, and streams candidate events over mutual TLS 1.3 (**mTLS**) to the central hub.
### Key Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone native binary (`Linux_Client.bin`). No Python environment, pip packages, or GnuPG binaries are required on the host.
- **Mutual TLS 1.3 (mTLS) Ingestion**: Streams directly over hardware-authenticated TLS 1.3 sockets with machine-bound client certificates.
- **Automated Client Enrollment**: On first run with an `enrollment_secret`, the client automatically calls `POST /api/client/enroll` on the hub, saves its certificates into `/etc/logar/certs/`, and establishes secure mTLS streaming.
- **Proactive Expiry Check & Reactive Self-Healing**: Before each run, the client evaluates `client.crt` validity. If within 30 days of expiry, it automatically contacts the hub to renew certificates. If the server Root CA rotates or a TLS handshake error occurs, the client catch-heals by re-enrolling immediately and re-establishing connection without human intervention.
- **Source-Level Filtering**: Retains events spanning `INFO`, `WARNING`, and `ERROR` (`journalctl -p warning`). Drops debug noise and skips events older than 24 hours.
- **State Tracking & Deduplication**: Maintains persistent client state in `client_state.json` (tracking systemd journalctl cursors and microsecond timestamps) so every log record is forwarded exactly once without duplicates.
- **Fleet Seat Accounting & License Revocation**: Each enrolled client consumes 1 seat from the hub's master license. If revoked by an administrator via the hub WebUI, socket connections are immediately dropped and blocked until reactivated.
- **Dynamic Seat Expansion & Automatic Hub Synchronization**: The forwarder benefits from the hub's dynamic seat quota expansion; as the hub's license seats are increased and synced with the master authority (`logar.eibl.tech`), edge capacity expands automatically without requiring forwarder configuration changes.
- **Master Authority High Availability (Portainer GitOps)**: The authoritative license server at `logar.eibl.tech` runs containerized via Portainer GitOps with automated pipeline builds, ensuring high availability for client seat verification and renewals.
---
## 1. Automated Installation via Script (Recommended)
Run the automated installer script:
```bash
sudo ./compilation/install_linux_client.sh "http://<HUB_HOST>:8443" "<ENROLLMENT_SECRET>"
```
This script:
1. Installs the binary to `/opt/logar-client/Linux_Client`.
2. Creates `/etc/logar/certs` with strict permissions.
3. Automatically queries `/etc/machine-id` and enrolls with the hub via `curl`.
4. Deploys, enables, and starts the systemd service unit `/etc/systemd/system/logar-client.service`.
---
## 2. Generating & Deploying the Configuration File
### Step 1: Generate `client_config.json` on the Server
Run the following command on your central LOGAR server:
```bash
python src/Server.py --create-client-config --server-host <SERVER_IP_OR_DNS> --server-port 9443 --client-out client_config.json
```
- Replace `<SERVER_IP_OR_DNS>` with the reachable IP address or FQDN of your central LOGAR server hub.
- Default mTLS socket port is `9443`; Hermes REST API port is `8443`.
### Step 2: Configuration Structure
The generated `client_config.json` contains:
```json
{
"server_host": "192.168.1.100",
"server_port": 9443,
"hermes_host": "192.168.1.100",
"hermes_port": 8443,
"enrollment_secret": "a1b2c3d4e5f6...",
"cert_dir": "certs",
"server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2",
"server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"auth_token": "a1b2c3d4e5f6..."
}
```
> [!NOTE]
> The configuration contains **no host-specific names or site names** to ensure client anonymity and easy redistribution.
### Step 3: Copy to Edge Node
Place `Linux_Client.bin` and `client_config.json` into the target directory (e.g. `/opt/logar/`):
```bash
sudo mkdir -p /opt/logar
sudo cp Linux_Client.bin client_config.json /opt/logar/
sudo chmod +x /opt/logar/Linux_Client.bin
```
---
## 3. Running Manually
Test the forwarder interactively:
```bash
cd /opt/logar
./Linux_Client.bin --hours 24
```
On first run, the client contacts `http://<hermes_host>:<hermes_port>/api/client/enroll`, downloads `ca.crt`, `client.crt`, and `client.key` into `certs/`, and streams logs over mTLS.
### Command-Line Arguments
| Argument | Default | Description |
| :--- | :--- | :--- |
| `--config` | `client_config.json` | Path to client configuration file |
| `--hours` | `24` | Lookback window in hours for journal logs |
| `--state-file` | `client_state.json` | Path to persistent state file |
| `--no-state` | `False` | Disable state tracking and send all events matching lookback window |
---
## 4. Manual Systemd Service & Timer Setup
### Step 1: Create the Systemd Service Unit
Create `/etc/systemd/system/logar-client.service`:
```ini
[Unit]
Description=LOGAR Edge Log Forwarder
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/logar
ExecStart=/opt/logar/Linux_Client.bin --hours 24
Restart=always
RestartSec=5s
User=root
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
### Step 2: Enable and Start the Service
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now logar-client.service
```
### Step 3: Check Logs
```bash
sudo journalctl -u logar-client.service -n 50 -f
```
---
## 5. Uninstallation & Removal
```bash
sudo systemctl disable --now logar-client.service
sudo rm -f /etc/systemd/system/logar-client.service
sudo systemctl daemon-reload
sudo rm -rf /opt/logar-client /opt/logar /etc/logar
```
@@ -1,7 +0,0 @@
{
"server_host": "192.168.1.100",
"server_port": 9443,
"server_fingerprint": "PASTE_SERVER_FINGERPRINT_HERE",
"server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----\n",
"auth_token": "PASTE_AUTH_TOKEN_HERE"
}
-160
View File
@@ -1,160 +0,0 @@
# LOGAR Linux Server Hub
Standalone compiled binary and automated systemd service distribution for Linux server environments (`Server.bin`).
---
## Overview
`Server.bin` is a self-contained, pre-compiled Linux ELF executable that operates as the central coordination, log analysis, dynamic PKI, and reporting hub of the LOGAR telemetry architecture.
### Key Architecture & Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone native Linux ELF binary (`Server.bin`). No Python runtime, pip dependencies, or GnuPG binaries are required on the host system.
- **Mutual TLS 1.3 (mTLS) Ingestion (Port 9443)**: Enforces mutual TLS 1.3 authentication for all incoming edge connections. Validates client certificates against an internal Subordinate CA and verifies active licensing in SQLite.
- **Master License Authority (`logar.eibl.tech`) & Seat-Bearing CA**: Integrates with the upstream authority. Subordinate CA certificates embed exact seat quotas via custom enterprise X.509 OID `1.3.6.1.4.1.59999.1.1`.
- **Zero-Touch mTLS Renewals**: Once initial signed CA cert is imported, the server hub automatically executes in-flight mTLS renewals with `https://logar.eibl.tech/api/license/renew` when nearing expiration (< 30 days).
- **Fleet Management & WebUI Dashboard (Port 8443)**: Modern browser WebUI accessible at `http://localhost:8443/webui` to monitor seat quotas, export `.lic` request files, copy CA CSRs, activate signed CA certs, and revoke/reactivate forwarder seats with immediate socket enforcement.
- **Direct Hermes Intelligence & Temporal Telemetry**: All ingested actionable anomalies (`WARNING`, `ERROR`, `INFO`) are immediately promoted to `VERIFIED` and surfaced directly to Hermes with rich temporal metadata (run counts, timestamps) across a rolling 12-hour evaluation window, allowing Hermes AI to perform all contextual reasoning and decisions.
- **Embedded Hermes Reporting API (Port 8443)**: Integrated REST API exposing `/api/hermes/report`, `/api/clients`, and `/api/client/enroll`.
- **State Database**: Stores issue lifecycle records, client telemetry, and licensing quotas in a local SQLite database (`logar_state.db`).
---
## 1. Automated Installation via Script (Recommended)
Deploy using the automated installer:
```bash
sudo ./compilation/install_linux_server.sh
```
This script:
1. Installs the server binary to `/opt/logar-server/Server`.
2. Creates `/etc/logar` and `/var/log/logar`.
3. Deploys, enables, and starts the systemd service unit `/etc/systemd/system/logar-server.service` with `LimitNOFILE=65536`.
---
## 2. Initializing & Generating Server Configuration
### Step 1: Automatic First-Run Generation
When launched without an existing `server_config.json`, `Server.bin` automatically generates:
1. An internal Root CA (`certs/ca.crt` and `certs/ca.key`).
2. A server TLS certificate (`certs/server.crt` and `certs/server.key`) with SANs.
3. An OpenPGP RSA-2048 keypair (`private_key` and `public_key`).
4. Cryptographically random authentication tokens and enrollment secrets.
5. Default network socket coordinates (mTLS 9443, Hermes API 8443).
Run `Server.bin` once to initialize:
```bash
./Server.bin
```
Output:
```
[!] Config 'server_config.json' not found. Initializing first-run configuration...
[+] Successfully generated new server config and OpenPGP keypair.
[+] Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
[+] Saved to: server_config.json
============================================================
LOGAR Server Hub: LOGAR-Cloud-Hub
Transport Security: mTLS (TLS 1.3)
License Quota: 10 Active Seats
Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
Evaluation Window: 12 hours | Direct Hermes Reasoning Enabled
============================================================
[*] LOGAR mTLS TLSv1.3 Socket Server listening on 0.0.0.0:9443
[*] Hermes Reporting API available at http://0.0.0.0:8443/api/hermes/report
[*] Client Enrollment API available at http://0.0.0.0:8443/api/client/enroll
```
### Step 2: Configuration Fields Reference
The generated `server_config.json` contains:
```json
{
"server_name": "LOGAR-Linux-Hub",
"tcp_host": "0.0.0.0",
"tcp_port": 9443,
"hermes_host": "0.0.0.0",
"hermes_port": 8443,
"auth_token": "a1b2c3d4e5f67890abcdef1234567890...",
"enrollment_secret": "e1f2a3b4c5d6...",
"max_seats": 10,
"cert_dir": "certs",
"tls_enabled": true,
"db_path": "logar_state.db",
"evaluation_window_hours": 12,
"min_persistence_runs": 1,
"server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2",
"public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"private_key": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n..."
}
```
---
## 3. Exporting Client Configurations
Generate a client configuration bundle to deploy onto Windows or Linux forwarders:
```bash
./Server.bin --create-client-config --server-host 192.168.1.100 --server-port 9443 --client-out client_config.json
```
The output file contains the server coordinates, enrollment secret, and fingerprint, ready for client deployment.
---
## 4. Fleet Licensing, .lic Files & WebUI Dashboard
### Accessing the WebUI
Open your browser and navigate to:
```
http://<SERVER_IP>:8443/webui
```
The WebUI provides:
1. **Real-time Seat Quota Meter**: Visual meter displaying active clients against approved seat quota.
2. **Export .lic Request**: 1-click download of `logar_server.lic` containing your Subordinate CA CSR and machine fingerprint for `logar.eibl.tech`.
3. **Import License (.lic / ca.crt)**: Modal to paste or upload the activated `.lic` package or `ca.crt` issued by `logar.eibl.tech`. Activating updates your seat limit immediately and reloads mTLS context in-flight.
4. **Sync & Renew via mTLS**: Button to test or trigger immediate mTLS renewal against `logar.eibl.tech` and pull seat quota increases without restart.
5. **Client Seat Management**: View all enrolled nodes; click **Revoke Seat** to immediately drop and block an edge node at the socket layer, or **Reactivate** to restore access.
### CLI Licensing Utilities
Export a `.lic` request file via CLI:
```bash
./Server.bin --export-lic server.lic
```
Import an activated `.lic` license package or signed CA certificate via CLI:
```bash
./Server.bin --import-lic activated.lic
# Or alternatively with --import-ca:
./Server.bin --import-ca ca.crt
```
Synchronize license and check for seat quota increases against the master authority:
```bash
./Server.bin --sync-license
```
### Upstream Authority Deployment (Portainer GitOps & Reverse Proxy)
The upstream Master License Authority service (`logar.eibl.tech`) is compiled and deployed via Portainer GitOps from the [`LOGAR-LICENSING`](https://gitea.eibl.tech/me0nline/LOGAR-LICENSING.git) repository, running from `gitea.eibl.tech/me0nline/logar-licensing:latest` with automated pipeline compilation and persistent volume storage. Public traffic passes through a reverse proxy (e.g., Nginx Proxy Manager with **Websockets Support** enabled), handling WebSocket licensing handshakes (`wss://logar.eibl.tech/ws/license`), multi-tenant fleet isolation, user management, and authenticated WebUI dashboard access.
---
## 5. Manual Systemd Service Management
Check service status:
```bash
sudo systemctl status logar-server.service
```
Inspect live service logs:
```bash
sudo journalctl -u logar-server.service -f -n 50
```
---
## 6. Uninstallation & Removal
```bash
sudo systemctl disable --now logar-server.service
sudo rm -f /etc/systemd/system/logar-server.service
sudo systemctl daemon-reload
sudo rm -rf /opt/logar-server /etc/logar /var/log/logar
```
@@ -1,14 +0,0 @@
{
"server_name": "LOGAR-Linux-Hub",
"tcp_host": "0.0.0.0",
"tcp_port": 9443,
"hermes_host": "0.0.0.0",
"hermes_port": 8443,
"auth_token": "replace_with_secure_random_hex_token",
"db_path": "logar_state.db",
"evaluation_window_hours": 12,
"min_persistence_runs": 4,
"server_fingerprint": "AUTO_GENERATED_ON_FIRST_RUN",
"public_key": "AUTO_GENERATED_ON_FIRST_RUN",
"private_key": "AUTO_GENERATED_ON_FIRST_RUN"
}
-134
View File
@@ -1,134 +0,0 @@
# LOGAR Windows Edge Forwarder
Standalone compiled executable and installer distribution for Windows Server and workstation environments.
---
## Overview
`Win_Client.exe` is a self-contained executable that queries the Windows Application Event Log, filters candidate events at the source, auto-enrolls with the central LOGAR hub to receive signed mTLS certificates, and streams records over mutual TLS 1.3 (**mTLS**) socket connection.
### Key Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone native Windows executable (`Win_Client.exe`) or full installer (`LOGAR-Client-Setup.exe`). No Python installation, pip packages, or GnuPG binaries are required on the host.
- **Mutual TLS 1.3 (mTLS) Ingestion**: Streams directly over hardware-authenticated TLS 1.3 sockets with hardware/machine-bound client certificates.
- **Automated Client Enrollment**: On first run with an `enrollment_secret`, the client automatically calls `POST /api/client/enroll` on the hub, saves its certificates into `certs/`, and establishes secure mTLS streaming.
- **Proactive Expiry Check & Reactive Self-Healing**: Before each run, the client evaluates `client.crt` validity. If within 30 days of expiry, it automatically contacts the hub to renew certificates. If the server Root CA rotates or a TLS handshake error occurs, the client catch-heals by re-enrolling immediately and re-establishing connection without human intervention.
- **Source-Level Filtering**: Retains events spanning `INFO`, `WARNING`, and `ERROR`. Strips audit events and debug noise, skipping events older than 24 hours.
- **State Tracking & Deduplication**: Maintains persistent client state in `client_state.json` (tracking event record numbers and timestamp signatures) so every log record is forwarded exactly once without duplicates.
- **Fleet Seat Accounting & License Revocation**: Each enrolled client consumes 1 seat from the hub's master license. If revoked by an administrator via the hub WebUI, socket connections are immediately dropped and blocked until reactivated.
- **Dynamic Seat Expansion & Automatic Hub Synchronization**: The forwarder benefits from the hub's dynamic seat quota expansion; as the hub's license seats are increased and synced with the master authority (`logar.eibl.tech`), edge capacity expands automatically without requiring forwarder configuration changes.
- **Master Authority High Availability (Portainer GitOps)**: The authoritative license server at `logar.eibl.tech` runs containerized via Portainer GitOps with automated pipeline builds, ensuring high availability for client seat verification and renewals.
---
## 1. Automated Installation via Inno Setup (Recommended)
Run the self-contained installer built from `compilation/installer_client.iss`:
```powershell
.\LOGAR-Client-Setup.exe
```
This installer:
1. Installs `Win_Client.exe` and bundled `nssm.exe` to `C:\Program Files\LOGAR\`.
2. Sets up directory permissions in `C:\ProgramData\LOGAR\`.
3. Registers and starts the `LOGAR_Client` Windows service automatically via NSSM.
4. Redirects stdout and stderr logs to `C:\ProgramData\LOGAR\client.log` and `client_err.log`.
---
## 2. Generating & Deploying the Configuration File
### Step 1: Generate `client_config.json` on the Server
Run the following command on your central LOGAR server:
```bash
python src/Server.py --create-client-config --server-host <SERVER_IP_OR_DNS> --server-port 9443 --client-out client_config.json
```
- Replace `<SERVER_IP_OR_DNS>` with the reachable IP address or FQDN of your central LOGAR server hub.
- Default mTLS socket port is `9443`; Hermes REST API port is `8443`.
### Step 2: Configuration Structure
The generated `client_config.json` contains:
```json
{
"server_host": "192.168.1.100",
"server_port": 9443,
"hermes_host": "192.168.1.100",
"hermes_port": 8443,
"enrollment_secret": "a1b2c3d4e5f6...",
"cert_dir": "certs",
"server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2",
"server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"auth_token": "a1b2c3d4e5f6..."
}
```
> [!NOTE]
> The configuration contains **no host-specific names or site names** to ensure client anonymity and easy redistribution.
### Step 3: Copy to Edge Node
Place `client_config.json` next to `Win_Client.exe` (e.g. `C:\Program Files\LOGAR\` or `C:\LOGAR\`):
```powershell
New-Item -ItemType Directory -Path "C:\LOGAR" -Force
Copy-Item "Win_Client.exe", "client_config.json" -Destination "C:\LOGAR\"
```
---
## 3. Running Manually
Test the forwarder interactively from PowerShell or Command Prompt:
```powershell
cd C:\LOGAR
.\Win_Client.exe --hours 24
```
On first run, the client contacts `http://<hermes_host>:<hermes_port>/api/client/enroll`, downloads `ca.crt`, `client.crt`, and `client.key` into `certs/`, and streams logs over mTLS.
### Command-Line Arguments
| Argument | Default | Description |
| :--- | :--- | :--- |
| `--config` | `client_config.json` | Path to client configuration file |
| `--hours` | `24` | Lookback window in hours for event logs |
| `--state-file` | `client_state.json` | Path to persistent state tracking file |
| `--no-state` | `False` | Disable state tracking and send all events matching lookback window |
---
## 4. Manual Service Installation (NSSM or Scheduled Task)
### Method A: Windows Service via Bundled NSSM
```powershell
# From the compilation directory or with bundled nssm.exe:
.\nssm.exe install LOGAR_Client "C:\LOGAR\Win_Client.exe" "--hours 24"
.\nssm.exe set LOGAR_Client AppDirectory "C:\LOGAR"
.\nssm.exe set LOGAR_Client AppStdout "C:\ProgramData\LOGAR\client.log"
.\nssm.exe set LOGAR_Client AppStderr "C:\ProgramData\LOGAR\client_err.log"
.\nssm.exe start LOGAR_Client
```
### Method B: Windows Scheduled Task via PowerShell
```powershell
$Action = New-ScheduledTaskAction -Execute "C:\LOGAR\Win_Client.exe" -Argument "--hours 24" -WorkingDirectory "C:\LOGAR"
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 3)
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 15)
Register-ScheduledTask -TaskName "LOGAR_Forwarder" `
-Action $Action `
-Trigger $Trigger `
-Settings $Settings `
-User "NT AUTHORITY\SYSTEM" `
-RunLevel Highest `
-Description "LOGAR Windows Edge Log Forwarder Service"
Start-ScheduledTask -TaskName "LOGAR_Forwarder"
```
---
## 5. Uninstallation
If installed via the Inno Setup installer, use **Windows Add/Remove Programs** or run `unins000.exe` in `C:\Program Files\LOGAR\`.
If installed manually via NSSM:
```powershell
.\nssm.exe stop LOGAR_Client
.\nssm.exe remove LOGAR_Client confirm
Remove-Item -Recurse -Force "C:\LOGAR"
```
-7
View File
@@ -1,7 +0,0 @@
{
"server_host": "192.168.1.100",
"server_port": 9443,
"server_fingerprint": "PASTE_SERVER_FINGERPRINT_HERE",
"server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----\n",
"auth_token": "PASTE_AUTH_TOKEN_HERE"
}
-159
View File
@@ -1,159 +0,0 @@
# LOGAR Windows Server Hub
Standalone compiled executable and installer distribution for Windows Server environments (`Server.exe`).
---
## Overview
`Server.exe` is a self-contained, pre-compiled native Windows PE executable that serves as the central log aggregation, temporal persistence analyzer, dynamic PKI certificate authority, and reporting hub of the LOGAR infrastructure.
### Key Architecture & Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone Windows executable (`Server.exe`) or full installer (`LOGAR-Server-Setup.exe`). No Python installation, pip packages, or GnuPG binaries are required on Windows Server.
- **Mutual TLS 1.3 (mTLS) Ingestion (Port 9443)**: Enforces mutual TLS 1.3 authentication for all incoming edge connections. Validates client certificates against an internal Subordinate CA and verifies active licensing in SQLite.
- **Master License Authority (`logar.eibl.tech`) & Seat-Bearing CA**: Integrates with the upstream authority. Subordinate CA certificates embed exact seat quotas via custom enterprise X.509 OID `1.3.6.1.4.1.59999.1.1`.
- **Zero-Touch mTLS Renewals**: Once initial signed CA cert is imported, the server hub automatically executes in-flight mTLS renewals with `https://logar.eibl.tech/api/license/renew` when nearing expiration (< 30 days).
- **Fleet Management & WebUI Dashboard (Port 8443)**: Modern browser WebUI accessible at `http://localhost:8443/webui` to monitor seat quotas, export `.lic` request files, copy CA CSRs, activate signed CA certs, and revoke/reactivate forwarder seats with immediate socket enforcement.
- **Direct Hermes Intelligence & Temporal Telemetry**: All ingested actionable anomalies (`WARNING`, `ERROR`, `INFO`) are immediately promoted to `VERIFIED` and surfaced directly to Hermes with rich temporal metadata (run counts, timestamps) across a rolling 12-hour evaluation window, allowing Hermes AI to perform all contextual reasoning and decisions.
- **Embedded Hermes Reporting API (Port 8443)**: Integrated REST API exposing `/api/hermes/report`, `/api/clients`, and `/api/client/enroll`.
- **State Database**: Stores issue lifecycle records, client telemetry, and licensing quotas in a local SQLite database (`logar_state.db`).
---
## 1. Automated Installation via Inno Setup (Recommended)
Run the self-contained installer built from `compilation/installer_server.iss`:
```powershell
.\LOGAR-Server-Setup.exe
```
This installer:
1. Installs `Server.exe` and bundled `nssm.exe` to `C:\Program Files\LOGAR-Server\`.
2. Registers and starts the `LOGAR_Server` Windows service automatically via NSSM.
3. Redirects stdout and stderr logs to `C:\ProgramData\LOGAR-Server\server.log` and `server_err.log`.
---
## 2. Initializing & Generating Server Configuration
### Step 1: Automatic First-Run Generation
When launched without an existing `server_config.json`, `Server.exe` automatically initializes:
1. An internal Root CA (`certs/ca.crt` and `certs/ca.key`).
2. A server TLS certificate (`certs/server.crt` and `certs/server.key`) with SANs.
3. An OpenPGP RSA-2048 keypair (`private_key` and `public_key`).
4. Cryptographically random authentication tokens and enrollment secrets.
5. Default network socket coordinates (mTLS 9443, Hermes API 8443).
Open PowerShell and run:
```powershell
.\Server.exe
```
Output:
```
[!] Config 'server_config.json' not found. Initializing first-run configuration...
[+] Successfully generated new server config and OpenPGP keypair.
[+] Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
[+] Saved to: server_config.json
============================================================
LOGAR Server Hub: LOGAR-Cloud-Hub
Transport Security: mTLS (TLS 1.3)
License Quota: 10 Active Seats
Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
Evaluation Window: 12 hours | Direct Hermes Reasoning Enabled
============================================================
[*] LOGAR mTLS TLSv1.3 Socket Server listening on 0.0.0.0:9443
[*] Hermes Reporting API available at http://0.0.0.0:8443/api/hermes/report
[*] Client Enrollment API available at http://0.0.0.0:8443/api/client/enroll
```
### Step 2: Configuration Fields Reference
The generated `server_config.json` contains:
```json
{
"server_name": "LOGAR-Windows-Hub",
"tcp_host": "0.0.0.0",
"tcp_port": 9443,
"hermes_host": "0.0.0.0",
"hermes_port": 8443,
"auth_token": "a1b2c3d4e5f67890abcdef1234567890...",
"enrollment_secret": "e1f2a3b4c5d6...",
"max_seats": 10,
"cert_dir": "certs",
"tls_enabled": true,
"db_path": "logar_state.db",
"evaluation_window_hours": 12,
"min_persistence_runs": 1,
"server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2",
"public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"private_key": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n..."
}
```
---
## 3. Exporting Client Configurations
Generate a client configuration bundle to deploy onto Windows or Linux forwarders:
```powershell
.\Server.exe --create-client-config --server-host 192.168.1.100 --server-port 9443 --client-out client_config.json
```
The output file contains the server coordinates, enrollment secret, and fingerprint, ready for client deployment.
---
## 4. Fleet Licensing, .lic Files & WebUI Dashboard
### Accessing the WebUI
Open your browser and navigate to:
```
http://localhost:8443/webui
```
The WebUI provides:
1. **Real-time Seat Quota Meter**: Visual meter displaying active clients against approved seat quota.
2. **Export .lic Request**: 1-click download of `logar_server.lic` containing your Subordinate CA CSR and machine fingerprint for `logar.eibl.tech`.
3. **Import License (.lic / ca.crt)**: Modal to paste or upload the activated `.lic` package or `ca.crt` issued by `logar.eibl.tech`. Activating updates your seat limit immediately and reloads mTLS context in-flight.
4. **Sync & Renew via mTLS**: Button to test or trigger immediate mTLS renewal against `logar.eibl.tech` and pull seat quota increases without restart.
5. **Client Seat Management**: View all enrolled nodes; click **Revoke Seat** to immediately drop and block an edge node at the socket layer, or **Reactivate** to restore access.
### CLI Licensing Utilities
Export a `.lic` request file via CLI:
```powershell
.\Server.exe --export-lic C:\LOGAR-Server\server.lic
```
Import an activated `.lic` license package or signed CA certificate via CLI:
```powershell
.\Server.exe --import-lic C:\LOGAR-Server\activated.lic
# Or alternatively with --import-ca:
.\Server.exe --import-ca C:\LOGAR-Server\ca.crt
```
Synchronize license and check for seat quota increases against the master authority:
```powershell
.\Server.exe --sync-license
```
### Upstream Authority Deployment (Portainer GitOps & Reverse Proxy)
The upstream Master License Authority service (`logar.eibl.tech`) is compiled and deployed via Portainer GitOps from the [`LOGAR-LICENSING`](https://gitea.eibl.tech/me0nline/LOGAR-LICENSING.git) repository, running from `gitea.eibl.tech/me0nline/logar-licensing:latest` with automated pipeline compilation and persistent volume storage. Public traffic passes through a reverse proxy (e.g., Nginx Proxy Manager with **Websockets Support** enabled), handling WebSocket licensing handshakes (`wss://logar.eibl.tech/ws/license`), multi-tenant fleet isolation, user management, and authenticated WebUI dashboard access.
---
## 5. Manual Windows Service Setup (via NSSM)
```powershell
.\nssm.exe install LOGAR_Server "C:\LOGAR-Server\Server.exe"
.\nssm.exe set LOGAR_Server AppDirectory "C:\LOGAR-Server"
.\nssm.exe set LOGAR_Server AppStdout "C:\ProgramData\LOGAR-Server\server.log"
.\nssm.exe set LOGAR_Server AppStderr "C:\ProgramData\LOGAR-Server\server_err.log"
.\nssm.exe start LOGAR_Server
```
---
## 6. Uninstallation
If installed via the Inno Setup installer, use **Windows Add/Remove Programs**.
If installed manually via NSSM:
```powershell
.\nssm.exe stop LOGAR_Server
.\nssm.exe remove LOGAR_Server confirm
```
-14
View File
@@ -1,14 +0,0 @@
{
"server_name": "LOGAR-Windows-Hub",
"tcp_host": "0.0.0.0",
"tcp_port": 9443,
"hermes_host": "0.0.0.0",
"hermes_port": 8443,
"auth_token": "replace_with_secure_random_hex_token",
"db_path": "logar_state.db",
"evaluation_window_hours": 12,
"min_persistence_runs": 4,
"server_fingerprint": "AUTO_GENERATED_ON_FIRST_RUN",
"public_key": "AUTO_GENERATED_ON_FIRST_RUN",
"private_key": "AUTO_GENERATED_ON_FIRST_RUN"
}
+395 -12
View File
@@ -10,6 +10,8 @@ import asyncio
import secrets import secrets
import hashlib import hashlib
import hmac import hmac
import base64
import urllib.parse
import time import time
import html import html
import warnings import warnings
@@ -276,6 +278,89 @@ def change_user_password(username: str, new_password: str, db_path: str = DEFAUL
return changed return changed
# --- Two-Factor Authentication (TOTP / RFC 6238) ---
def generate_totp_secret(length: int = 20) -> str:
"""Generates a cryptographically random Base32-encoded TOTP secret key."""
return base64.b32encode(secrets.token_bytes(length)).decode("utf-8").replace("=", "")
def get_totp_token(secret: str, intervals_no: Optional[int] = None) -> str:
"""Calculates a 6-digit TOTP code for the given counter interval according to RFC 6238."""
if intervals_no is None:
intervals_no = int(time.time()) // 30
key = base64.b32decode(secret + "=" * ((8 - len(secret) % 8) % 8), casefold=True)
msg = struct.pack(">Q", intervals_no)
h = hmac.new(key, msg, hashlib.sha1).digest()
o = h[19] & 15
h_int = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
return f"{h_int:06d}"
def verify_totp_code(secret: str, code: str, window: int = 1) -> bool:
"""Verifies a 6-digit TOTP code with standard drift window (+/- 30s)."""
if not secret or not code:
return False
code = str(code).strip().replace(" ", "")
if len(code) != 6 or not code.isdigit():
return False
now = int(time.time())
current_step = now // 30
for offset in range(-window, window + 1):
if hmac.compare_digest(get_totp_token(secret, current_step + offset), code):
return True
return False
def get_totp_uri(username: str, secret: str, issuer: str = "LOGAR Server Hub") -> str:
"""Generates standard otpauth URI for QR code generation."""
user_enc = urllib.parse.quote(username)
issuer_enc = urllib.parse.quote(issuer)
return f"otpauth://totp/{issuer_enc}:{user_enc}?secret={secret}&issuer={issuer_enc}&algorithm=SHA1&digits=6&period=30"
def get_user_totp_info(username: str, db_path: str = DEFAULT_DB_FILE) -> Dict[str, Any]:
"""Retrieves TOTP configuration for a user from the SQLite database."""
conn = sqlite3.connect(db_path)
c = conn.cursor()
try:
c.execute("SELECT totp_secret, totp_enabled FROM users WHERE username = ?", (username,))
row = c.fetchone()
if row:
return {"secret": row[0] or "", "enabled": bool(row[1])}
except Exception:
pass
finally:
conn.close()
return {"secret": "", "enabled": False}
def enable_user_totp(username: str, secret: str, db_path: str = DEFAULT_DB_FILE) -> bool:
"""Enables and activates TOTP for a user in the SQLite database."""
conn = sqlite3.connect(db_path)
c = conn.cursor()
try:
c.execute("UPDATE users SET totp_secret = ?, totp_enabled = 1 WHERE username = ?", (secret, username))
changed = c.rowcount > 0
conn.commit()
return changed
finally:
conn.close()
def disable_user_totp(username: str, db_path: str = DEFAULT_DB_FILE) -> bool:
"""Disables TOTP for a user in the SQLite database."""
conn = sqlite3.connect(db_path)
c = conn.cursor()
try:
c.execute("UPDATE users SET totp_secret = NULL, totp_enabled = 0 WHERE username = ?", (username,))
changed = c.rowcount > 0
conn.commit()
return changed
finally:
conn.close()
# --- Hermes Token Management --- # --- Hermes Token Management ---
def create_hermes_token(name: str, token: Optional[str] = None, db_path: str = DEFAULT_DB_FILE) -> Dict[str, Any]: def create_hermes_token(name: str, token: Optional[str] = None, db_path: str = DEFAULT_DB_FILE) -> Dict[str, Any]:
@@ -765,10 +850,22 @@ def init_db(db_path: str, enrollment_secret: Optional[str] = None, max_seats: in
password_hash TEXT NOT NULL, password_hash TEXT NOT NULL,
salt TEXT NOT NULL, salt TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'admin', role TEXT NOT NULL DEFAULT 'admin',
created_at TEXT NOT NULL created_at TEXT NOT NULL,
totp_secret TEXT,
totp_enabled INTEGER DEFAULT 0
) )
""") """)
# Migrations for users table (2FA)
for col, col_def in [
("totp_secret", "TEXT"),
("totp_enabled", "INTEGER DEFAULT 0")
]:
try:
conn.execute(f"ALTER TABLE users ADD COLUMN {col} {col_def}")
except sqlite3.OperationalError:
pass
# Ensure default admin user exists # Ensure default admin user exists
c = conn.cursor() c = conn.cursor()
c.execute("SELECT COUNT(*) FROM users") c.execute("SELECT COUNT(*) FROM users")
@@ -1306,6 +1403,7 @@ def login_page(request: Request):
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LOGAR Hub - Login</title> <title>LOGAR Hub - Login</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpolygon points='256,24 456,139 456,373 256,488 56,373 56,139' fill='%230E1A2E' stroke='%2300E5FF' stroke-width='24'/%3E%3Crect x='140' y='140' width='46' height='232' rx='10' fill='%2300E5FF'/%3E%3Crect x='140' y='326' width='220' height='46' rx='10' fill='%2300E5FF'/%3E%3Crect x='220' y='210' width='36' height='80' rx='8' fill='%2300FFA3'/%3E%3Crect x='286' y='150' width='36' height='140' rx='8' fill='%2300E5FF'/%3E%3C/svg%3E">
<style> <style>
:root { --bg: #0b0f19; --card: #151c2e; --primary: #3b82f6; --primary-hover: #2563eb; --border: #1e293b; --text: #f8fafc; --text-muted: #94a3b8; --danger: #ef4444; } :root { --bg: #0b0f19; --card: #151c2e; --primary: #3b82f6; --primary-hover: #2563eb; --border: #1e293b; --text: #f8fafc; --text-muted: #94a3b8; --danger: #ef4444; }
* { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
@@ -1341,14 +1439,19 @@ def login_page(request: Request):
</div> </div>
<div id="loginError" class="alert-error"></div> <div id="loginError" class="alert-error"></div>
<form id="loginForm" onsubmit="handleLogin(event)"> <form id="loginForm" onsubmit="handleLogin(event)">
<div class="form-group"> <div class="form-group" id="userGroup">
<label class="form-label">Username</label> <label class="form-label">Username</label>
<input type="text" id="username" class="form-control" placeholder="admin" required autofocus> <input type="text" id="username" class="form-control" placeholder="admin" required autofocus>
</div> </div>
<div class="form-group"> <div class="form-group" id="passGroup">
<label class="form-label">Password</label> <label class="form-label">Password</label>
<input type="password" id="password" class="form-control" placeholder="••••••••" required> <input type="password" id="password" class="form-control" placeholder="••••••••" required>
</div> </div>
<div class="form-group" id="totpGroup" style="display: none;">
<label class="form-label">🛡️ 2FA Verification Code</label>
<p style="font-size: 12px; color: var(--text-muted); margin-bottom: 6px;">Enter the 6-digit code from your authenticator app:</p>
<input type="text" id="totpCode" class="form-control" placeholder="123456" maxlength="6" inputmode="numeric" style="font-family: 'JetBrains Mono', monospace; font-size: 18px; letter-spacing: 4px; text-align: center;">
</div>
<button type="submit" class="btn" id="submitBtn">Sign In</button> <button type="submit" class="btn" id="submitBtn">Sign In</button>
</form> </form>
</div> </div>
@@ -1357,26 +1460,37 @@ def login_page(request: Request):
e.preventDefault(); e.preventDefault();
const u = document.getElementById('username').value.trim(); const u = document.getElementById('username').value.trim();
const p = document.getElementById('password').value; const p = document.getElementById('password').value;
const totp = document.getElementById('totpCode').value.trim();
const errEl = document.getElementById('loginError'); const errEl = document.getElementById('loginError');
const btn = document.getElementById('submitBtn'); const btn = document.getElementById('submitBtn');
const totpGrp = document.getElementById('totpGroup');
errEl.style.display = 'none'; errEl.style.display = 'none';
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Authenticating...'; btn.textContent = 'Authenticating...';
const payload = { username: u, password: p };
if (totp) payload.totp_code = totp;
try { try {
const res = await fetch('/login', { const res = await fetch('/login', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: u, password: p }) body: JSON.stringify(payload)
}); });
const data = await res.json(); const data = await res.json();
if (res.ok && data.status === 'success') { if (res.ok && data.status === 'success') {
window.location.href = '/'; window.location.href = '/';
} else if (data.requires_2fa || data.status === '2fa_required') {
totpGrp.style.display = 'block';
document.getElementById('totpCode').focus();
btn.disabled = false;
btn.textContent = 'Verify & Sign In';
errEl.style.display = 'none';
} else { } else {
errEl.textContent = data.detail || 'Invalid username or password'; errEl.textContent = data.detail || 'Invalid username or password';
errEl.style.display = 'block'; errEl.style.display = 'block';
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Sign In'; btn.textContent = totpGrp.style.display === 'block' ? 'Verify & Sign In' : 'Sign In';
} }
} catch(ex) { } catch(ex) {
errEl.textContent = 'Connection error: ' + ex.message; errEl.textContent = 'Connection error: ' + ex.message;
@@ -1392,24 +1506,26 @@ def login_page(request: Request):
@app.post("/login") @app.post("/login")
async def process_login(request: Request): async def process_login(request: Request):
"""Processes login credentials and issues session cookie.""" """Processes login credentials and issues session cookie with 2FA TOTP enforcement."""
client_ip = get_client_ip(request) client_ip = get_client_ip(request)
if not check_ip_rate_limit(client_ip, _LOGIN_FAILURES, max_attempts=10, window_seconds=60): if not check_ip_rate_limit(client_ip, _LOGIN_FAILURES, max_attempts=10, window_seconds=60):
raise HTTPException(status_code=429, detail="Too many failed login attempts. Please try again later.") raise HTTPException(status_code=429, detail="Too many failed login attempts. Please try again later.")
username, password = None, None username, password, totp_code = None, None, None
content_type = request.headers.get("content-type", "") content_type = request.headers.get("content-type", "")
if "application/json" in content_type: if "application/json" in content_type:
try: try:
body = await request.json() body = await request.json()
username = body.get("username") username = body.get("username")
password = body.get("password") password = body.get("password")
totp_code = body.get("totp_code")
except Exception: except Exception:
pass pass
else: else:
form = await request.form() form = await request.form()
username = form.get("username") username = form.get("username")
password = form.get("password") password = form.get("password")
totp_code = form.get("totp_code")
if not username or not password: if not username or not password:
raise HTTPException(status_code=400, detail="Username and password are required") raise HTTPException(status_code=400, detail="Username and password are required")
@@ -1420,6 +1536,20 @@ async def process_login(request: Request):
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
raise HTTPException(status_code=401, detail="Invalid username or password") raise HTTPException(status_code=401, detail="Invalid username or password")
# Check 2FA TOTP requirement
totp_info = get_user_totp_info(username, db_path=db_path)
if totp_info.get("enabled"):
if not totp_code:
return JSONResponse(status_code=200, content={
"status": "2fa_required",
"requires_2fa": True,
"message": "Two-factor authentication code required"
})
if not verify_totp_code(totp_info["secret"], totp_code):
record_failed_ip_attempt(client_ip, _LOGIN_FAILURES)
await asyncio.sleep(0.3)
raise HTTPException(status_code=401, detail="Invalid 6-digit 2FA verification code")
reset_failed_ip_attempts(client_ip, _LOGIN_FAILURES) reset_failed_ip_attempts(client_ip, _LOGIN_FAILURES)
token = generate_session_token(username) token = generate_session_token(username)
is_https = request.url.scheme == "https" or request.headers.get("x-forwarded-proto") == "https" is_https = request.url.scheme == "https" or request.headers.get("x-forwarded-proto") == "https"
@@ -1455,6 +1585,55 @@ async def api_change_password(req: ChangePasswordRequest, current_user: str = De
return {"status": "success", "message": "Password updated successfully"} return {"status": "success", "message": "Password updated successfully"}
# --- 2FA Authentication Management Endpoints ---
@app.get("/api/user/2fa/status")
def api_user_2fa_status(auth_user: str = Depends(require_admin_auth)):
"""Returns 2FA status for currently logged in administrator."""
db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE)
info = get_user_totp_info(auth_user, db_path=db_path)
return {"enabled": info["enabled"], "username": auth_user}
@app.get("/api/user/2fa/setup")
def api_user_2fa_setup(auth_user: str = Depends(require_admin_auth)):
"""Generates a new TOTP secret and otpauth URI for 2FA provisioning."""
secret = generate_totp_secret()
otpauth = get_totp_uri(auth_user, secret, issuer="LOGAR Server Hub")
return {
"secret": secret,
"otpauth_uri": otpauth,
"username": auth_user
}
@app.post("/api/user/2fa/enable")
async def api_user_2fa_enable(request: Request, auth_user: str = Depends(require_admin_auth)):
"""Enables TOTP for the current user after validating 6-digit code."""
body = await request.json()
secret = body.get("secret", "").strip()
code = body.get("code", "").strip()
if not secret or not code:
raise HTTPException(status_code=400, detail="Secret and 6-digit verification code required")
if not verify_totp_code(secret, code):
raise HTTPException(status_code=400, detail="Invalid 6-digit TOTP verification code. Please check your device clock.")
db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE)
enable_user_totp(auth_user, secret, db_path=db_path)
return {"status": "success", "message": "2FA successfully activated"}
@app.post("/api/user/2fa/disable")
async def api_user_2fa_disable(request: Request, auth_user: str = Depends(require_admin_auth)):
"""Disables TOTP for the current user after verifying password."""
body = await request.json()
password = body.get("password", "")
db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE)
if not verify_user_credentials(auth_user, password, db_path=db_path):
raise HTTPException(status_code=403, detail="Current password incorrect")
disable_user_totp(auth_user, db_path=db_path)
return {"status": "success", "message": "2FA successfully disabled"}
@app.post("/api/client/enroll") @app.post("/api/client/enroll")
def enroll_client(req: ClientEnrollRequest, request: Request = None): def enroll_client(req: ClientEnrollRequest, request: Request = None):
""" """
@@ -2241,6 +2420,7 @@ def serve_webui(request: Request = None):
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LOGAR Hub - Fleet Aggregation & Management</title> <title>LOGAR Hub - Fleet Aggregation & Management</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpolygon points='256,24 456,139 456,373 256,488 56,373 56,139' fill='%230E1A2E' stroke='%2300E5FF' stroke-width='24'/%3E%3Crect x='140' y='140' width='46' height='232' rx='10' fill='%2300E5FF'/%3E%3Crect x='140' y='326' width='220' height='46' rx='10' fill='%2300E5FF'/%3E%3Crect x='220' y='210' width='36' height='80' rx='8' fill='%2300FFA3'/%3E%3Crect x='286' y='150' width='36' height='140' rx='8' fill='%2300E5FF'/%3E%3C/svg%3E">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style> <style>
:root {{ :root {{
@@ -2301,7 +2481,7 @@ def serve_webui(request: Request = None):
border-radius: 10px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6); border-radius: 10px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
min-width: 200px; z-index: 200; overflow: hidden; min-width: 200px; z-index: 200; overflow: hidden;
}} }}
.user-menu-container:hover .user-dropdown, .user-menu-container:focus-within .user-dropdown {{ display: block; }} .user-menu-container:hover .user-dropdown, .user-menu-container:focus-within .user-dropdown, .user-dropdown.show {{ display: block; }}
.user-dropdown-header {{ padding: 10px 14px; font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-muted); background: #0f172a; border-bottom: 1px solid var(--border); }} .user-dropdown-header {{ padding: 10px 14px; font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-muted); background: #0f172a; border-bottom: 1px solid var(--border); }}
.user-dropdown-item {{ .user-dropdown-item {{
display: flex; align-items: center; gap: 8px; padding: 10px 14px; display: flex; align-items: center; gap: 8px; padding: 10px 14px;
@@ -2440,13 +2620,14 @@ def serve_webui(request: Request = None):
<div class="header-actions"> <div class="header-actions">
<button onclick="openExportProfileModal()" class="btn btn-primary">📥 Export Client Profile</button> <button onclick="openExportProfileModal()" class="btn btn-primary">📥 Export Client Profile</button>
<div class="user-menu-container" tabindex="0"> <div class="user-menu-container" tabindex="0">
<div class="user-tag"> <div class="user-tag" onclick="toggleUserDropdown(event)">
<span>👤 <strong>{auth_user}</strong></span> <span>👤 <strong>{auth_user}</strong></span>
<span style="font-size: 10px; color: var(--text-muted);">▼</span> <span style="font-size: 10px; color: var(--text-muted);">▼</span>
</div> </div>
<div class="user-dropdown"> <div class="user-dropdown" id="userDropdown">
<div class="user-dropdown-header">Logged in as {auth_user}</div> <div class="user-dropdown-header">Logged in as {auth_user}</div>
<button class="user-dropdown-item" onclick="openPasswordModal()">🔑 Change Password</button> <button class="user-dropdown-item" onclick="openTwoFactorModal(); hideUserDropdown();">🛡️ Two-Factor Auth (2FA)</button>
<button class="user-dropdown-item" onclick="openPasswordModal(); hideUserDropdown();">🔑 Change Password</button>
<a href="/logout" class="user-dropdown-item danger">🚪 Logout</a> <a href="/logout" class="user-dropdown-item danger">🚪 Logout</a>
</div> </div>
</div> </div>
@@ -2581,7 +2762,7 @@ def serve_webui(request: Request = None):
<div class="tab-pane" id="tabPane-anomalies"> <div class="tab-pane" id="tabPane-anomalies">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<span class="card-title" style="font-size: 15px;">Verified Anomaly Stream (Rolling 12h Window)</span> <span class="card-title" style="font-size: 15px;">Verified Anomaly Stream (Latest 50 Events • Rolling 12h Window)</span>
<span class="badge badge-active">Direct Hermes Intelligence</span> <span class="badge badge-active">Direct Hermes Intelligence</span>
</div> </div>
<div style="overflow-x: auto;"> <div style="overflow-x: auto;">
@@ -2730,7 +2911,92 @@ def serve_webui(request: Request = None):
</div> </div>
</div> </div>
<!-- Two-Factor Authentication Modal -->
<div id="twoFactorModal" class="modal">
<div class="modal-content" style="max-width: 480px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 14px;">
<div class="modal-title" style="margin-bottom: 0; display: flex; align-items: center; gap: 8px;">
<span>🛡️</span> Two-Factor Authentication (2FA)
</div>
<div style="cursor: pointer; font-size: 22px; color: var(--text-muted); line-height: 1;" onclick="closeModal('twoFactorModal')">&times;</div>
</div>
<div id="twoFactorLoading" style="text-align: center; padding: 24px; color: var(--text-muted);">
Loading 2FA security status...
</div>
<!-- 2FA Active Section -->
<div id="twoFactorActiveSection" style="display: none;">
<div style="background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 8px; padding: 16px; margin-bottom: 20px; display: flex; align-items: center; gap: 12px;">
<div style="font-size: 24px;">🟢</div>
<div>
<div style="font-weight: 700; color: #34d399;">2FA is Currently Active</div>
<div style="font-size: 12px; color: var(--text-muted); margin-top: 2px;">Your account is secured with TOTP authenticator verification.</div>
</div>
</div>
<div style="margin-bottom: 20px; background: #0f172a; padding: 16px; border-radius: 8px; border: 1px solid var(--border);">
<div style="font-size: 13px; font-weight: 700; color: #ef4444; margin-bottom: 8px;">Disable Two-Factor Authentication</div>
<p style="font-size: 12px; color: var(--text-muted); margin-bottom: 12px;">Enter your account password to confirm disabling 2FA:</p>
<div class="form-group" style="margin-bottom: 12px;">
<input type="password" id="twoFactorDisablePassword" class="form-control" placeholder="Current Account Password">
</div>
<button onclick="submitDisable2FA()" class="btn btn-danger" style="width: 100%;">Disable 2FA</button>
</div>
<div style="text-align: center; margin-top: 16px;">
<button onclick="start2FASetup()" class="btn btn-outline" style="font-size: 12px;">🔄 Re-configure on New Device</button>
</div>
<div class="modal-actions" style="margin-top: 20px;">
<button onclick="closeModal('twoFactorModal')" class="btn btn-primary">Close</button>
</div>
</div>
<!-- 2FA Setup Section -->
<div id="twoFactorSetupSection" style="display: none;">
<p style="font-size: 13px; color: var(--text-muted); margin-bottom: 16px;">
Scan this QR code with an authenticator app (Google Authenticator, Authy, 1Password, Bitwarden, etc.):
</p>
<div style="display: flex; justify-content: center; align-items: center; background: #ffffff; padding: 16px; border-radius: 12px; margin-bottom: 16px; min-height: 190px;">
<div id="qrcodeCanvas"></div>
</div>
<div style="background: #0f172a; padding: 12px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 18px;">
<div style="font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 4px;">Manual Setup Secret Key</div>
<div style="display: flex; justify-content: space-between; align-items: center; gap: 8px;">
<code id="twoFactorSecretText" style="font-size: 13px; color: #60a5fa; word-break: break-all; user-select: all;"></code>
<button onclick="copy2FASecret()" class="btn btn-outline" style="padding: 4px 8px; font-size: 11px; white-space: nowrap;">📋 Copy</button>
</div>
</div>
<div class="form-group" style="margin-bottom: 18px;">
<label class="form-label">Enter 6-Digit Authenticator Code</label>
<input type="text" id="twoFactorVerifyCode" class="form-control" placeholder="123456" maxlength="6" inputmode="numeric" style="text-align: center; letter-spacing: 4px; font-size: 20px; font-family: 'JetBrains Mono', monospace; font-weight: 700;">
</div>
<div class="modal-actions">
<button onclick="closeModal('twoFactorModal')" class="btn btn-outline">Cancel</button>
<button onclick="submitEnable2FA()" class="btn btn-accent">✅ Verify &amp; Activate 2FA</button>
</div>
</div>
</div>
</div>
<script> <script>
function toggleUserDropdown(e) {{
if (e) e.stopPropagation();
const d = document.getElementById('userDropdown');
if (d) d.classList.toggle('show');
}}
function hideUserDropdown() {{
const d = document.getElementById('userDropdown');
if (d) d.classList.remove('show');
}}
window.addEventListener('click', () => hideUserDropdown());
function switchTab(tabId) {{ function switchTab(tabId) {{
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')); document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active'));
@@ -2946,6 +3212,123 @@ def serve_webui(request: Request = None):
alert(data.detail || 'Failed to change password'); alert(data.detail || 'Failed to change password');
}} }}
}} }}
// --- 2FA Management & QRCode Logic ---
let current2FASecret = '';
async function openTwoFactorModal() {{
openModal('twoFactorModal');
document.getElementById('twoFactorLoading').style.display = 'block';
document.getElementById('twoFactorActiveSection').style.display = 'none';
document.getElementById('twoFactorSetupSection').style.display = 'none';
try {{
const res = await fetch('/api/user/2fa/status');
const data = await res.json();
document.getElementById('twoFactorLoading').style.display = 'none';
if (data.enabled) {{
document.getElementById('twoFactorActiveSection').style.display = 'block';
}} else {{
start2FASetup();
}}
}} catch(ex) {{
document.getElementById('twoFactorLoading').textContent = 'Failed to load 2FA status: ' + ex.message;
}}
}}
async function start2FASetup() {{
document.getElementById('twoFactorLoading').style.display = 'block';
document.getElementById('twoFactorActiveSection').style.display = 'none';
document.getElementById('twoFactorSetupSection').style.display = 'none';
try {{
const res = await fetch('/api/user/2fa/setup');
const data = await res.json();
current2FASecret = data.secret;
document.getElementById('twoFactorSecretText').textContent = data.secret;
document.getElementById('twoFactorVerifyCode').value = '';
// Render QR code
renderQRCode('qrcodeCanvas', data.otpauth_uri);
document.getElementById('twoFactorLoading').style.display = 'none';
document.getElementById('twoFactorSetupSection').style.display = 'block';
}} catch(ex) {{
document.getElementById('twoFactorLoading').textContent = 'Failed to generate 2FA setup: ' + ex.message;
}}
}}
function copy2FASecret() {{
if (!current2FASecret) return;
navigator.clipboard.writeText(current2FASecret);
showAlert('2FA Secret copied to clipboard!');
}}
async function submitEnable2FA() {{
const code = document.getElementById('twoFactorVerifyCode').value.trim();
if (!code || code.length !== 6) {{
alert('Please enter a valid 6-digit verification code.');
return;
}}
try {{
const res = await fetch('/api/user/2fa/enable', {{
method: 'POST',
headers: {{ 'Content-Type': 'application/json' }},
body: JSON.stringify({{ secret: current2FASecret, code: code }})
}});
const data = await res.json();
if (res.ok) {{
showAlert('Two-factor authentication (2FA) successfully activated!');
closeModal('twoFactorModal');
setTimeout(() => location.reload(), 1000);
}} else {{
alert(data.detail || 'Failed to verify code. Please check your authenticator clock.');
}}
}} catch(ex) {{
alert('Connection error: ' + ex.message);
}}
}}
async function submitDisable2FA() {{
const pwd = document.getElementById('twoFactorDisablePassword').value;
if (!pwd) {{
alert('Please enter your account password to confirm.');
return;
}}
if (!confirm('Are you sure you want to disable two-factor authentication?')) return;
try {{
const res = await fetch('/api/user/2fa/disable', {{
method: 'POST',
headers: {{ 'Content-Type': 'application/json' }},
body: JSON.stringify({{ password: pwd }})
}});
const data = await res.json();
if (res.ok) {{
showAlert('2FA disabled successfully.');
closeModal('twoFactorModal');
setTimeout(() => location.reload(), 1000);
}} else {{
alert(data.detail || 'Failed to disable 2FA. Incorrect password.');
}}
}} catch(ex) {{
alert('Connection error: ' + ex.message);
}}
}}
function renderQRCode(containerId, text) {{
const el = document.getElementById(containerId);
if (!el) return;
el.innerHTML = '';
const img = document.createElement('img');
img.style.width = '180px';
img.style.height = '180px';
img.style.display = 'block';
img.alt = '2FA QR Code';
img.src = 'https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=' + encodeURIComponent(text);
el.appendChild(img);
}}
</script> </script>
</body> </body>
</html>""" </html>"""
+40
View File
@@ -470,6 +470,46 @@ class TestServerComponent(unittest.TestCase):
self.assertEqual(client_data["active_seats"], 0) self.assertEqual(client_data["active_seats"], 0)
self.assertEqual(len(client_data["clients"]), 1) self.assertEqual(len(client_data["clients"]), 1)
def test_two_factor_auth_and_totp_flow(self):
"""Tests TOTP secret generation, token verification, URI generation, and enable/disable lifecycle."""
Server.init_db(self.test_db)
# 1. Test TOTP helpers
secret = Server.generate_totp_secret()
self.assertTrue(len(secret) >= 16)
token = Server.get_totp_token(secret)
self.assertEqual(len(token), 6)
self.assertTrue(token.isdigit())
self.assertTrue(Server.verify_totp_code(secret, token))
self.assertFalse(Server.verify_totp_code(secret, "000000" if token != "000000" else "999999"))
self.assertFalse(Server.verify_totp_code(secret, "invalid"))
# 2. Test OTPAuth URI
uri = Server.get_totp_uri("admin", secret, issuer="LOGAR Fleet Hub")
self.assertTrue(uri.startswith("otpauth://totp/"))
self.assertIn("secret=" + secret, uri)
self.assertIn("LOGAR", uri)
# 3. Test initial user 2FA status
info = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertFalse(info["enabled"])
self.assertEqual(info["secret"], "")
# 4. Test enabling 2FA
enabled = Server.enable_user_totp("admin", secret, db_path=self.test_db)
self.assertTrue(enabled)
info_active = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertTrue(info_active["enabled"])
self.assertEqual(info_active["secret"], secret)
# 5. Test disabling 2FA
disabled = Server.disable_user_totp("admin", db_path=self.test_db)
self.assertTrue(disabled)
info_disabled = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertFalse(info_disabled["enabled"])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()