Skip to content

Instantly share code, notes, and snippets.

@kidonng
Created August 15, 2025 14:33
Show Gist options
  • Save kidonng/f56cb090d8b9b07d6b24982f2c1d40d0 to your computer and use it in GitHub Desktop.
Save kidonng/f56cb090d8b9b07d6b24982f2c1d40d0 to your computer and use it in GitHub Desktop.
Usage: deno run -A sing-box-sub.ts --address <address> --secret $(uuidgen) out </etc/sing-box/config.json
import { readAllSync } from "jsr:@std/[email protected]";
import { ensureDirSync } from "jsr:@std/[email protected]";
import { join } from "jsr:@std/[email protected]";
import { v5 } from "jsr:@std/[email protected]";
import { parseArgs } from "jsr:@std/[email protected]";
// https://sing-box.sagernet.org/manual/proxy-protocol/shadowsocks/#server-example
interface SingBoxConfig {
inbounds: SingBoxInbound[];
}
interface SingBoxInbound {
type: "shadowsocks";
listen_port: number;
method: string;
password: string;
users: { name: string; password: string }[];
}
// https://github.com/Shadowsocks-NET/OpenOnlineConfig/blob/master/docs/0001-open-online-config-v1.md#3-api-specification
interface OOC1Config {
username: string;
protocols: string[];
shadowsocks: OOC1ShadowsocksServer[];
}
// https://github.com/Shadowsocks-NET/OpenOnlineConfig/blob/master/docs/0002-open-online-config-v1-shadowsocks.md#2-api-specification
interface OOC1ShadowsocksServer {
id: string;
name: string;
address: string;
port: number;
method: string;
password: string;
}
interface OOC1APIToken {
version: number;
baseUrl: string;
secret: string;
userId: string;
}
const args: {
address: string;
secret: string;
_: [string];
} = parseArgs(Deno.args, {
alias: {
a: "address",
s: "secret",
},
});
const config: SingBoxConfig = JSON.parse(
new TextDecoder().decode(readAllSync(Deno.stdin)),
);
const users = new Map<string, OOC1ShadowsocksServer[]>();
for (const inbound of config.inbounds) {
if (inbound.type !== "shadowsocks") {
continue;
}
for (const user of inbound.users) {
const password = [inbound.password, user.password].join(":");
users.set(user.name, [
...(users.get(user.name) || []),
{
id: await v5.generate(args.secret, new TextEncoder().encode(password)),
name: `[${args.address}] ${inbound.type}`,
address: args.address,
port: inbound.listen_port,
method: inbound.method,
password,
},
]);
}
}
const out = args._[0];
const dir = join(out, args.secret, "ooc", "v1");
ensureDirSync(dir);
for (const [name, servers] of users) {
const id = await v5.generate(args.secret, new TextEncoder().encode(name));
Deno.writeTextFileSync(
join(dir, id),
JSON.stringify({
username: name,
protocols: ["shadowsocks"],
shadowsocks: servers,
} as OOC1Config),
);
// https://github.com/Shadowsocks-NET/OpenOnlineConfig/blob/master/docs/0001-open-online-config-v1.md#22-delivery
console.log(name, JSON.stringify({
version: 1,
baseUrl: `https://${args.address}`,
secret: args.secret,
userId: id,
} as OOC1APIToken));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment