Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49b0e3025d | ||
|
|
aefec51581 | ||
|
|
7555e92aac | ||
|
|
b97b554f84 | ||
|
|
77d7eaa0b5 | ||
|
|
f91f45ad92 | ||
|
|
c27b53ea0d | ||
|
|
f9d7647046 | ||
|
|
d37fba137e | ||
|
|
e73d79b520 | ||
|
|
b0e3e7dc74 | ||
|
|
290033680b | ||
|
|
3ea0baa147 | ||
|
|
e01aa3dce3 | ||
|
|
8bc1d78fc9 |
@@ -33,8 +33,9 @@ jobs:
|
||||
|
||||
build-client-windows:
|
||||
needs: validate-client
|
||||
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.build_windows == 'true')
|
||||
runs-on: windows
|
||||
# .NET can publish a self-contained Windows x64 client from Linux.
|
||||
# This keeps releases independent of a Windows Gitea runner.
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -50,10 +51,14 @@ jobs:
|
||||
./build/build-client-package.ps1
|
||||
|
||||
- name: Build release manifest for tags
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tag = "${{ github.ref_name }}"
|
||||
$ref = if ($env:GITHUB_REF) { $env:GITHUB_REF } else { $env:GITEA_REF }
|
||||
$tag = if ($env:GITHUB_REF_NAME) { $env:GITHUB_REF_NAME } else { Split-Path -Leaf $ref }
|
||||
if ($tag -notlike "v*") {
|
||||
Write-Host "Not a version tag; skipping release manifest."
|
||||
exit 0
|
||||
}
|
||||
$artifactUrl = "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/$tag/OCSentinelClient-win-x64.zip"
|
||||
./build/build-release-manifest.ps1 -ArtifactUrl $artifactUrl
|
||||
|
||||
@@ -66,3 +71,41 @@ jobs:
|
||||
artifacts/OCSentinelClient-win-x64.zip.sha256
|
||||
artifacts/version.json
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Publish Gitea release assets
|
||||
shell: pwsh
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
$ref = if ($env:GITHUB_REF) { $env:GITHUB_REF } else { $env:GITEA_REF }
|
||||
$tag = if ($env:GITHUB_REF_NAME) { $env:GITHUB_REF_NAME } else { Split-Path -Leaf $ref }
|
||||
if ($tag -notlike "v*") {
|
||||
Write-Host "Not a version tag; skipping Gitea release publication."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$headers = @{ Authorization = "token $env:GITEA_TOKEN" }
|
||||
$repository = if ($env:GITEA_REPOSITORY) { $env:GITEA_REPOSITORY } else { $env:GITHUB_REPOSITORY }
|
||||
$serverUrl = if ($env:GITEA_SERVER_URL) { $env:GITEA_SERVER_URL } else { $env:GITHUB_SERVER_URL }
|
||||
if ([string]::IsNullOrWhiteSpace($env:GITEA_TOKEN) -or [string]::IsNullOrWhiteSpace($repository) -or [string]::IsNullOrWhiteSpace($serverUrl)) {
|
||||
throw "Gitea release environment is incomplete. Expected GITEA_TOKEN, repository, and server URL."
|
||||
}
|
||||
$baseUrl = "$serverUrl/api/v1/repos/$repository"
|
||||
$releaseBody = @{
|
||||
tag_name = $tag
|
||||
target_commitish = "${{ github.sha }}"
|
||||
name = "OfficeCom Sentinel $tag"
|
||||
body = "Automated OfficeCom Sentinel client release."
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$release = Invoke-RestMethod -Method Get -Headers $headers -Uri "$baseUrl/releases/tags/$tag"
|
||||
}
|
||||
catch {
|
||||
$release = Invoke-RestMethod -Method Post -Headers $headers -ContentType "application/json" -Body $releaseBody -Uri "$baseUrl/releases"
|
||||
}
|
||||
|
||||
foreach ($file in @("artifacts/OCSentinelClient-win-x64.zip", "artifacts/OCSentinelClient-win-x64.zip.sha256", "artifacts/version.json")) {
|
||||
$assetName = [System.IO.Path]::GetFileName($file)
|
||||
Invoke-RestMethod -Method Post -Headers $headers -InFile $file -ContentType "application/octet-stream" -Uri "$baseUrl/releases/$($release.id)/assets?name=$assetName" | Out-Null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"channel": "stable",
|
||||
"version": "1.2.4",
|
||||
"publishedAtUtc": "2026-07-25T00:00:00Z",
|
||||
"artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.2.4/OCSentinelClient-win-x64.zip",
|
||||
"sha256": "eab8217bdda725e4969849d881fa6c9d892b6c5658aaf153a783b1d0f611f481",
|
||||
"version": "1.2.10",
|
||||
"publishedAtUtc": "2026-07-25T19:14:36.9210206Z",
|
||||
"artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.2.10/OCSentinelClient-win-x64.zip",
|
||||
"sha256": "4de7abf96ff9ed47da02ac7f586cc39102fa508372825c2f7bf5e1df2b0f6c80",
|
||||
"minUpdaterVersion": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,20 @@ $clientConfigPath = Join-Path $installRoot "config\ocsentinel-client.json"
|
||||
$secretScriptPath = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
|
||||
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
|
||||
|
||||
# NinjaOne script variables are exposed as process environment variables.
|
||||
if ([string]::IsNullOrWhiteSpace($WebhookUrl)) {
|
||||
$WebhookUrl = $env:WebhookUrl
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SecretValue)) {
|
||||
$SecretValue = $env:SecretValue
|
||||
}
|
||||
|
||||
$runInitialScan = $RunInitialStatusScan.IsPresent
|
||||
if (-not $runInitialScan -and -not [string]::IsNullOrWhiteSpace($env:RunInitialStatusScan)) {
|
||||
$runInitialScan = $env:RunInitialStatusScan -match '^(1|true|yes|on)$'
|
||||
}
|
||||
|
||||
function Assert-ArtifactSignature {
|
||||
param([Parameter(Mandatory)][string]$ExecutablePath)
|
||||
|
||||
@@ -108,7 +122,7 @@ if (-not [string]::IsNullOrWhiteSpace($SecretValue)) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($RunInitialStatusScan) {
|
||||
if ($runInitialScan) {
|
||||
if (-not (Test-Path -LiteralPath $monitorPath)) {
|
||||
throw "OCSentinel was installed, but the monitor script is missing."
|
||||
}
|
||||
|
||||
62
scripts/configure-ocsentinel-ninja.ps1
Normal file
62
scripts/configure-ocsentinel-ninja.ps1
Normal file
@@ -0,0 +1,62 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$WebhookUrl = "",
|
||||
[string]$SecretValue = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-NinjaValue {
|
||||
param([Parameter(Mandatory)][string]$Name)
|
||||
|
||||
$value = [Environment]::GetEnvironmentVariable($Name, "Process")
|
||||
if ($null -eq $value) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return $value.Trim()
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($WebhookUrl)) {
|
||||
$WebhookUrl = Get-NinjaValue -Name "webhookurl"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SecretValue)) {
|
||||
$SecretValue = Get-NinjaValue -Name "secretvalue"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($WebhookUrl) -or [string]::IsNullOrWhiteSpace($SecretValue)) {
|
||||
throw "WebhookUrl and SecretValue must be supplied as NinjaOne script variables."
|
||||
}
|
||||
|
||||
$installRoot = Join-Path $env:ProgramFiles "OCSentinel"
|
||||
$configPath = Join-Path $installRoot "config\ocsentinel-client.json"
|
||||
$secretScript = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
|
||||
$monitorScript = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1"
|
||||
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
|
||||
|
||||
foreach ($path in @($configPath, $secretScript, $monitorScript)) {
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "OCSentinel installation is incomplete. Missing: $path"
|
||||
}
|
||||
}
|
||||
|
||||
$config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
|
||||
$config.n8nWebhookUrl = $WebhookUrl
|
||||
$config.environment = "production"
|
||||
$config | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $configPath -Encoding UTF8
|
||||
Write-Host "OCSentinel upload endpoint configured."
|
||||
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $secretScript -SecretValue $SecretValue
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Writing the protected upload secret failed with code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
Write-Host "Running signed OCSentinel test scan and upload."
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript `
|
||||
-Mode status `
|
||||
-ClientConfigPath $configPath `
|
||||
-SecretPath $secretPath `
|
||||
-UploadMode required
|
||||
|
||||
exit $LASTEXITCODE
|
||||
74
scripts/install-ocsentinel-ninja-once.ps1
Normal file
74
scripts/install-ocsentinel-ninja-once.ps1
Normal file
@@ -0,0 +1,74 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ManifestUrl = "https://gitea.officecom.cloud/officecom/oc-sentinel/raw/main/release/stable/version.json",
|
||||
[string]$WebhookUrl = "",
|
||||
[string]$SecretValue = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
function Get-NinjaValue {
|
||||
param([Parameter(Mandatory)][string]$Name)
|
||||
|
||||
$value = [Environment]::GetEnvironmentVariable($Name, "Process")
|
||||
if ($null -eq $value) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return $value.Trim()
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($WebhookUrl)) { $WebhookUrl = Get-NinjaValue -Name "webhookurl" }
|
||||
if ([string]::IsNullOrWhiteSpace($SecretValue)) { $SecretValue = Get-NinjaValue -Name "secretvalue" }
|
||||
if ([string]::IsNullOrWhiteSpace($WebhookUrl) -or [string]::IsNullOrWhiteSpace($SecretValue)) {
|
||||
throw "WebhookUrl and SecretValue must be set as NinjaOne script variables."
|
||||
}
|
||||
|
||||
$manifest = Invoke-RestMethod -Method Get -Uri $ManifestUrl -TimeoutSec 60
|
||||
if ([string]::IsNullOrWhiteSpace($manifest.artifactUrl) -or [string]::IsNullOrWhiteSpace($manifest.sha256)) {
|
||||
throw "The release manifest is incomplete."
|
||||
}
|
||||
|
||||
$downloadRoot = Join-Path $env:ProgramData ("OCSentinel\\install-" + [Guid]::NewGuid().ToString("N"))
|
||||
$zipPath = Join-Path $downloadRoot "OCSentinelClient.zip"
|
||||
$extractPath = Join-Path $downloadRoot "payload"
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $extractPath | Out-Null
|
||||
Write-Host "Downloading OCSentinel $($manifest.version)."
|
||||
Invoke-WebRequest -Uri $manifest.artifactUrl -OutFile $zipPath -TimeoutSec 300
|
||||
$actualHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if ($actualHash -ne ([string]$manifest.sha256).ToLowerInvariant()) {
|
||||
throw "Release package SHA-256 validation failed."
|
||||
}
|
||||
|
||||
Expand-Archive -LiteralPath $zipPath -DestinationPath $extractPath -Force
|
||||
$installer = Get-ChildItem -Path $extractPath -Recurse -Filter "install-ocsentinel.ps1" | Select-Object -First 1
|
||||
if ($null -eq $installer) { throw "The release package does not contain the installer." }
|
||||
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer.FullName
|
||||
if ($LASTEXITCODE -ne 0) { throw "Installer failed with code $LASTEXITCODE" }
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $downloadRoot) { Remove-Item -LiteralPath $downloadRoot -Recurse -Force }
|
||||
}
|
||||
|
||||
$installRoot = Join-Path $env:ProgramFiles "OCSentinel"
|
||||
$configPath = Join-Path $installRoot "config\ocsentinel-client.json"
|
||||
$secretScript = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
|
||||
$monitorScript = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1"
|
||||
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
|
||||
|
||||
$config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
|
||||
$config.n8nWebhookUrl = $WebhookUrl
|
||||
$config.environment = "production"
|
||||
$config | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $configPath -Encoding UTF8
|
||||
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $secretScript -SecretValue $SecretValue
|
||||
if ($LASTEXITCODE -ne 0) { throw "Writing the protected upload secret failed with code $LASTEXITCODE" }
|
||||
|
||||
Write-Host "Running initial signed scan and upload."
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript -Mode status -ClientConfigPath $configPath -SecretPath $secretPath -UploadMode required
|
||||
exit $LASTEXITCODE
|
||||
@@ -5,7 +5,10 @@ internal static class ScanAndUploadCommand
|
||||
public static int Execute(string[] args)
|
||||
{
|
||||
string outputPath = @"C:\ProgramData\OCSentinel\reports\latest.json";
|
||||
string? clientConfigPath = null;
|
||||
string? secretPath = null;
|
||||
bool hasOutput = false;
|
||||
var scanArgs = new List<string>();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
@@ -13,11 +16,26 @@ internal static class ScanAndUploadCommand
|
||||
{
|
||||
outputPath = args[i + 1];
|
||||
hasOutput = true;
|
||||
break;
|
||||
scanArgs.Add(args[i]);
|
||||
scanArgs.Add(args[++i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(args[i], "--client-config", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
|
||||
{
|
||||
clientConfigPath = args[++i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(args[i], "--secret-path", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
|
||||
{
|
||||
secretPath = args[++i];
|
||||
continue;
|
||||
}
|
||||
|
||||
scanArgs.Add(args[i]);
|
||||
}
|
||||
|
||||
List<string> scanArgs = [.. args];
|
||||
if (!hasOutput)
|
||||
{
|
||||
scanArgs.Add("--output");
|
||||
@@ -36,6 +54,16 @@ internal static class ScanAndUploadCommand
|
||||
outputPath
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(clientConfigPath))
|
||||
{
|
||||
uploadArgs.AddRange(["--client-config", clientConfigPath]);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(secretPath))
|
||||
{
|
||||
uploadArgs.AddRange(["--secret-path", secretPath]);
|
||||
}
|
||||
|
||||
return UploadCommand.Execute([.. uploadArgs]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ internal static class JsonOptions
|
||||
public static readonly JsonSerializerOptions Default = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
// Client configuration is also written by PowerShell/NinjaOne scripts.
|
||||
// Accept their conventional camelCase names (for example n8nWebhookUrl).
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<RootNamespace>OCSentinelCli</RootNamespace>
|
||||
<Product>OfficeCom Sentinel</Product>
|
||||
<Company>OfficeCom</Company>
|
||||
<Version>1.2.5</Version>
|
||||
<AssemblyVersion>1.2.3.0</AssemblyVersion>
|
||||
<FileVersion>1.2.3.0</FileVersion>
|
||||
<InformationalVersion>1.2.3</InformationalVersion>
|
||||
<Version>1.2.10</Version>
|
||||
<AssemblyVersion>1.2.10.0</AssemblyVersion>
|
||||
<FileVersion>1.2.10.0</FileVersion>
|
||||
<InformationalVersion>1.2.10</InformationalVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user