Files
NeoTachyon/tachyon/files/usr/lib/server/service.uc
T
Dushnilin a254cfc8b6 fix(components): robust version installation and updates across Docker, APK v3 and OPKG
- fix(updater): support single release JSON object for specific tag queries and unblock prereleases on explicit selection
- fix(action): replace wget --header with -U for uclient-fetch compatibility on OpenWrt 23.05
- fix(action): make package list updates best-effort to prevent failures on snapshot kmod feeds
- fix(action): add offline/corrupt extract fallbacks (apk extract and opkg data.tar.gz) for kernel dependency mismatch
- fix(action): ensure /var/lock and /tmp/run directories exist before opkg/procd operations
- fix(action): fix fallback asset URL pattern for remittor zapret releases
- fix(packages): add binary presence and --version detection fallbacks for zapret, zapret2, byedpi, sing-box
- fix(byedpi): support direct apk list parsing for installed package version
- fix(state): include schedule, profile, and guest_mode in reload state signatures
- fix(imports): fix missing command_status_from_args, write_json, and remove_file imports
- fix(updates): extract meaningful ucode errors and strip trailing caret lines
2026-09-10 19:38:26 +03:00

1140 lines
37 KiB
Ucode

#!/usr/bin/env ucode
let fs = require("fs");
let core_ip = require("core.ip");
let core_url = require("core.url");
let uci = require("core.uci");
let common = require("core.common");
let as_string = common.as_string;
let shell_quote = common.shell_quote;
let read_json_file = common.read_json_file;
let read_stdin = common.read_stdin;
let read_stdin_json = common.read_stdin_json;
let write_json = common.write_json;
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
function ascii_lower(value) {
let upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let lower = "abcdefghijklmnopqrstuvwxyz";
return replace(as_string(value), /[A-Z]/g, function(ch) {
return substr(lower, index(upper, ch), 1);
});
}
function run(command) {
return system(command) == 0;
}
function output(command) {
let pipe = fs.popen(command, "r");
if (!pipe)
return "";
let data = pipe.read("all");
let status = pipe.close();
if (status != 0 || data == null)
return "";
return replace(as_string(data), /[\r\n]+$/g, "");
}
function log(message, level) {
level = as_string(level || "info");
run("logger -t " + shell_quote("tachyon") + " " + shell_quote("[" + level + "] " + as_string(message)));
}
function fatal(message) {
log(message, "fatal");
exit(1);
}
function uci_get(path) {
return uci.get(path);
}
function uci_set(path, value) {
return uci.set(path, value);
}
function uci_commit(package_name) {
return uci.commit(package_name);
}
function words(value) {
value = trim(as_string(value));
return value == "" ? [] : split(value, /[ \t\r\n]+/);
}
function uci_sections(type_name) {
return uci.sections(CONFIG_NAME, type_name);
}
function config_path(section, option) {
return CONFIG_NAME + "." + as_string(section) + "." + as_string(option);
}
function config_get(section, option, fallback) {
let value = uci_get(config_path(section, option));
return value == "" ? as_string(fallback || "") : value;
}
let SERVER_DEFAULTS_CHANGED = false;
function server_default_set_option(section, option, value) {
value = as_string(value);
if (value == "")
return;
if (uci_get(config_path(section, option)) != "")
return;
if (uci_set(config_path(section, option), value))
SERVER_DEFAULTS_CHANGED = true;
}
function server_set_option(section, option, value) {
value = as_string(value);
if (value == "")
return;
if (uci_get(config_path(section, option)) == value)
return;
if (uci_set(config_path(section, option), value))
SERVER_DEFAULTS_CHANGED = true;
}
function stdin_first_nonempty_line() {
for (let line in split(read_stdin(), "\n")) {
if (trim(as_string(line)) != "") {
print(line, "\n");
return;
}
}
}
function stdin_remove_newlines() {
print(replace(read_stdin(), /\n/g, ""));
}
function shell_single_quote(value) {
value = as_string(value);
if (value == "")
return;
print("'", replace(value, /'/g, "'\\''"), "'\n");
}
function array_or_empty(value) {
return type(value) == "array" ? value : [];
}
function array_append_string(value) {
let result = array_or_empty(read_stdin_json());
push(result, as_string(value));
write_json(result);
}
function regex_matches(value, pattern) {
pattern = as_string(pattern);
if (pattern == "")
return false;
try {
return match(as_string(value), regexp(pattern)) != null;
}
catch (e) {
return false;
}
}
function valid_dotted_ipv4(value) {
return regex_matches(value, "^([0-9]{1,3}\\.){3}[0-9]{1,3}$");
}
function valid_file_path(value) {
value = as_string(value);
return value != "" &&
value != "/" &&
substr(value, length(value) - 1) != "/" &&
regex_matches(value, "^/[A-Za-z0-9_./-]+$");
}
function valid_duration(value) {
value = as_string(value);
return value != "" && regex_matches(value, "^([0-9]+(ns|us|ms|s|m|h))+$");
}
function valid_client_value(value) {
value = as_string(value);
return value != "" && !regex_matches(value, "[[:cntrl:]]");
}
function valid_port(value) {
value = as_string(value);
if (value == "" || regex_matches(value, "[^0-9]"))
return false;
value = int(value);
return value >= 1 && value <= 65535;
}
function normalize_host(value) {
value = as_string(value);
if (substr(value, 0, 1) == "[")
value = substr(value, 1);
if (substr(value, length(value) - 1) == "]")
value = substr(value, 0, length(value) - 1);
return lc(value);
}
function str_endswith(value, suffix) {
value = as_string(value);
suffix = as_string(suffix);
return length(value) >= length(suffix) && substr(value, length(value) - length(suffix)) == suffix;
}
function str_remove_suffix(value, suffix) {
value = as_string(value);
suffix = as_string(suffix);
return str_endswith(value, suffix) ? substr(value, 0, length(value) - length(suffix)) : value;
}
function valid_ipv4(value) {
return core_ip.valid_ipv4(value, false, false);
}
function valid_domain(value) {
return regex_matches(ascii_lower(value), "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$");
}
function valid_host(value) {
value = normalize_host(value);
return value != "" && (valid_ipv4(value) || core_ip.valid_ipv6(value) || valid_domain(value));
}
function valid_absolute_path(value) {
return substr(as_string(value), 0, 1) == "/";
}
function valid_http_url(value) {
value = as_string(value);
let scheme = index(value, "://");
if (scheme < 0)
return false;
let prefix = substr(value, 0, scheme + 3);
if (prefix != "http://" && prefix != "https://")
return false;
let rest = substr(value, scheme + 3);
if (rest == "")
return false;
return valid_host(core_url.host(value));
}
function valid_uuid(value) {
return regex_matches(value, "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
}
function valid_nonnegative_integer(value) {
return regex_matches(value, "^[0-9]+$");
}
function valid_reality_short_id(value) {
return regex_matches(value, "^[0-9a-fA-F]{1,8}$");
}
function valid_transport_service_name(value) {
return regex_matches(value, "^[A-Za-z0-9_.-]+$");
}
function mtproto_base_secret_value(value) {
value = lc(as_string(value));
if (regex_matches(value, "^[0-9a-f]{32}$"))
return value;
if (regex_matches(value, "^ee[0-9a-f]{32}([0-9a-f]{2})+$"))
return substr(value, 2, 32);
return "";
}
function mtproto_base_secret_from_value(value) {
let result = mtproto_base_secret_value(value);
if (result == "")
exit(1);
print(result, "\n");
}
function hex_digit_value(value) {
value = ord(lc(as_string(value)));
if (value >= 48 && value <= 57)
return value - 48;
if (value >= 97 && value <= 102)
return value - 87;
return -1;
}
function hex_to_string_value(value) {
value = as_string(value);
if (length(value) == 0 || length(value) % 2 != 0 || !regex_matches(value, "^([0-9a-fA-F]{2})+$"))
return null;
let result = "";
for (let i = 0; i < length(value); i += 2) {
let high = hex_digit_value(substr(value, i, 1));
let low = hex_digit_value(substr(value, i + 1, 1));
if (high < 0 || low < 0)
return null;
result += chr(high * 16 + low);
}
return result;
}
function string_to_hex(value) {
value = as_string(value);
for (let i = 0; i < length(value); i++)
print(sprintf("%02x", ord(substr(value, i, 1))));
}
function mtproto_faketls_value(value) {
value = lc(as_string(value));
if (!regex_matches(value, "^ee[0-9a-f]{32}([0-9a-f]{2})+$"))
return "";
let result = hex_to_string_value(substr(value, 34));
return result == null ? "" : result;
}
function mtproto_faketls_from_secret(value) {
let result = mtproto_faketls_value(value);
if (result == "")
exit(1);
print(result);
}
function mtproto_build_secret(base, faketls, padding) {
if (as_string(padding) != "1")
exit(1);
print("ee", as_string(base));
string_to_hex(faketls);
print("\n");
}
function valid_mtproto_base_secret(value) {
value = lc(as_string(value));
return regex_matches(value, "^[0-9a-f]{32}$") && value != "00000000000000000000000000000000";
}
function default_security_for_protocol(protocol) {
protocol = as_string(protocol);
if (protocol == "vless")
print("reality\n");
else if (protocol == "trojan" || protocol == "hysteria2")
print("tls\n");
else
print("none\n");
}
function safe_filename(value) {
value = as_string(value);
for (let i = 0; i < length(value); i++) {
let chr = substr(value, i, 1);
print(regex_matches(chr, "^[A-Za-z0-9_.-]$") ? chr : "_");
}
}
function valid_public_ipv4(value) {
value = as_string(value);
if (!valid_ipv4(value))
return false;
let parts = split(value, ".");
let a = int(parts[0]);
let b = int(parts[1]);
if (a == 0 || a == 10 || a == 127 || a >= 224)
return false;
if (a == 169 && b == 254)
return false;
if (a == 192 && (b == 168 || b == 0 || b == 2))
return false;
if (a == 198 && (b == 18 || b == 19 || b == 51))
return false;
if (a == 203 && b == 0)
return false;
if (a == 100 && b >= 64 && b <= 127)
return false;
if (a == 172 && b >= 16 && b <= 31)
return false;
return true;
}
function valid_ipv6_literal(value) {
return core_ip.valid_ipv6(value);
}
function valid_cidr(value) {
value = as_string(value);
let slash = index(value, "/");
if (slash < 0 || index(substr(value, slash + 1), "/") >= 0)
return false;
let address = substr(value, 0, slash);
let prefix = substr(value, slash + 1);
if (prefix == "" || regex_matches(prefix, "[^0-9]"))
return false;
prefix = int(prefix);
if (valid_ipv4(address))
return prefix >= 0 && prefix <= 32;
if (valid_ipv6_literal(address))
return prefix >= 0 && prefix <= 128;
return false;
}
function server_users_from_tsv(protocol, path) {
let result = [];
let data = fs.readfile(path);
if (data != null) {
for (let line in split(as_string(data), "\n")) {
if (line == "")
continue;
let parts = split(line, "\t");
let name = as_string(parts[0] || "");
let credential = as_string(parts[1] || "");
let flow = as_string(parts[2] || "");
if (credential == "")
continue;
if (protocol == "vless") {
let user = { uuid: credential };
if (name != "")
user.name = name;
if (flow != "")
user.flow = flow;
push(result, user);
}
else if (protocol == "vmess") {
let user = { uuid: credential, alterId: int(flow || "0", 10) || 0 };
if (name != "")
user.name = name;
push(result, user);
}
else if (protocol == "trojan") {
let user = { password: credential };
if (name != "")
user.name = name;
push(result, user);
}
else if (protocol == "hysteria2") {
let user = { password: credential };
if (name != "")
user.name = name;
push(result, user);
}
else if (protocol == "mtproto") {
let user = { secret: credential };
if (name != "")
user.name = name;
push(result, user);
}
else if (protocol == "socks") {
let user = {
username: name != "" ? name : "user",
password: credential
};
push(result, user);
}
}
}
write_json(result);
}
function json_length(path) {
let value = read_json_file(path);
if (type(value) == "array" || type(value) == "object")
print(length(value), "\n");
else
print("0\n");
}
function error_response(message) {
write_json({
success: false,
message: as_string(message)
});
}
function reality_keypair_response(private_key, public_key) {
write_json({
success: true,
private_key: as_string(private_key),
public_key: as_string(public_key)
});
}
function tls_fingerprint_response(fingerprint) {
write_json({
success: true,
sha256: as_string(fingerprint)
});
}
function first_key_value_line_value(data, prefix) {
prefix = as_string(prefix);
for (let line in split(as_string(data), "\n")) {
line = as_string(line);
if (!regex_matches(line, "^" + prefix + ":"))
continue;
let value = substr(line, length(prefix) + 1);
return replace(value, /^[ \t\r]*/, "");
}
return null;
}
function reality_keypair_tsv() {
let data = read_stdin();
let private_key = first_key_value_line_value(data, "PrivateKey");
let public_key = first_key_value_line_value(data, "PublicKey");
if (private_key == null || public_key == null || private_key == "" || public_key == "")
exit(1);
print(private_key, "\t", public_key, "\n");
}
function pem_block(data, begin_marker, end_marker) {
let lines = [];
let in_block = false;
for (let line in split(as_string(data), "\n")) {
line = str_remove_suffix(as_string(line), "\r");
if (!in_block && line != begin_marker)
continue;
in_block = true;
push(lines, line);
if (line == end_marker)
return join("\n", lines);
}
return "";
}
function write_tls_keypair_files(key_path, certificate_path) {
let data = read_stdin();
let key = pem_block(data, "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
let cert = pem_block(data, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
if (key == "" || cert == "")
exit(1);
if (!fs.writefile(key_path, key + "\n"))
exit(1);
if (!fs.writefile(certificate_path, cert + "\n"))
exit(1);
}
function certificate_base64(path) {
let data = fs.readfile(path);
let cert = pem_block(data, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
let lines = [];
if (cert == "")
exit(1);
for (let line in split(cert, "\n")) {
line = replace(as_string(line), /\r/g, "");
if (line == "-----BEGIN CERTIFICATE-----" || line == "-----END CERTIFICATE-----" || line == "")
continue;
push(lines, line);
}
if (length(lines) == 0)
exit(1);
print(join("\n", lines), "\n");
}
function valid_sha256_hex(value) {
return regex_matches(value, "^[0-9a-f]{64}$");
}
function command_first_nonempty_line(command) {
for (let line in split(output(command), "\n"))
if (trim(as_string(line)) != "")
return line;
return "";
}
function server_generate_uuid() {
return command_first_nonempty_line("sing-box generate uuid 2>/dev/null");
}
function server_generate_password() {
return replace(output("sing-box generate rand --base64 18 2>/dev/null"), /\n/g, "");
}
function server_generate_short_id() {
return replace(output("sing-box generate rand --hex 4 2>/dev/null"), /\n/g, "");
}
function server_generate_mtproto_secret() {
let random_hex = replace(output("sing-box generate rand --hex 16 2>/dev/null"), /\n/g, "");
if (random_hex == "")
random_hex = replace(output("head -c 16 /dev/urandom | hexdump -ve '1/1 \"%02x\"' 2>/dev/null"), /\n/g, "");
if (random_hex == "00000000000000000000000000000000")
random_hex = "11111111111111111111111111111111";
return random_hex;
}
function default_security_value(protocol) {
protocol = as_string(protocol);
if (protocol == "vless")
return "reality";
if (protocol == "trojan" || protocol == "hysteria2")
return "tls";
return "none";
}
function server_effective_security_value(section, protocol) {
protocol = as_string(protocol);
let security = config_get(section, "security", "");
if (security == "")
security = default_security_value(protocol);
if (protocol == "shadowsocks" || protocol == "socks" || protocol == "mtproto" || protocol == "tailscale" || protocol == "json_inbound" || protocol == "awg")
security = "none";
else if (protocol == "hysteria2")
security = "tls";
else if ((protocol == "vmess" || protocol == "trojan") && security == "reality")
security = default_security_value(protocol);
return security;
}
function print_effective_security(section, protocol) {
print(server_effective_security_value(section, protocol), "\n");
}
function generate_reality_keypair_values() {
let data = output("sing-box generate reality-keypair 2>/dev/null");
let private_key = first_key_value_line_value(data, "PrivateKey");
let public_key = first_key_value_line_value(data, "PublicKey");
if (private_key == null || public_key == null || private_key == "" || public_key == "")
return null;
return {
private_key: private_key,
public_key: public_key
};
}
function generate_wg_keypair_values() {
let data = output("sing-box generate wg-keypair 2>/dev/null");
let private_key = first_key_value_line_value(data, "PrivateKey");
let public_key = first_key_value_line_value(data, "PublicKey");
if (private_key == null || public_key == null || private_key == "" || public_key == "")
return null;
return {
private_key: private_key,
public_key: public_key
};
}
function generate_wg_keypair_cli() {
let pair = generate_wg_keypair_values();
if (pair == null) {
error_response("Failed to generate WireGuard key pair");
exit(1);
}
print(sprintf("PrivateKey: %s\nPublicKey: %s\n", pair.private_key, pair.public_key));
}
function generate_reality_keypair_cli() {
let pair = generate_reality_keypair_values();
if (pair == null) {
error_response("Failed to generate Reality key pair");
exit(1);
}
reality_keypair_response(pair.private_key, pair.public_key);
}
function write_tls_keypair_data(data, key_path, certificate_path) {
let key = pem_block(data, "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
let cert = pem_block(data, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
if (key == "" || cert == "")
return false;
return fs.writefile(key_path, key + "\n") && fs.writefile(certificate_path, cert + "\n");
}
function server_generate_tls_keypair_files(server_name, certificate_path, key_path) {
let data = output("sing-box generate tls-keypair " + shell_quote(server_name) + " 2>/dev/null");
if (data == "")
return false;
let key_dir = replace(as_string(key_path), /\/[^\/]*$/, "");
let cert_dir = replace(as_string(certificate_path), /\/[^\/]*$/, "");
if (!run("mkdir -p " + shell_quote(key_dir) + " " + shell_quote(cert_dir)))
return false;
if (!write_tls_keypair_data(data, key_path, certificate_path))
return false;
run("chmod 600 " + shell_quote(key_path) + " >/dev/null 2>&1");
return true;
}
function certificate_base64_text(path) {
let data = fs.readfile(path);
let cert = pem_block(data, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
let lines = [];
if (cert == "")
return "";
for (let line in split(cert, "\n")) {
line = replace(as_string(line), /\r/g, "");
if (line == "-----BEGIN CERTIFICATE-----" || line == "-----END CERTIFICATE-----" || line == "")
continue;
push(lines, line);
}
return length(lines) == 0 ? "" : join("\n", lines) + "\n";
}
function server_tls_certificate_sha256_from_file(certificate_path) {
if (!valid_file_path(certificate_path))
return "";
let base64_text = certificate_base64_text(certificate_path);
if (base64_text == "")
return "";
let tmp_path = output("mktemp 2>/dev/null");
if (tmp_path == "")
return "";
if (!fs.writefile(tmp_path, base64_text)) {
fs.unlink(tmp_path);
return "";
}
let fingerprint = output("base64 -d " + shell_quote(tmp_path) + " 2>/dev/null | sha256sum 2>/dev/null");
fs.unlink(tmp_path);
fingerprint = substr(fingerprint, 0, index(fingerprint, " ") >= 0 ? index(fingerprint, " ") : length(fingerprint));
return valid_sha256_hex(fingerprint) ? fingerprint : "";
}
function get_tls_certificate_sha256_cli(section) {
if (as_string(section) == "") {
error_response("Missing server section");
exit(1);
}
let protocol = config_get(section, "protocol", "vless");
let security = server_effective_security_value(section, protocol);
if (security != "tls") {
error_response("Server does not use TLS");
exit(1);
}
let certificate_path = config_get(section, "tls_certificate_path", "");
if (certificate_path == "") {
error_response("TLS certificate path is empty");
exit(1);
}
let fingerprint = server_tls_certificate_sha256_from_file(certificate_path);
if (fingerprint == "") {
error_response("Failed to read TLS certificate fingerprint");
exit(1);
}
tls_fingerprint_response(fingerprint);
}
function network_status_ipv4_address(data) {
try {
data = json(as_string(data));
}
catch (e) {
return "";
}
let addresses = data["ipv4-address"];
if (type(addresses) != "array")
return "";
for (let item in addresses)
if (type(item) == "object" && as_string(item.address) != "")
return as_string(item.address);
return "";
}
function server_detect_default_public_host() {
for (let interface in [ "wan", "wwan" ]) {
let ip = network_status_ipv4_address(output("ubus -S call " + shell_quote("network.interface." + interface) + " status 2>/dev/null"));
if (valid_public_ipv4(ip))
return ip;
}
let lan_ip = uci_get("network.lan.ipaddr");
return valid_dotted_ipv4(lan_ip) ? lan_ip : "";
}
function first_server_user(section) {
let users = words(uci_get(config_path(section, "server_users")));
return length(users) > 0 ? users[0] : "";
}
function server_prepare_legacy_user_defaults(section, protocol) {
let entry = first_server_user(section);
if (entry == "")
return;
let parts = split(entry, "|");
let name = "client";
let credential = entry;
let extra = "";
if (length(parts) > 1) {
name = as_string(parts[0]);
credential = as_string(parts[1]);
extra = as_string(parts[2] || "");
}
if (protocol == "vless" || protocol == "vmess")
server_default_set_option(section, "server_uuid", credential);
else if (protocol == "shadowsocks" || protocol == "socks" || protocol == "trojan" || protocol == "hysteria2")
server_default_set_option(section, "server_password", credential);
else if (protocol == "mtproto") {
let mtproto_faketls = mtproto_faketls_value(credential);
let base_secret = mtproto_base_secret_value(credential);
server_default_set_option(section, "mtproto_secret", base_secret != "" ? base_secret : credential);
server_default_set_option(section, "mtproto_faketls", mtproto_faketls != "" ? mtproto_faketls : "google.com");
server_default_set_option(section, "mtproto_padding", "1");
}
if (protocol == "socks" && name != "")
server_default_set_option(section, "server_username", name);
if (protocol == "vless" && extra != "")
server_default_set_option(section, "vless_flow", extra);
}
function server_prepare_tls_defaults(section, protocol, security) {
if (security != "tls")
return;
if (config_get(section, "enabled", "1") == "0")
return;
let tls_server_name = config_get(section, "tls_server_name", "");
if (tls_server_name == "")
tls_server_name = "www.microsoft.com";
server_default_set_option(section, "tls_server_name", tls_server_name);
let certificate_path = config_get(section, "tls_certificate_path", "");
let key_path = config_get(section, "tls_key_path", "");
if (certificate_path == "" || key_path == "") {
let safe_name = safe_filename_string(section);
let default_dir = "/etc/tachyon/server-certs";
if (certificate_path == "")
certificate_path = default_dir + "/" + safe_name + ".crt";
if (key_path == "")
key_path = default_dir + "/" + safe_name + ".key";
server_set_option(section, "tls_certificate_path", certificate_path);
server_set_option(section, "tls_key_path", key_path);
}
if (!valid_file_path(certificate_path))
fatal("Server '" + section + "' has invalid TLS certificate path. Specify a file path. Aborted.");
if (!valid_file_path(key_path))
fatal("Server '" + section + "' has invalid TLS key path. Specify a file path. Aborted.");
if (certificate_path == key_path)
fatal("Server '" + section + "' has the same TLS certificate and key path. Specify different files. Aborted.");
if (!run("[ -s " + shell_quote(certificate_path) + " ] && [ -s " + shell_quote(key_path) + " ]")) {
log("Generating self-signed TLS certificate for server '" + section + "'", "info");
if (!server_generate_tls_keypair_files(tls_server_name, certificate_path, key_path))
fatal("Failed to generate TLS certificate for server '" + section + "'. Aborted.");
}
}
function safe_filename_string(value) {
value = as_string(value);
let result = [];
for (let i = 0; i < length(value); i++) {
let chr = substr(value, i, 1);
push(result, regex_matches(chr, "^[A-Za-z0-9_.-]$") ? chr : "_");
}
return join("", result);
}
function prepare_server_defaults(section) {
let protocol = config_get(section, "protocol", "vless");
if (!(protocol == "shadowsocks" || protocol == "socks" || protocol == "vmess" || protocol == "vless" ||
protocol == "trojan" || protocol == "hysteria2" || protocol == "mtproto" || protocol == "tailscale" ||
protocol == "json_inbound" || protocol == "awg"))
fatal("Server '" + section + "' has unsupported protocol '" + protocol + "'. Aborted.");
server_default_set_option(section, "protocol", protocol);
server_default_set_option(section, "label", section);
server_default_set_option(section, "enabled", "1");
server_default_set_option(section, "routing_mode", "rules");
if (protocol == "json_inbound") {
server_set_option(section, "security", "none");
return;
}
server_default_set_option(section, "listen", "0.0.0.0");
server_default_set_option(section, "listen_port", "443");
server_default_set_option(section, "public_host", server_detect_default_public_host());
let security = server_effective_security_value(section, protocol);
server_set_option(section, "security", security);
server_prepare_legacy_user_defaults(section, protocol);
if (protocol == "vless" || protocol == "vmess") {
if (uci_get(config_path(section, "server_uuid")) == "") {
let uuid = server_generate_uuid();
server_default_set_option(section, "server_uuid", uuid);
}
}
else if (protocol == "shadowsocks" || protocol == "socks" || protocol == "trojan" || protocol == "hysteria2") {
if (uci_get(config_path(section, "server_password")) == "") {
let password = server_generate_password();
server_default_set_option(section, "server_password", password);
}
}
if (protocol == "mtproto") {
let mtproto_secret = uci_get(config_path(section, "mtproto_secret"));
let mtproto_faketls = "";
let mtproto_base_secret = "";
if (mtproto_secret != "") {
mtproto_faketls = mtproto_faketls_value(mtproto_secret);
mtproto_base_secret = mtproto_base_secret_value(mtproto_secret);
}
if (mtproto_base_secret == "")
mtproto_base_secret = server_generate_mtproto_secret();
if (mtproto_secret != mtproto_base_secret)
server_set_option(section, "mtproto_secret", mtproto_base_secret);
server_default_set_option(section, "mtproto_faketls", mtproto_faketls != "" ? mtproto_faketls : "google.com");
server_default_set_option(section, "mtproto_padding", "1");
server_default_set_option(section, "mtproto_domain_fronting_port", "443");
server_default_set_option(section, "mtproto_prefer_ip", "prefer-ipv4");
server_default_set_option(section, "mtproto_tolerate_time_skewness", "3s");
server_default_set_option(section, "mtproto_idle_timeout", "5m");
server_default_set_option(section, "mtproto_handshake_timeout", "10s");
}
if (protocol == "socks") {
server_default_set_option(section, "socks_auth_enabled", "1");
if (uci_get(config_path(section, "server_username")) == "")
server_default_set_option(section, "server_username", config_get(section, "label", section));
}
if (protocol == "vless")
server_default_set_option(section, "vless_flow", "none");
if (protocol == "vmess")
server_default_set_option(section, "vmess_alter_id", "0");
if (protocol == "shadowsocks")
server_default_set_option(section, "shadowsocks_method", "aes-128-gcm");
if (protocol == "awg") {
server_default_set_option(section, "listen_port", "51820");
server_default_set_option(section, "awg_mtu", "1280");
server_default_set_option(section, "awg_local_address", "10.0.0.1/24");
server_default_set_option(section, "awg_client_address", "10.0.0.2/32");
server_default_set_option(section, "awg_jc", "4");
server_default_set_option(section, "awg_jmin", "40");
server_default_set_option(section, "awg_jmax", "70");
server_default_set_option(section, "awg_s1", "0");
server_default_set_option(section, "awg_s2", "0");
server_default_set_option(section, "awg_s3", "0");
server_default_set_option(section, "awg_s4", "0");
server_default_set_option(section, "awg_h1", "1");
server_default_set_option(section, "awg_h2", "2");
server_default_set_option(section, "awg_h3", "3");
server_default_set_option(section, "awg_h4", "4");
server_default_set_option(section, "awg_keepalive", "25");
let server_private_key = uci_get(config_path(section, "awg_private_key"));
let peer_public_key = uci_get(config_path(section, "awg_peer_public_key"));
if (server_private_key == "" || peer_public_key == "") {
let srv_pair = generate_wg_keypair_values();
let cli_pair = generate_wg_keypair_values();
if (srv_pair != null) {
server_set_option(section, "awg_private_key", srv_pair.private_key);
server_set_option(section, "awg_server_public_key", srv_pair.public_key);
}
if (cli_pair != null) {
server_set_option(section, "awg_client_private_key", cli_pair.private_key);
server_set_option(section, "awg_peer_public_key", cli_pair.public_key);
}
}
}
if (protocol == "tailscale") {
let safe_name = safe_filename_string(section);
server_default_set_option(section, "tailscale_control_url", "https://controlplane.tailscale.com");
server_default_set_option(section, "tailscale_hostname", "tachyon-" + safe_name);
server_default_set_option(section, "tailscale_advertise_exit_node", "1");
}
if (security == "reality") {
server_default_set_option(section, "tls_server_name", "www.microsoft.com");
server_default_set_option(section, "client_fingerprint", "chrome");
server_default_set_option(section, "reality_handshake_server", "www.microsoft.com");
server_default_set_option(section, "reality_handshake_server_port", "443");
if (uci_get(config_path(section, "reality_short_id")) == "")
server_default_set_option(section, "reality_short_id", server_generate_short_id());
server_default_set_option(section, "reality_max_time_difference", "1m");
let reality_private_key = uci_get(config_path(section, "reality_private_key"));
let reality_public_key = uci_get(config_path(section, "reality_public_key"));
if (reality_private_key == "" || reality_public_key == "") {
log("Generating Reality key pair for server '" + section + "'", "info");
let pair = generate_reality_keypair_values();
if (pair == null)
fatal("Failed to generate Reality key pair for server '" + section + "'. Aborted.");
server_set_option(section, "reality_private_key", pair.private_key);
server_set_option(section, "reality_public_key", pair.public_key);
}
}
server_prepare_tls_defaults(section, protocol, security);
}
function cleanup_orphaned_server_certs() {
let cert_dir = "/etc/tachyon/server-certs";
try {
let dir = fs.opendir(cert_dir);
if (!dir) return;
let active_names = {};
for (let section in uci_sections("server")) {
let name = safe_filename_string(section);
active_names[name + ".crt"] = true;
active_names[name + ".key"] = true;
}
let entry;
while ((entry = dir.read()) != null) {
if (substr(entry, 0, 1) == ".") continue;
if (active_names[entry]) continue;
let path = cert_dir + "/" + entry;
// Absent file already satisfies the caller; fs.unlink throws on ENOENT.
try { fs.unlink(path); } catch(e) {}
}
dir.close();
}
catch (e) {
// Best-effort housekeeping: a missing cert directory or an unreadable
// UCI section leaves stale certs on disk, which costs a few kilobytes
// and nothing else. Never worth failing the caller over.
}
}
function prepare_all_server_defaults() {
SERVER_DEFAULTS_CHANGED = false;
for (let section in uci_sections("server"))
prepare_server_defaults(section);
cleanup_orphaned_server_certs();
if (SERVER_DEFAULTS_CHANGED)
uci_commit(CONFIG_NAME);
}
let mode = ARGV[0] || "";
if (mode == "valid-file-path")
exit(valid_file_path(ARGV[1]) ? 0 : 1);
else if (mode == "valid-dotted-ipv4")
exit(valid_dotted_ipv4(ARGV[1]) ? 0 : 1);
else if (mode == "stdin-first-nonempty-line")
stdin_first_nonempty_line();
else if (mode == "stdin-remove-newlines")
stdin_remove_newlines();
else if (mode == "shell-single-quote")
shell_single_quote(ARGV[1]);
else if (mode == "valid-port")
exit(valid_port(ARGV[1]) ? 0 : 1);
else if (mode == "normalize-host")
print(normalize_host(ARGV[1]), "\n");
else if (mode == "valid-host")
exit(valid_host(ARGV[1]) ? 0 : 1);
else if (mode == "valid-absolute-path")
exit(valid_absolute_path(ARGV[1]) ? 0 : 1);
else if (mode == "valid-http-url")
exit(valid_http_url(ARGV[1]) ? 0 : 1);
else if (mode == "valid-duration")
exit(valid_duration(ARGV[1]) ? 0 : 1);
else if (mode == "valid-client-value")
exit(valid_client_value(ARGV[1]) ? 0 : 1);
else if (mode == "valid-uuid")
exit(valid_uuid(ARGV[1]) ? 0 : 1);
else if (mode == "valid-nonnegative-integer")
exit(valid_nonnegative_integer(ARGV[1]) ? 0 : 1);
else if (mode == "valid-reality-short-id")
exit(valid_reality_short_id(ARGV[1]) ? 0 : 1);
else if (mode == "valid-transport-service-name")
exit(valid_transport_service_name(ARGV[1]) ? 0 : 1);
else if (mode == "mtproto-base-secret")
mtproto_base_secret_from_value(ARGV[1]);
else if (mode == "mtproto-faketls-from-secret")
mtproto_faketls_from_secret(ARGV[1]);
else if (mode == "mtproto-build-secret")
mtproto_build_secret(ARGV[1], ARGV[2], ARGV[3]);
else if (mode == "valid-mtproto-base-secret")
exit(valid_mtproto_base_secret(ARGV[1]) ? 0 : 1);
else if (mode == "default-security-for-protocol")
default_security_for_protocol(ARGV[1]);
else if (mode == "effective-security")
print_effective_security(ARGV[1], ARGV[2]);
else if (mode == "safe-filename")
safe_filename(ARGV[1]);
else if (mode == "valid-public-ipv4")
exit(valid_public_ipv4(ARGV[1]) ? 0 : 1);
else if (mode == "valid-cidr")
exit(valid_cidr(ARGV[1]) ? 0 : 1);
else if (mode == "server-users-from-tsv")
server_users_from_tsv(ARGV[1], ARGV[2]);
else if (mode == "json-length")
json_length(ARGV[1]);
else if (mode == "array-append-string")
array_append_string(ARGV[1]);
else if (mode == "error-response")
error_response(ARGV[1]);
else if (mode == "prepare-all-defaults")
prepare_all_server_defaults();
else if (mode == "generate-reality-keypair")
generate_reality_keypair_cli();
else if (mode == "generate-wg-keypair")
generate_wg_keypair_cli();
else if (mode == "tls-certificate-sha256")
get_tls_certificate_sha256_cli(ARGV[1]);
else if (mode == "reality-keypair-response")
reality_keypair_response(ARGV[1], ARGV[2]);
else if (mode == "reality-keypair-tsv")
reality_keypair_tsv();
else if (mode == "write-tls-keypair-files")
write_tls_keypair_files(ARGV[1], ARGV[2]);
else if (mode == "certificate-base64")
certificate_base64(ARGV[1]);
else if (mode == "valid-sha256-hex")
exit(valid_sha256_hex(ARGV[1]) ? 0 : 1);
else if (mode == "tls-fingerprint-response")
tls_fingerprint_response(ARGV[1]);
else {
warn("Usage: server/service.uc <operation> ...\n");
exit(1);
}