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"; \
|
||||
fi
|
||||
|
||||
# Pre-fetch the latest SGN (Shikata Ga Nai) polymorphic encoder.
|
||||
# Used post-Donut to encode raw shellcode for AV/EDR evasion. The runtime
|
||||
# sgn-manager will re-check GitHub daily and update automatically; this
|
||||
# step just ensures a working binary is available offline / on first use.
|
||||
# Pre-fetch the latest Rust SGN (Shikata Ga Nai) binary. The Rust releases use
|
||||
# target-triple tarballs instead of the legacy sgn_linux_amd64_*.zip naming.
|
||||
# The runtime sgn-manager re-checks GitHub daily; this provides an offline
|
||||
# binary for supported Docker architectures on first start.
|
||||
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) \
|
||||
&& if [ -n "${SGN_ASSET}" ] && curl -sSfL "${SGN_ASSET}" -o /tmp/sgn.zip \
|
||||
&& unzip -j -o /tmp/sgn.zip -d /usr/local/bin >/dev/null 2>&1 \
|
||||
&& if [ "${TARGETARCH:-amd64}" = "amd64" ] \
|
||||
&& [ -n "${SGN_ASSET}" ] \
|
||||
&& curl -sSfL "${SGN_ASSET}" \
|
||||
| tar -xzf - -C /usr/local/bin sgn \
|
||||
&& [ -f /usr/local/bin/sgn ]; then \
|
||||
chmod +x /usr/local/bin/sgn; \
|
||||
echo "SGN pre-installed from ${SGN_ASSET}"; \
|
||||
else \
|
||||
echo "WARNING: SGN pre-fetch failed — will fall back to system PATH or download on first use"; \
|
||||
fi \
|
||||
&& rm -f /tmp/sgn.zip
|
||||
echo "WARNING: SGN Rust binary is unavailable for ${TARGETARCH:-unknown} — will fall back to system PATH or runtime download"; \
|
||||
fi
|
||||
|
||||
# Full bun install (includes devDeps needed for tailwind / vendor / minify steps)
|
||||
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 path from "path";
|
||||
import { $ } from "bun";
|
||||
import AdmZip from "adm-zip";
|
||||
import { ensureDataDir } from "../paths";
|
||||
import { logger } from "../logger";
|
||||
|
||||
@@ -42,7 +43,54 @@ async function findSystemSgn(): Promise<string | 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> {
|
||||
try {
|
||||
@@ -53,53 +101,48 @@ async function fetchLatest(): Promise<AssetInfo | null> {
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json() as any;
|
||||
const tag: string = data.tag_name;
|
||||
const assets: { name: string; browser_download_url: string }[] = data.assets ?? [];
|
||||
|
||||
// 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;
|
||||
const selected = selectSgnReleaseAsset(data.assets ?? []);
|
||||
return selected ? { tag, ...selected } : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndExtract(url: string, dest: string): Promise<boolean> {
|
||||
async function downloadAndExtract(
|
||||
url: string,
|
||||
dest: string,
|
||||
archiveFormat: SgnArchiveFormat,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return false;
|
||||
const buf = await res.arrayBuffer();
|
||||
const tmpZip = dest + ".tmp.zip";
|
||||
fs.writeFileSync(tmpZip, Buffer.from(buf));
|
||||
const tmpArchive = `${dest}.tmp.${archiveFormat === "zip" ? "zip" : "tar.gz"}`;
|
||||
const tmpBinary = `${dest}.tmp.bin`;
|
||||
fs.writeFileSync(tmpArchive, Buffer.from(buf));
|
||||
try {
|
||||
const destDir = path.dirname(dest);
|
||||
const binName = process.platform === "win32" ? "sgn.exe" : "sgn";
|
||||
const r = await $`unzip -o -j ${tmpZip} -d ${destDir}`.nothrow().quiet();
|
||||
if (r.exitCode !== 0) return false;
|
||||
const extracted = path.join(destDir, binName);
|
||||
if (!fs.existsSync(extracted)) {
|
||||
const entries = fs.readdirSync(destDir);
|
||||
const found = entries.find(e => e.toLowerCase().startsWith("sgn") && !e.endsWith(".version") && !e.endsWith(".tmp.zip"));
|
||||
if (!found) return false;
|
||||
fs.renameSync(path.join(destDir, found), dest);
|
||||
} else if (extracted !== dest) {
|
||||
fs.renameSync(extracted, dest);
|
||||
if (archiveFormat === "zip") {
|
||||
const zip = new AdmZip(tmpArchive);
|
||||
const entry = zip.getEntries().find((candidate) =>
|
||||
!candidate.isDirectory &&
|
||||
path.basename(candidate.entryName).toLowerCase() === binName.toLowerCase()
|
||||
);
|
||||
if (!entry) return false;
|
||||
fs.writeFileSync(tmpBinary, entry.getData());
|
||||
} else {
|
||||
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;
|
||||
} finally {
|
||||
try { fs.unlinkSync(tmpZip); } catch {}
|
||||
try { fs.unlinkSync(tmpArchive); } catch {}
|
||||
try { fs.unlinkSync(tmpBinary); } catch {}
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
@@ -149,7 +192,7 @@ export async function ensureSgn(
|
||||
}
|
||||
|
||||
log(`SGN: downloading ${latest.assetName}…`);
|
||||
const ok = await downloadAndExtract(latest.downloadUrl, bin);
|
||||
const ok = await downloadAndExtract(latest.downloadUrl, bin, latest.archiveFormat);
|
||||
if (!ok) {
|
||||
if (binExists) {
|
||||
log(`SGN: download failed — using cached ${cached?.tag ?? "unknown"}`, "warn");
|
||||
|
||||
Reference in New Issue
Block a user