🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=1.1.7-r1
PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for shadowrt
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+lsblk +docker +dockerd +docker-lan-bridge +luci-lib-taskd
define Package/luci-app-shadowrt/conffiles
/etc/config/shadowrt
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
+33
View File
@@ -0,0 +1,33 @@
module("luci.controller.shadowrt", package.seeall)
function index()
entry({"admin", "services", "shadowrt"}, alias("admin", "services", "shadowrt", "overview"), _("ShadoWRT"), 21).dependent = true
entry({"admin", "services", "shadowrt", "overview"}, form("shadowrt/overview"), _("Overview"), 10).dependent = true
entry({"admin", "services", "shadowrt", "config"}, cbi("shadowrt/config"), _("Install"), 20).dependent = true
entry({"admin", "services", "shadowrt", "action"}, post("shadowrt_action"))
entry({"admin", "services", "shadowrt", "ls"}, call("shadowrt_ls"))
end
function shadowrt_action()
local os = require "os"
local rshift = nixio.bit.rshift
local action = luci.http.formvalue("action")
local name = luci.http.formvalue("name")
local r = 1
if action == "start" or action == "stop" or action == "restart" or action == "reset_network" or action == "rm" or action == "rmd" or action == "clone" then
r = os.execute("/usr/libexec/istorec/shadowrt.sh " .. action .. " " .. luci.util.shellquote(name) .. " >/dev/null 2>&1")
r = rshift(r,8)
end
luci.http.prepare_content("application/json")
luci.http.write_json({ code = r })
end
function shadowrt_ls()
local util = require "luci.util"
local clones = util.trim(util.exec("/usr/libexec/istorec/shadowrt.sh ls"))
luci.http.prepare_content("application/json")
luci.http.write("{\n\"code\":200,\n\"clones\":")
luci.http.write(clones)
luci.http.write("\n}\n")
end
@@ -0,0 +1,114 @@
--[[
LuCI - Lua Configuration Interface
]]--
local taskd = require "luci.model.tasks"
local util = require "luci.util"
local jsonc = require "luci.jsonc"
local block_dir = function()
local f = io.popen("lsblk -s -f -b -o NAME,FSSIZE,MOUNTPOINT,FSTYPE --json", "r")
local vals = {}
if f then
local ret = f:read("*all")
f:close()
local obj = jsonc.parse(ret)
for _, val in pairs(obj["blockdevices"]) do
local fsize = val["fssize"]
if fsize ~= nil and string.len(fsize) > 9 and val["mountpoint"]
and val["mountpoint"] ~= "/rom" and val["mountpoint"] ~= "/overlay" and val["mountpoint"] ~= "/ext_overlay" then
-- fsize > 1GB and has mountpoint
vals[#vals+1] = {val["mountpoint"], val["fstype"]}
end
end
end
return vals
end
local homedir = function()
local uci = require "luci.model.uci".cursor()
local home_dirs = {}
home_dirs["main_dir"] = uci:get_first("quickstart", "main", "main_dir", "/root")
home_dirs["Configs"] = uci:get_first("quickstart", "main", "conf_dir", home_dirs["main_dir"].."/Configs")
return home_dirs
end
local find_paths = function()
local blocks = block_dir()
local home_dirs = homedir()
local path_name = "Configs"
local default_path = ''
local configs = {}
default_path = home_dirs[path_name] .. "/ShadoWRT"
if #blocks == 0 then
table.insert(configs, {default_path, "rootfs"})
else
for _, val in pairs(blocks) do
table.insert(configs, {val[1] .. "/" .. path_name .. "/ShadoWRT", val[2]})
end
local without_conf_dir = "/root/" .. path_name .. "/ShadoWRT"
if default_path == without_conf_dir then
default_path = configs[1][1]
end
end
return configs, default_path
end
local m, s, o
m = taskd.docker_map("shadowrt", "shadowrt", "/usr/libexec/istorec/shadowrt.sh",
translate("ShadoWRT"),
translate("ShadoWRT uses the host's /rom as the base image for its clones, so it only supports hosts using SquashFS firmware. After the host system is upgraded, the clones will also use the new version.") .. "<br>"
.. translate("The clone cannot directly access the disk or mount the disk, but /mnt can share the host's.") .. "<br>"
.. translate("Some services are unavailable in the clone, such as hardware-related services and OAF. In addition, the Docker service is disabled by default because the host already has the Docker service. If you need to enable the Docker service in the clone, you can enable it under the \"Startup\" menu on the clone's LuCI."))
s = m:section(TypedSection, "instance", translate("Configuration"),
translate("The following network parameters will only take effect during instance fresh installation or after reset:"))
s.addremove=false
s.anonymous=true
o = s:option(Value, "id", "ID".."<b>*</b>", translate("This ID will be used as hostname and container name, only letters, numbers, underscore and hyphen are allowed. Same ID will overwrite existing instance, please avoid using the name as other Docker containers"))
o.rmempty = false
o.datatype = "hostname"
o = s:option(Value, "data", translate("Data Directory").."<b>*</b>", translate("Will create sub-directory by ID under the selected path, so the path can be shared among multiple instances. Please select a file system with good Linux compatibility, such as ext4, btrfs, zfs, etc."))
o.rmempty = false
o.datatype = "string"
o:value("", translate("-- Please choose --"))
local paths, default_path = find_paths()
for _, val in pairs(paths) do
o:value(val[1], val[1] .. " (" .. val[2] .. ")")
end
o = s:option(Flag, "mnt", translate("Share /mnt"), translate("Share host's /mnt directory"))
o.default = 0
o = s:option(ListValue, "proto", translate("IP Protocol"), translate("Select how this instance gets its IP address"))
o:value("static", translate("Static address"))
o:value("dhcp", translate("DHCP client"))
o.default = "static"
o = s:option(Value, "address", translate("IP Address").."<b>*</b>", translate("Format: xxx.xxx.xxx.xxx/xx"))
o.rmempty = false
o.datatype = "cidr4"
o:depends("proto", "static")
o = s:option(Value, "gateway", translate("Gateway"))
o.datatype = "ip4addr(\"nomask\")"
o:depends("proto", "static")
o = s:option(Value, "dns", translate("DNS Servers"), translate("Multiple DNS servers can be separated by spaces, e.g., '8.8.8.8 8.8.4.4'"))
o.datatype = "string"
o:depends("proto", "static")
o = s:option(Flag, "dhcp_server", translate("DHCP Server"), translate("Enable DHCP server on this instance"))
o.default = 0
o.rmempty = false
o:depends("proto", "static")
return m
@@ -0,0 +1,14 @@
--[[
LuCI - Lua Configuration Interface
]]--
local m, s, o
m = SimpleForm("shadowrt", translate("ShadoWRT"), translate("ShadoWRT can create isolated clones of iStoreOS/OpenWRT."))
m.submit = false
m.reset = false
s = m:section(SimpleSection)
s.template = "shadowrt/status"
return m
@@ -0,0 +1,351 @@
<div class="cbi-section cbi-tblsection">
<h3><%:Instance List%></h3>
<div class="cbi-section-descr">
<details>
<summary><%:Instructions%></summary>
<ul>
<li><%:Executing a shutdown within the clone will restart the clone. To shut down, use the "Stop" button below.%></li>
<li><%:Reset network will restore the instance's network configuration to the state at installation.%></li>
<li><%:When deleting, you can choose whether to delete the data.%></li>
<li><%:"Copy/Edit" will redirect to the "Install" page. If the ID is changed, a new instance will be created; otherwise, the existing instance will be overwritten.%></li>
</ul>
</details>
</div>
<table class="table cbi-section-table">
<thead>
<tr class="tr cbi-section-table-titles anonymous">
<th class="th cbi-section-table-cell" ></th>
<th class="th cbi-section-table-cell" >ID</th>
<th class="th cbi-section-table-cell" ><%:Status%></th>
<th class="th cbi-section-table-cell" >IP</th>
<th class="th cbi-section-table-cell cbi-section-actions" id="cbi-section-table-head-actions"><em><%:Refreshing…%></em></th>
</tr>
</thead>
<tbody id="cbi-table-shadowrt-instances">
<tr class="tr cbi-section-table-row placeholder"><td class="td"><em><%:Loading view…%></em></td></tr>
<% if false then %>
<!--
<tr class="tr cbi-section-table-row" >
<td class="td cbi-value-field">
<div>
<div class="cbi-checkbox">
<input name="selected" type="checkbox" value="istoreos-shadow1">
</div>
</div>
</td>
<td class="td cbi-value-field" data-title="ID" ><b>istoreos-shadow1</b>
</td>
<td class="td cbi-value-field" data-title="状态">
<b>running</b></td>
<td class="td cbi-value-field" data-title="IP">192.168.100.2</td>
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
<div>
<input type="button" class="cbi-button cbi-button-neutral stop" title="停止" value="停止">
<input type="button" class="cbi-button cbi-button-neutral start" title="启动" disabled="" value="启动">
<input type="button" class="cbi-button cbi-button-neutral reload" title="重启" value="重启">
<input type="button" title="复制/编辑" class="btn cbi-button cbi-button-edit" value="复制/编辑">
<input type="button" title="删除" class="btn cbi-button cbi-button-remove" value="删除">
</div>
</td>
</tr>
-->
<% end %>
</tbody>
</table>
<style type="text/css">
#cbi-table-shadowrt-instances.busy {
cursor: progress;
}
#cbi-table-shadowrt-instances.busy input[type="button"] {
pointer-events: none;
opacity: 0.6;
cursor: progress;
}
#cbi-section-table-head-actions>em {
display: none;
}
#cbi-section-table-head-actions.refreshing>em {
display: initial;
}
</style>
</div>
<div class="cbi-section">
<div class="cbi-section-node">
<div id="cbi-section-batch-actions" class="empty">
<div style="display: inline-block;">
<input class="btn cbi-button cbi-button-neutral" type="button" name="stop" value="<%:Stop%>">
</div>
<div style="display: inline-block;">
<input class="btn cbi-button cbi-button-neutral" type="button" name="start" value="<%:Start%>">
</div>
<div style="display: inline-block;">
<input class="btn cbi-button cbi-button-neutral" type="button" name="restart" value="<%:Restart%>">
</div>
</div>
</div>
<style type="text/css">
#cbi-section-batch-actions.busy, #cbi-section-batch-actions.busy input {
cursor: progress;
}
#cbi-section-batch-actions.busy input, #cbi-section-batch-actions.empty input {
pointer-events: none;
opacity: 0.6;
}
</style>
<%
local util = require "luci.util"
local containers = util.trim(util.exec("/usr/libexec/istorec/shadowrt.sh ls"))
-%>
<script type="text/javascript">
window.addEventListener("load", function() {
var containers = <%=containers%>;
console.log(containers);
var table = document.getElementById("cbi-table-shadowrt-instances");
var tableHeadActions = document.getElementById("cbi-section-table-head-actions");
var batchActionsView = document.getElementById("cbi-section-batch-actions");
const State = {
IDLE: 0,
BUSY: 1,
REFRESH: 2
};
var state = State.IDLE;
var busy = function() {
table.classList.add("busy");
tableHeadActions.classList.add("spinning");
batchActionsView.classList.add("busy");
};
var idle = function() {
table.classList.remove("busy");
tableHeadActions.classList.remove("spinning");
batchActionsView.classList.remove("busy");
};
var render = undefined;
var refreshTimer = undefined;
var refresh = function(inbusy) {
if (state !== State.IDLE) {
return;
}
state = State.REFRESH;
if (!inbusy)
busy();
tableHeadActions.classList.add("refreshing");
(new XHR()).get('<%=url([[admin/services/shadowrt/ls]])%>', {},
function(xhr) {
if (state !== State.REFRESH) {
return;
}
try {
var resp = JSON.parse(xhr.responseText);
if (resp.code === 200) {
containers = resp.clones;
render();
} else {
console.error("Failed to get instance list.");
}
} finally {
idle();
tableHeadActions.classList.remove("refreshing");
refreshTimer = setTimeout(refresh, 5000);
state = State.IDLE;
}
}
);
};
var doAction = function(action, name, cb) {
if (state !== State.IDLE) {
return;
}
if (refreshTimer !== undefined) {
clearTimeout(refreshTimer);
refreshTimer = undefined;
}
state = State.BUSY;
busy();
(new XHR()).post('<%=url([[admin/services/shadowrt/action]])%>', { token: '<%=token%>', action: action, name: name },
function() {
state = State.IDLE;
if (cb) {
try {
cb();
} finally {
idle();
}
} else {
refresh(true);
}
}
);
};
var action_stop = function(name) {
console.log("Stop ", name);
doAction("stop", name);
};
var action_start = function(name) {
console.log("Start ", name);
doAction("start", name);
};
var action_restart = function(name) {
console.log("Restart ", name);
doAction("restart", name);
};
var action_edit = function(name) {
console.log("Edit ", name);
doAction("clone", name, function() {
refreshTimer = setTimeout(refresh, 1000);
location.href = '<%=url([[admin/services/shadowrt/config]])%>';
});
};
var action_reset_network = function(name) {
console.log("Reset Network ", name);
if (!confirm("<%:Are you sure you want to reset the network configuration of this instance?%>"))
return;
doAction("reset_network", name);
};
var action_remove = function(name) {
console.log("Remove ", name);
if (!confirm("<%:Are you sure you want to delete this instance?%>"))
return;
var deleteData = confirm("<%:Delete instance data as well? (If retained, future instances with the same ID in the same data directory will use this data)%>");
doAction(deleteData?"rmd":"rm", name);
};
var selected = [];
var on_selected = function(name, checked) {
if (checked) {
selected.push(name);
} else {
var index = selected.indexOf(name);
if (index !== -1)
selected.splice(index, 1);
}
if (selected.length === 1) {
batchActionsView.classList.remove("empty");
} else if (selected.length === 0) {
batchActionsView.classList.add("empty");
}
};
var reset_selected = function(newSelected) {
selected = newSelected || [];
if (selected.length > 0) {
batchActionsView.classList.remove("empty");
} else {
batchActionsView.classList.add("empty");
}
};
var batchActions = {
stop: action_stop,
start: action_start,
restart: action_restart
};
batchActionsView.querySelectorAll('input[type="button"]').forEach(function(button) {
var action = button.name;
if (batchActions[action]) {
var fn = batchActions[action];
button.addEventListener("click", function() {
fn(selected.join(" "));
});
}
});
var status_map = {
"running": "<b><%:Running%></b>",
"starting": "<i><%:Starting%></i>",
"restarting": "<i><%:Restarting%></i>",
"exited": "<%:Stopped%>"
};
var actions = [
{fn: action_stop, title: "<%:Stop%>", class: "cbi-button cbi-button-neutral", disabled: function(status) { return status === "created" || status === "exited"; }},
{fn: action_start, title: "<%:Start%>", class: "cbi-button cbi-button-neutral", disabled: function(status) { return status === "running" || status === "starting"; }},
{fn: action_restart, title: "<%:Restart%>", class: "cbi-button cbi-button-neutral", disabled: function(status) { return false; }},
{fn: action_edit, title: "<%:Copy/Edit%>", class: "cbi-button cbi-button-edit", disabled: function(status) { return false; }},
{fn: action_reset_network, title: "<%:Reset Network%>", class: "cbi-button cbi-button-remove", disabled: function(status) { return false; }},
{fn: action_remove, title: "<%:Delete%>", class: "cbi-button cbi-button-remove", disabled: function(status) { return false; }},
];
render = function() {
var newSelected = [];
var rows = containers.map(function(container) {
var name = container.name;
var status = container.status;
var ip = container.ip;
var row = document.createElement("tr");
row.className = "tr cbi-section-table-row";
var selectCell = document.createElement("td");
selectCell.className = "td cbi-value-field";
var selectDiv = document.createElement("div");
var checkboxDiv = document.createElement("div");
checkboxDiv.className = "cbi-checkbox";
var checkboxInput = document.createElement("input");
checkboxInput.type = "checkbox";
checkboxInput.name = "selected";
checkboxInput.value = name;
checkboxInput.checked = selected.indexOf(name) !== -1;
if (checkboxInput.checked)
newSelected.push(name);
checkboxInput.addEventListener('change', function(e){on_selected(e.target.value, e.target.checked)});
checkboxDiv.appendChild(checkboxInput);
selectDiv.appendChild(checkboxDiv);
selectCell.appendChild(selectDiv);
row.appendChild(selectCell);
var idCell = document.createElement("td");
idCell.className = "td cbi-value-field";
idCell.setAttribute("data-title", "ID");
idCell.innerHTML = "<b>" + name + "</b>";
row.appendChild(idCell);
var statusCell = document.createElement("td");
statusCell.className = "td cbi-value-field";
statusCell.setAttribute("data-title", "<%:Status%>");
statusCell.innerHTML = status_map[status] || status;
row.appendChild(statusCell);
var ipCell = document.createElement("td");
ipCell.className = "td cbi-value-field";
ipCell.setAttribute("data-title", "IP");
ipCell.innerHTML = '<a href="http://' + ip + '" target="_blank">' + ip + '</a>';
row.appendChild(ipCell);
var actionCell = document.createElement("td");
actionCell.className = "td cbi-section-table-cell nowrap cbi-section-actions";
var actionDiv = document.createElement("div");
var actionBtns = actions.map(function(action) {
var input = document.createElement("input");
input.type = "button";
input.className = action.class;
input.title = action.title;
input.value = action.title;
if (action.disabled(status)) {
input.disabled = true;
}
input.addEventListener("click", action.fn.bind(null, name));
return input;
});
actionDiv.replaceChildren(...actionBtns);
actionCell.appendChild(actionDiv);
row.appendChild(actionCell);
return row;
});
if (rows.length === 0) {
var row = document.createElement("tr");
row.className = "tr cbi-section-table-row placeholder";
var cell = document.createElement("td");
cell.className = "td";
cell.innerHTML = '<em><%=translatef([[No instances yet, go to <a href="%s">Install</a>.]], url([[admin/services/shadowrt/config]]))%></em>';
row.appendChild(cell);
rows.push(row);
}
reset_selected(newSelected);
table.replaceChildren(...rows);
};
render();
refreshTimer = setTimeout(refresh, 10000);
});
</script>
</div>
+228
View File
@@ -0,0 +1,228 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: luasrc/view/shadowrt/status.htm:10
msgid ""
"\"Copy/Edit\" will redirect to the \"Install\" page. If the ID is changed, a "
"new instance will be created; otherwise, the existing instance will be "
"overwritten."
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:81
msgid "-- Please choose --"
msgstr ""
#: luasrc/view/shadowrt/status.htm:210
msgid "Are you sure you want to delete this instance?"
msgstr ""
#: luasrc/view/shadowrt/status.htm:204
msgid ""
"Are you sure you want to reset the network configuration of this instance?"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:68
msgid "Configuration"
msgstr ""
#: luasrc/view/shadowrt/status.htm:262
msgid "Copy/Edit"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:109
msgid "DHCP Server"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:93
msgid "DHCP client"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:105
msgid "DNS Servers"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:77
msgid "Data Directory"
msgstr ""
#: luasrc/view/shadowrt/status.htm:264
msgid "Delete"
msgstr ""
#: luasrc/view/shadowrt/status.htm:212
msgid ""
"Delete instance data as well? (If retained, future instances with the same "
"ID in the same data directory will use this data)"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:109
msgid "Enable DHCP server on this instance"
msgstr ""
#: luasrc/view/shadowrt/status.htm:7
msgid ""
"Executing a shutdown within the clone will restart the clone. To shut down, "
"use the \"Stop\" button below."
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:96
msgid "Format: xxx.xxx.xxx.xxx/xx"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:101
msgid "Gateway"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:96
msgid "IP Address"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:91
msgid "IP Protocol"
msgstr ""
#: luasrc/controller/shadowrt.lua:7
msgid "Install"
msgstr ""
#: luasrc/view/shadowrt/status.htm:2
msgid "Instance List"
msgstr ""
#: luasrc/view/shadowrt/status.htm:5
msgid "Instructions"
msgstr ""
#: luasrc/view/shadowrt/status.htm:25
msgid "Loading view…"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:105
msgid ""
"Multiple DNS servers can be separated by spaces, e.g., '8.8.8.8 8.8.4.4'"
msgstr ""
#: luasrc/view/shadowrt/status.htm:338
msgid "No instances yet, go to <a href=\"%s\">Install</a>."
msgstr ""
#: luasrc/controller/shadowrt.lua:6
msgid "Overview"
msgstr ""
#: luasrc/view/shadowrt/status.htm:21
msgid "Refreshing…"
msgstr ""
#: luasrc/view/shadowrt/status.htm:263
msgid "Reset Network"
msgstr ""
#: luasrc/view/shadowrt/status.htm:8
msgid ""
"Reset network will restore the instance's network configuration to the state "
"at installation."
msgstr ""
#: luasrc/view/shadowrt/status.htm:82 luasrc/view/shadowrt/status.htm:261
msgid "Restart"
msgstr ""
#: luasrc/view/shadowrt/status.htm:255
msgid "Restarting"
msgstr ""
#: luasrc/view/shadowrt/status.htm:253
msgid "Running"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:91
msgid "Select how this instance gets its IP address"
msgstr ""
#: luasrc/controller/shadowrt.lua:5 luasrc/model/cbi/shadowrt/overview.lua:7
#: luasrc/model/cbi/shadowrt/config.lua:63
msgid "ShadoWRT"
msgstr ""
#: luasrc/model/cbi/shadowrt/overview.lua:7
msgid "ShadoWRT can create isolated clones of iStoreOS/OpenWRT."
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:64
msgid ""
"ShadoWRT uses the host's /rom as the base image for its clones, so it only "
"supports hosts using SquashFS firmware. After the host system is upgraded, "
"the clones will also use the new version."
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:88
msgid "Share /mnt"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:88
msgid "Share host's /mnt directory"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:66
msgid ""
"Some services are unavailable in the clone, such as hardware-related "
"services and OAF. In addition, the Docker service is disabled by default "
"because the host already has the Docker service. If you need to enable the "
"Docker service in the clone, you can enable it under the \"Startup\" menu on "
"the clone's LuCI."
msgstr ""
#: luasrc/view/shadowrt/status.htm:79 luasrc/view/shadowrt/status.htm:260
msgid "Start"
msgstr ""
#: luasrc/view/shadowrt/status.htm:254
msgid "Starting"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:92
msgid "Static address"
msgstr ""
#: luasrc/view/shadowrt/status.htm:19 luasrc/view/shadowrt/status.htm:301
msgid "Status"
msgstr ""
#: luasrc/view/shadowrt/status.htm:76 luasrc/view/shadowrt/status.htm:259
msgid "Stop"
msgstr ""
#: luasrc/view/shadowrt/status.htm:256
msgid "Stopped"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:65
msgid ""
"The clone cannot directly access the disk or mount the disk, but /mnt can "
"share the host's."
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:69
msgid ""
"The following network parameters will only take effect during instance fresh "
"installation or after reset:"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:73
msgid ""
"This ID will be used as hostname and container name, only letters, numbers, "
"underscore and hyphen are allowed. Same ID will overwrite existing instance, "
"please avoid using the name as other Docker containers"
msgstr ""
#: luasrc/view/shadowrt/status.htm:9
msgid "When deleting, you can choose whether to delete the data."
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:77
msgid ""
"Will create sub-directory by ID under the selected path, so the path can be "
"shared among multiple instances. Please select a file system with good Linux "
"compatibility, such as ext4, btrfs, zfs, etc."
msgstr ""
+242
View File
@@ -0,0 +1,242 @@
msgid ""
msgstr ""
"Language: zh_Hans\n"
"Content-Type: text/plain; charset=UTF-8"
#: luasrc/view/shadowrt/status.htm:10
msgid ""
"\"Copy/Edit\" will redirect to the \"Install\" page. If the ID is changed, a "
"new instance will be created; otherwise, the existing instance will be "
"overwritten."
msgstr ""
"“复制/编辑”将跳转到“安装”页面,如果修改ID则创建新实例,否则覆盖原有实例。"
#: luasrc/model/cbi/shadowrt/config.lua:81
msgid "-- Please choose --"
msgstr ""
#: luasrc/view/shadowrt/status.htm:210
msgid "Are you sure you want to delete this instance?"
msgstr "确定要删除此实例吗?"
#: luasrc/view/shadowrt/status.htm:204
msgid ""
"Are you sure you want to reset the network configuration of this instance?"
msgstr "确定要重置此实例的网络配置吗?"
#: luasrc/model/cbi/shadowrt/config.lua:68
msgid "Configuration"
msgstr "配置"
#: luasrc/view/shadowrt/status.htm:262
msgid "Copy/Edit"
msgstr "复制/编辑"
#: luasrc/model/cbi/shadowrt/config.lua:109
msgid "DHCP Server"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:93
msgid "DHCP client"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:105
msgid "DNS Servers"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:77
msgid "Data Directory"
msgstr "数据文件夹"
#: luasrc/view/shadowrt/status.htm:264
msgid "Delete"
msgstr "删除"
#: luasrc/view/shadowrt/status.htm:212
msgid ""
"Delete instance data as well? (If retained, future instances with the same "
"ID in the same data directory will use this data)"
msgstr ""
"是否同时删除实例数据?(若保留,未来相同数据目录下创建相同 ID 的实例将会使用"
"此数据)"
#: luasrc/model/cbi/shadowrt/config.lua:109
msgid "Enable DHCP server on this instance"
msgstr "这个实例提供 DHCP 服务"
#: luasrc/view/shadowrt/status.htm:7
msgid ""
"Executing a shutdown within the clone will restart the clone. To shut down, "
"use the \"Stop\" button below."
msgstr "分身内执行关机会重启分身,如果要关机,使用下面的“停止”按钮。"
#: luasrc/model/cbi/shadowrt/config.lua:96
msgid "Format: xxx.xxx.xxx.xxx/xx"
msgstr "格式:xxx.xxx.xxx.xxx/xx"
#: luasrc/model/cbi/shadowrt/config.lua:101
msgid "Gateway"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:96
msgid "IP Address"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:91
msgid "IP Protocol"
msgstr ""
#: luasrc/controller/shadowrt.lua:7
msgid "Install"
msgstr ""
#: luasrc/view/shadowrt/status.htm:2
msgid "Instance List"
msgstr "实例列表"
#: luasrc/view/shadowrt/status.htm:5
msgid "Instructions"
msgstr "操作说明"
#: luasrc/view/shadowrt/status.htm:25
msgid "Loading view…"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:105
msgid ""
"Multiple DNS servers can be separated by spaces, e.g., '8.8.8.8 8.8.4.4'"
msgstr "多个 DNS 可以用空格隔开,例如‘8.8.8.8 8.8.4.4’"
#: luasrc/view/shadowrt/status.htm:338
msgid "No instances yet, go to <a href=\"%s\">Install</a>."
msgstr "还没有实例,前往<a href=\"%s\">安装</a>"
#: luasrc/controller/shadowrt.lua:6
msgid "Overview"
msgstr ""
#: luasrc/view/shadowrt/status.htm:21
msgid "Refreshing…"
msgstr "刷新中…"
#: luasrc/view/shadowrt/status.htm:263
msgid "Reset Network"
msgstr "重置网络"
#: luasrc/view/shadowrt/status.htm:8
msgid ""
"Reset network will restore the instance's network configuration to the state "
"at installation."
msgstr "重置网络可将实例的网络配置重置为安装时的状态。"
#: luasrc/view/shadowrt/status.htm:82 luasrc/view/shadowrt/status.htm:261
msgid "Restart"
msgstr ""
#: luasrc/view/shadowrt/status.htm:255
msgid "Restarting"
msgstr "重启中"
#: luasrc/view/shadowrt/status.htm:253
msgid "Running"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:91
msgid "Select how this instance gets its IP address"
msgstr "实例如何获取自身 IP 地址"
#: luasrc/controller/shadowrt.lua:5 luasrc/model/cbi/shadowrt/overview.lua:7
#: luasrc/model/cbi/shadowrt/config.lua:63
msgid "ShadoWRT"
msgstr "ShadoWRT"
#: luasrc/model/cbi/shadowrt/overview.lua:7
msgid "ShadoWRT can create isolated clones of iStoreOS/OpenWRT."
msgstr "ShadoWRT 可创建隔离的 iStoreOS/OpenWRT 的分身"
#: luasrc/model/cbi/shadowrt/config.lua:64
msgid ""
"ShadoWRT uses the host's /rom as the base image for its clones, so it only "
"supports hosts using SquashFS firmware. After the host system is upgraded, "
"the clones will also use the new version."
msgstr ""
"ShadoWRT 使用宿主的 /rom 作为分身的基础镜像,所以只支持使用 SquashFS 固件的宿"
"主。宿主系统升级后,分身也会使用新版本。"
#: luasrc/model/cbi/shadowrt/config.lua:88
msgid "Share /mnt"
msgstr "共享 /mnt"
#: luasrc/model/cbi/shadowrt/config.lua:88
msgid "Share host's /mnt directory"
msgstr "共享宿主的 /mnt 文件夹"
#: luasrc/model/cbi/shadowrt/config.lua:66
msgid ""
"Some services are unavailable in the clone, such as hardware-related "
"services and OAF. In addition, the Docker service is disabled by default "
"because the host already has the Docker service. If you need to enable the "
"Docker service in the clone, you can enable it under the \"Startup\" menu on "
"the clone's LuCI."
msgstr ""
"分身中部分服务无法使用,例如硬件有关的服务、应用过滤等;另外,Docker 服务默认"
"禁用,因为宿主已经有 Docker 服务,如需在分身中启用 Docker 服务,可在分身的后"
"台页面“启动项”中开启。"
#: luasrc/view/shadowrt/status.htm:79 luasrc/view/shadowrt/status.htm:260
msgid "Start"
msgstr ""
#: luasrc/view/shadowrt/status.htm:254
msgid "Starting"
msgstr "启动中"
#: luasrc/model/cbi/shadowrt/config.lua:92
msgid "Static address"
msgstr ""
#: luasrc/view/shadowrt/status.htm:19 luasrc/view/shadowrt/status.htm:301
msgid "Status"
msgstr ""
#: luasrc/view/shadowrt/status.htm:76 luasrc/view/shadowrt/status.htm:259
msgid "Stop"
msgstr ""
#: luasrc/view/shadowrt/status.htm:256
msgid "Stopped"
msgstr ""
#: luasrc/model/cbi/shadowrt/config.lua:65
msgid ""
"The clone cannot directly access the disk or mount the disk, but /mnt can "
"share the host's."
msgstr "分身无法直接访问磁盘,也无法挂载磁盘,不过 /mnt 可以共享宿主的。"
#: luasrc/model/cbi/shadowrt/config.lua:69
msgid ""
"The following network parameters will only take effect during instance fresh "
"installation or after reset:"
msgstr "以下网络参数只在实例全新安装或重置以后才生效:"
#: luasrc/model/cbi/shadowrt/config.lua:73
msgid ""
"This ID will be used as hostname and container name, only letters, numbers, "
"underscore and hyphen are allowed. Same ID will overwrite existing instance, "
"please avoid using the name as other Docker containers"
msgstr ""
"ID 将作为实例的主机名和容器名,只允许字母数字下划线和减号。相同 ID 将覆盖已有"
"实例,请避免使用与其他 Docker 容器相同的名称"
#: luasrc/view/shadowrt/status.htm:9
msgid "When deleting, you can choose whether to delete the data."
msgstr "“删除”时可选择是否删除数据。"
#: luasrc/model/cbi/shadowrt/config.lua:77
msgid ""
"Will create sub-directory by ID under the selected path, so the path can be "
"shared among multiple instances. Please select a file system with good Linux "
"compatibility, such as ext4, btrfs, zfs, etc."
msgstr ""
"会在选择的路径下创建以 ID 命名的子文件夹,所以此路径可以被多个实例共用。请选"
"择 Linux 兼容性好的文件系统,例如 ext4,btrfs,zfs 等"
+1
View File
@@ -0,0 +1 @@
zh-cn
@@ -0,0 +1,12 @@
config main 'main'
config instance
option 'id' 'shadowrt0'
# option 'data' '/mnt/data/shadowrt'
option 'mnt' '0'
option 'proto' 'static'
option 'address' '192.168.100.2/24'
option 'gateway' '192.168.100.1'
option 'dns' '192.168.100.1'
option 'dhcp_server' '0'
+267
View File
@@ -0,0 +1,267 @@
#!/bin/sh
# Author jjm2473@gmail.com
ACTION="${1}"
shift 1
do_install() {
local id="`uci get shadowrt.@instance[0].id 2>/dev/null`"
local data="`uci get shadowrt.@instance[0].data 2>/dev/null`"
local mnt="`uci get shadowrt.@instance[0].mnt 2>/dev/null`"
local proto=`uci get shadowrt.@instance[0].proto 2>/dev/null`
local address=`uci get shadowrt.@instance[0].address 2>/dev/null`
local gateway=`uci get shadowrt.@instance[0].gateway 2>/dev/null`
local dns="`uci get shadowrt.@instance[0].dns 2>/dev/null`"
local dhcp_server=`uci get shadowrt.@instance[0].dhcp_server 2>/dev/null`
local ports="`uci get shadowrt.@instance[0].ports 2>/dev/null`"
if [ -z "$data" ]; then
echo "data path is empty!" >&2
exit 1
fi
[ -s /rom/etc/openwrt_release ] || {
echo "/rom is not a openwrt rootfs!" >&2
exit 1
}
if [ "$proto" = "static" ]; then
if [ -z "$address" ]; then
echo "static ip requires address!" >&2
exit 1
fi
fi
/etc/init.d/docker-lan start || {
echo "create docker-lan bridge failed!" >&2
exit 1
}
local alpine_image="alpine:3.22.2"
if ! docker image inspect -f '{}' "$alpine_image" >/dev/null 2>&1; then
echo "pulling alpine image $alpine_image ..."
docker pull "$alpine_image" || exit 1
fi
if [ -d "$data/$id" ]; then
echo "WARNING: $data/$id already exists, may use old data." >&2
fi
local config="{\"id\":\"$id\",\"data\":\"$data\",\"mnt\":\"$mnt\",\"proto\":\"$proto\",\"address\":\"$address\",\"gateway\":\"$gateway\",\"dns\":\"$dns\",\"dhcp_server\":\"$dhcp_server\",\"ports\":\"$ports\"}"
local cmd="docker run --restart=unless-stopped -d \
--stop-signal SIGINT \
--stop-timeout 30 \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--cap-add=SYS_ADMIN \
--cap-add=SYS_CHROOT \
--cap-drop=MKNOD \
--cap-add=LEASE \
--cap-add=SETGID \
--cap-add=SETUID \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--cap-add=NET_BIND_SERVICE \
--network docker-lan \
-v /usr/share/shadowrt/container:/shadowrt:ro \
--entrypoint /shadowrt/entrypoint.sh \
-v /dev/net:/dev/net \
--device /dev/fuse:/dev/fuse \
-v /rom:/rom:ro \
--label creator=shadowrt \
--name '$id' \
--hostname '$id' \
--label 'com.shadowrt.config=$config' \
-v '$data/$id/overlay:/overlay:rw' "
[ -n "$proto" ] && cmd="$cmd -e IP_PROTO=$proto"
if [ "$proto" = "static" ]; then
[ -n "$address" ] && cmd="$cmd -e IP_ADDRESS=$address"
[ -n "$gateway" ] && cmd="$cmd -e IP_GATEWAY=$gateway"
[ -n "$dns" ] && cmd="$cmd -e 'IP_DNS=$dns'"
fi
[ "$dhcp_server" = "1" -o "$dhcp_server" = "on" ] && cmd="$cmd -e DHCP_SERVER=on"
if [ -n "$ports" ]; then
for p in $ports; do
cmd="$cmd -p $p:$p"
done
fi
if [ -n "$dns" ]; then
for d in $dns; do
cmd="$cmd --dns $d"
done
fi
local tz="`uci get system.@system[0].zonename | sed 's/ /_/g'`"
[ -z "$tz" ] || cmd="$cmd -e TZ=$tz"
if [ "$mnt" = "1" -o "$mnt" = "on" ]; then
cmd="$cmd -v /mnt:/mnt"
mountpoint -q /mnt && cmd="$cmd:rshared"
fi
cmd="$cmd $alpine_image"
echo "stopping existing container..."
docker stop "$id" >/dev/null 2>&1
docker rm -f "$id"
echo "starting shadowrt instance $id..."
echo "$cmd"
eval "$cmd"
}
do_ls() {
local name state ip
echo "["
docker ps -a -f 'label=creator=shadowrt' --format '{{.Names}} {{.State}}' | sort -n | while read name state; do
ip=
if [ "$state" = "running" ]; then
ip=`docker exec "$name" ip addr show dev br-lan | grep -m1 'inet ' | head -1 | sed -nE 's#.*inet ([0-9\.]*)/([0-9]*) .*#\1#p'`
if [ -z "$ip" ]; then
docker exec "$name" test -e /etc/openwrt_release -a ! -e /rom/note || state="starting"
fi
fi
echo '{"name":"'"$name"'","status":"'"$state"'","ip":"'"$ip"'"},'
#docker container inspect -f '{"name":"'"$name"'","status":"'"$state"'","config":{{index .Config.Labels "com.shadowrt.config"}},"ip":"'"$ip"'"},' "$name"
done | head -c -2
echo ""
echo "]"
}
do_clone() {
local config_json="`docker container inspect -f '{{index .Config.Labels "com.shadowrt.config"}}' "$1"`"
[ -z "$config_json" ] && {
echo "container $1 not found!" >&2
return 1
}
{
echo "delete shadowrt.@instance[0]"
echo "add shadowrt instance"
echo "$config_json" | jsonfilter -e 'id=$.id' \
-e 'data=$.data' \
-e 'mnt=$.mnt' \
-e 'proto=$.proto' \
-e 'address=$.address' \
-e 'gateway=$.gateway' \
-e 'dns=$.dns' \
-e 'dhcp_server=$.dhcp_server' \
-e 'ports=$.ports' | sed -e 's/; /\n/g' | sed -e 's/^export /set shadowrt.@instance[0]./g'
echo "commit shadowrt"
} | uci batch
return 0
}
do_reset_network() {
local name="$1"
local running=false
local shell
docker exec "$name" test -e /etc/openwrt_release -a ! -e /rom/note >/dev/null 2>&1 && running=true
if $running; then
shell="exec docker exec -i -w / '$name' /bin/sh"
else
docker stop "$name" >/dev/null 2>&1
local data="`docker container inspect -f '{{index .Config.Labels "com.shadowrt.config"}}' "$name" | jsonfilter -e '$.data'`"
local dir="$data/$name/overlay/upper"
[ -d "$dir" ] || return 0
shell="cd '$dir' && exec /bin/sh"
fi
{
cat <<-EOF
for f in etc/config/network etc/board.json; do
rm -f "\$f"
done
EOF
if $running; then
cat <<-EOF
/bin/board_detect
/bin/config_generate
/bin/sh -c ". /rom/etc/uci-defaults/zzz-dockerenv"
/bin/sh -c '. /rom/etc/uci-defaults/12_network-generate-ula'
/etc/init.d/network restart
sleep 2
EOF
else
echo 'rm -f etc/uci-defaults/zzz-dockerenv etc/uci-defaults/12_network-generate-ula'
fi
} | sh -c "$shell"
}
check_all_ready() {
local names="$1"
local name
local ret=0
for name in $names; do
if ! docker exec "$name" test -e /etc/openwrt_release -a ! -e /rom/note; then
ret=1
break
fi
done
return $ret
}
usage() {
echo "usage: $0 sub-command"
echo "where sub-command is one of:"
echo " install Install/Replace a instance"
echo " ls List all instances"
echo " rm/start/stop/restart {ID} Remove/Start/Stop/Restart the instance"
echo " clone {ID} Clone an existing instance to uci"
echo " rmd {ID} Remove an existing instance and its data"
echo " reset_network {ID} Reset network configuration inside the instance"
echo " status Dummy status for taskd"
}
case "${ACTION}" in
"install")
do_install
;;
"rm" | "rmd")
if [ -n "$1" ]; then
docker stop "$1" >/dev/null 2>&1
if [ "$ACTION" = "rmd" ]; then
data="`docker container inspect -f '{{index .Config.Labels "com.shadowrt.config"}}' "$1" | jsonfilter -e '$.data'`"
[ -n "$data" ] && rm -rf "$data/$1"
fi
docker rm -f "$1"
fi
;;
"reset_network")
if [ -n "$1" ]; then
do_reset_network "$1"
fi
;;
"start" | "stop" | "restart")
if [ -n "$1" ]; then
docker "${ACTION}" $1
if [ "$ACTION" = "start" -o "$ACTION" = "restart" ]; then
sleep 2
for i in $(seq 1 5); do
check_all_ready "$1" && break
sleep 1
done
fi
fi
;;
"clone")
if [ -n "$1" ]; then
do_clone "$1" || exit 1
fi
;;
"status")
# hack, return empty string, so lib-taskd thinks container is not installed
exit 0
;;
"ls")
do_ls
;;
*)
usage
exit 1
;;
esac
@@ -0,0 +1,11 @@
{
"luci-app-shadowrt": {
"description": "Grant UCI access for luci-app-shadowrt",
"read": {
"uci": [ "shadowrt" ]
},
"write": {
"uci": [ "shadowrt" ]
}
}
}
+33
View File
@@ -0,0 +1,33 @@
#!/bin/sh
#docker run -d --rm \
exec docker run -it --rm \
--stop-signal SIGINT \
--stop-timeout 30 \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--cap-add=SYS_ADMIN \
--cap-add=SYS_CHROOT \
--cap-drop=MKNOD \
--cap-add=LEASE \
--cap-add=SETGID \
--cap-add=SETUID \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--cap-add=NET_BIND_SERVICE \
-v /usr/share/shadowrt/container:/shadowrt:ro \
--entrypoint /shadowrt/entrypoint.sh \
--name istoreos-shadow0 \
--hostname istoreos-shadow0 \
--label creator=istoreos-shadow \
--label com.istoreos.shadow.id=istoreos-shadow0 \
--dns 172.17.0.1 \
--dns 8.8.8.8 \
-p 8080:80 \
-p 17681:7681 \
-v /dev/net:/dev/net \
--device /dev/fuse:/dev/fuse \
-v /rom:/rom:ro \
-v /mnt:/mnt:rshared \
-v /mnt/data/is-diff/0:/overlay:rw \
alpine
@@ -0,0 +1,42 @@
#!/bin/sh
# env
# IP_PROTO=dhcp|static
# IP_ADDRESS=ipv4/prefixlen
# IP_GATEWAY=ipv4
# IP_DNS="dns1 dns2 ..."
# DHCP_SERVER=on|off
[ -s /rom/etc/openwrt_release ] || {
echo "/rom is not a openwrt rootfs!" >&2
exit 1
}
[ -e /rom/.dockerenv ] && {
echo "/rom is already a docker container rootfs!" >&2
exit 1
}
CONSOLE=`readlink /proc/$$/fd/2`
if echo "$CONSOLE" | grep -q '^/dev/'; then
[ -e /dev/kmsg ] || ln -s "$CONSOLE" /dev/kmsg
[ -e /dev/console ] || ln -s "$CONSOLE" /dev/console
else
[ -e /dev/kmsg ] || ln -s /proc/self/fd/2 /dev/kmsg
[ -e /dev/console ] || ln -s /dev/null /dev/console
fi
mount --make-private /
mount --make-private /rom
mount --make-private /overlay
if unshare -m --propagation unchanged true 2>/dev/null; then
exec unshare -m --propagation unchanged /shadowrt/s1-unshared.sh "$CONSOLE"
elif unshare -m --propagation shared true 2>/dev/null; then
exec unshare -m --propagation shared /shadowrt/s1-unshared.sh "$CONSOLE"
else
echo "unshare does not support propagation flag, fallback to default" >&2
exec unshare -m /shadowrt/s1-unshared.sh "$CONSOLE"
fi
Whitespace-only changes.
@@ -0,0 +1,32 @@
#!/bin/sh
CFG=$1
IP_PROTO=dhcp
IP_ADDRESS=
[ -n "$CFG" ] || CFG=/etc/board.json
. /lib/functions/uci-defaults.sh
board_config_update
json_is_a network object && exit 0
ucidef_set_hostname "iStoreOS"
ucidef_set_timezone "CST-8"
ucidef_set_ntpserver "0.cn.pool.ntp.org" "1.cn.pool.ntp.org" "2.cn.pool.ntp.org" "3.cn.pool.ntp.org"
if [ "static" = "$IP_PROTO" -a -n "$IP_ADDRESS" ]; then
eval $(ipcalc.sh "$IP_ADDRESS")
ucidef_set_interface_lan "eth0"
ucidef_set_interface "lan" ipaddr "$IP" netmask "$NETMASK"
else
ucidef_set_interface_lan "eth0" "dhcp"
fi
board_config_flush
[ -s "$CFG" ] || return 1
@@ -0,0 +1,12 @@
# This file is interpreted as shell script.
# Put your custom iptables rules here, they will
# be executed with each firewall (re-)start.
# Internal uci firewall chains are flushed and recreated on reload, so
# put custom rules into the root chains e.g. INPUT or FORWARD or into the
# special user chains, e.g. input_wan_rule or postrouting_lan_rule.
# workaround route shortcut issue in shadowrt clones on openwrt 22.03
iptables -t mangle -I PREROUTING -p tcp -m conntrack --ctstate RELATED,ESTABLISHED -m socket -j ACCEPT || true
ip6tables -t mangle -I PREROUTING -p tcp -m conntrack --ctstate RELATED,ESTABLISHED -m socket -j ACCEPT || true
@@ -0,0 +1,38 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
set quickstart.main.disable_dhns=1
commit quickstart
EOF
uci -q batch <<-EOF >/dev/null
set argon.@global[0].primary='#5b7e99'
set argon.@global[0].dark_primary='#337d8c'
commit argon
EOF
GATEWAY=
DNS=
DHCP_SERVER=
[ -n "$GATEWAY" ] && uci set network.lan.gateway="$GATEWAY"
if [ -n "$DNS" ]; then
for dns in $DNS; do
uci add_list network.lan.dns="$dns"
done
fi
uci commit network
uci -q batch <<-EOF >/dev/null
del dhcp.lan.ra
del dhcp.lan.ra_slaac
del dhcp.lan.ra_flags
del dhcp.lan.dhcpv6
EOF
if [ -z "$DHCP_SERVER" ]; then
uci set dhcp.lan.ignore='1'
fi
uci commit dhcp
exit 0
@@ -0,0 +1,8 @@
do_sysinfo_generic() {
mkdir -p /tmp/sysinfo
echo "docker" > /tmp/sysinfo/board_name
echo "Docker" > /tmp/sysinfo/model
}
boot_hook_add preinit_main do_sysinfo_generic
@@ -0,0 +1,4 @@
#!/bin/sh
echo "ignore '$0 $@' in container" >&2
exit 0
@@ -0,0 +1 @@
/usr/libexec/kmodloader
@@ -0,0 +1 @@
/usr/libexec/kmodloader
@@ -0,0 +1,127 @@
# > getbootdisk
# mmcblk0
_getbootdisk()
{
local rootpart="`grep -Fm1 ' - squashfs /dev/root ' /proc/self/mountinfo | cut -d' ' -f3`"
[ -z "$rootpart" ] && return 1
local devpath="`readlink /sys/dev/block/$rootpart`"
[ -z "$devpath" ] && return 1
rootpart="${devpath##*/}"
devpath="${devpath%%/$rootpart}"
local rootdisk="${devpath##*/}"
echo "$rootdisk"
}
# > getbootdisk_lvm
# dm-0
_getbootdisk_lvm()
{
local rootpart
if [ -e /rom/note ]; then
rootpart="`grep -Fm1 ' / / ' /proc/self/mountinfo | grep -F ' - squashfs ' | cut -d' ' -f3`"
else
rootpart="`grep -Fm1 ' / /rom ' /proc/self/mountinfo | grep -F ' - squashfs ' | cut -d' ' -f3`"
fi
[ -z "$rootpart" ] && return 1
local major=${rootpart%%:*}
local minor=${rootpart##*:}
minor="$(( $minor & 0xfffc ))"
local devpath="`readlink /sys/dev/block/$major:$minor`"
[ -z "$devpath" ] && return 1
local rootdisk="${devpath##*/}"
echo "$rootdisk"
}
# > getpartofdisk sda 3
# sda3
# > getpartofdisk mmcblk0 3
# mmcblk0p3
_getpartofdisk()
{
local disk="$1" offset="$2" part
if [[ "$offset" = 0 ]]; then
echo "$disk"
else
part="$disk"
echo "$part" | grep -q '^.*[0-9]$' && part="${part}p"
part="${part}"$(( ${offset} ))
if [ ! -b "/dev/$part" ]; then
# lvm
local line
local MAJOR MINOR DEVNAME DEVTYPE
while read line; do
export -n "$line"
done < "/sys/block/$disk/uevent"
local devpath="`readlink /sys/dev/block/$MAJOR:$(($MINOR + $offset))`"
if [ -n "$devpath" ]; then
part="${devpath##*/}"
fi
fi
echo "$part"
fi
return 0
}
_get_overlay_partition_default()
{
local bootdisk="`_getbootdisk`"
[ -z "$bootdisk" ] && {
log "getbootdisk failed, try lvm"
bootdisk="`_getbootdisk_lvm`"
}
[ -z "$bootdisk" ] && {
log "getbootdisk_lvm failed"
return 1
}
if [ ! -e "/sys/block/$bootdisk/uevent" ]; then
log "/sys/block/$bootdisk/uevent does not exist"
return 1
fi
if [ -e /rom/note ]; then
# we are in mount_root if /rom/note exists
cat "/sys/block/$bootdisk/uevent" > /tmp/.bootdisk
fi
local overlay_dev="`_getpartofdisk $bootdisk 3`"
[ -z "$overlay_dev" ] && {
log "getpartofdisk $bootdisk 3 failed"
return 1
}
OVERLAY_DEV="/dev/$overlay_dev"
return 0
}
_get_overlay_partition_fallback()
{
log "get_overlay_partition_fallback"
rm -f /tmp/.bootdisk >/dev/null 2>&1
local overlay_dev=$(
. /lib/functions.sh
. /lib/upgrade/common.sh
export_bootdevice && export_partdevice overlay_dev 3 && echo $overlay_dev
)
[ -z "$overlay_dev" ] && return 1
OVERLAY_DEV="/dev/$overlay_dev"
return 0
}
get_overlay_partition()
{
if [ -e /.dockerenv ]; then
if mountpoint -q /overlay; then
log "Overlay partition in Docker was mounted /overlay"
OVERLAY_DEV="/overlay"
OVERLAY_FS="DIR"
FSTYPE="DIR"
[ -f "/overlay/.reset" ] && FSTYPE="RESET"
return 0
else
log "No overlay partition in Docker"
return 1
fi
fi
_get_overlay_partition_default || _get_overlay_partition_fallback || {
log "Unable to determine overlay partition"
return 1
}
return 0
}
@@ -0,0 +1,98 @@
#!/bin/sh
# Copyright (C) 2022 jjm2473@gmail.com
DEFAULT_OVERLAY_FS="ext4"
OVERLAY_LABEL="etc"
OVERLAY_FS=${DEFAULT_OVERLAY_FS}
OVERLAY_DEV=
log() {
echo "$1" >&2
}
inlog() {
$@ >&2
}
. /lib/functions/istoreos-boot.sh
CONFIRM=n
REBOOT=n
confirm()
{
[ "$1" = "y" ] && return 0
echo "This will erase all settings and remove any installed packages. Are you sure? [N/y]"
read CONFIRM
[ "$CONFIRM" = "y" -o "$CONFIRM" = "Y" ] && return 0
return 1
}
do_reset_nofs()
{
get_overlay_partition || exit 1
if [ -z "$OVERLAY_DEV" ]; then
echo "Unable to determine overlay partition" >&2
return 1
fi
if [ "RESET" = "$FSTYPE" ]; then
echo "Already resetted" >&2
return 1
fi
if [ "DIR" = "$FSTYPE" ]; then
touch /$OVERLAY_DEV/.reset
sync /$OVERLAY_DEV
return 0
fi
# if ! blkid -o device -t LABEL=$OVERLAY_LABEL "$OVERLAY_DEV" >/dev/null; then
# echo "Overlay partition $OVERLAY_DEV not labeled $OVERLAY_LABEL" >&2
# return 1
# fi
echo "RESET000" | dd of="$OVERLAY_DEV" bs=512 count=1 conv=notrunc,sync,fsync 2>/dev/null
echo 3 > /proc/sys/vm/drop_caches
return 0
}
do_reset_ext_overlay()
{
[ -d /ext_overlay ] && mountpoint -q /ext_overlay || return 0
rm -f /ext_overlay/etc/.extroot-uuid
touch /ext_overlay/.reset
sync /ext_overlay
}
do_reset()
{
if [ -f /.recovery_mode ]; then
mount_root || ( echo "/overlay mount failed!" ; exit 1 )
rm -rf /overlay/upper
mkdir /overlay/upper
sync /overlay
else
do_reset_nofs
do_reset_ext_overlay
fi
if [ -f /lib/upgrade/keep.d/grub ]; then
cat /lib/upgrade/keep.d/grub | while read; do
rm -f "$REPLY"
done
fi
[ "$REBOOT" = "y" ] && reboot
exit 0
}
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-y) CONFIRM=y ;;
-r) REBOOT=y ;;
esac
shift
done
confirm $CONFIRM && do_reset
exit 0
@@ -0,0 +1 @@
/sbin/firstboot
@@ -0,0 +1,549 @@
#!/bin/sh
# Copyright (C) 2010 OpenWrt.org
# Copyright (C) 2020-2023 jjm2473@gmail.com
DEFAULT_OVERLAY_FS="ext4"
OVERLAY_LABEL="etc"
OVERLAY_FS=${DEFAULT_OVERLAY_FS}
OVERLAY_DEV=
log() {
echo "<1>mount_root: $1" >/dev/kmsg
}
inlog() {
$@ >/dev/kmsg 2>&1
}
. /lib/functions/istoreos-boot.sh
validate_overlay_partition()
{
log "- mount overlayfs -"
# Check device existence
[ ! -e "$OVERLAY_DEV" ] && \
log "overlay: $OVERLAY_DEV not found" && return 1
[ -n "$FSTYPE" ] && return 0
# get filesystem
if dd if="$OVERLAY_DEV" bs=5 count=1 2>/dev/null | grep -qF RESET; then
FSTYPE=RESET
log "overlay: $OVERLAY_DEV marked as RESET, format later"
return 0
elif blkid -o device -t LABEL=$OVERLAY_LABEL "$OVERLAY_DEV" >/dev/null; then
FSTYPE=`blkid -o value -s TYPE $OVERLAY_DEV`
else
FSTYPE=RESET
log "overlay: $OVERLAY_DEV not labeled, format later"
return 0
fi
if [ -z "$FSTYPE" ]; then
log "overlay: no valid filesystem"
else
log "overlay: filesystem $FSTYPE found"
fi
}
format_overlay_partition()
{
local RET
[ "$FSTYPE" = "$OVERLAY_FS" ] && return 0
log "overlay: try to format $OVERLAY_DEV as $OVERLAY_FS partition"
case "$OVERLAY_FS" in
ext4)
mkfs.ext4 -q -j -FF -L $OVERLAY_LABEL "$OVERLAY_DEV"
RET=$?
[ $RET -ne 0 ] || tune2fs -o '^user_xattr,^acl' -r 1024 "$OVERLAY_DEV"
;;
DIR)
rm -rf "$OVERLAY_DEV/upper" "$OVERLAY_DEV/work" "$OVERLAY_DEV/.reset"
RET=0
;;
*)
log "overlay: unhandled fs type : $OVERLAY_FS"
RET=1
;;
esac
if [ $RET -ne 0 ]; then
log "overlay: format command failed ret: $RET"
else
echo 3 > /proc/sys/vm/drop_caches
FSTYPE=$OVERLAY_FS
fi
return $RET
}
check_overlay_partition()
{
case "$FSTYPE" in
ext4)
fsck.ext4 -y "$OVERLAY_DEV" >/dev/null
sync
resize2fs "$OVERLAY_DEV"
sync
;;
esac
}
mount_overlay_partition()
{
local RET
[ "DIR" = "$OVERLAY_FS" ] && return 0
mount -t $OVERLAY_FS "$OVERLAY_DEV" /overlay
RET=$?
[ $RET -ne 0 ] && \
log "overlay: failed to mount $OVERLAY_DEV as $OVERLAY_FS partition ret: $RET" && return $RET
log 'overlay: successfully mounted'
}
OVERLAYFS_XATTR=""
is_overlayfs_use_xattr() {
# only linux 4.13 or above
[ -z "$OVERLAYFS_XATTR" ] && OVERLAYFS_XATTR=$([ "0$((`uname -r | cut -d. -f1-2 | sed 's/\./ * 100 + /'`))" -ge 413 ] && echo 1 || echo 0)
[ "$OVERLAYFS_XATTR" = "1" ]
}
ROOTFS_UUID=""
rootfs_uuid() {
if [ -z "$ROOTFS_UUID" ]; then
ROOTFS_UUID=`block info | grep -Fw 'MOUNT="/"' | sed -E 's/.* UUID="([^"]+)" .*/\1/'`
[ -z "$ROOTFS_UUID" ] && [ -e /.dockerenv ] && ROOTFS_UUID=rel-`md5sum /etc/openwrt_release | cut -d' ' -f1`
[ -z "$ROOTFS_UUID" ] && ROOTFS_UUID="UNKNOWN"
fi
echo "$ROOTFS_UUID"
}
update_overlay_lower_uuid() {
local realuuid=`rootfs_uuid`
local lastuuid="NULL"
if [ -f "$1/.rootfs-uuid" ]; then
lastuuid=`cat "$1/.rootfs-uuid"`
fi
if [ "$realuuid" != "$lastuuid" ]; then
echo "$realuuid" >"$1/.rootfs-uuid"
return 0
fi
return 1
}
clean_overlayfs_origin() {
local line
cd "$1"
getfattr -R -P -h --match='trusted.overlay.origin' . 2>/dev/null | grep '^# file: ' | cut -c9- | while read; do
line="$REPLY"
setfattr -x "trusted.overlay.origin" -h "$line" 2>/dev/null
done
cd - >/dev/null 2>&1
}
clean_overlayfs_redundant_opaque() {
local line
cd "$1"
getfattr -R -P -h --match='trusted.overlay.opaque' . 2>/dev/null | grep '^# file: ' | cut -c9- | while read; do
line="$REPLY"
[ -z "$line" -o -d "/$line" ] && continue
setfattr -x "trusted.overlay.opaque" -h "$line" 2>/dev/null
done
cd - >/dev/null 2>&1
}
clean_overlayfs_redundant_whiteout_marker()
{
local line
cd "$1"
find upper -xdev -path 'upper/opt' -prune ! -type d -o -type c -perm 0 -user 0 -group 0 | sed 's#^upper/#/#g' | while read; do
line="$REPLY"
[ ! -e "$line" -a \( -z "$2" -o ! -e "$2/upper$line" \) ] && \
rm -f "upper$line"
done
cd - >/dev/null 2>&1
}
clean_overlayfs_redundant_whiteout_marker_luci()
{
local line
[ -d "$1/upper/usr/lib/lua/luci" ] || return 0
cd "$1/upper/usr/lib/lua"
find luci -xdev -type c -perm 0 -user 0 -group 0 | while read; do
line="$REPLY"
[ ! -e "/usr/lib/lua/$line" -a \( -z "$2" -o ! -e "$2/upper/usr/lib/lua/$line" \) ] && \
rm -f "$line"
done
cd - >/dev/null 2>&1
}
clean_os_version_files()
{
cd "$1"
rm -f upper/etc/openwrt_release \
upper/etc/openwrt_version \
upper/usr/lib/os-release
cd - >/dev/null 2>&1
}
nonconf_files_of_pkg() {
local pkgname="$1"
if [ -s /usr/lib/opkg/info/${pkgname}.conffiles ]; then
grep -hvxFf /usr/lib/opkg/info/${pkgname}.conffiles \
/usr/lib/opkg/info/${pkgname}.list
else
cat /usr/lib/opkg/info/${pkgname}.list
fi
}
clean_pkg_content() {
local overlay="$1"
local pkgname="$2"
local line
if [ -f /usr/lib/opkg/info/${pkgname}.control ]; then
if [ -f /usr/lib/opkg/info/${pkgname}.list ]; then
nonconf_files_of_pkg "${pkgname}" | grep -v '^/etc/uci-defaults/' | while read; do
line="$REPLY"
rm -f "$overlay/upper/${line}"
done
rm -f $overlay/upper/usr/lib/opkg/info/${pkgname}.list
fi
rm -f $overlay/upper/usr/lib/opkg/info/${pkgname}.*
fi
}
pkg_depends_rec() {
local pkgname="$1"
local line
local dep
local deps
echo "${pkgname}" | grep -q '^kmod-' && return
if [ -f /usr/lib/opkg/info/${pkgname}.control -a ! -f /tmp/reset_core_packages/${pkgname} ]; then
deps=`grep '^Depends: ' /usr/lib/opkg/info/${pkgname}.control | sed -e 's/^Depends: //' -e 's/ ([^)]*)//g' -e 's/,/ /g'`
touch /tmp/reset_core_packages/${pkgname}
for dep in $deps ; do
pkg_depends_rec "$dep"
done
fi
}
list_core_packages()
{
local pkg
mkdir -p /tmp/reset_core_packages
for pkg in \
luci-compat luci-lua-runtime luci-lib-base luci-base uhttpd luci-ssl-openssl luci-ssl \
rpcd procd \
netifd dnsmasq-full dnsmasq firewall4 \
block-mount \
curl \
ttyd dropbear kernel
do
pkg_depends_rec $pkg
done
}
reset_core_packages()
{
local overlay="$1"
local running_ver=$(grep 'DISTRIB_RELEASE=' /etc/openwrt_release 2>/dev/null | sed -E "s/.*='(.+)'.*/\1/")
[ "v$(cat "$overlay/.upgrading" 2>/dev/null)" = "v$running_ver" ] && return
local pkg
[ -d /tmp/reset_core_packages ] || list_core_packages
echo "Reset packages in $overlay: `ls /tmp/reset_core_packages/ | xargs echo`" | dd bs=960 2>/dev/null
ls /tmp/reset_core_packages/ | while read; do
pkg="$REPLY"
clean_pkg_content "$overlay" $pkg
done
}
clean_kmods()
{
local DUMMY_VERSION=0.0.0-r1
if [ -d "$1/upper/usr/lib/opkg/info" ]; then
local compat=`grep -m1 '^Version: ' /usr/lib/opkg/info/kernel.control | sed 's/^Version: //'`
if [ -n "$compat" ]; then
cd "$1/upper/usr/lib/opkg/info"
# ignore processed
grep -Lm1 '^Version: '$DUMMY_VERSION'$' kmod-*.control 2>/dev/null | sed 's/.control$//' > /tmp/cand_kmods.txt
if [ -s /tmp/cand_kmods.txt ]; then
grep -LFm1 "kernel (=$compat)" kmod-*.control | sed 's/.control$//' | grep -Fxf /tmp/cand_kmods.txt > /tmp/del_kmods.txt
grep -lFm1 "kernel (=$compat)" kmod-*.control | sed 's/.control$//' | grep -Fxf /tmp/cand_kmods.txt > /tmp/safe_kmods.txt
else
> /tmp/del_kmods.txt
fi
rm -f /tmp/cand_kmods.txt
# remove duplicated
if [ -s /tmp/safe_kmods.txt ]; then
( cd /usr/lib/opkg/info && ls kmod-*.control 2>/dev/null | sed 's/.control$//' ) | grep -Fxf /tmp/safe_kmods.txt >> /tmp/del_kmods.txt
fi
rm -f /tmp/safe_kmods.txt
if [ -s /tmp/del_kmods.txt ]; then
echo "Clean kmods in $1: `cat /tmp/del_kmods.txt | xargs echo`" | dd bs=960 2>/dev/null
cat /tmp/del_kmods.txt | sed 's/$/.list/' | xargs cat | sed "s#^/#$1/upper/#" | xargs -r rm -f
cat /tmp/del_kmods.txt | xargs -rn1 sh -c 'rm -f $0.list $0.prerm $0.postinst'
cat /tmp/del_kmods.txt | sed 's/$/.control/' | xargs -r sed -i 's/^Version: .*$/Version: '$DUMMY_VERSION'/g'
fi
rm -f /tmp/del_kmods.txt
cd - >/dev/null 2>&1
return 0
fi
else
return 0
fi
# rm -rf $1/upper/lib/modules $1/upper/etc/modules-boot.d \
# $1/upper/etc/modules.d $1/upper/usr/lib/opkg/info/kmod-* \
# $1/upper/usr/lib/opkg/info/kernel.* 2>/dev/null
}
prepare_primary_overlay()
{
if [ -f /overlay/.reset ]; then
rm -rf /overlay/upper /overlay/work
rm -f /overlay/.reset
sync /overlay
fi
}
prepare_primary_overlay_after_merge()
{
if [ -d /overlay/upper ]; then
if update_overlay_lower_uuid /overlay || [ -f /overlay/.upgrading ] ; then
clean_os_version_files /overlay
reset_core_packages /overlay
touch /overlay/.upgrading
fi
if [ -f /overlay/.upgrading -o -f /overlay/.merged ] ; then
if is_overlayfs_use_xattr ; then
clean_overlayfs_origin /overlay/upper
clean_overlayfs_redundant_opaque /overlay/upper
fi
clean_overlayfs_redundant_whiteout_marker /overlay
else
clean_overlayfs_redundant_whiteout_marker_luci /overlay
fi
else
update_overlay_lower_uuid /overlay
fi
if [ -f /overlay/upper/usr/lib/opkg/.upgrading ]; then
clean_kmods /overlay
fi
rm -f /overlay/.upgrading /overlay/.merged
mkdir -p /overlay/upper /overlay/work
}
pivot_root_to_overlayfs()
{
# pivot
mount --move /proc /mnt/proc
pivot_root /mnt /mnt/rom || return 1
mount --move /rom/dev /dev
mount --move /rom/tmp /tmp
mount --move /rom/sys /sys
mount --move /rom/overlay /overlay
}
mount_extroot()
{
[ "DIR" = "$OVERLAY_FS" ] && return 1
[ -f /overlay/upper/etc/config/fstab ] || return 1
# let `block extroot` read /overlay/upper/etc/config/fstab
mount --bind /overlay/upper/etc/config /etc/config || return 1
block extroot
local ret=$?
umount /etc/config
return $ret
}
apply_overlayfs_opaque() {
local line
local dest="$2"
cd "$1"
if is_overlayfs_use_xattr; then
getfattr -R -P -h --match='trusted.overlay.opaque' upper 2>/dev/null | grep '^# file: ' | cut -c9- | while read; do
line="$REPLY"
rm -rf "$dest/$line"
done
fi
find upper -xdev -type c | while read; do
line="$REPLY"
rm -rf "$dest/$line"
done
cd - >/dev/null 2>&1
}
prepare_ext_overlay()
{
local newoverlaydir=$1
local overlaydir=$2
if [ -f "$newoverlaydir/.reset" ]; then
rm -rf "$newoverlaydir/upper"
rm -f "$newoverlaydir/.commit" "$newoverlaydir/.reset"
elif [ -f "$newoverlaydir/.commit" ]; then
if [ -d "$newoverlaydir/upper" ]; then
apply_overlayfs_opaque "$newoverlaydir" "$overlaydir"
fi
cp -a "$newoverlaydir/upper/." "$overlaydir/upper" && {
touch "$overlaydir/.merged"
rmdir "$overlaydir/upper/ext_overlay"
rm -rf "$newoverlaydir/upper"
mkdir -m 0755 -p "$newoverlaydir/upper/ext_overlay"
}
rm -f "$newoverlaydir/.commit"
else
prepare_primary_overlay_after_merge
touch "$overlaydir/.clean"
if [ -f "$newoverlaydir/upper/usr/lib/opkg/.upgrading" ]; then
clean_kmods "$newoverlaydir"
fi
if [ -f "$newoverlaydir/.upgrading" ]; then
clean_os_version_files "$newoverlaydir"
reset_core_packages "$newoverlaydir"
is_overlayfs_use_xattr && clean_overlayfs_origin "$newoverlaydir/upper"
fi
if [ -d "$newoverlaydir/upper" -a \( -f "$newoverlaydir/upper/usr/lib/opkg/.upgrading" -o ! -f "$overlaydir/.sandbox" \) ]; then
clean_overlayfs_redundant_whiteout_marker "$newoverlaydir" "$overlaydir"
else
clean_overlayfs_redundant_whiteout_marker_luci "$newoverlaydir" "$overlaydir"
fi
fi
rm -f "$newoverlaydir/.upgrading"
( mkdir -m 0755 -p $newoverlaydir/upper/ext_overlay && \
mkdir -m 0755 -p $newoverlaydir/work ) || return 1
return 0
}
prepare_mnt()
{
rm -rf /tmp/reset_core_packages
prepare_primary_overlay
if mount_extroot && mountpoint -q /tmp/extroot/mnt; then
local dir
for dir in proc dev tmp sys overlay rom root; do
mkdir -m 0755 /tmp/extroot/mnt/$dir
done
if mount --move /tmp/extroot/mnt /mnt; then
# writeable extroot existed, we don't need overlay
umount /tmp/extroot/overlay
return 0
fi
umount /tmp/extroot/mnt
fi
local lowerdir=/
local overlaydir=/overlay
if mountpoint -q /tmp/extroot/overlay; then
# use ext overlay as upper layer
local newoverlaydir=/tmp/extroot/overlay
if [ `mountpoint -x "$OVERLAY_DEV"` != `mountpoint -d $newoverlaydir` ] && \
prepare_ext_overlay $newoverlaydir $overlaydir && \
mount --move /tmp/extroot/overlay /ext_overlay; then
lowerdir=$overlaydir/upper:$lowerdir
overlaydir=/ext_overlay
else
umount /tmp/extroot/overlay
fi
fi
if [ -f /overlay/.clean ]; then
rm -f /overlay/.clean
else
prepare_primary_overlay_after_merge
fi
rm -rf /tmp/reset_core_packages
mount -o noatime,lowerdir=$lowerdir,upperdir=$overlaydir/upper,workdir=$overlaydir/work \
-t overlay "overlayfs:/overlay" /mnt \
|| return 1
if [ "$overlaydir" = "/ext_overlay" ]; then
[ -f "/overlay/.sandbox" ] || touch "/overlay/.sandbox"
else
rm -f "/overlay/.sandbox"
fi
sync
return 0
}
post_extroot()
{
mountpoint -q /rom/ext_overlay && mount --move /rom/ext_overlay /ext_overlay
mountpoint -q /tmp/extroot/overlay && umount /tmp/extroot/overlay
rmdir /tmp/extroot/overlay
mountpoint -q /tmp/extroot/mnt && umount /tmp/extroot/mnt
rmdir /tmp/extroot/mnt
rmdir /tmp/extroot
mountpoint -q /rom/dev && umount /rom/dev
}
do_mount_overlayfs()
{
local SHAREDMNT=false
[ ! -e /rom/note ] && return 0
inlog get_overlay_partition || return 1
inlog validate_overlay_partition || return 1
inlog format_overlay_partition || return 1
inlog check_overlay_partition
inlog mount_overlay_partition || return 1
# if we are in recovery mode, just skip pivot_root
if [ -f /.recovery_mode ]; then
rm -f /rom/note >/dev/null 2>&1
return 0
fi
if [ -e /.dockerenv ] && mountpoint -q /mnt; then
SHAREDMNT=true
mkdir /dev/mnt
mount --move /mnt /dev/mnt
fi
inlog prepare_mnt || return 1
inlog pivot_root_to_overlayfs || return 1
post_extroot >/dev/null 2>&1
if $SHAREDMNT; then
mount --rbind /dev/mnt /mnt
mount --make-rprivate /dev/mnt
umount -R /dev/mnt || /bin/umount -r -l /dev/mnt
rmdir /dev/mnt
else
inlog mount --make-shared -t tmpfs -o size=4M tmpfs /mnt
fi
return 0
}
[ -n "$1" -a "$1" != "start" ] || ( do_mount_overlayfs && exit 0 )
@@ -0,0 +1,31 @@
#!/bin/sh
mkdir -p /var/lock
for service in adb-enablemodem appfilter dockerd gpio_switch hd-idle \
kmods kmods-unload lcdsimple led lm-sensors luci-fan \
mdadm modemmanager odhcpd smartd sysfixtime sysfsutils sysntpd \
tuning_net umount usbmode usbmuxd wan_drop zprintk zram
do
[ -x /etc/init.d/$service ] && /etc/init.d/$service disable
done
for file in \
/etc/board.d \
/etc/config/appfilter \
/etc/config/kmods \
/etc/init.d/appfilter \
/etc/init.d/umount \
/ext_overlay \
/lib/upgrade/ota.sh \
/lib/upgrade/platform.sh \
/lib/board \
/sbin/ujail \
/usr/lib/opkg/info/luci-app-oaf.control \
/usr/libexec/fan-control \
/usr/sbin/sandbox
do
rm -rf "$file"
done
rm -rf /var/lock/*.lock
@@ -0,0 +1,149 @@
#!/bin/sh
NEWROOT=/rom
PATCHROM=/tmp/rom
SHAREDMNT=false
mountpoint -q /mnt && SHAREDMNT=true
mount --make-private /
mount --make-private /sys
mount --make-rprivate /tmp 2>/dev/null
mount --make-rprivate /proc
mount --make-private /rom
mount --make-private /overlay
mount -t tmpfs -o ro,relatime tmpfs /sys/block
mount -t tmpfs -o size=1M tmpfs /tmp
# make all private except /mnt
mkdir -p $PATCHROM
mount --bind / $PATCHROM
mount --move /proc $PATCHROM/proc
# move /mnt to tmproot/mnt
$SHAREDMNT && mount --move /mnt $PATCHROM/mnt
pivot_root $PATCHROM $PATCHROM/tmp || exit 1
# old root now in /tmp
mount --make-rprivate /tmp
$SHAREDMNT && mount --move /mnt /tmp/mnt
mount --move /proc /tmp/proc
pivot_root /tmp /tmp$PATCHROM || exit 1
umount $PATCHROM
# umount annoying docker rootfs
if $SHAREDMNT; then
OLDROOTFS=`grep -m1 "^overlay / overlay" /proc/1/mounts | sed -nE 's#^.*,workdir=(/mnt/.*)/work 0 0$#\1/merged#p'`
if [ -n "$OLDROOTFS" ]; then
echo "umount $OLDROOTFS" >&2
mount --make-rprivate "$OLDROOTFS"
umount -l "$OLDROOTFS"
fi
fi
# patch rom
LOWERDIR=/rom
mkdir -p /tmp/tmp/shadowrt /tmp/patch/upper/usr/libexec /tmp/patch/work
cp -a /rom/sbin/kmodloader /tmp/patch/upper/usr/libexec/
for o in 0 2; do
cp -a /shadowrt/overwrite/$o/. /tmp/patch/upper/
done
OVERLAYFS_UUID=",uuid=null"
if ! mount -o noatime,lowerdir=$LOWERDIR,upperdir=/tmp/patch/upper,workdir=/tmp/patch/work,xino=off$OVERLAYFS_UUID \
-t overlay "/dev/root" $PATCHROM ; then
OVERLAYFS_UUID=""
mount -o noatime,lowerdir=$LOWERDIR,upperdir=/tmp/patch/upper,workdir=/tmp/patch/work,xino=off \
-t overlay "/dev/root" $PATCHROM || exit 1
fi
mount --bind /tmp/tmp $PATCHROM/tmp
mount --bind /shadowrt $PATCHROM/tmp/shadowrt
chroot $PATCHROM /tmp/shadowrt/s1-chroot-patchrom.sh
umount $PATCHROM/tmp/shadowrt
umount $PATCHROM/tmp
rm -rf /tmp/tmp
set_default_ip()
{
local ip=$1
[ -n "$ip" ] || return 1
echo "pass ip address to shadow: $ip" >&2
sed -i -e "s/^IP_PROTO=.*/IP_PROTO=static/g" -e "s#^IP_ADDRESS=.*#IP_ADDRESS=$ip#g" "$PATCHROM/bin/board_detect"
}
set_default_gw_dns()
{
local gw=$1
local dns="$2"
echo "pass gateway/dns to shadow: $gw/$dns" >&2
sed -i -e "s/^GATEWAY=.*/GATEWAY=$gw/g" -e "s/^DNS=.*/DNS='$dns'/g" "$PATCHROM/etc/uci-defaults/zzz-dockerenv"
}
set_default_network()
{
if [ "dhcp" != "$IP_PROTO" ]; then
# ip
local ip=$IP_ADDRESS
[ -z "$ip" ] && ip=`ip addr show dev eth0 | grep -m1 'inet ' | head -1 | sed -nE 's#.*inet ([0-9\.]*)/([0-9]*) .*#\1/\2#p'`
set_default_ip "$ip"
# gateway dns
local gw dns
gw=$IP_GATEWAY
dns="$IP_DNS"
[ -z "$gw" ] && gw="$(route -n | grep -m1 '^0\.0\.0\.0 .* eth0$' | head -1 | xargs -r sh -c 'echo $1')"
[ -z "$dns" ] && dns="$(grep '^nameserver ' /etc/resolv.conf | grep -oE '\d+\.\d+\.\d+\.\d+' | xargs -r sh -c 'echo $@' ignored)"
set_default_gw_dns "$gw" "$dns"
[ "on" = "$DHCP_SERVER" ] && sed -i -e "s/^DHCP_SERVER=.*/DHCP_SERVER=1/g" "$PATCHROM/etc/uci-defaults/zzz-dockerenv"
fi
}
set_default_network
export -n IP_PROTO IP_ADDRESS IP_GATEWAY IP_DNS DHCP_SERVER
unset IP_PROTO IP_ADDRESS IP_GATEWAY IP_DNS DHCP_SERVER
umount $PATCHROM
# make sysctl net.* writable
mount --bind -o rw /proc/sys/net /proc/sys/net
# workaround busybox mount bug (alpine 3.22.2 arm64/v8)
grep '^proc /proc' /proc/mounts > /tmp/proconly.txt
sh -c 'mount --bind /tmp/proconly.txt /proc/$$/mounts && exec mount -o rw,remount /proc/sys/net'
rm -f /tmp/proconly.txt
PROCD_VER=`grep -o '^Version: [0-9]*' /rom/usr/lib/opkg/info/procd.control | cut -d' ' -f2`
if [ -n "$PROCD_VER" -a "$PROCD_VER" -lt 2024 ]; then
# workaround procd < 2024 infite loop on resolving rootfs type, e.g. `ubus call system board`
echo "overwrite /proc/$$/mounts for bug on procd v$PROCD_VER" >&2
echo "/dev/root /rom squashfs ro,relatime 0 0" > /tmp/procd_mounts.txt
cat /proc/mounts | grep -v '^tmpfs /tmp tmpfs' | grep -v ' /shadowrt ' >> /tmp/procd_mounts.txt
mount --bind /tmp/procd_mounts.txt /proc/$$/mounts
rm -f /tmp/procd_mounts.txt
fi
# mount openwrt rootfs
mount -o ro,noatime,lowerdir=/tmp/patch/upper:$LOWERDIR,xino=off$OVERLAYFS_UUID \
-t overlay "/dev/root" /rom || exit 1
umount /tmp
mount --bind -o private /overlay $NEWROOT/overlay
$SHAREDMNT && mount --move /mnt $NEWROOT/mnt
mount --move /dev $NEWROOT/dev
mount --move /sys $NEWROOT/sys
mount --move /shadowrt $NEWROOT/tmp/busy
mount --move /proc $NEWROOT/proc
pivot_root $NEWROOT $NEWROOT/tmp/oldroot || exit 1
cd /
exec /tmp/busy/s2-wrtroot.sh "$1" "/tmp/oldroot"
@@ -0,0 +1,19 @@
#!/bin/sh
cd /
umount -R $2 || umount -l $2
umount -l /tmp/busy
if echo "$1" | grep -q '^/dev/' ; then
/bin/sh -i </dev/console >/dev/console 2>&1 &
fi
echo "exec /sbin/init" >&2
export -n TZ SHLVL HOSTNAME OLDPWD PWD USER LOGNAME
unset TZ SHLVL HOSTNAME OLDPWD PWD USER LOGNAME
export HOME=/ TERM=linux PATH=/sbin:/bin
exec /sbin/init </dev/null