Configure separate Windows and Linux Gitea release workflows with dedicated SHA-256 checksums
This commit is contained in:
@@ -1,19 +1,25 @@
|
|||||||
name: Release Binaries
|
name: Release Linux Binaries
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag (e.g. v1.0.1)'
|
||||||
|
required: false
|
||||||
|
default: 'v1.0.1'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release-linux:
|
||||||
|
name: Build & Release Linux Binaries
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Python and Dependencies
|
- name: Install Python and Build Dependencies
|
||||||
run: |
|
run: |
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
apt-get update -y
|
apt-get update -y
|
||||||
@@ -22,15 +28,15 @@ jobs:
|
|||||||
python3 -m pip install --upgrade pip --break-system-packages || python3 -m pip install --upgrade pip || true
|
python3 -m pip install --upgrade pip --break-system-packages || python3 -m pip install --upgrade pip || true
|
||||||
pip3 install pyinstaller -r requirements.txt --break-system-packages || pip3 install pyinstaller -r requirements.txt
|
pip3 install pyinstaller -r requirements.txt --break-system-packages || pip3 install pyinstaller -r requirements.txt
|
||||||
|
|
||||||
- name: Compile Standalone Binaries
|
- name: Compile Standalone Linux Binaries
|
||||||
run: |
|
run: |
|
||||||
python3 package_dist.py
|
python3 package_dist.py --target linux
|
||||||
|
|
||||||
- name: Publish Release
|
- name: Publish Linux Release Assets
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.TAG_TOKEN || github.token }}
|
GITEA_TOKEN: ${{ secrets.TAG_TOKEN || github.token }}
|
||||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||||
GITEA_REPOSITORY: ${{ github.repository }}
|
GITEA_REPOSITORY: ${{ github.repository }}
|
||||||
GITEA_REF_NAME: ${{ github.ref_name }}
|
GITEA_REF_NAME: ${{ inputs.tag || github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
python3 upload_release.py --skip-build
|
python3 upload_release.py --skip-build
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
name: Release Windows Binaries
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag (e.g. v1.0.1)'
|
||||||
|
required: false
|
||||||
|
default: 'v1.0.1'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-windows:
|
||||||
|
name: Build & Release Windows Binaries
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$py = "python"
|
||||||
|
if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) {
|
||||||
|
if (Get-Command "py" -ErrorAction SilentlyContinue) {
|
||||||
|
$py = "py -3.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& $py -m pip install --upgrade pip
|
||||||
|
& $py -m pip install pyinstaller -r requirements.txt
|
||||||
|
|
||||||
|
- name: Compile Standalone Windows Binaries
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$py = "python"
|
||||||
|
if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) {
|
||||||
|
if (Get-Command "py" -ErrorAction SilentlyContinue) {
|
||||||
|
$py = "py -3.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& $py package_dist.py --target windows
|
||||||
|
|
||||||
|
- name: Publish Windows Release Assets
|
||||||
|
shell: powershell
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.TAG_TOKEN || github.token }}
|
||||||
|
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||||
|
GITEA_REPOSITORY: ${{ github.repository }}
|
||||||
|
GITEA_REF_NAME: ${{ inputs.tag || github.ref_name }}
|
||||||
|
run: |
|
||||||
|
$py = "python"
|
||||||
|
if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) {
|
||||||
|
if (Get-Command "py" -ErrorAction SilentlyContinue) {
|
||||||
|
$py = "py -3.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& $py upload_release.py --skip-build
|
||||||
@@ -175,7 +175,8 @@ LOGAR/
|
|||||||
├── .gitea/
|
├── .gitea/
|
||||||
│ └── workflows/
|
│ └── workflows/
|
||||||
│ ├── ci.yml # Continuous Integration automated test suite (runs on every push)
|
│ ├── ci.yml # Continuous Integration automated test suite (runs on every push)
|
||||||
│ └── release.yml # Automated standalone binary release workflow (runs on tag v*)
|
│ ├── release-linux.yml # Linux release workflow (compiles Server.bin, Linux_Client.bin, checksums)
|
||||||
|
│ └── release-windows.yml # Windows release workflow (compiles Server.exe, Win_Client.exe, checksums)
|
||||||
├── .gitignore # Ignore venv, caches, DBs, and private keys
|
├── .gitignore # Ignore venv, caches, DBs, and private keys
|
||||||
├── requirements.txt # Unified dependencies
|
├── requirements.txt # Unified dependencies
|
||||||
├── README.md # Comprehensive documentation
|
├── README.md # Comprehensive documentation
|
||||||
@@ -308,7 +309,9 @@ Continuous integration is automated via [`.gitea/workflows/ci.yml`](.gitea/workf
|
|||||||
|
|
||||||
## Automated Releases via Gitea Actions
|
## Automated Releases via Gitea Actions
|
||||||
|
|
||||||
Release builds are automated via [`.gitea/workflows/release.yml`](.gitea/workflows/release.yml) using your Gitea action runner:
|
Release builds are automated via two dedicated Gitea Actions workflows running concurrently on native platform runners:
|
||||||
|
- [`.gitea/workflows/release-linux.yml`](.gitea/workflows/release-linux.yml) (`ubuntu-latest`)
|
||||||
|
- [`.gitea/workflows/release-windows.yml`](.gitea/workflows/release-windows.yml) (`windows-latest`)
|
||||||
|
|
||||||
### Publishing a Release
|
### Publishing a Release
|
||||||
Whenever you want to release a new version with compiled standalone binaries:
|
Whenever you want to release a new version with compiled standalone binaries:
|
||||||
@@ -316,22 +319,54 @@ Whenever you want to release a new version with compiled standalone binaries:
|
|||||||
git tag v1.0.1
|
git tag v1.0.1
|
||||||
git push origin v1.0.1
|
git push origin v1.0.1
|
||||||
```
|
```
|
||||||
|
*(You can also trigger builds manually via the Gitea UI using the **Run workflow** button (`workflow_dispatch`) on either workflow).*
|
||||||
|
|
||||||
### What Gitea Actions Does Automatically:
|
### Automated Multi-Platform Compilation:
|
||||||
1. Gitea runner executes the workflow on tag push.
|
1. **Linux Runner** (`release-linux.yml`):
|
||||||
2. Installs Python, system build tools (`binutils`, `zip`), PyInstaller, and project dependencies via `apt-get` and `pip3`.
|
- Compiles native Linux ELF executables: `Linux_Client.bin` and `Server.bin`.
|
||||||
3. Runs `package_dist.py` to compile standalone binaries:
|
- Generates dedicated SHA-256 checksum files:
|
||||||
- `Linux_Client.bin` (native ELF binary compiled with PyInstaller)
|
- `linux_client_sha256sum` (verification for `Linux_Client.bin`)
|
||||||
- `Server.bin` (native server ELF binary compiled with PyInstaller)
|
- `linux_agent_sha256sum` (alias for client/agent integrations)
|
||||||
- `Win_Client.pyz` (standalone executable zipapp)
|
- `linux_server_sha256sum` (verification for `Server.bin`)
|
||||||
- `SHA256SUMS.txt` (SHA-256 cryptographic checksums)
|
- `SHA256SUMS_linux.txt` (summary manifest)
|
||||||
4. Publishes the Gitea release directly via Python (`python3 upload_release.py --skip-build`) using the Gitea REST API to attach the compiled binary assets (avoiding runner Node runtime limitations).
|
- Attaches all Linux assets to the Gitea release.
|
||||||
|
|
||||||
### Building & Publishing Windows Executables (`.exe`) Locally
|
2. **Windows Runner** (`release-windows.yml`):
|
||||||
Because the Linux Gitea runner compiles ELF binaries, native Windows PE executables (`Win_Client.exe`, `Server.exe`) can be built and published directly from a Windows workstation:
|
- Compiles native Windows PE executables: `Win_Client.exe` and `Server.exe`.
|
||||||
|
- Generates dedicated SHA-256 checksum files:
|
||||||
|
- `win_client_sha256sum` (verification for `Win_Client.exe`)
|
||||||
|
- `win_agent_sha256sum` (alias for client/agent integrations)
|
||||||
|
- `win_server_sha256sum` (verification for `Server.exe`)
|
||||||
|
- `SHA256SUMS_windows.txt` (summary manifest)
|
||||||
|
- Attaches all Windows assets to the Gitea release.
|
||||||
|
|
||||||
|
3. **Concurrent Publishing & Conflict Handling**:
|
||||||
|
`upload_release.py` includes automatic retry and conflict resolution so concurrent Windows and Linux runners attach their respective assets to the release without collision.
|
||||||
|
|
||||||
|
### Verifying Checksums
|
||||||
|
- On Linux:
|
||||||
|
```bash
|
||||||
|
sha256sum -c linux_client_sha256sum
|
||||||
|
# or
|
||||||
|
sha256sum -c linux_server_sha256sum
|
||||||
|
```
|
||||||
|
- On Windows (PowerShell):
|
||||||
```powershell
|
```powershell
|
||||||
# Compiles Win_Client.exe, Server.exe, Linux_Client.bin, and uploads to Gitea
|
Get-FileHash .\Win_Client.exe -Algorithm SHA256
|
||||||
python upload_release.py --tag v1.0.0 --token <YOUR_GITEA_TOKEN>
|
Get-Content .\win_client_sha256sum
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local Packaging & Manual Upload
|
||||||
|
You can also compile and package binaries locally anytime:
|
||||||
|
```bash
|
||||||
|
# Windows
|
||||||
|
py -3.12 package_dist.py --target windows
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
python3 package_dist.py --target linux
|
||||||
|
```
|
||||||
|
To upload local builds directly to Gitea:
|
||||||
|
```powershell
|
||||||
|
python upload_release.py --tag v1.0.1 --token <YOUR_GITEA_TOKEN>
|
||||||
```
|
```
|
||||||
*(Environment variables `GITEA_TOKEN`, `GITEA_SERVER_URL`, `GITEA_REPOSITORY`, and `GITEA_REF_NAME` are also supported automatically).*
|
*(Environment variables `GITEA_TOKEN`, `GITEA_SERVER_URL`, `GITEA_REPOSITORY`, and `GITEA_REF_NAME` are also supported automatically).*
|
||||||
|
|||||||
+3
-1
@@ -1,8 +1,10 @@
|
|||||||
# LOGAR Release v1.0.1
|
# LOGAR Release v1.0.1
|
||||||
|
|
||||||
### Changes in this Release:
|
### Changes in this Release:
|
||||||
|
- **Dual Platform Gitea Release Automation**: Added dedicated Windows (`release-windows.yml`) and Linux (`release-linux.yml`) Gitea Actions to compile native platform binaries (`Win_Client.exe` and `Server.exe` on Windows; `Linux_Client.bin` and `Server.bin` on Linux).
|
||||||
|
- **Dedicated SHA-256 Checksums**: Release assets now include dedicated checksum files matching `[win/linux]_[client/agent]_sha256sum` (`win_client_sha256sum`, `win_agent_sha256sum`, `win_server_sha256sum`, `linux_client_sha256sum`, `linux_agent_sha256sum`, `linux_server_sha256sum`).
|
||||||
- **Removed Client Filter Logic**: Removed restrictive source-level noise filtering on edge forwarders. Clients now collect and stream all candidate events from `INFO` up to `ERROR` over the lookback window instead of discarding them at the source.
|
- **Removed Client Filter Logic**: Removed restrictive source-level noise filtering on edge forwarders. Clients now collect and stream all candidate events from `INFO` up to `ERROR` over the lookback window instead of discarding them at the source.
|
||||||
- **State Tracking & Deduplication**: Added persistent client state tracking (`client_state.json`) with journalctl cursors and Windows Event Log record numbers to guarantee that previously transmitted events are never resent.
|
- **State Tracking & Deduplication**: Added persistent client state tracking (`client_state.json`) with journalctl cursors and Windows Event Log record numbers to guarantee that previously transmitted events are never resent.
|
||||||
- **24-Hour Lookback Window**: Forwarders now scan and upload events from the last 24 hours (default `--hours 24`), skipping older entries.
|
- **24-Hour Lookback Window**: Forwarders now scan and upload events from the last 24 hours (default `--hours 24`), skipping older entries.
|
||||||
- **Lightweight Distribution Structure**: Cleaned `out/` to strictly contain deployment documentation and sample configurations.
|
- **Lightweight Distribution Structure**: Cleaned `out/` to strictly contain deployment documentation and sample configurations.
|
||||||
- **Automated Gitea CI/CD**: Integrated push testing workflow (`ci.yml`) and automated release asset packaging (`release.yml`).
|
- **Automated Gitea CI/CD**: Integrated push testing workflow (`ci.yml`) and multi-platform release asset packaging.
|
||||||
|
|||||||
+132
-50
@@ -5,10 +5,10 @@ import zipapp
|
|||||||
import hashlib
|
import hashlib
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
|
||||||
ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
|
ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
DIST_DIR = os.path.abspath("dist")
|
DIST_DIR = os.path.abspath("dist")
|
||||||
OUT_DIR = os.path.abspath("out")
|
|
||||||
BUILD_TEMP = os.path.abspath("build_temp")
|
BUILD_TEMP = os.path.abspath("build_temp")
|
||||||
|
|
||||||
def clean_and_prep():
|
def clean_and_prep():
|
||||||
@@ -37,84 +37,166 @@ def build_pyinstaller_binary(script_path, binary_name):
|
|||||||
raise RuntimeError(f"Failed to build {binary_name}")
|
raise RuntimeError(f"Failed to build {binary_name}")
|
||||||
print(f"[+] Successfully compiled {binary_name}")
|
print(f"[+] Successfully compiled {binary_name}")
|
||||||
|
|
||||||
def build_linux_zipapp_binary():
|
def calculate_sha256(filepath):
|
||||||
print("[*] Packaging Linux_Client.bin executable binary...")
|
h = hashlib.sha256()
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
while chunk := f.read(65536):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
def write_checksum_file(filename, digest, binary_filename):
|
||||||
|
out_path = os.path.join(DIST_DIR, filename)
|
||||||
|
with open(out_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(f"{digest} {binary_filename}\n")
|
||||||
|
print(f"[+] Generated checksum file: {filename} ({digest[:16]}...)")
|
||||||
|
|
||||||
|
def build_linux_zipapp_fallback():
|
||||||
|
print("[*] Packaging Linux standalone zipapp fallback binaries...")
|
||||||
|
# Linux Client zipapp
|
||||||
app_dir = os.path.join(BUILD_TEMP, "linux_app")
|
app_dir = os.path.join(BUILD_TEMP, "linux_app")
|
||||||
os.makedirs(app_dir, exist_ok=True)
|
os.makedirs(app_dir, exist_ok=True)
|
||||||
shutil.copy(os.path.join(ROOT_DIR, "Linux_Client.py"), os.path.join(app_dir, "Linux_Client.py"))
|
shutil.copy(os.path.join(ROOT_DIR, "Linux_Client.py"), os.path.join(app_dir, "Linux_Client.py"))
|
||||||
|
client_out = os.path.join(DIST_DIR, "Linux_Client.bin")
|
||||||
bin_output = os.path.join(DIST_DIR, "Linux_Client.bin")
|
|
||||||
zipapp.create_archive(
|
zipapp.create_archive(
|
||||||
source=app_dir,
|
source=app_dir,
|
||||||
target=bin_output,
|
target=client_out,
|
||||||
interpreter="/usr/bin/env python3",
|
interpreter="/usr/bin/env python3",
|
||||||
main="Linux_Client:main"
|
main="Linux_Client:main"
|
||||||
)
|
)
|
||||||
print(f"[+] Successfully generated {bin_output}")
|
# Server zipapp
|
||||||
|
srv_dir = os.path.join(BUILD_TEMP, "linux_srv")
|
||||||
|
os.makedirs(srv_dir, exist_ok=True)
|
||||||
|
shutil.copy(os.path.join(ROOT_DIR, "Server.py"), os.path.join(srv_dir, "Server.py"))
|
||||||
|
server_out = os.path.join(DIST_DIR, "Server.bin")
|
||||||
|
zipapp.create_archive(
|
||||||
|
source=srv_dir,
|
||||||
|
target=server_out,
|
||||||
|
interpreter="/usr/bin/env python3",
|
||||||
|
main="Server:main"
|
||||||
|
)
|
||||||
|
|
||||||
def generate_checksums():
|
def build_windows():
|
||||||
checksum_file = os.path.join(DIST_DIR, "SHA256SUMS.txt")
|
print("[*] Compiling Windows standalone executables...")
|
||||||
lines = []
|
|
||||||
for fname in sorted(os.listdir(DIST_DIR)):
|
|
||||||
if fname == "SHA256SUMS.txt":
|
|
||||||
continue
|
|
||||||
fpath = os.path.join(DIST_DIR, fname)
|
|
||||||
if os.path.isfile(fpath):
|
|
||||||
with open(fpath, "rb") as f:
|
|
||||||
digest = hashlib.sha256(f.read()).hexdigest()
|
|
||||||
lines.append(f"{digest} {fname}")
|
|
||||||
with open(checksum_file, "w", encoding="utf-8") as f:
|
|
||||||
f.write("\n".join(lines) + "\n")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("=" * 60)
|
|
||||||
print(" LOGAR Binary Packaging (Binaries Only)")
|
|
||||||
print(f" Platform: {platform.system()} ({platform.machine()})")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
clean_and_prep()
|
|
||||||
|
|
||||||
is_windows = platform.system() == "Windows"
|
|
||||||
|
|
||||||
if is_windows:
|
|
||||||
# Build Windows client executable
|
|
||||||
win_client_script = os.path.join(ROOT_DIR, "Win_Client.py")
|
win_client_script = os.path.join(ROOT_DIR, "Win_Client.py")
|
||||||
build_pyinstaller_binary(win_client_script, "Win_Client")
|
build_pyinstaller_binary(win_client_script, "Win_Client")
|
||||||
|
|
||||||
# Build Windows server executable
|
|
||||||
server_script = os.path.join(ROOT_DIR, "Server.py")
|
server_script = os.path.join(ROOT_DIR, "Server.py")
|
||||||
build_pyinstaller_binary(server_script, "Server")
|
build_pyinstaller_binary(server_script, "Server")
|
||||||
|
|
||||||
# Build Linux client standalone binary
|
client_bin = os.path.join(DIST_DIR, "Win_Client.exe")
|
||||||
build_linux_zipapp_binary()
|
server_bin = os.path.join(DIST_DIR, "Server.exe")
|
||||||
|
|
||||||
|
sums = []
|
||||||
|
if os.path.exists(client_bin):
|
||||||
|
client_hash = calculate_sha256(client_bin)
|
||||||
|
write_checksum_file("win_client_sha256sum", client_hash, "Win_Client.exe")
|
||||||
|
write_checksum_file("win_agent_sha256sum", client_hash, "Win_Client.exe")
|
||||||
|
sums.append(f"{client_hash} Win_Client.exe")
|
||||||
else:
|
else:
|
||||||
# On Linux runner: Build native Linux binaries
|
print(f"[!] Warning: Expected {client_bin} was not found.")
|
||||||
|
|
||||||
|
if os.path.exists(server_bin):
|
||||||
|
server_hash = calculate_sha256(server_bin)
|
||||||
|
write_checksum_file("win_server_sha256sum", server_hash, "Server.exe")
|
||||||
|
sums.append(f"{server_hash} Server.exe")
|
||||||
|
else:
|
||||||
|
print(f"[!] Warning: Expected {server_bin} was not found.")
|
||||||
|
|
||||||
|
sums_path = os.path.join(DIST_DIR, "SHA256SUMS_windows.txt")
|
||||||
|
with open(sums_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(sums) + "\n")
|
||||||
|
|
||||||
|
def build_linux():
|
||||||
|
print("[*] Compiling Linux standalone binaries...")
|
||||||
|
is_linux_host = platform.system() == "Linux"
|
||||||
|
|
||||||
|
if is_linux_host:
|
||||||
linux_client_script = os.path.join(ROOT_DIR, "Linux_Client.py")
|
linux_client_script = os.path.join(ROOT_DIR, "Linux_Client.py")
|
||||||
build_pyinstaller_binary(linux_client_script, "Linux_Client.bin")
|
build_pyinstaller_binary(linux_client_script, "Linux_Client.bin")
|
||||||
|
|
||||||
server_script = os.path.join(ROOT_DIR, "Server.py")
|
server_script = os.path.join(ROOT_DIR, "Server.py")
|
||||||
build_pyinstaller_binary(server_script, "Server.bin")
|
build_pyinstaller_binary(server_script, "Server.bin")
|
||||||
|
|
||||||
# Also package standalone Windows zipapp executable
|
# Normalize extensions in case PyInstaller dropped .bin
|
||||||
win_app_dir = os.path.join(BUILD_TEMP, "win_app")
|
for name in ["Linux_Client", "Server"]:
|
||||||
os.makedirs(win_app_dir, exist_ok=True)
|
plain_path = os.path.join(DIST_DIR, name)
|
||||||
shutil.copy(os.path.join(ROOT_DIR, "Win_Client.py"), os.path.join(win_app_dir, "Win_Client.py"))
|
bin_path = os.path.join(DIST_DIR, f"{name}.bin")
|
||||||
win_bin_output = os.path.join(DIST_DIR, "Win_Client.pyz")
|
if os.path.exists(plain_path) and not os.path.exists(bin_path):
|
||||||
zipapp.create_archive(
|
os.rename(plain_path, bin_path)
|
||||||
source=win_app_dir,
|
|
||||||
target=win_bin_output,
|
|
||||||
interpreter="/usr/bin/env python3",
|
|
||||||
main="Win_Client:main"
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_checksums()
|
# Ensure executable permissions on Linux
|
||||||
|
for b in ["Linux_Client.bin", "Server.bin"]:
|
||||||
|
p = os.path.join(DIST_DIR, b)
|
||||||
|
if os.path.exists(p):
|
||||||
|
try:
|
||||||
|
os.chmod(p, 0o755)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print("[!] Note: Host platform is not Linux. Generating executable zipapps for Linux target.")
|
||||||
|
build_linux_zipapp_fallback()
|
||||||
|
|
||||||
|
client_bin = os.path.join(DIST_DIR, "Linux_Client.bin")
|
||||||
|
server_bin = os.path.join(DIST_DIR, "Server.bin")
|
||||||
|
|
||||||
|
sums = []
|
||||||
|
if os.path.exists(client_bin):
|
||||||
|
client_hash = calculate_sha256(client_bin)
|
||||||
|
write_checksum_file("linux_client_sha256sum", client_hash, "Linux_Client.bin")
|
||||||
|
write_checksum_file("linux_agent_sha256sum", client_hash, "Linux_Client.bin")
|
||||||
|
sums.append(f"{client_hash} Linux_Client.bin")
|
||||||
|
else:
|
||||||
|
print(f"[!] Warning: Expected {client_bin} was not found.")
|
||||||
|
|
||||||
|
if os.path.exists(server_bin):
|
||||||
|
server_hash = calculate_sha256(server_bin)
|
||||||
|
write_checksum_file("linux_server_sha256sum", server_hash, "Server.bin")
|
||||||
|
sums.append(f"{server_hash} Server.bin")
|
||||||
|
else:
|
||||||
|
print(f"[!] Warning: Expected {server_bin} was not found.")
|
||||||
|
|
||||||
|
sums_path = os.path.join(DIST_DIR, "SHA256SUMS_linux.txt")
|
||||||
|
with open(sums_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(sums) + "\n")
|
||||||
|
|
||||||
|
def main(target=None):
|
||||||
|
if target is None:
|
||||||
|
parser = argparse.ArgumentParser(description="LOGAR Standalone Binary Compiler & Packager")
|
||||||
|
parser.add_argument(
|
||||||
|
"--target", "-t",
|
||||||
|
choices=["windows", "win", "linux", "auto"],
|
||||||
|
default="auto",
|
||||||
|
help="Target platform to compile binaries for (default: auto-detect)"
|
||||||
|
)
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
target = args.target
|
||||||
|
|
||||||
|
if target == "auto":
|
||||||
|
target = "windows" if platform.system() == "Windows" else "linux"
|
||||||
|
elif target == "win":
|
||||||
|
target = "windows"
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(" LOGAR Binary Packaging")
|
||||||
|
print(f" Host Platform: {platform.system()} ({platform.machine()})")
|
||||||
|
print(f" Target Platform: {target.upper()}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
clean_and_prep()
|
||||||
|
|
||||||
|
if target == "windows":
|
||||||
|
build_windows()
|
||||||
|
elif target == "linux":
|
||||||
|
build_linux()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported target: {target}")
|
||||||
|
|
||||||
# Clean temporary build directory
|
# Clean temporary build directory
|
||||||
if os.path.exists(BUILD_TEMP):
|
if os.path.exists(BUILD_TEMP):
|
||||||
shutil.rmtree(BUILD_TEMP, ignore_errors=True)
|
shutil.rmtree(BUILD_TEMP, ignore_errors=True)
|
||||||
|
|
||||||
print("\n[+] Binary shipping artifacts assembled in 'dist/':")
|
print("\n[+] Binary shipping artifacts assembled in 'dist/':")
|
||||||
for f in os.listdir(DIST_DIR):
|
for f in sorted(os.listdir(DIST_DIR)):
|
||||||
sz = os.path.getsize(os.path.join(DIST_DIR, f))
|
sz = os.path.getsize(os.path.join(DIST_DIR, f))
|
||||||
print(f" - {f} ({sz / (1024*1024):.2f} MB)" if sz > 1024*1024 else f" - {f} ({sz} bytes)")
|
print(f" - {f} ({sz / (1024*1024):.2f} MB)" if sz > 1024*1024 else f" - {f} ({sz} bytes)")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|||||||
+30
-3
@@ -7,6 +7,8 @@ import urllib.parse
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import package_dist
|
import package_dist
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
DEFAULT_GITEA_URL = os.environ.get("GITEA_SERVER_URL", "https://gitea.eibl.tech")
|
DEFAULT_GITEA_URL = os.environ.get("GITEA_SERVER_URL", "https://gitea.eibl.tech")
|
||||||
DEFAULT_REPO = os.environ.get("GITEA_REPOSITORY", "me0nline/LOGAR")
|
DEFAULT_REPO = os.environ.get("GITEA_REPOSITORY", "me0nline/LOGAR")
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ def delete_asset(base_url, repo, release_id, asset_id, token):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def upload_file_to_release(base_url, repo, release_id, token, file_path):
|
def upload_file_to_release(base_url, repo, release_id, token, file_path, max_retries=3):
|
||||||
filename = os.path.basename(file_path)
|
filename = os.path.basename(file_path)
|
||||||
|
|
||||||
# Clean up existing asset with same name if already present
|
# Clean up existing asset with same name if already present
|
||||||
@@ -42,6 +44,7 @@ def upload_file_to_release(base_url, repo, release_id, token, file_path):
|
|||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
file_bytes = f.read()
|
file_bytes = f.read()
|
||||||
|
|
||||||
|
for attempt in range(1, max_retries + 1):
|
||||||
req = urllib.request.Request(url, data=file_bytes, method="POST")
|
req = urllib.request.Request(url, data=file_bytes, method="POST")
|
||||||
req.add_header("Authorization", f"token {token}")
|
req.add_header("Authorization", f"token {token}")
|
||||||
req.add_header("Content-Type", "application/octet-stream")
|
req.add_header("Content-Type", "application/octet-stream")
|
||||||
@@ -54,7 +57,16 @@ def upload_file_to_release(base_url, repo, release_id, token, file_path):
|
|||||||
return data
|
return data
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
err = e.read().decode("utf-8", errors="ignore")
|
err = e.read().decode("utf-8", errors="ignore")
|
||||||
print(f"[!] Error uploading {filename}: HTTP {e.code} - {err}")
|
print(f"[!] Attempt {attempt}/{max_retries} - Error uploading {filename}: HTTP {e.code} - {err}")
|
||||||
|
if attempt < max_retries:
|
||||||
|
time.sleep(2 * attempt)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except Exception as ex:
|
||||||
|
print(f"[!] Attempt {attempt}/{max_retries} - Exception uploading {filename}: {ex}")
|
||||||
|
if attempt < max_retries:
|
||||||
|
time.sleep(2 * attempt)
|
||||||
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_or_get_release(base_url, repo, tag, token, title=None, notes=None):
|
def create_or_get_release(base_url, repo, tag, token, title=None, notes=None):
|
||||||
@@ -103,10 +115,23 @@ def create_or_get_release(base_url, repo, tag, token, title=None, notes=None):
|
|||||||
"prerelease": False
|
"prerelease": False
|
||||||
}
|
}
|
||||||
req = urllib.request.Request(url, data=json.dumps(payload).encode("utf-8"), headers=headers, method="POST")
|
req = urllib.request.Request(url, data=json.dumps(payload).encode("utf-8"), headers=headers, method="POST")
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req) as resp:
|
with urllib.request.urlopen(req) as resp:
|
||||||
created = json.loads(resp.read().decode("utf-8"))
|
created = json.loads(resp.read().decode("utf-8"))
|
||||||
print(f"[+] Created release {tag} (ID: {created['id']})")
|
print(f"[+] Created release {tag} (ID: {created['id']})")
|
||||||
return created["id"]
|
return created["id"]
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print(f"[*] Release creation returned HTTP {e.code}. Checking if peer runner created it concurrently...")
|
||||||
|
for attempt in range(1, 6):
|
||||||
|
time.sleep(2)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(check_req) as resp:
|
||||||
|
existing = json.loads(resp.read().decode("utf-8"))
|
||||||
|
print(f"[+] Retrieved peer-created release for tag {tag} (ID: {existing['id']})")
|
||||||
|
return existing["id"]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
raise
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Upload LOGAR compiled binaries directly to Gitea Release")
|
parser = argparse.ArgumentParser(description="Upload LOGAR compiled binaries directly to Gitea Release")
|
||||||
@@ -144,11 +169,13 @@ def main():
|
|||||||
notes = (
|
notes = (
|
||||||
f"## LOGAR Release {tag}\n\n"
|
f"## LOGAR Release {tag}\n\n"
|
||||||
"### Changes in this Release:\n"
|
"### Changes in this Release:\n"
|
||||||
|
"- **Dual Platform Gitea Release Automation**: Added dedicated Windows (`release-windows.yml`) and Linux (`release-linux.yml`) Gitea Actions to compile native executables and publish assets concurrently.\n"
|
||||||
|
"- **Dedicated SHA-256 Checksums**: Release assets now include dedicated checksum files matching `[win/linux]_[client/agent]_sha256sum` (e.g., `win_client_sha256sum`, `win_agent_sha256sum`, `win_server_sha256sum`, `linux_client_sha256sum`, `linux_agent_sha256sum`, `linux_server_sha256sum`).\n"
|
||||||
"- **Removed Client Filter Logic**: Removed restrictive source-level noise filtering on edge forwarders. Clients now stream all candidate events from `INFO` up to `ERROR` across the lookback window instead of discarding them at the source.\n"
|
"- **Removed Client Filter Logic**: Removed restrictive source-level noise filtering on edge forwarders. Clients now stream all candidate events from `INFO` up to `ERROR` across the lookback window instead of discarding them at the source.\n"
|
||||||
"- **State Tracking & Deduplication**: Added persistent state tracking (`client_state.json`) with cursor and record number deduplication so previously transmitted events are never resent.\n"
|
"- **State Tracking & Deduplication**: Added persistent state tracking (`client_state.json`) with cursor and record number deduplication so previously transmitted events are never resent.\n"
|
||||||
"- **24-Hour Lookback Window**: Forwarders now scan and upload events from the last 24 hours, skipping older entries.\n"
|
"- **24-Hour Lookback Window**: Forwarders now scan and upload events from the last 24 hours, skipping older entries.\n"
|
||||||
"- **Lightweight Distribution Structure**: Cleaned `out/` to strictly contain deployment documentation and sample configurations.\n"
|
"- **Lightweight Distribution Structure**: Cleaned `out/` to strictly contain deployment documentation and sample configurations.\n"
|
||||||
"- **Automated Gitea CI/CD**: Integrated push testing workflow (`ci.yml`) and automated release asset packaging (`release.yml`).\n"
|
"- **Automated Gitea CI/CD**: Integrated push testing workflow (`ci.yml`) and automated release asset packaging.\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
title = args.title or f"LOGAR Release {tag}"
|
title = args.title or f"LOGAR Release {tag}"
|
||||||
|
|||||||
Reference in New Issue
Block a user