fix sgn
This commit is contained in:
+11
-10
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user