Files
NeoTachyon/tachyon/files/usr/lib/config/migration.uc
T

1585 lines
55 KiB
Ucode

#!/usr/bin/env ucode
// Keep migrations in this file. A migration is identified by a stable name;
// release checks are optional conditions inside the migration itself.
let fs = require("fs");
let common = require("core.common");
let uci_core = require("core.uci");
let core_url = require("core.url");
let constants_module = require("core.constants");
let singbox_constants_module = require("singbox.constants");
let domain_config = require("config.domain");
let subscription_share_link = require("subscription.share_link");
let as_string = common.as_string;
let read_json_file = common.read_json_file;
let write_json = common.write_json;
let option = common.option;
let list_option = common.list_option;
let bool_option = common.bool_option;
let object_or_empty = common.object_or_empty;
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
const TMP_SUBSCRIPTION_FOLDER = getenv("TMP_SUBSCRIPTION_FOLDER") || "/tmp/sing-box/subscriptions";
const TACHYON_RUNTIME_STATE_DIR = getenv("TACHYON_RUNTIME_STATE_DIR") || "/var/run/tachyon";
const TACHYON_SUBSCRIPTION_LINKS_DIR = getenv("TACHYON_SUBSCRIPTION_LINKS_DIR") || TACHYON_RUNTIME_STATE_DIR + "/subscription-links";
const TACHYON_SUBSCRIPTION_METADATA_DIR = getenv("TACHYON_SUBSCRIPTION_METADATA_DIR") || TACHYON_RUNTIME_STATE_DIR + "/subscription-metadata";
const TACHYON_OUTBOUND_METADATA_DIR = getenv("TACHYON_OUTBOUND_METADATA_DIR") || TACHYON_RUNTIME_STATE_DIR + "/outbound-metadata";
const TACHYON_SECTION_CACHE_DIR = getenv("TACHYON_SECTION_CACHE_DIR") || TACHYON_RUNTIME_STATE_DIR + "/section-cache";
const TACHYON_RUNTIME_CACHE_FORMAT_FILE = getenv("TACHYON_RUNTIME_CACHE_FORMAT_FILE") || TACHYON_RUNTIME_STATE_DIR + "/cache-format";
const TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR = getenv("TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR") || "/etc/tachyon/subscription-cache";
const TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE = getenv("TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE") || TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR + "/cache-format";
const TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT = getenv("TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT") || "7";
const TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD = getenv("TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD") || "/var/run/tachyon.internal-config-change";
const TACHYON_RUNTIME_CACHE_FORMAT = getenv("TACHYON_RUNTIME_CACHE_FORMAT") || "8";
const CONFIG_VERSION_OPTION = "config_version";
const APPLIED_MIGRATIONS_OPTION = "applied_migrations";
const SERVER_COUNTRY_METHOD_FLAG_EMOJI = "flag_emoji";
const SERVER_COUNTRY_METHOD_COUNTRY_IS = "country_is";
const CHILD_ITEM_TYPES = [
"subscription_url",
"section_interface",
"urltest"
];
function shell_quote(value) {
return "'" + replace(as_string(value), /'/g, "'\\''") + "'";
}
function command_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 run(command) {
return system(command) == 0;
}
function constants_context() {
let constants = object_or_empty(constants_module);
return {
zapret_legacy_default_nfqws_opt: as_string(constants.ZAPRET_LEGACY_DEFAULT_NFQWS_OPT),
zapret_default_nfqws_opt: as_string(constants.ZAPRET_DEFAULT_NFQWS_OPT),
urltest_default_idle_timeout: as_string(object_or_empty(singbox_constants_module).URLTEST_DEFAULT_IDLE_TIMEOUT || "30m")
};
}
function section_name(section) {
return option(section, ".name", "");
}
function clone_section(section) {
let result = {};
for (let key in keys(object_or_empty(section)))
result[key] = section[key];
return result;
}
function fixture_section_list(data, type_name) {
let value = object_or_empty(data)[type_name];
if (type(value) == "array")
return value;
if (type(value) == "object")
return [ value ];
let plural = object_or_empty(data)[type_name + "s"];
return type(plural) == "array" ? plural : [];
}
function model_from_fixture(path) {
let data = object_or_empty(read_json_file(path));
let model = {
settings: clone_section(object_or_empty(data.settings)),
rules: [],
sections: []
};
for (let type_name in CHILD_ITEM_TYPES)
model[type_name] = [];
if (model.settings[".name"] == null)
model.settings[".name"] = "settings";
if (model.settings[".type"] == null)
model.settings[".type"] = "settings";
for (let section in fixture_section_list(data, "rule"))
push(model.rules, clone_section(section));
for (let section in fixture_section_list(data, "section"))
push(model.sections, clone_section(section));
for (let type_name in CHILD_ITEM_TYPES)
for (let section in fixture_section_list(data, type_name))
push(model[type_name], clone_section(section));
return model;
}
function model_from_uci(cursor) {
let model = {
settings: clone_section(object_or_empty(cursor.get_all(CONFIG_NAME, "settings"))),
rules: [],
sections: []
};
for (let type_name in CHILD_ITEM_TYPES)
model[type_name] = [];
cursor.foreach(CONFIG_NAME, "rule", function(section) {
push(model.rules, clone_section(section));
});
cursor.foreach(CONFIG_NAME, "section", function(section) {
push(model.sections, clone_section(section));
});
for (let type_name in CHILD_ITEM_TYPES) {
cursor.foreach(CONFIG_NAME, type_name, function(section) {
push(model[type_name], clone_section(section));
});
}
return model;
}
function export_model(model) {
let result = {
settings: model.settings,
section: model.sections
};
if (length(model.rules) > 0)
result.rule = model.rules;
for (let type_name in CHILD_ITEM_TYPES)
if (length(model[type_name] || []) > 0)
result[type_name] = model[type_name];
return result;
}
function migration_context(model) {
return {
model,
operations: [],
removed_caches: [],
added_lists: {},
changed: false
};
}
function record_operation(ctx, op) {
push(ctx.operations, op);
ctx.changed = true;
}
function option_exists(section, key) {
return object_or_empty(section)[key] != null;
}
function set_option(ctx, section, key, value) {
value = as_string(value);
if (option(section, key, "") == value && option_exists(section, key))
return;
section[key] = value;
record_operation(ctx, { op: "set", section: section_name(section), option: key, value });
}
function set_option_if_missing(ctx, section, key, value) {
if (option_exists(section, key))
return;
set_option(ctx, section, key, value);
}
function list_values_equal(left, right) {
if (length(left) != length(right))
return false;
for (let i = 0; i < length(left); i++)
if (as_string(left[i]) != as_string(right[i]))
return false;
return true;
}
function set_list_option(ctx, section, key, values) {
let normalized = [];
for (let value in values) {
value = as_string(value);
if (value != "")
push(normalized, value);
}
let current = object_or_empty(section)[key];
let current_values = [];
if (type(current) == "array")
current_values = current;
else if (current != null && as_string(current) != "")
current_values = [ as_string(current) ];
if (option_exists(section, key) && list_values_equal(current_values, normalized))
return;
section[key] = normalized;
record_operation(ctx, { op: "set_list", section: section_name(section), option: key, values: normalized });
}
function set_list_option_if_not_empty(ctx, section, key, values) {
if (length(values || []) > 0)
set_list_option(ctx, section, key, values);
}
function delete_option(ctx, section, key) {
if (!option_exists(section, key))
return;
delete section[key];
record_operation(ctx, { op: "delete", section: section_name(section), option: key });
}
function list_contains(section, key, value) {
value = as_string(value);
for (let item in list_option(section, key))
if (as_string(item) == value)
return true;
return false;
}
function add_list_unique(ctx, section, key, value) {
value = as_string(value);
if (value == "")
return;
let list_key = section_name(section) + "." + key + "=" + value;
if (ctx.added_lists[list_key] || list_contains(section, key, value))
return;
let current = section[key];
if (type(current) != "array")
current = list_option(section, key);
push(current, value);
section[key] = current;
ctx.added_lists[list_key] = true;
record_operation(ctx, { op: "add_list", section: section_name(section), option: key, values: current });
}
function create_child_section(ctx, type_name) {
ctx.child_index = int(ctx.child_index || 0) + 1;
let item_id = "__" + type_name + "_" + ctx.child_index;
let section = {
".name": item_id,
".type": type_name
};
if (ctx.model[type_name] == null)
ctx.model[type_name] = [];
push(ctx.model[type_name], section);
record_operation(ctx, { op: "create", section: item_id, type: type_name, anonymous: true });
return section;
}
function create_child_for_section(ctx, parent, type_name) {
let child = create_child_section(ctx, type_name);
set_option(ctx, child, "section", section_name(parent));
return child;
}
function option_list_values(section, key) {
let value = object_or_empty(section)[key];
if (type(value) == "array")
return value;
if (value == null)
return [];
value = as_string(value);
return value == "" ? [] : [ value ];
}
function whitespace_list_values(section, key) {
let value = object_or_empty(section)[key];
if (type(value) == "array")
return value;
return list_option(section, key);
}
function subscription_url_entry_profile(value) {
let entry = trim(as_string(value));
let result = {
raw: entry,
value: entry,
user_agent: "",
changed: false
};
let delimiter = " | ";
let delimiter_index = rindex(entry, delimiter);
if (delimiter_index < 0)
return result;
let url = trim(substr(entry, 0, delimiter_index));
let user_agent = trim(substr(entry, delimiter_index + length(delimiter)));
if (url == "" || user_agent == "")
return result;
result.value = url;
result.user_agent = user_agent;
result.changed = true;
return result;
}
function normalize_connections_list(ctx, section, old_key, new_key) {
let old_values = option_list_values(section, old_key);
for (let value in old_values)
add_list_unique(ctx, section, new_key, value);
if (length(old_values) > 0)
delete_option(ctx, section, old_key);
}
function set_section_type(ctx, section, type_name) {
if (option(section, ".type", "") == type_name)
return;
section[".type"] = type_name;
record_operation(ctx, { op: "set_type", section: section_name(section), type: type_name });
}
function normalize_detect_server_country_method(value) {
value = as_string(value);
if (value == SERVER_COUNTRY_METHOD_COUNTRY_IS)
return SERVER_COUNTRY_METHOD_COUNTRY_IS;
return SERVER_COUNTRY_METHOD_FLAG_EMOJI;
}
function urltest_filter_mode_filters_enabled(value) {
return value == "exclude" || value == "include" || value == "mixed";
}
function duration_to_seconds_value(value) {
let rest = as_string(value);
if (rest == "")
return null;
let total = 0.0;
let multipliers = {
ns: 0.000000001,
us: 0.000001,
ms: 0.001,
s: 1,
m: 60,
h: 3600,
d: 86400
};
while (rest != "") {
let matched = match(rest, /^([0-9]+(\.[0-9]+)?)(ns|us|ms|s|m|h|d)/);
if (!matched)
return null;
let token = as_string(matched[0]);
let amount = matched[1] * 1;
let unit = matched[3];
total += amount * multipliers[unit];
rest = substr(rest, length(token));
}
return total <= 0 ? null : int(total + 0.5);
}
function legacy_urltest_idle_timeout(section, constants) {
let interval = option(section, "urltest_check_interval", "3m") || "3m";
let interval_seconds = duration_to_seconds_value(interval);
let default_idle_seconds = duration_to_seconds_value(object_or_empty(constants).urltest_default_idle_timeout || "30m");
return interval_seconds != null && default_idle_seconds != null && interval_seconds > default_idle_seconds
? interval
: "";
}
const LEGACY_URLTEST_OPTIONS = [
"urltest_enabled",
"urltest_check_interval",
"urltest_tolerance",
"urltest_testing_url",
"urltest_filter_mode",
"urltest_hide_filtered_outbounds",
"detect_server_country",
"urltest_include_countries",
"urltest_include_outbounds",
"urltest_include_regex",
"urltest_exclude_countries",
"urltest_exclude_outbounds",
"urltest_exclude_regex"
];
function delete_legacy_urltest_options(ctx, section) {
for (let key in LEGACY_URLTEST_OPTIONS)
delete_option(ctx, section, key);
}
function migrate_urltest_filter_mode(ctx, section) {
if (option_exists(section, "urltest_filter_mode"))
return;
if (option_exists(section, "urltest_exclude_countries") ||
option_exists(section, "urltest_include_countries") ||
option_exists(section, "urltest_exclude_outbounds") ||
option_exists(section, "urltest_exclude_regex")) {
set_option(ctx, section, "urltest_filter_mode", "exclude");
}
}
function migrate_detect_server_country(ctx, section) {
if (!option_exists(section, "detect_server_country"))
return;
let value = option(section, "detect_server_country", "");
if (value != "0" && value != "1")
return;
if (bool_option(section, "urltest_enabled", false) &&
urltest_filter_mode_filters_enabled(option(section, "urltest_filter_mode", "disabled"))) {
set_option(ctx, section, "detect_server_country", normalize_detect_server_country_method(value));
}
else {
delete_option(ctx, section, "detect_server_country");
}
}
function trim_lines(value) {
let result = [];
for (let line in split(as_string(value), "\n")) {
line = trim(replace(as_string(line), /\r/g, ""));
if (line != "")
push(result, line);
}
return result;
}
function migrate_urltest_link(ctx, section, link) {
add_list_unique(ctx, section, "selector_proxy_links", link);
}
function migrate_proxy_string(ctx, section) {
let proxy_string = option(section, "proxy_string", "");
if (proxy_string == "")
return;
let migrated = false;
for (let link in trim_lines(proxy_string)) {
if (substr(link, 0, 2) == "//")
continue;
add_list_unique(ctx, section, "selector_proxy_links", link);
migrated = true;
}
if (migrated)
delete_option(ctx, section, "proxy_string");
}
function cache_section_safe(section) {
section = as_string(section);
return section != "" && index(section, "/") < 0 && index(section, "..") < 0
&& match(section, /[^A-Za-z0-9_+.\-]/) == null;
}
function subscription_cache_paths(section) {
return [
TMP_SUBSCRIPTION_FOLDER + "/" + section + ".json",
TMP_SUBSCRIPTION_FOLDER + "/" + section + ".url",
TMP_SUBSCRIPTION_FOLDER + "/" + section + ".user_agent",
TACHYON_SUBSCRIPTION_METADATA_DIR + "/" + section + ".json",
TACHYON_SUBSCRIPTION_LINKS_DIR + "/" + section + ".json",
TACHYON_OUTBOUND_METADATA_DIR + "/" + section + ".json",
TACHYON_SECTION_CACHE_DIR + "/" + section + ".json",
TMP_SUBSCRIPTION_FOLDER + "/" + section + "-subscription-*.json",
TMP_SUBSCRIPTION_FOLDER + "/" + section + "-subscription-*.url",
TMP_SUBSCRIPTION_FOLDER + "/" + section + "-subscription-*.user_agent"
];
}
function delete_subscription_cache(ctx, section) {
section = as_string(section);
if (!cache_section_safe(section))
return;
for (let path in subscription_cache_paths(section))
push(ctx.removed_caches, path);
}
function migrate_subscription_url(ctx, section) {
let subscription_url = option(section, "subscription_url", "");
if (subscription_url == "")
return;
let subscription_user_agent = option(section, "subscription_user_agent", "");
let entry = subscription_user_agent != ""
? subscription_url + " | " + subscription_user_agent
: subscription_url;
add_list_unique(ctx, section, "subscription_urls", entry);
delete_option(ctx, section, "subscription_url");
delete_option(ctx, section, "subscription_user_agent");
delete_subscription_cache(ctx, section_name(section));
}
function migrate_interval_flags(ctx, section, proxy_config_type) {
if (proxy_config_type == "urltest" || proxy_config_type == "subscription") {
if (option(section, "urltest_check_interval_disabled", "") == "1")
set_option(ctx, section, "urltest_enabled", "0");
else
set_option_if_missing(ctx, section, "urltest_enabled", "1");
}
else if (proxy_config_type == "url" || proxy_config_type == "selector") {
set_option_if_missing(ctx, section, "urltest_enabled", "0");
}
if (proxy_config_type == "subscription") {
if (option(section, "subscription_update_interval_disabled", "") == "1")
set_option(ctx, section, "subscription_update_enabled", "0");
else
set_option_if_missing(ctx, section, "subscription_update_enabled", "1");
}
delete_option(ctx, section, "urltest_check_interval_disabled");
delete_option(ctx, section, "subscription_update_interval_disabled");
}
function migrate_proxy_rule(ctx, section, proxy_config_type) {
if (proxy_config_type == "url")
migrate_proxy_string(ctx, section);
else if (proxy_config_type == "urltest") {
for (let link in list_option(section, "urltest_proxy_links"))
migrate_urltest_link(ctx, section, link);
delete_option(ctx, section, "urltest_proxy_links");
}
else if (proxy_config_type == "subscription") {
migrate_subscription_url(ctx, section);
delete_subscription_cache(ctx, section_name(section));
}
migrate_interval_flags(ctx, section, proxy_config_type);
delete_option(ctx, section, "proxy_config_type");
}
// Podkop Plus -> Tachyon source migration.
function migrated_rule_action(section) {
let action = option(section, "action", "");
let proxy_config_type = option(section, "proxy_config_type", "");
let connection_type = option(section, "connection_type", "");
let iface = option(section, "interface", "");
let outbound_json = option(section, "outbound_json", "");
let selector_proxy_links = option(section, "selector_proxy_links", "");
let subscription_urls = option(section, "subscription_urls", "");
if (action == "proxy" || action == "vpn" || action == "outbound")
return "connection";
if (action == "direct")
return "bypass";
if (action != "")
return action;
if (connection_type == "proxy")
return "connection";
if (connection_type == "vpn")
return "connection";
if (connection_type == "block")
return "block";
if (connection_type == "exclusion")
return "bypass";
if (proxy_config_type == "interface")
return "connection";
if (proxy_config_type == "outbound")
return "connection";
if (proxy_config_type == "url" || proxy_config_type == "selector" ||
proxy_config_type == "urltest" || proxy_config_type == "subscription")
return "connection";
if (outbound_json != "")
return "connection";
if (iface != "")
return "connection";
if (selector_proxy_links != "" || subscription_urls != "")
return "connection";
return "";
}
function legacy_rule_connection_kind(section) {
let action = option(section, "action", "");
let proxy_config_type = option(section, "proxy_config_type", "");
let connection_type = option(section, "connection_type", "");
if (action == "vpn" || connection_type == "vpn" || proxy_config_type == "interface")
return "interface";
if (action == "outbound" || proxy_config_type == "outbound")
return "outbound";
if (action == "proxy" || connection_type == "proxy" ||
proxy_config_type == "url" || proxy_config_type == "selector" ||
proxy_config_type == "urltest" || proxy_config_type == "subscription")
return "proxy";
if (option(section, "interface", "") != "")
return "interface";
if (option(section, "outbound_json", "") != "")
return "outbound";
return "proxy";
}
function migrate_byedpi_cmd_opts(ctx, section) {
let cmd_opts = option(section, "cmd_opts", "");
if (cmd_opts == "")
return;
if (option(section, "byedpi_cmd_opts", "") == "")
set_option(ctx, section, "byedpi_cmd_opts", cmd_opts);
delete_option(ctx, section, "cmd_opts");
}
function migrate_zapret_nfqws_default(ctx, section, constants) {
if (migrated_rule_action(section) != "zapret")
return;
let nfqws_opt = option(section, "nfqws_opt", "");
if (nfqws_opt == "" || nfqws_opt != constants.zapret_legacy_default_nfqws_opt)
return;
set_option(ctx, section, "nfqws_opt", constants.zapret_default_nfqws_opt);
}
function migrate_subscription_url_item_settings(ctx, section) {
let values = whitespace_list_values(section, "subscription_urls");
let seen_values = {};
let update_enabled = bool_option(section, "subscription_update_enabled", true);
let update_interval = option(section, "subscription_update_interval", "");
if (update_interval == "")
update_interval = "1h";
let index = 1;
for (let value in values) {
let profile = subscription_url_entry_profile(value);
if (profile.value == "")
continue;
if (seen_values[profile.value])
continue;
seen_values[profile.value] = true;
let child = create_child_for_section(ctx, section, "subscription_url");
set_option(ctx, child, "url", profile.value);
set_option(ctx, child, "subscription_update_enabled", update_enabled ? "1" : "0");
set_option(ctx, child, "subscription_update_interval", update_enabled ? update_interval : "");
set_option(ctx, child, "auto_user_agent", profile.user_agent != "" ? "0" : "1");
if (profile.user_agent != "")
set_option(ctx, child, "user_agent", profile.user_agent);
set_option(ctx, child, "auto_hwid", "1");
set_option(ctx, child, "show_dashboard_metadata", "1");
set_option(ctx, child, "prefix_nodes", "0");
set_option(ctx, child, "include_urltest_groups", "1");
set_option(ctx, child, "hide_urltest_group_outbounds", "1");
set_option(ctx, child, "hide_detour_outbounds", "1");
index++;
}
delete_option(ctx, section, "subscription_urls");
delete_option(ctx, section, "subscription_url_settings");
}
function migrate_urltest_item_settings(ctx, section, constants) {
let legacy_enabled = bool_option(section, "urltest_enabled", false);
if (legacy_enabled) {
let child = create_child_for_section(ctx, section, "urltest");
set_option(ctx, child, "name", "Fastest");
set_option(ctx, child, "check_interval", option(section, "urltest_check_interval", "3m") || "3m");
set_option(ctx, child, "tolerance", option(section, "urltest_tolerance", "50") || "50");
set_option(ctx, child, "testing_url", option(section, "urltest_testing_url", "https://www.gstatic.com/generate_204") || "https://www.gstatic.com/generate_204");
set_option(ctx, child, "filter_mode", option(section, "urltest_filter_mode", "disabled") || "disabled");
set_option(ctx, child, "detect_server_country", normalize_detect_server_country_method(option(section, "detect_server_country", SERVER_COUNTRY_METHOD_FLAG_EMOJI)));
set_option(ctx, child, "interrupt_exist_connections", "1");
set_option(ctx, child, "pin_dashboard", "1");
let idle_timeout = legacy_urltest_idle_timeout(section, constants);
if (idle_timeout != "")
set_option(ctx, child, "idle_timeout", idle_timeout);
set_list_option_if_not_empty(ctx, child, "include_countries", option_list_values(section, "urltest_include_countries"));
set_list_option_if_not_empty(ctx, child, "include_outbounds", option_list_values(section, "urltest_include_outbounds"));
set_list_option_if_not_empty(ctx, child, "include_regex", option_list_values(section, "urltest_include_regex"));
set_list_option_if_not_empty(ctx, child, "exclude_countries", option_list_values(section, "urltest_exclude_countries"));
set_list_option_if_not_empty(ctx, child, "exclude_outbounds", option_list_values(section, "urltest_exclude_outbounds"));
set_list_option_if_not_empty(ctx, child, "exclude_regex", option_list_values(section, "urltest_exclude_regex"));
}
delete_legacy_urltest_options(ctx, section);
delete_option(ctx, section, "urltest_settings");
}
function migrate_interface_item_settings(ctx, section) {
let owner = section_name(section);
let resolver_enabled = bool_option(section, "domain_resolver_enabled", false) ? "1" : "0";
let dns_type = option(section, "domain_resolver_dns_type", "udp") || "udp";
let dns_server = option(section, "domain_resolver_dns_server", "8.8.8.8") || "8.8.8.8";
let seen_values = {};
for (let child in ctx.model.section_interface || []) {
if (option(child, "section", "") != owner)
continue;
let value = option(child, "name", "");
if (value == "" || seen_values[value])
continue;
seen_values[value] = true;
set_option_if_missing(ctx, child, "domain_resolver_enabled", resolver_enabled);
set_option_if_missing(ctx, child, "domain_resolver_dns_type", dns_type);
set_option_if_missing(ctx, child, "domain_resolver_dns_server", dns_server);
}
let values = option_list_values(section, "interfaces");
let legacy_interface = option(section, "interface", "");
if (legacy_interface != "")
push(values, legacy_interface);
for (let value in values) {
value = as_string(value);
if (value == "" || seen_values[value])
continue;
seen_values[value] = true;
let child = create_child_for_section(ctx, section, "section_interface");
set_option(ctx, child, "name", value);
set_option(ctx, child, "domain_resolver_enabled", resolver_enabled);
set_option(ctx, child, "domain_resolver_dns_type", dns_type);
set_option(ctx, child, "domain_resolver_dns_server", dns_server);
}
delete_option(ctx, section, "interface");
delete_option(ctx, section, "interfaces");
delete_option(ctx, section, "interface_settings");
delete_option(ctx, section, "domain_resolver_enabled");
delete_option(ctx, section, "domain_resolver_dns_type");
delete_option(ctx, section, "domain_resolver_dns_server");
}
function migrate_legacy_outbound_json_detour(ctx, section, legacy_connection_kind) {
if (legacy_connection_kind != "outbound" ||
!bool_option(section, "outbound_detour_enabled", false))
return;
let detour_section = option(section, "outbound_detour_section", "");
let outbound_json = option(section, "outbound_json", "");
if (detour_section == "" || outbound_json == "")
return;
let outbound;
try {
outbound = json(outbound_json);
}
catch (e) {
return;
}
if (type(outbound) != "object")
return;
if (as_string(outbound.detour || "") == "")
outbound.detour = singbox_constants_module.outbound_tag(detour_section);
set_option(ctx, section, "outbound_json", sprintf("%J", outbound));
delete_option(ctx, section, "outbound_detour_enabled");
delete_option(ctx, section, "outbound_detour_section");
}
function migrate_outbound_json_list(ctx, section, legacy_connection_kind) {
migrate_legacy_outbound_json_detour(ctx, section, legacy_connection_kind);
let outbound_json = option(section, "outbound_json", "");
if (outbound_json != "") {
let outbound;
try {
outbound = json(outbound_json);
}
catch (e) {
outbound = null;
}
if (type(outbound) == "object" && trim(as_string(outbound.tag || "")) == "") {
let existing = option_list_values(section, "outbound_jsons");
let taken = {};
for (let value in existing) {
try {
let item = json(value);
let tag_name = type(item) == "object" ? trim(as_string(item.tag || "")) : "";
if (tag_name != "")
taken[tag_name] = true;
}
catch (e) {
}
}
let base = singbox_constants_module.outbound_tag(
section_name(section) + "-json-" + (length(existing) + 1)
);
let tag_name = base;
for (let suffix = 1; taken[tag_name]; suffix++)
tag_name = base + "-" + suffix;
outbound.tag = tag_name;
set_option(ctx, section, "outbound_json", sprintf("%J", outbound));
}
}
normalize_connections_list(ctx, section, "outbound_json", "outbound_jsons");
}
function migrate_connection_section(ctx, section, constants, legacy_connection_kind) {
migrate_subscription_url_item_settings(ctx, section);
migrate_urltest_item_settings(ctx, section, constants);
migrate_interface_item_settings(ctx, section);
migrate_outbound_json_list(ctx, section, legacy_connection_kind);
delete_option(ctx, section, "subscription_update_enabled");
delete_option(ctx, section, "subscription_update_interval");
delete_option(ctx, section, "enable_udp_over_tcp");
}
function strip_list_comment(line) {
line = replace(as_string(line), /[[:space:]]*\/\/.*$/, "");
return replace(line, /[[:space:]]*#.*$/, "");
}
function text_list_values(value, separator_mode) {
let result = [];
separator_mode = as_string(separator_mode);
for (let line in split(as_string(value), "\n")) {
line = strip_list_comment(line);
line = separator_mode == "comma-space"
? replace(line, /[ ,]/g, "\n")
: replace(line, /,/g, "\n");
for (let item in split(line, "\n")) {
item = trim(replace(item, /\r/g, ""));
if (item != "")
push(result, item);
}
}
return result;
}
function filter_domain_values(values) {
let result = [];
for (let value in values) {
let normalized = domain_config.suffix_to_ascii(value);
if (normalized != null)
push(result, normalized);
}
return result;
}
function generic_values_from_text(value) {
return text_list_values(value, "comma");
}
function legacy_condition_values(kind, text_mode, conditions_text_mode, text_value, list_value) {
if (int(text_mode || 0) == 1 || int(conditions_text_mode || 0) == 1)
return kind == "domains"
? filter_domain_values(text_list_values(text_value, "comma-space"))
: generic_values_from_text(text_value);
let result = [];
for (let item in list_option({ value: list_value }, "value"))
push(result, item);
if (length(result) > 0)
return result;
if (as_string(text_value) != "")
return kind == "domains"
? filter_domain_values(text_list_values(text_value, "comma-space"))
: generic_values_from_text(text_value);
return [];
}
function add_unique_value(result, seen, value) {
value = as_string(value);
if (value == "" || seen[value])
return;
seen[value] = true;
push(result, value);
}
function has_domain_condition_prefix(value) {
let colon = index(value, ":");
if (colon <= 0)
return false;
let prefix = domain_config.ascii_lower(substr(value, 0, colon));
return prefix == "full" || prefix == "keyword" || prefix == "regex";
}
function add_values_with_prefix(result, seen, values, prefix) {
for (let value in values) {
value = trim(as_string(value));
add_unique_value(result, seen, has_domain_condition_prefix(value) ? value : prefix + value);
}
}
function legacy_values_for_option(section, option_name, kind) {
return legacy_condition_values(
kind,
option(section, option_name + "_text_mode", "0"),
option(section, "conditions_text_mode", "0"),
option(section, option_name + "_text", ""),
section[option_name]
);
}
function raw_text_condition_values(section, option_name) {
return text_list_values(option(section, option_name, ""), "comma-space");
}
function add_domain_values_with_prefix(result, seen, section, option_name, prefix, kind) {
let values = legacy_condition_values(
kind,
option(section, option_name + "_text_mode", "0"),
option(section, "conditions_text_mode", "0"),
option(section, option_name + "_text", ""),
section[option_name]
);
add_values_with_prefix(result, seen, values, prefix);
}
function migrate_combined_domain_conditions(ctx, section) {
let values = [];
let seen = {};
for (let value in list_option(section, "domain_suffix"))
add_unique_value(values, seen, value);
for (let value in raw_text_condition_values(section, "domain_suffix_text"))
add_unique_value(values, seen, value);
add_domain_values_with_prefix(values, seen, section, "domain", "full:", "domains");
add_domain_values_with_prefix(values, seen, section, "domain_keyword", "keyword:", "generic");
add_domain_values_with_prefix(values, seen, section, "domain_regex", "regex:", "generic");
if (length(values) > 0)
set_option(ctx, section, "domain", join("\n", values));
else
delete_option(ctx, section, "domain");
delete_option(ctx, section, "domain_suffix");
delete_option(ctx, section, "domain_suffix_text");
delete_option(ctx, section, "domain_suffix_text_mode");
delete_option(ctx, section, "domain_keyword");
delete_option(ctx, section, "domain_regex");
delete_option(ctx, section, "domain_text");
delete_option(ctx, section, "domain_keyword_text");
delete_option(ctx, section, "domain_regex_text");
delete_option(ctx, section, "domain_text_mode");
delete_option(ctx, section, "domain_keyword_text_mode");
delete_option(ctx, section, "domain_regex_text_mode");
}
function migrate_text_condition(ctx, section, option_name, kind) {
let values = legacy_values_for_option(section, option_name, kind);
if (length(values) > 0)
set_option(ctx, section, option_name, join("\n", values));
else if (option_exists(section, option_name) && type(section[option_name]) == "array")
delete_option(ctx, section, option_name);
delete_option(ctx, section, option_name + "_text");
delete_option(ctx, section, option_name + "_text_mode");
}
function migrate_rule(ctx, section, converted_from_rule, constants) {
let action = migrated_rule_action(section);
let legacy_connection_kind = legacy_rule_connection_kind(section);
let proxy_config_type = option(section, "proxy_config_type", "");
let subscription_urls = option(section, "subscription_urls", "");
if (action != "")
set_option(ctx, section, "action", action);
delete_option(ctx, section, "connection_type");
delete_option(ctx, section, "subscription_group_by_countries");
delete_option(ctx, section, "group_by_countries");
delete_option(ctx, section, "subscription_detect_server_countries");
if (action == "connection") {
migrate_urltest_filter_mode(ctx, section);
migrate_detect_server_country(ctx, section);
if (legacy_connection_kind == "proxy")
migrate_proxy_rule(ctx, section, proxy_config_type);
else {
delete_option(ctx, section, "proxy_config_type");
delete_option(ctx, section, "proxy_string");
delete_option(ctx, section, "urltest_proxy_links");
delete_option(ctx, section, "subscription_url");
delete_option(ctx, section, "subscription_user_agent");
delete_option(ctx, section, "urltest_check_interval_disabled");
delete_option(ctx, section, "subscription_update_interval_disabled");
}
migrate_connection_section(ctx, section, constants, legacy_connection_kind);
if (converted_from_rule && subscription_urls != "")
delete_subscription_cache(ctx, section_name(section));
}
else if (action == "block" ||
action == "bypass" || action == "zapret" || action == "zapret2" || action == "byedpi") {
delete_option(ctx, section, "proxy_config_type");
delete_option(ctx, section, "proxy_string");
delete_option(ctx, section, "urltest_proxy_links");
delete_option(ctx, section, "subscription_url");
delete_option(ctx, section, "subscription_user_agent");
delete_option(ctx, section, "urltest_check_interval_disabled");
delete_option(ctx, section, "subscription_update_interval_disabled");
}
if (action != "connection")
delete_legacy_urltest_options(ctx, section);
migrate_byedpi_cmd_opts(ctx, section);
migrate_zapret_nfqws_default(ctx, section, constants);
}
function migrate_rule_section(ctx, section, constants) {
set_section_type(ctx, section, "section");
migrate_rule(ctx, section, true, constants);
migrate_combined_domain_conditions(ctx, section);
migrate_text_condition(ctx, section, "ip_cidr", "subnets");
}
function migrate_list_update_enabled(ctx) {
let settings = ctx.model.settings;
if (option_exists(settings, "list_update_enabled")) {
if (bool_option(settings, "list_update_enabled", true) &&
option(settings, "update_interval", "") == "") {
set_option(ctx, settings, "update_interval", "1d");
}
return;
}
if (option(settings, "update_interval", "") != "") {
set_option(ctx, settings, "list_update_enabled", "1");
}
else {
set_option(ctx, settings, "list_update_enabled", "0");
set_option(ctx, settings, "update_interval", "1d");
}
}
function normalize_existing_list_option(ctx, section, key) {
let current = object_or_empty(section)[key];
if (current == null || type(current) == "array")
return;
let value = trim(as_string(current));
let values = value == "" ? [] : [ value ];
section[key] = values;
record_operation(ctx, { op: "set_list", section: section_name(section), option: key, values });
}
function migrate_dns_server_lists(ctx) {
normalize_existing_list_option(ctx, ctx.model.settings, "dns_server");
normalize_existing_list_option(ctx, ctx.model.settings, "bootstrap_dns_server");
}
function migrate_download_via_proxy_flags(ctx) {
let settings = ctx.model.settings;
let legacy_section = option(settings, "download_lists_via_proxy_section", "");
let lists_enabled = bool_option(settings, "download_lists_via_proxy", false);
let components_enabled = option_exists(settings, "download_components_via_proxy")
? bool_option(settings, "download_components_via_proxy", false)
: lists_enabled;
set_option(ctx, settings, "download_lists_via_proxy", lists_enabled ? "1" : "0");
if (!lists_enabled)
delete_option(ctx, settings, "download_lists_via_proxy_section");
set_option(ctx, settings, "download_components_via_proxy", components_enabled ? "1" : "0");
if (components_enabled) {
if (option(settings, "download_components_via_proxy_section", "") == "" && legacy_section != "")
set_option(ctx, settings, "download_components_via_proxy_section", legacy_section);
}
else {
delete_option(ctx, settings, "download_components_via_proxy_section");
}
}
function migrate_subscription_download_via_proxy_settings(ctx) {
let settings_section = ctx.model.settings;
let enabled = bool_option(settings_section, "download_subscriptions_via_proxy", false);
let target_section = option(settings_section, "download_lists_via_proxy_section", "");
if (enabled && target_section != "") {
for (let child in ctx.model.subscription_url || []) {
let name = option(child, "section", "");
if (name == "" || name == target_section)
continue;
set_option(ctx, child, "download_via_proxy_enabled", "1");
set_option(ctx, child, "download_via_proxy_section", target_section);
}
}
delete_option(ctx, settings_section, "download_subscriptions_via_proxy");
}
function migrate_rule_set_settings(ctx, section) {
let normalize_list = function(key) {
let seen = {};
let result = [];
for (let reference in option_list_values(section, key)) {
reference = as_string(reference);
if (reference == "" || seen[reference])
continue;
seen[reference] = true;
push(result, reference);
}
if (length(result) > 0)
set_list_option(ctx, section, key, result);
else
delete_option(ctx, section, key);
};
normalize_list("community_lists");
normalize_list("rule_set");
normalize_list("rule_set_with_subnets");
delete_option(ctx, section, "rule_set_settings");
}
function version_parts(value) {
let matched = match(as_string(value), /^([0-9]+)\.([0-9]+)\.([0-9]+)$/);
if (!matched)
return [ 0, 0, 0 ];
return [ int(matched[1]), int(matched[2]), int(matched[3]) ];
}
function compare_versions(left, right) {
left = version_parts(left);
right = version_parts(right);
for (let i = 0; i < 3; i++) {
if (left[i] < right[i])
return -1;
if (left[i] > right[i])
return 1;
}
return 0;
}
function release_at_most(ctx, version) {
return compare_versions(ctx.source_release, version) <= 0;
}
function migrate_interface_sections(ctx) {
if (!release_at_most(ctx, "1.0.1"))
return;
for (let section in ctx.model.sections)
migrate_interface_item_settings(ctx, section);
}
function migrate_enable_component_checks(ctx) {
if (!release_at_most(ctx, "1.0.1"))
return;
set_option(ctx, ctx.model.settings, "component_update_check_enabled", "1");
}
function migration_port(value) {
value = as_string(value);
if (match(value, /^[0-9]+$/) == null)
return null;
value = int(value);
return value >= 1 && value <= 65535 ? value : null;
}
function unique_migration_tag(base, taken) {
base = trim(as_string(base));
if (base == "")
base = "http";
if (!taken[base])
return base;
for (let suffix = 1; suffix < 100000; suffix++) {
let candidate = base + "-" + suffix;
if (!taken[candidate])
return candidate;
}
return base + "-overflow";
}
function migrated_http_outbound(link, tag_name) {
link = core_url.strip_fragment(core_url.decode(link));
let scheme = core_url.scheme(link);
if (scheme != "http" && scheme != "https")
return null;
let host = core_url.host(link);
let port = migration_port(core_url.port(link));
let path = core_url.path(link);
if (host == "" || port == null || (path != "" && path != "/") || index(link, "?") >= 0)
return null;
let outbound = {
type: "http",
tag: tag_name,
server: host,
server_port: port
};
let userinfo = core_url.userinfo(link);
if (userinfo != "") {
let colon = index(userinfo, ":");
outbound.username = colon >= 0 ? substr(userinfo, 0, colon) : userinfo;
if (colon >= 0)
outbound.password = substr(userinfo, colon + 1);
}
if (scheme == "https")
outbound.tls = { enabled: true };
return outbound;
}
function migrate_http_connection_urls(ctx) {
if (!release_at_most(ctx, "1.0.4"))
return;
for (let section in ctx.model.sections) {
let existing_jsons = option_list_values(section, "outbound_jsons");
let taken = {};
for (let value in existing_jsons) {
try {
let outbound = json(value);
let tag_name = type(outbound) == "object" ? trim(as_string(outbound.tag || "")) : "";
if (tag_name != "")
taken[tag_name] = true;
}
catch (e) {
}
}
let remaining_links = [];
let migrated_jsons = [];
for (let link in option_list_values(section, "selector_proxy_links")) {
let tag_name = unique_migration_tag(core_url.fragment(link), taken);
let outbound = migrated_http_outbound(link, tag_name);
if (outbound == null) {
push(remaining_links, link);
continue;
}
taken[tag_name] = true;
push(migrated_jsons, sprintf("%J", outbound));
}
if (length(migrated_jsons) == 0)
continue;
if (length(remaining_links) > 0)
set_list_option(ctx, section, "selector_proxy_links", remaining_links);
else
delete_option(ctx, section, "selector_proxy_links");
for (let value in existing_jsons)
push(migrated_jsons, value);
set_list_option(ctx, section, "outbound_jsons", migrated_jsons);
}
}
const MIGRATIONS = [
{ id: "interface_sections", run: migrate_interface_sections },
{ id: "enable_component_checks", run: migrate_enable_component_checks },
{ id: "http_connection_urls", run: migrate_http_connection_urls }
];
function apply_migrations(ctx) {
ctx.source_release = option(ctx.model.settings, CONFIG_VERSION_OPTION, "");
let applied = [];
let seen = {};
let added = false;
for (let id in list_option(ctx.model.settings, APPLIED_MIGRATIONS_OPTION)) {
id = as_string(id);
if (id == "" || seen[id])
continue;
seen[id] = true;
push(applied, id);
}
for (let migration in MIGRATIONS) {
if (seen[migration.id])
continue;
migration.run(ctx);
seen[migration.id] = true;
push(applied, migration.id);
added = true;
}
if (added)
set_list_option(ctx, ctx.model.settings, APPLIED_MIGRATIONS_OPTION, applied);
if (release_at_most(ctx, "1.0.4"))
set_option(ctx, ctx.model.settings, CONFIG_VERSION_OPTION, "1.0.5");
}
function migrate_podkop_model(model, constants) {
let ctx = migration_context(model);
constants = object_or_empty(constants);
delete_option(ctx, model.settings, "routing_excluded_ips");
migrate_dns_server_lists(ctx);
migrate_list_update_enabled(ctx);
let converted_sections = [];
for (let section in model.rules) {
migrate_rule_section(ctx, section, constants);
push(converted_sections, section);
}
model.rules = [];
for (let section in model.sections) {
migrate_rule(ctx, section, false, constants);
migrate_combined_domain_conditions(ctx, section);
migrate_text_condition(ctx, section, "ip_cidr", "subnets");
migrate_rule_set_settings(ctx, section);
}
for (let section in converted_sections) {
migrate_rule_set_settings(ctx, section);
push(model.sections, section);
}
migrate_subscription_download_via_proxy_settings(ctx);
migrate_download_via_proxy_flags(ctx);
apply_migrations(ctx);
return ctx;
}
function migrate_tachyon_model(model) {
let ctx = migration_context(model);
apply_migrations(ctx);
return ctx;
}
// Runtime UCI adapter and external command dispatcher.
function first_line(path) {
let data = fs.readfile(as_string(path));
if (data == null)
return "";
let newline = index(data, "\n");
return newline >= 0 ? substr(data, 0, newline) : data;
}
function ensure_dir(path) {
run("mkdir -p " + shell_quote(path) + " >/dev/null 2>&1");
}
function clear_subscription_runtime_cache() {
run("rm -rf " +
shell_quote(TMP_SUBSCRIPTION_FOLDER) + " " +
shell_quote(TACHYON_SUBSCRIPTION_LINKS_DIR) + " " +
shell_quote(TACHYON_SUBSCRIPTION_METADATA_DIR) + " " +
shell_quote(TACHYON_OUTBOUND_METADATA_DIR) + " " +
shell_quote(TACHYON_SECTION_CACHE_DIR));
}
function ensure_runtime_dirs() {
ensure_dir(TMP_SUBSCRIPTION_FOLDER);
ensure_dir(TACHYON_RUNTIME_STATE_DIR);
ensure_dir(TACHYON_SUBSCRIPTION_METADATA_DIR);
ensure_dir(TACHYON_OUTBOUND_METADATA_DIR);
ensure_dir(TACHYON_SECTION_CACHE_DIR);
}
function ensure_runtime_cache_format() {
ensure_dir(TACHYON_RUNTIME_STATE_DIR);
if (first_line(TACHYON_RUNTIME_CACHE_FORMAT_FILE) != TACHYON_RUNTIME_CACHE_FORMAT) {
if (first_line(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE) == TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT)
subscription_share_link.populate_subscription_dir(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR);
clear_subscription_runtime_cache();
ensure_runtime_dirs();
fs.writefile(TACHYON_RUNTIME_CACHE_FORMAT_FILE, TACHYON_RUNTIME_CACHE_FORMAT + "\n");
}
if (first_line(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE) != TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT) {
run("rm -rf " + shell_quote(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR));
ensure_dir(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR);
run("chmod 700 " + shell_quote(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR) + " >/dev/null 2>&1");
fs.writefile(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE, TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT + "\n");
run("chmod 600 " + shell_quote(TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE) + " >/dev/null 2>&1");
}
}
function remove_legacy_server_country_cache() {
fs.unlink(TACHYON_RUNTIME_STATE_DIR + "/server-country-cache.json");
}
function remove_cache_path(path) {
path = as_string(path);
if (index(path, "*") >= 0)
run("rm -f " + shell_quote(path));
else
fs.unlink(path);
}
function apply_operations(cursor, operations) {
let created = {};
let section_ref = function(name) {
name = as_string(name);
return as_string(created[name] || name);
};
for (let op in operations) {
if (op.op == "create") {
if (op.anonymous && type(cursor.add) == "function")
created[as_string(op.section)] = cursor.add(CONFIG_NAME, op.type);
else
cursor.set(CONFIG_NAME, op.section, op.type);
}
else if (op.op == "set")
cursor.set(CONFIG_NAME, section_ref(op.section), op.option, op.value);
else if (op.op == "delete")
cursor.delete(CONFIG_NAME, section_ref(op.section), op.option);
else if (op.op == "add_list")
cursor.set(CONFIG_NAME, section_ref(op.section), op.option, op.values);
else if (op.op == "set_list")
cursor.set(CONFIG_NAME, section_ref(op.section), op.option, op.values);
else if (op.op == "set_type")
cursor.set(CONFIG_NAME, section_ref(op.section), op.type);
}
}
function runtime_cursor() {
return {
load: function(package_name) {
return uci_core.load(package_name);
},
get_all: function(package_name, section_name) {
return uci_core.get_all(package_name, section_name);
},
foreach: function(package_name, type_name, callback) {
for (let section in uci_core.section_objects(package_name, type_name))
callback(section);
},
add: function(package_name, type_name) {
return uci_core.add(package_name, type_name);
},
set: function(package_name, section_name, option_name, value) {
if (value == null)
return uci_core.set_section(package_name + "." + section_name, option_name);
return uci_core.set(package_name + "." + section_name + "." + option_name, value);
},
delete: function(package_name, section_name, option_name) {
return uci_core.delete(package_name + "." + section_name + "." + option_name);
},
commit: function(package_name) {
return uci_core.commit(package_name);
}
};
}
function current_config_hash() {
let config_path = "/etc/config/" + CONFIG_NAME;
if (fs.stat(config_path) == null)
return "";
let output = command_output("md5sum " + shell_quote(config_path) + " 2>/dev/null");
let fields = split(trim(output), /[ \t\r\n]+/);
return length(fields) > 0 ? as_string(fields[0]) : "";
}
function mark_internal_config_guard() {
let hash = current_config_hash();
if (hash == "") {
fs.unlink(TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD);
return;
}
let stamp = clock();
let tmp_path = TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD + "." + stamp[0] + "." + stamp[1];
fs.writefile(tmp_path, as_string(stamp[0]) + "\n" + hash + "\n");
if (!fs.rename(tmp_path, TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD))
fs.unlink(tmp_path);
}
function commit_cursor(cursor) {
if (!cursor.commit(CONFIG_NAME))
return false;
mark_internal_config_guard();
return true;
}
function migrate_model(model, source) {
return source == "podkop"
? migrate_podkop_model(model, constants_context())
: migrate_tachyon_model(model);
}
function migrate_runtime(source) {
ensure_runtime_cache_format();
remove_legacy_server_country_cache();
let cursor = runtime_cursor();
cursor.load(CONFIG_NAME);
let ctx = migrate_model(model_from_uci(cursor), source);
if (!ctx.changed)
return true;
apply_operations(cursor, ctx.operations);
for (let path in ctx.removed_caches)
remove_cache_path(path);
return commit_cursor(cursor);
}
function commit_runtime() {
let cursor = runtime_cursor();
cursor.load(CONFIG_NAME);
return commit_cursor(cursor);
}
function migrate_fixture(path, source) {
let ctx = migrate_model(model_from_fixture(path), source);
write_json({
changed: ctx.changed,
config: export_model(ctx.model),
operations: ctx.operations,
removed_caches: ctx.removed_caches
});
}
function main(argv) {
let mode = argv[0] || "";
if (mode == "migrate")
return migrate_runtime("tachyon") ? 0 : 1;
if (mode == "migrate-podkop")
return migrate_runtime("podkop") ? 0 : 1;
if (mode == "commit")
return commit_runtime() ? 0 : 1;
if (mode == "migrate-fixture") {
migrate_fixture(argv[1], argv[2] || "tachyon");
return 0;
}
warn("Usage: config/migration.uc migrate\n");
warn(" config/migration.uc migrate-podkop\n");
warn(" config/migration.uc commit\n");
warn(" config/migration.uc migrate-fixture <fixture.json> [tachyon|podkop]\n");
return 1;
}
function module_exports() {
return {
main: main,
migrate_model: migrate_model,
migrate_tachyon_model: migrate_tachyon_model,
migrate_podkop_model: migrate_podkop_model,
mark_internal_config_guard: mark_internal_config_guard
};
}
if (sourcepath(1) != null && sourcepath(1) != "")
return module_exports();
exit(main(ARGV));