This commit is contained in:
KingKDot
2026-07-30 09:27:48 -05:00
parent efa8917168
commit 1854dd6b1b
3 changed files with 131 additions and 44 deletions
+11 -10
View File
@@ -61,22 +61,23 @@ RUN DONUT_TAG=$(curl -sSf "https://api.github.com/repos/TheWover/donut/releases/
echo "WARNING: Donut pre-fetch failed — will fall back to system PATH or download on first use"; \ echo "WARNING: Donut pre-fetch failed — will fall back to system PATH or download on first use"; \
fi fi
# Pre-fetch the latest SGN (Shikata Ga Nai) polymorphic encoder. # Pre-fetch the latest Rust SGN (Shikata Ga Nai) binary. The Rust releases use
# Used post-Donut to encode raw shellcode for AV/EDR evasion. The runtime # target-triple tarballs instead of the legacy sgn_linux_amd64_*.zip naming.
# sgn-manager will re-check GitHub daily and update automatically; this # The runtime sgn-manager re-checks GitHub daily; this provides an offline
# step just ensures a working binary is available offline / on first use. # binary for supported Docker architectures on first start.
RUN SGN_ASSET=$(curl -sSf "https://api.github.com/repos/EgeBalci/sgn/releases/latest" \ RUN SGN_ASSET=$(curl -sSf "https://api.github.com/repos/EgeBalci/sgn/releases/latest" \
| grep -oE '"browser_download_url":[[:space:]]*"[^"]*sgn_linux_amd64[^"]*\.zip"' \ | grep -oE '"browser_download_url":[[:space:]]*"[^"]*sgn-x86_64-unknown-linux-musl\.tar\.gz"' \
| head -1 | cut -d'"' -f4) \ | head -1 | cut -d'"' -f4) \
&& if [ -n "${SGN_ASSET}" ] && curl -sSfL "${SGN_ASSET}" -o /tmp/sgn.zip \ && if [ "${TARGETARCH:-amd64}" = "amd64" ] \
&& unzip -j -o /tmp/sgn.zip -d /usr/local/bin >/dev/null 2>&1 \ && [ -n "${SGN_ASSET}" ] \
&& curl -sSfL "${SGN_ASSET}" \
| tar -xzf - -C /usr/local/bin sgn \
&& [ -f /usr/local/bin/sgn ]; then \ && [ -f /usr/local/bin/sgn ]; then \
chmod +x /usr/local/bin/sgn; \ chmod +x /usr/local/bin/sgn; \
echo "SGN pre-installed from ${SGN_ASSET}"; \ echo "SGN pre-installed from ${SGN_ASSET}"; \
else \ else \
echo "WARNING: SGN pre-fetch failed — will fall back to system PATH or download on first use"; \ echo "WARNING: SGN Rust binary is unavailable for ${TARGETARCH:-unknown} — will fall back to system PATH or runtime download"; \
fi \ fi
&& rm -f /tmp/sgn.zip
# Full bun install (includes devDeps needed for tailwind / vendor / minify steps) # Full bun install (includes devDeps needed for tailwind / vendor / minify steps)
COPY Overlord-Server/package.json Overlord-Server/bun.lock* ./ COPY Overlord-Server/package.json Overlord-Server/bun.lock* ./
@@ -0,0 +1,43 @@
import { describe, expect, test } from "bun:test";
import { selectSgnReleaseAsset } from "./sgn-manager";
const rustAssets = [
{
name: "sgn-aarch64-apple-darwin.tar.gz",
browser_download_url: "https://example.test/sgn-aarch64-apple-darwin.tar.gz",
},
{
name: "sgn-x86_64-pc-windows-msvc.zip",
browser_download_url: "https://example.test/sgn-x86_64-pc-windows-msvc.zip",
},
{
name: "sgn-x86_64-unknown-linux-musl.tar.gz",
browser_download_url: "https://example.test/sgn-x86_64-unknown-linux-musl.tar.gz",
},
{
name: "sgn-x86_64-unknown-linux-musl.tar.gz.zip",
browser_download_url: "https://example.test/sgn-x86_64-unknown-linux-musl.tar.gz.zip",
},
];
describe("SGN Rust release selection", () => {
test("selects the native target-triple archive", () => {
expect(selectSgnReleaseAsset(rustAssets, "linux", "x64")).toMatchObject({
assetName: "sgn-x86_64-unknown-linux-musl.tar.gz",
archiveFormat: "tar.gz",
});
expect(selectSgnReleaseAsset(rustAssets, "win32", "x64")).toMatchObject({
assetName: "sgn-x86_64-pc-windows-msvc.zip",
archiveFormat: "zip",
});
expect(selectSgnReleaseAsset(rustAssets, "darwin", "arm64")).toMatchObject({
assetName: "sgn-aarch64-apple-darwin.tar.gz",
archiveFormat: "tar.gz",
});
});
test("does not substitute an incompatible architecture", () => {
expect(selectSgnReleaseAsset(rustAssets, "linux", "arm64")).toBeNull();
expect(selectSgnReleaseAsset(rustAssets, "darwin", "x64")).toBeNull();
});
});
+77 -34
View File
@@ -1,6 +1,7 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { $ } from "bun"; import { $ } from "bun";
import AdmZip from "adm-zip";
import { ensureDataDir } from "../paths"; import { ensureDataDir } from "../paths";
import { logger } from "../logger"; import { logger } from "../logger";
@@ -42,7 +43,54 @@ async function findSystemSgn(): Promise<string | null> {
return null; return null;
} }
type AssetInfo = { tag: string; downloadUrl: string; assetName: string }; type SgnArchiveFormat = "zip" | "tar.gz";
type ReleaseAsset = { name: string; browser_download_url: string };
type AssetInfo = {
tag: string;
downloadUrl: string;
assetName: string;
archiveFormat: SgnArchiveFormat;
};
export function selectSgnReleaseAsset(
assets: ReleaseAsset[],
platform = process.platform,
arch = process.arch,
): Omit<AssetInfo, "tag"> | null {
const target = platform === "win32" && arch === "x64"
? { triple: "x86_64-pc-windows-msvc", format: "zip" as const }
: platform === "linux" && arch === "x64"
? { triple: "x86_64-unknown-linux-musl", format: "tar.gz" as const }
: platform === "darwin" && arch === "arm64"
? { triple: "aarch64-apple-darwin", format: "tar.gz" as const }
: null;
if (target) {
const expectedName = `sgn-${target.triple}.${target.format}`.toLowerCase();
const exact = assets.find((asset) => asset.name.toLowerCase() === expectedName);
if (exact) {
return {
downloadUrl: exact.browser_download_url,
assetName: exact.name,
archiveFormat: target.format,
};
}
}
const osTag = platform === "win32" ? "windows" : platform === "darwin" ? "macos" : "linux";
const archTag = arch === "x64" ? "amd64" : arch === "ia32" ? "386" : arch;
const legacy = assets.find((asset) => {
const name = asset.name.toLowerCase();
return name.startsWith(`sgn_${osTag}_${archTag}`) && name.endsWith(".zip");
});
return legacy
? {
downloadUrl: legacy.browser_download_url,
assetName: legacy.name,
archiveFormat: "zip",
}
: null;
}
async function fetchLatest(): Promise<AssetInfo | null> { async function fetchLatest(): Promise<AssetInfo | null> {
try { try {
@@ -53,53 +101,48 @@ async function fetchLatest(): Promise<AssetInfo | null> {
if (!res.ok) return null; if (!res.ok) return null;
const data = await res.json() as any; const data = await res.json() as any;
const tag: string = data.tag_name; const tag: string = data.tag_name;
const assets: { name: string; browser_download_url: string }[] = data.assets ?? []; const selected = selectSgnReleaseAsset(data.assets ?? []);
return selected ? { tag, ...selected } : null;
// SGN release is named sgn_<os>_<arch>_<version>.zip
const osTag = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux";
const archTag = process.arch === "x64" ? "amd64" : process.arch === "ia32" ? "386" : process.arch;
const exact = assets.find(a => {
const n = a.name.toLowerCase();
return n.startsWith(`sgn_${osTag}_${archTag}`) && n.endsWith(".zip");
});
if (exact) return { tag, downloadUrl: exact.browser_download_url, assetName: exact.name };
const looseOs = assets.find(a => a.name.toLowerCase().includes(`_${osTag}_`) && a.name.endsWith(".zip"));
if (looseOs) return { tag, downloadUrl: looseOs.browser_download_url, assetName: looseOs.name };
return null;
} catch { } catch {
return null; return null;
} }
} }
async function downloadAndExtract(url: string, dest: string): Promise<boolean> { async function downloadAndExtract(
url: string,
dest: string,
archiveFormat: SgnArchiveFormat,
): Promise<boolean> {
try { try {
fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.mkdirSync(path.dirname(dest), { recursive: true });
const res = await fetch(url); const res = await fetch(url);
if (!res.ok) return false; if (!res.ok) return false;
const buf = await res.arrayBuffer(); const buf = await res.arrayBuffer();
const tmpZip = dest + ".tmp.zip"; const tmpArchive = `${dest}.tmp.${archiveFormat === "zip" ? "zip" : "tar.gz"}`;
fs.writeFileSync(tmpZip, Buffer.from(buf)); const tmpBinary = `${dest}.tmp.bin`;
fs.writeFileSync(tmpArchive, Buffer.from(buf));
try { try {
const destDir = path.dirname(dest);
const binName = process.platform === "win32" ? "sgn.exe" : "sgn"; const binName = process.platform === "win32" ? "sgn.exe" : "sgn";
const r = await $`unzip -o -j ${tmpZip} -d ${destDir}`.nothrow().quiet(); if (archiveFormat === "zip") {
if (r.exitCode !== 0) return false; const zip = new AdmZip(tmpArchive);
const extracted = path.join(destDir, binName); const entry = zip.getEntries().find((candidate) =>
if (!fs.existsSync(extracted)) { !candidate.isDirectory &&
const entries = fs.readdirSync(destDir); path.basename(candidate.entryName).toLowerCase() === binName.toLowerCase()
const found = entries.find(e => e.toLowerCase().startsWith("sgn") && !e.endsWith(".version") && !e.endsWith(".tmp.zip")); );
if (!found) return false; if (!entry) return false;
fs.renameSync(path.join(destDir, found), dest); fs.writeFileSync(tmpBinary, entry.getData());
} else if (extracted !== dest) { } else {
fs.renameSync(extracted, dest); const result = await $`tar -xOzf ${tmpArchive} sgn`.nothrow().quiet();
if (result.exitCode !== 0 || result.stdout.length === 0) return false;
fs.writeFileSync(tmpBinary, result.stdout);
} }
try { fs.chmodSync(dest, 0o755); } catch {}
try { fs.chmodSync(tmpBinary, 0o755); } catch {}
fs.renameSync(tmpBinary, dest);
return true; return true;
} finally { } finally {
try { fs.unlinkSync(tmpZip); } catch {} try { fs.unlinkSync(tmpArchive); } catch {}
try { fs.unlinkSync(tmpBinary); } catch {}
} }
} catch { } catch {
return false; return false;
@@ -149,7 +192,7 @@ export async function ensureSgn(
} }
log(`SGN: downloading ${latest.assetName}…`); log(`SGN: downloading ${latest.assetName}…`);
const ok = await downloadAndExtract(latest.downloadUrl, bin); const ok = await downloadAndExtract(latest.downloadUrl, bin, latest.archiveFormat);
if (!ok) { if (!ok) {
if (binExists) { if (binExists) {
log(`SGN: download failed — using cached ${cached?.tag ?? "unknown"}`, "warn"); log(`SGN: download failed — using cached ${cached?.tag ?? "unknown"}`, "warn");