migrate away from the old luci-app lua control system.
See: https://github.com/openwrt/luci/issues/7310
Signed-off-by: Paul Donald <newtwen+github@gmail.com>
#
-# Copyright (C) 2017 Dan Luedtke <mail@danrl.com>
+# Copyright (C) 2026 Paul Donald <newtwen+github@gmail.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LXC management Web UI
-LUCI_DEPENDS:=@!arc +luci-compat +luci-base +lxc +lxc-attach +lxc-console +lxc-create +liblxc +rpcd-mod-lxc +getopt +!LXC_BUSYBOX_OPTIONS:tar
+LUCI_DEPENDS:=@!arc +luci-base +lxc +lxc-attach +lxc-console +lxc-create +liblxc +rpcd-mod-lxc +getopt +!LXC_BUSYBOX_OPTIONS:tar
define Package/luci-app-lxc/conffiles
/etc/config/lxc
endef
PKG_LICENSE:=Apache-2.0
-PKG_MAINTAINER:=Petar Koretic <petar.koretic@sartura.hr>, \
- Dirk Brenken <dev@brenken.org>
+PKG_MAINTAINER:=Paul Donald <newtwen+github@gmail.com>
include ../../luci.mk
--- /dev/null
+/*
+ * Copyright (c) 2026. All Rights Reserved.
+ * Paul Donald <newtwen+github@gmail.com>
+ */
+
+'use strict';
+'require rpc';
+'require form';
+'require network';
+'require uci';
+'require tools.widgets as widgets';
+
+return L.view.extend({
+ load() {
+ return Promise.all([
+
+ ]);
+ },
+
+ imagepanel() {
+
+ window.img = { 'red' : '🟥', 'green' : '🟩', 'blue' : '🟦' };
+ window.states = { 'STOPPED' : 'red', 'RUNNING' : 'green', 'FROZEN' : 'blue' };
+
+ const t_lxc_list = document.getElementById('t_lxc_list');
+ const loader_html = `<img src='${L.resource('icons/loading.svg')}' alt='loading' width='16' height='16' style='vertical-align:middle' />`;
+ const output_list = document.getElementById('lxc-list-output');
+ const output_add = document.getElementById('lxc-add-output');
+ const loader_add = document.getElementById('lxc-add-loader');
+ const div_create = document.getElementById('div_create');
+ const bt_create = div_create.querySelector('#bt_create');
+
+ bt_create.disabled = true;
+ info_message(output_add, _('Template download in progress, please be patient!'));
+ bt_create.addEventListener('click', lxc_create);
+
+ function lxc_create() {
+ const lxc_name = div_create.querySelector('#tx_name').value.replace(/[\s!@#$%^&*()+=[\]{};':'\\|,<>/?]/g,'');
+ const lxc_template = div_create.querySelector('#s_template').value;
+
+ if (t_lxc_list.querySelector(`[data-id="${lxc_name}"]`) != null) {
+ return info_message(output_add, _('Container with that name already exists!'), 2000);
+ }
+
+ bt_create.disabled = true;
+ output_add.innerHTML = '';
+
+ if (!lxc_template) {
+ return set_no_template();
+ }
+
+ if (!lxc_name || !lxc_name.length) {
+ bt_create.disabled = false;
+ return info_message(output_add, _('The Name field must not be empty!'), 2000);
+ }
+
+ loading(loader_add);
+
+ new window.XHR().get(`/cgi-bin/luci/admin/services/lxc/lxc_create/${lxc_name}/${lxc_template}`, null,
+ function(x) {
+ bt_create.disabled = false;
+ loading(loader_add, 0);
+
+ if (!x) {
+ info_message(output_add, _('Container creation failed!'), 2000);
+ }
+ })
+ }
+
+ function lxc_create_template(lxc_name, lxc_state) {
+ if (document.getElementById(lxc_name)) {
+ return;
+ }
+
+ info_message(output_list, '');
+ let actions = '';
+ actions += `<input type='button' onclick='action_handler(this)' data-action='start' value='${_('Start')}' class='cbi-button cbi-button-apply' />`;
+ actions += ` <input type='button' onclick='action_handler(this)' data-action='stop' value='${_('Stop')}' class='cbi-button cbi-button-reset' />`;
+ actions += ` <input type='button' onclick='action_handler(this)' data-action='destroy' value='${_('Delete')}' class='cbi-button cbi-button-remove' />`;
+ actions += ` <select class='cbi-input-select cbi-button' style='width:10em' onchange='action_more_handler(this)'>\
+ <option selected='selected' disabled='disabled'>more</option>\
+ <option>configure</option>\
+ <option>freeze</option>\
+ <option>unfreeze</option>\
+ <option>reboot</option>\
+ </select>`;
+ actions += `<span data-loader='' style='display:inline-block; width:16px; height:16px; margin:0 5px'></span>`;
+
+ const div0 = document.createElement('div');
+ div0.className = 'tr cbi-section-table-row';
+ div0.id = lxc_name;
+ div0.setAttribute('data-id', lxc_name);
+
+ const div1 = document.createElement('div');
+ div1.className = 'td';
+ div1.style.width = "30%";
+ div1.setAttribute('data-id', lxc_name);
+ div1.innerHTML = `<strong>${lxc_name}</strong>`;
+
+ const div2 = document.createElement('div');
+ div2.className = 'td statusimg';
+ div2.style.width = '20%';
+ div2.innerHTML = window.img[lxc_state];
+
+ const div3 = document.createElement('div');
+ div3.className = 'td';
+ div3.style.width = '50%';
+ div3.innerHTML = actions;
+
+ document.getElementById('t_lxc_list').appendChild(div0);
+ div0.appendChild(div1);
+ div0.appendChild(div2);
+ div0.appendChild(div3);
+ }
+
+ function action_handler(self) {
+ const bt_action = self;
+ const action = self.dataset['action'];
+ const lxc_name = self.parentNode.parentNode.dataset['id'];
+ const status_img = self.parentNode.parentNode.querySelector('.statusimg');
+ const loader = self.parentNode.querySelector('[data-loader]');
+
+ bt_action.disabled = true;
+
+ if (action == 'stop') {
+ loading(loader);
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(action, lxc_name)), null,
+ function(x, ec) {
+ loading(loader, 0);
+ bt_action.disabled = false;
+
+ if (!x || ec) {
+ return info_message(output_list, _('Action failed!'), 2000);
+ }
+ set_status(status_img, 'red');
+ });
+ }
+ else if (action == 'start') {
+ loading(loader);
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(action, lxc_name)), null,
+ function(x, data) {
+ loading(loader, 0);
+ bt_action.disabled = false;
+
+ if (!x || data) {
+ return info_message(output_list, _('Action failed!'), 2000);
+ }
+ set_status(status_img, 'green');
+ });
+ }
+ else if (action == 'destroy') {
+ const div = self.parentNode.parentNode;
+ const img = div.querySelector('.statusimg');;
+
+ if (img.innerHTML != window.img['red']) {
+ bt_action.disabled = false;
+ return info_message(output_list, _('Container is still running!'), 2000);
+ }
+
+ if (!confirm(_('This will completely remove a stopped LXC container from disk. Are you sure?'))) {
+ bt_action.disabled = false;
+ return;
+ }
+ loading(loader);
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(action, lxc_name)), null,
+ function(x, ec) {
+ loading(loader, 0);
+ bt_action.disabled = false;
+
+ if (!x || ec) {
+ return info_message(output_list, _('Action failed!'), 2000);
+ }
+ const div = self.parentNode.parentNode;
+ div.parentNode.removeChild(div);
+ });
+ }
+ }
+
+ function lxc_configure_handler(self) {
+ const div = self.parentNode;
+ const textarea = div.querySelector('[data-id]');
+ const lxc_name = textarea.dataset['id'];
+ const lxc_conf = textarea.value;
+
+ new window.XHR().post(L.url('admin/services/lxc/lxc_configuration_set/' + lxc_name), {'lxc_conf': encodeURIComponent(lxc_conf)},
+ function(x) {
+ if (!x || x.responseText != '0') {
+ return info_message(output_list, _('Action failed!'), 2000);
+ }
+ info_message(output_list, _('LXC configuration updated'), 2000);
+ var rmdiv = div.parentNode;
+ rmdiv.parentNode.removeChild(rmdiv);
+ })
+ }
+
+ function lxc_configure_template(lxc_name, lxc_conf) {
+ const h =
+ `<textarea data-id="${lxc_name}" rows="20" style="width:600px;font-family:monospace;white-space:pre;overflow-wrap:normal;overflow-x:scroll;">` +
+ lxc_conf + `</textarea>` +
+ `<input data-id="bt_confirm" onclick="lxc_configure_handler(this)" type="button" class="cbi-button" value="${_('Confirm')}" />`;
+ return h;
+ }
+
+ function action_more_handler(self) {
+ const lxc_name = self.parentNode.parentNode.dataset['id'];
+ const loader = self.parentNode.querySelector('[data-loader]');
+ const option = self.options[self.selectedIndex].text;
+ self.value = 'more';
+
+ let img;
+
+ const div0 = document.createElement('div');
+ const div1 = self.parentNode.parentNode;
+ const next_div = div1.nextSibling;
+
+ switch(option) {
+ case 'configure':
+ if (next_div && next_div.dataset['action'] !== null) {
+ div1.parentNode.removeChild(next_div);
+ }
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_configuration_get/' + lxc_name), null,
+ function(x) {
+ div0.innerHTML=`<div>${lxc_configure_template(lxc_name, x.responseText)}</div>`;
+ div0.setAttribute('data-action','');
+ div1.parentNode.insertBefore(div0, div1.nextSibling);
+ })
+ break;
+
+ case 'freeze':
+ img = self.parentNode.parentNode.querySelector('.statusimg');
+ if(img.innerHTML != window.img['green']) {
+ return info_message(output_list, _('Container is not running!'), 2000);
+ }
+
+ loading(loader);
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(option, lxc_name)), null,
+ function(x, ec) {
+ loading(loader, 0)
+ if (!x || ec) {
+ return info_message(output_list, _('Action failed!'), 2000);
+ }
+ set_status(img, 'blue');
+ })
+ break;
+
+ case 'unfreeze':
+ img = self.parentNode.parentNode.querySelector('.statusimg');
+ if(img.innerHTML != window.img['blue']) {
+ return info_message(output_list, _('Container is not frozen!'), 2000);
+ }
+
+ loading(loader);
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(option, lxc_name)), null,
+ function(x, ec) {
+ loading(loader, 0);
+ if (!x || ec) {
+ return info_message(output_list, _('Action failed!'), 2000);
+ }
+ set_status(img, 'green');
+ })
+ break;
+
+ case 'reboot':
+ img = self.parentNode.parentNode.querySelector('.statusimg');
+ if(img.innerHTML != window.img['green']) {
+ return info_message(output_list, _('Container is not running!'), 2000);
+ }
+
+ if (!confirm('Are you sure?')) {
+ return;
+ }
+
+ loading(loader);
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(option, lxc_name)), null,
+ function(x, ec) {
+ loading(loader, 0)
+ if (!x || ec) {
+ return info_message(output_list, _('Action failed!'), 2000);
+ }
+ info_message(output_list, _('LXC container rebooted'), 2000);
+ })
+ break;
+ }
+ }
+
+ function set_no_container() {
+ info_message(output_list, _('There are no containers available yet.'));
+ }
+
+ function set_no_template() {
+ bt_create.disabled = true;
+ info_message(output_add, _('There are no templates for your architecture available.') + ' ' +
+ _('Please select another containers URL.'));
+ }
+
+ function lxc_list_update() {
+ window.XHR.poll(4, L.url('admin/services/lxc/lxc_action/list'), null,
+ function(x, data) {
+ if (!x || !data)
+ {
+ return;
+ }
+
+ const lxc_count = Object.keys(data).length;
+ if (!lxc_count) {
+ return set_no_container();
+ }
+
+ const lxcs = t_lxc_list.querySelectorAll('.td[data-id]');
+ const lxc_name_div = {};
+ for (let i = 0, len = lxcs.length; i < len; i++) {
+ const lxc_name = lxcs[i].dataset['id'];
+ if (!(lxc_name in data)) {
+ const div = t_lxc_list.querySelector(`[data-id="${lxc_name}"]`).parentNode;
+ div.parentNode.removeChild(div);
+ continue;
+ }
+ lxc_name_div[lxc_name] = lxcs[i].parentNode.querySelector('.statusimg');
+ }
+
+ for(let key in data) {
+ const lxc_name = key;
+ const state = window.states[data[key]];
+
+ if (!(lxc_name in lxc_name_div))
+ {
+ lxc_create_template(lxc_name, state);
+ }
+ else if (state != get_status(lxc_name_div[lxc_name]))
+ {
+ set_status(lxc_name_div[lxc_name], state);
+ }
+ }
+ })
+ }
+
+ function loading(elem, state) {
+ state = (typeof state === 'undefined') ? 1 : state;
+ if (state === 1) {
+ elem.innerHTML = loader_html;
+ }
+ else {
+ setTimeout(function() { elem.innerHTML = ''}, 2000);
+ }
+ }
+
+ function set_status(elem, state) {
+ if (!elem || typeof elem.setAttribute !== 'function') {
+ console.warn('set_status: invalid element for', state, elem);
+ return;
+ }
+
+ state = (typeof state === 'undefined') ? 1 : state;
+ setTimeout(function() { elem.innerHTML = window.img[state] }, 300);
+ }
+
+ function get_status(elem) {
+ if (!elem || typeof elem.getAttribute !== 'function') {
+ console.warn('get_status: invalid element', elem);
+ return undefined;
+ }
+ const src = elem.innerHTML;
+ for (let i in window.img) {
+ if (window.img[i] == src) {
+ return i;
+ }
+ }
+ return undefined;
+ }
+
+ function info_message(output, msg, timeout) {
+ timeout = timeout || 0;
+ output.innerHTML = '<em>' + msg + '</em>';
+ if (timeout > 0) {
+ setTimeout(function(){ output.innerHTML=''}, timeout);
+ }
+ }
+
+ // expose handlers for generated inline attributes (keeps parity with legacy Lua view)
+ window.action_handler = action_handler;
+ window.action_more_handler = action_more_handler;
+ window.lxc_configure_handler = lxc_configure_handler;
+ window.lxc_create = lxc_create;
+
+ new window.XHR().get(L.url('admin/services/lxc/lxc_get_downloadable'), null,
+ function(x, data) {
+ if (!x) return;
+
+ if (!data) return set_no_template();
+
+
+ var lxc_count = Object.keys(data).length;
+ if (!lxc_count) return set_no_template();
+
+ var select = document.getElementById('s_template');
+ for(var key in data) {
+ var option = document.createElement('option');
+ option.value = data[key];
+ option.text = data[key].replace(/[_:]/g, ' ');
+ select.add(option, -1);
+ }
+
+ info_message(output_add, '');
+ bt_create.disabled = false;
+ })
+
+ lxc_list_update();
+
+ },
+
+ /** @private */
+ populateBasicOptions(s, tab) {
+ let o;
+
+ o = s.taboption(tab, form.Value, 'url', _('Containers URL'))
+ o.value('images.linuxcontainers.org')
+ o.value('repo.turris.cz/lxc', 'repo.turris.cz/lxc (SSL req.)')
+ o.default = 'images.linuxcontainers.org'
+ o.rmempty = false
+
+ o = s.taboption(tab, form.Value, 'min_space', _('Free Space Threshold'),
+ _('Minimum required free space for LXC Container creation in KB'))
+ o.default = '100000'
+ o.datatype = 'min(50000)'
+ o.rmempty = false
+
+ o = s.taboption(tab, form.Value, 'min_temp', _('Free Temp Threshold'),
+ _('Minimum required free temp space for LXC Container creation in KB'))
+ o.default = '100000'
+ o.datatype = 'min(50000)'
+ o.rmempty = false
+
+ },
+
+ /** @private */
+ populateOptions(s) {
+
+ s.tab('basic', _('Basic Settings'));
+ this.populateBasicOptions(s, 'basic');
+
+ },
+
+ render() {
+ let m, s;
+
+ m = new form.Map('lxc', _('LXC Containers'),
+ _('<b>Please note:</b> LXC Containers require features not available on OpenWrt images for devices with small flash.') + '<br /> ' +
+ _("Also you may want to install 'kmod-veth' for optional network support."));
+
+ s = m.section(form.TypedSection, 'lxc');
+ s.addremove = false;
+ s.anonymous = true;
+
+ let lxc_list = E('div', {'class': 'cbi-section'}, [
+ E('h3', {}, [_('Available Containers')]),
+ E('div', {'class': 'cbi-section-node'}, [
+ E('div', { 'class': 'table cbi-section-table', id: 't_lxc_list' }, [
+ E('div', { 'class': 'tr cbi-section-table-titles' }, [
+ E('div', { 'class': 'th cbi-section-table-cell' }, [ _('Name') ]),
+ E('div', { 'class': 'th cbi-section-table-cell' }, [ _('Status') ]),
+ E('div', { 'class': 'th cbi-section-table-cell' }, [ _('Actions') ]),
+ ])
+ ])
+ ])
+ ]);
+
+ let lxc_output= E('div', { 'class': 'cbi-section' }, [
+ E('span', { 'id': 'lxc-list-output' }, [
+
+ ])
+ ]);
+
+ let create_new = E('div', {'class': 'cbi-section'}, [
+ E('h3', {}, [_('Create New Container')]),
+ E('div', {'class': 'cbi-section-node'}, [
+ E('table', { 'class': 'table cbi-section-table', id: 't_lxc_create' }, [
+ E('tr', { 'class': 'tr cbi-section-table-titles' }, [
+ E('th', { 'class': 'th cbi-section-table-cell' }, [ _('Name') ]),
+ E('th', { 'class': 'th cbi-section-table-cell' }, [ _('Status') ]),
+ E('th', { 'class': 'th cbi-section-table-cell' }, [ _('Actions') ]),
+ ]),
+ E('tr', { 'class': 'tr cbi-section-table-row', id: 'div_create' }, [
+ E('td', { 'class': 'td cbi-section-table-titles' }, [
+ E('input', { id: 'tx_name', 'class': 'cbi-input-text', type: 'text', placeholder: _('Enter new name') }),
+ ]),
+ E('td', { }, [
+ E('select', { id: 's_template', 'class': 'cbi-input-select cbi-button' }),
+ ]),
+ E('td', { }, [
+ E('button', { id: 'bt_create', 'class': 'cbi-button cbi-button-add', disabled: false }, [ _('Create') ]),
+ E('span', { 'id': 'lxc-add-loader' }, [ ]),
+ ]),
+ ]),
+ ]),
+ ])
+ ]);
+
+ let create_output = E('span', { id: 'lxc-add-output' }, []);
+
+ this.populateOptions(s);
+
+ return m.render().then(node => {
+ document.getElementById('tabmenu').append(node);
+ document.getElementById('tabmenu').append(lxc_list);
+ document.getElementById('tabmenu').append(lxc_output);
+ document.getElementById('tabmenu').append(create_new);
+ document.getElementById('tabmenu').append(create_output);
+ this.imagepanel();
+ });
+ },
+});
+++ /dev/null
---[[
-
-LuCI LXC module
-
-Copyright (C) 2014, Cisco Systems, Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Author: Petar Koretic <petar.koretic@sartura.hr>
-
-]]--
-
-module("luci.controller.lxc", package.seeall)
-
-local uci = require "luci.model.uci".cursor()
-local util = require "luci.util"
-local nx = require "nixio"
-local url = util.shellquote(uci:get("lxc", "lxc", "url"))
-
-function index()
- if not nixio.fs.access("/etc/config/lxc") then
- return
- end
-
- page = node("admin", "services", "lxc")
- page.target = cbi("lxc")
- page.title = _("LXC Containers")
- page.order = 70
- page.acl_depends = { "luci-app-lxc" }
-
- page = entry({"admin", "services", "lxc_create"}, call("lxc_create"), nil)
- page.acl_depends = { "luci-app-lxc" }
- page.leaf = true
-
- page = entry({"admin", "services", "lxc_action"}, call("lxc_action"), nil)
- page.acl_depends = { "luci-app-lxc" }
- page.leaf = true
-
- page = entry({"admin", "services", "lxc_get_downloadable"}, call("lxc_get_downloadable"), nil)
- page.acl_depends = { "luci-app-lxc" }
- page.leaf = true
-
- page = entry({"admin", "services", "lxc_configuration_get"}, call("lxc_configuration_get"), nil)
- page.acl_depends = { "luci-app-lxc" }
- page.leaf = true
-
- page = entry({"admin", "services", "lxc_configuration_set"}, call("lxc_configuration_set"), nil)
- page.acl_depends = { "luci-app-lxc" }
- page.leaf = true
-end
-
-function lxc_get_downloadable()
- local target = lxc_get_arch_target(url)
- local templates = {}
-
- local f = io.popen('sh /usr/share/lxc/templates/lxc-download --list --server %s 2>/dev/null'
- %{ url }, 'r')
- local line
- for line in f:lines() do
- local dist, version, dist_target = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+default%s+(%S+)%s*$")
- if dist and version and dist_target and dist_target == target then
- templates[#templates+1] = "%s:%s" %{ dist, version }
- end
- end
- f:close()
-
- luci.http.prepare_content("application/json")
- luci.http.write_json(templates)
-end
-
-function lxc_create(lxc_name, lxc_template)
- luci.http.prepare_content("text/plain")
- local path = lxc_get_config_path()
- if not path then
- return
- end
-
- local lxc_dist, lxc_release = lxc_template:match("^(.+):(.+)$")
- luci.sys.call('/usr/bin/lxc-create --quiet --name %s --bdev best --template download -- --dist %s --release %s --arch %s --server %s'
- %{ lxc_name, lxc_dist, lxc_release, lxc_get_arch_target(url), url })
-
- while (nx.fs.access(path .. lxc_name .. "/partial")) do
- nx.nanosleep(1)
- end
-
- luci.http.write("0")
-end
-
-function lxc_action(lxc_action, lxc_name)
- local data, ec = util.ubus("lxc", lxc_action, lxc_name and { name = lxc_name } or {})
-
- luci.http.prepare_content("application/json")
- luci.http.write_json(ec and {} or data)
-end
-
-function lxc_get_config_path()
- local f = io.open("/etc/lxc/lxc.conf", "r")
- local content = f:read("*all")
- f:close()
-
- local ret = content:match('^%s*lxc.lxcpath%s*=%s*([^%s]*)')
- if ret then
- if nx.fs.access(ret) then
- local min_space = tonumber(uci:get("lxc", "lxc", "min_space")) or 100000
- local free_space = tonumber(util.exec("df " ..ret.. " | awk '{if(NR==2)print $4}'"))
- if free_space and free_space >= min_space then
- local min_temp = tonumber(uci:get("lxc", "lxc", "min_temp")) or 100000
- local free_temp = tonumber(util.exec("df /tmp | awk '{if(NR==2)print $4}'"))
- if free_temp and free_temp >= min_temp then
- return ret .. "/"
- else
- util.perror("lxc error: not enough temporary space (< " ..min_temp.. " KB)")
- end
- else
- util.perror("lxc error: not enough space (< " ..min_space.. " KB)")
- end
- else
- util.perror("lxc error: directory not found")
- end
- else
- util.perror("lxc error: config path is empty")
- end
-end
-
-function lxc_configuration_get(lxc_name)
- luci.http.prepare_content("text/plain")
-
- local f = io.open(lxc_get_config_path() .. lxc_name .. "/config", "r")
- local content = f:read("*all")
- f:close()
-
- luci.http.write(content)
-end
-
-function lxc_configuration_set(lxc_name)
- luci.http.prepare_content("text/plain")
-
- local lxc_configuration = luci.http.formvalue("lxc_conf")
- lxc_configuration = luci.http.urldecode(lxc_configuration, true)
- if lxc_configuration == nil then
- util.perror("lxc error: config formvalue is empty")
- return
- end
-
- local f, err = io.open(lxc_get_config_path() .. lxc_name .. "/config","w+")
- if not f then
- util.perror("lxc error: config file not found")
- return
- end
-
- f:write(lxc_configuration)
- f:close()
-
- luci.http.write("0")
-end
-
-function lxc_get_arch_target(url)
- local target = nx.uname().machine
- if url and url:match("images.linuxcontainers.org") then
- local target_map = {
- armv5 = "armel",
- armv6 = "armel",
- armv7 = "armhf",
- armv8 = "arm64",
- aarch64 = "arm64",
- i686 = "i386",
- x86_64 = "amd64"
- }
- local k, v
- for k, v in pairs(target_map) do
- if target:find(k) then
- return v
- end
- end
- end
- return target
-end
+++ /dev/null
---[[
-
-LuCI LXC module
-
-Copyright (C) 2014, Cisco Systems, Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Author: Petar Koretic <petar.koretic@sartura.hr>
-
-]]--
-
-m = Map("lxc", translate("LXC Containers"),
- translate("<b>Please note:</b> LXC Containers require features not available on OpenWrt images for devices with small flash.<br />")
- .. translate("Also you may want to install 'kmod-veth' for optional network support."))
-m:section(SimpleSection).template = "lxc"
-
-s = m:section(TypedSection, "lxc", translate("Options"))
-s.anonymous = true
-
-o1 = s:option(Value, "url", translate("Containers URL"))
-o1:value("images.linuxcontainers.org")
-o1:value("repo.turris.cz/lxc", "repo.turris.cz/lxc (SSL req.)")
-o1.default = "images.linuxcontainers.org"
-o1.rmempty = false
-
-o3 = s:option(Value, "min_space", translate("Free Space Threshold"),
- translate("Minimum required free space for LXC Container creation in KB"))
-o3.default = "100000"
-o3.datatype = "min(50000)"
-o3.rmempty = false
-
-o4 = s:option(Value, "min_temp", translate("Free Temp Threshold"),
- translate("Minimum required free temp space for LXC Container creation in KB"))
-o4.default = "100000"
-o4.datatype = "min(50000)"
-o4.rmempty = false
-
-return m
+++ /dev/null
-<%#
-
-LuCI LXC module
-
-Copyright (C) 2014, Cisco Systems, Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Author: Petar Koretic <petar.koretic@sartura.hr>
-
--%>
-
-<%-
-local nx = require "nixio"
-local target = nx.uname().machine
--%>
-
-<div class="cbi-section">
- <h3><%:Available Containers%></h3>
- <div class="cbi-section-node">
- <div class="table cbi-section-table" id="t_lxc_list">
- <div class="tr cbi-section-table-titles">
- <div class="th cbi-section-table-cell"><%:Name%></div>
- <div class="th cbi-section-table-cell"><%:Status%></div>
- <div class="th cbi-section-table-cell"><%:Actions%></div>
- </div>
- </div>
- </div>
-</div>
-
-<div class="cbi-section">
- <span id="lxc-list-output"></span>
-</div>
-
-<hr />
-<div class="cbi-section">
- <h3><%:Create New Container%></h3>
- <div class="cbi-section-node">
- <div class="table cbi-section-table" id="t_lxc_create">
- <div class="tr cbi-section-table-titles">
- <div class="th cbi-section-table-cell"><%:Name%></div>
- <div class="th cbi-section-table-cell"><%:Template%></div>
- <div class="th cbi-section-table-cell"><%:Actions%></div>
- </div>
- <div class="tr cbi-section-table-row" id="div_create">
- <div class="td"><input class="cbi-input-text" type="text" id="tx_name" placeholder="<%:Enter new name%>" value='' /></div>
- <div class="td"><select id="s_template" class="cbi-input-select cbi-button"></select></div>
- <div class="td">
- <input type="button" id="bt_create" value="<%:Create%>" onclick="lxc_create()" class="cbi-button cbi-button-add" />
- <span id="lxc-add-loader" style="display:inline-block; width:16px; height:16px; margin:0 5px"></span>
- </div>
- </div>
- </div>
- </div>
-</div>
-
-<div class="cbi-section">
- <span id="lxc-add-output"></span>
-</div>
-
-<hr />
-
-<script>
- window.img = { "red" : "<%=resource%>/cbi/red.gif", "green" : "<%=resource%>/cbi/green.gif", "purple" : "<%=resource%>/cbi/purple.gif" };
- window.states = { "STOPPED" : "red", "RUNNING" : "green", "FROZEN" : "purple" };
-
- var t_lxc_list = document.getElementById('t_lxc_list');
- var loader_html = '<img src="<%=resource%>/icons/loading.svg" alt="<%:Loading%>" width="16" height="16" style="vertical-align:middle" />';
- var output_list = document.getElementById("lxc-list-output");
- var output_add = document.getElementById("lxc-add-output");
- var loader_add = document.getElementById("lxc-add-loader");
- var div_create = document.getElementById("div_create");
- var bt_create = div_create.querySelector("#bt_create");
-
- bt_create.disabled = true;
- info_message(output_add, "Template download in progress, please be patient!");
-
- function lxc_create()
- {
- var lxc_name = div_create.querySelector("#tx_name").value.replace(/[\s!@#$%^&*()+=\[\]{};':"\\|,<>\/?]/g,'');
- var lxc_template = div_create.querySelector("#s_template").value;
-
- if (t_lxc_list.querySelector("[data-id='" + lxc_name + "']") != null)
- {
- return info_message(output_add, "Container with that name already exists!", 2000);
- }
-
- bt_create.disabled = true;
- output_add.innerHTML = '';
-
- if (!lxc_template)
- {
- return set_no_template();
- }
-
- if (!lxc_name || !lxc_name.length)
- {
- bt_create.disabled = false;
- return info_message(output_add, "The 'Name' field must not be empty!", 2000);
- }
-
- loading(loader_add);
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_create/' + '%h/%h'.format(lxc_name, lxc_template) , null,
- function(x)
- {
- bt_create.disabled = false;
- loading(loader_add, 0);
-
- if (!x)
- {
- info_message(output_add, "Container creation failed!", 2000);
- }
- })
- }
-
- function lxc_create_template(lxc_name, lxc_state)
- {
- if (document.getElementById(lxc_name))
- {
- return;
- }
-
- info_message(output_list, "");
- var actions = '';
- actions += '<input type="button" onclick="action_handler(this)" data-action="start" value="<%:Start%>" class="cbi-button cbi-button-apply" />';
- actions += ' <input type="button" onclick="action_handler(this)" data-action="stop" value="<%:Stop%>" class="cbi-button cbi-button-reset" />';
- actions += ' <input type="button" onclick="action_handler(this)" data-action="destroy" value="<%:Delete%>" class="cbi-button cbi-button-remove" />';
- actions += ' <select class="cbi-input-select cbi-button" style="width:10em" onchange="action_more_handler(this)">\
- <option selected="selected" disabled="disabled">more</option>\
- <option>configure</option>\
- <option>freeze</option>\
- <option>unfreeze</option>\
- <option>reboot</option>\
- </select>';
- actions += '<span data-loader="" style="display:inline-block; width:16px; height:16px; margin:0 5px"></span>';
-
- var div0 = document.createElement("div");
- div0.className = "tr cbi-section-table-row";
- div0.id = lxc_name;
- div0.setAttribute("data-id", lxc_name);
-
- var div1 = document.createElement("div");
- div1.className = "td";
- div1.style.width = "30%";
- div1.innerHTML = '%q%h%q'.format("<strong>", lxc_name, "</strong>");
-
- var div2 = document.createElement("div");
- div2.className = "td";
- div2.style.width = "20%";
- div2.innerHTML = "<img src='"+window.img[lxc_state]+"'/>";
-
- var div3 = document.createElement("div");
- div3.className = "td";
- div3.style.width = "50%";
- div3.innerHTML = actions;
-
- document.getElementById("t_lxc_list").appendChild(div0);
- div0.appendChild(div1);
- div0.appendChild(div2);
- div0.appendChild(div3);
- }
-
- function action_handler(self)
- {
- var bt_action = self;
- var action = self.dataset['action'];
- var lxc_name = self.parentNode.parentNode.dataset['id'];
- var status_img = self.parentNode.parentNode.querySelector('img');
- var loader = self.parentNode.querySelector('[data-loader]');
-
- bt_action.disabled = true;
-
- if (action == "stop")
- {
- loading(loader);
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
- function(x, ec)
- {
- loading(loader, 0);
- bt_action.disabled = false;
-
- if (!x || ec)
- {
- return info_message(output_list,"Action failed!", 2000);
- }
- set_status(status_img, "red");
- });
- }
- else if (action == "start")
- {
- loading(loader);
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
- function(x, data)
- {
- loading(loader, 0);
- bt_action.disabled = false;
-
- if (!x || data)
- {
- return info_message(output_list,"Action failed!", 2000);
- }
- set_status(status_img, "green");
- });
- }
- else if (action == "destroy")
- {
- var div = self.parentNode.parentNode;
- var img = div.querySelector('img');
-
- if (img.getAttribute('src') != window.img["red"])
- {
- bt_action.disabled = false;
- return info_message(output_list,"Container is still running!", 2000);
- }
-
- if (!confirm("This will completely remove a stopped LXC container from disk. Are you sure?"))
- {
- bt_action.disabled = false;
- return;
- }
- loading(loader);
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
- function(x, ec)
- {
- loading(loader, 0);
- bt_action.disabled = false;
-
- if (!x || ec)
- {
- return info_message(output_list,"Action failed!", 2000);
- }
- var div = self.parentNode.parentNode;
- div.parentNode.removeChild(div);
- });
- }
- }
-
- function lxc_configure_handler(self)
- {
- var div = self.parentNode;
- var textarea = div.querySelector('[data-id]');
- var lxc_name = textarea.dataset['id'];
- var lxc_conf = textarea.value;
-
- new XHR().post('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_configuration_set/' + lxc_name, {"lxc_conf": encodeURIComponent(lxc_conf)},
- function(x)
- {
- if (!x || x.responseText != "0")
- {
- return info_message(output_list,"Action failed!", 2000);
- }
- info_message(output_list,"LXC configuration updated", 2000);
- var rmdiv = div.parentNode;
- rmdiv.parentNode.removeChild(rmdiv);
- })
- }
-
- function lxc_configure_template(lxc_name, lxc_conf)
- {
- var h = '\
- <textarea data-id="' + lxc_name + '" rows="20" style="width:600px;font-family:monospace;white-space:pre;overflow-wrap:normal;overflow-x:scroll;">'+ lxc_conf +'</textarea> \
- <input data-id="bt_confirm" onclick="lxc_configure_handler(this)" type="button" class="cbi-button" value="Confirm" />';
- return h;
- }
-
- function action_more_handler(self)
- {
- var lxc_name = self.parentNode.parentNode.dataset['id'];
- var loader = self.parentNode.querySelector('[data-loader]');
- var option = self.options[self.selectedIndex].text;
- self.value = "more";
-
- switch(option)
- {
- case "configure":
- var div0 = document.createElement('div');
- var div1 = self.parentNode.parentNode;
- var next_div = div1.nextSibling;
-
- if (next_div && next_div.dataset['action'] !== null)
- {
- div1.parentNode.removeChild(next_div);
- }
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_configuration_get/' + lxc_name, null,
- function(x)
- {
- div0.innerHTML="<div>" + lxc_configure_template(lxc_name, x.responseText) + "</div>";
- div0.setAttribute('data-action','');
- div1.parentNode.insertBefore(div0, div1.nextSibling);
- })
- break;
-
- case "freeze":
- var img = self.parentNode.parentNode.querySelector('img');
- if(img.getAttribute('src') != window.img["green"])
- {
- return info_message(output_list,"Container is not running!", 2000);
- }
-
- loading(loader);
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
- function(x, ec)
- {
- loading(loader, 0)
- if (!x || ec)
- {
- return info_message(output_list,"Action failed!", 2000);
- }
- set_status(img, "purple");
- })
- break;
-
- case "unfreeze":
- var img = self.parentNode.parentNode.querySelector('img');
- if(img.getAttribute('src') != window.img["purple"])
- {
- return info_message(output_list,"Container is not frozen!", 2000);
- }
-
- loading(loader);
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
- function(x, ec)
- {
- loading(loader, 0);
- if (!x || ec)
- {
- return info_message(output_list,"Action failed!", 2000);
- }
- set_status(img, "green");
- })
- break;
-
- case "reboot":
- var img = self.parentNode.parentNode.querySelector('img');
- if(img.getAttribute('src') != window.img["green"])
- {
- return info_message(output_list,"Container is not running!", 2000);
- }
-
- if (!confirm("Are you sure?"))
- {
- return;
- }
-
- loading(loader);
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
- function(x, ec)
- {
- loading(loader, 0)
- if (!x || ec)
- {
- return info_message(output_list,"Action failed!", 2000);
- }
- info_message(output_list,"LXC container rebooted", 2000);
- })
- break;
- }
- }
-
- function set_no_container()
- {
- info_message(output_list, "There are no containers available yet.");
- }
-
- function set_no_template()
- {
- bt_create.disabled = true;
- info_message(output_add, "There are no templates for your architecture (<%=target%>) available, please select another containers URL.");
- }
-
- function lxc_list_update()
- {
- XHR.poll(4, '<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/list', null,
- function(x, data)
- {
- if (!x || !data)
- {
- return;
- }
-
- var lxc_count = Object.keys(data).length;
- if (!lxc_count)
- {
- return set_no_container();
- }
-
- var lxcs = t_lxc_list.querySelectorAll('td[data-id]');
- var lxc_name_div = {};
- for (var i = 0, len = lxcs.length; i < len; i++)
- {
- var lxc_name = lxcs[i].dataset['id'];
- if (!(lxc_name in data))
- {
- var div = t_lxc_list.querySelector("[data-id='" + lxc_name + "']").parentNode;
- div.parentNode.removeChild(div);
- continue;
- }
- lxc_name_div[lxc_name] = lxcs[i].parentNode.querySelector('img');
- }
-
- for(var key in data)
- {
- var lxc_name = key;
- var state = window.states[data[key]];
-
- if (!(lxc_name in lxc_name_div))
- {
- lxc_create_template(lxc_name, state);
- }
- else if (state != get_status(lxc_name_div[lxc_name]))
- {
- set_status(lxc_name_div[lxc_name], state);
- }
- }
- })
- }
-
- function loading(elem, state)
- {
- state = (typeof state === 'undefined') ? 1 : state;
- if (state === 1)
- {
- elem.innerHTML = loader_html;
- }
- else
- {
- setTimeout(function() { elem.innerHTML = ''}, 2000);
- }
- }
-
- function set_status(elem, state)
- {
- state = (typeof state === 'undefined') ? 1 : state;
- setTimeout(function() { elem.setAttribute('src', window.img[state])}, 300);
- }
-
- function get_status(elem)
- {
- var src = elem.getAttribute('src');
- for (var i in img)
- {
- if (img[i] == src)
- {
- return i;
- }
- }
- }
-
- function info_message(output, msg, timeout)
- {
- timeout = timeout || 0;
- output.innerHTML = '<em>' + msg + '</em>';
- if (timeout > 0)
- {
- setTimeout(function(){ output.innerHTML=""}, timeout);
- }
- }
-
- new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_get_downloadable', null,
- function(x, data)
- {
- if (!x)
- {
- return;
- }
-
- if (!data)
- {
- return set_no_template();
- }
-
- var lxc_count = Object.keys(data).length;
- if (!lxc_count)
- {
- return set_no_template();
- }
-
- var select = document.getElementById("s_template");
- for(var key in data)
- {
- var option = document.createElement('option');
- option.value = data[key];
- option.text = data[key].replace(/[_:]/g, ' ');
- select.add(option, -1);
- }
-
- info_message(output_add, "");
- bt_create.disabled = false;
- })
-
- lxc_list_update();
-</script>
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Обърнете внимание:</b> LXC контейнерите изискват функции, които не са "
-"налични в изображенията на OpenWrt за устройства с малка флаш памет.<br />"
+"налични в изображенията на OpenWrt за устройства с малка флаш памет."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Uvědomte si prosím:</b> LXC kontejnery vyžadují funkce, které nejsou k "
"dispozici v obrazech OpenWrt, určených pro zařízení s malým úložištěm (flash "
-"paměť).<br />"
+"paměť)."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Bemærk venligst:</b> LXC Containers kræver funktioner, der ikke er "
-"tilgængelige på OpenWrt-aftryk for enheder med lille flash.<br />"
+"tilgængelige på OpenWrt-aftryk for enheder med lille flash."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Bitte beachten:</b> LXC-Container benötigen Funktionen, die bei OpenWrt-"
-"Images für Geräte mit kleinem Flashspeicher nicht verfügbar sind.<br />"
+"Images für Geräte mit kleinem Flashspeicher nicht verfügbar sind."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Tenga en cuenta:</b> los contenedores LXC requieren funciones que no "
"están disponibles en las imágenes OpenWrt para dispositivos con poco "
-"almacenamiento.<br />"
+"almacenamiento."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>لطفاً توجه کنید:</b> کانتینرهای LXC به ویژگیهایی نیاز دارند که در ایمیج "
-"OpenWrt برای دستگاههای با حافظه فلش کوچک در دسترس نیستند.<br />"
+"OpenWrt برای دستگاههای با حافظه فلش کوچک در دسترس نیستند."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Huomaa:</b> LXC-kontit vaativat ominaisuuksia, jotka eivät ole "
-"käytettävissä OpenWrt-kuvissa laitteissa, joissa on pieni flash-muisti.<br />"
+"käytettävissä OpenWrt-kuvissa laitteissa, joissa on pieni flash-muisti."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Note:</b> Les conteneurs LXC nécessitent des fonctionnalités non "
"disponibles dans les images OpenWrt pour les matériels avec une mémoire "
-"flash limitée.<br />"
+"flash limitée."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Tabhair faoi deara le do thoil:</b> Teastaíonn gnéithe nach bhfuil ar "
"fáil ar íomhánna OpenWrt le haghaidh gléasanna le splanc bheag ó choimeádáin "
-"LXC.<br />"
+"LXC."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Ne feledje:</b> az LXC-konténerek által igényelt funkciók nem érhetők el "
-"az OpenWrt lemezképeken a kis tárhellyel rendelkező eszközöknél.<br />"
+"az OpenWrt lemezképeken a kis tárhellyel rendelkező eszközöknél."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Nota:</b> I contenitori LXC richiedono funzionalità non disponibili nelle "
-"immagini OpenWrt per apparati con memoria limitata.<br />"
+"immagini OpenWrt per apparati con memoria limitata."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>注 :</b> LXC コンテナには、小さなフラッシュを搭載したデバイスの OpenWrt イ"
-"メージでは利用できない機能が必要です。<br />"
+"メージでは利用できない機能が必要です。"
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>참고:</b> 플래시 메모리 용량이 부족한 장치용 OpenWrt 이미지에서는 LXC "
-"컨테이너 기능을 사용할 수 없습니다.<br />"
+"컨테이너 기능을 사용할 수 없습니다."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Pastaba:</b> „LXC“ konteineriai reikalauja funkcijų, kurie nėra "
"pasiekiami ant „OpenWrt“ laikmenų, kurie skirti įrenginiams su mažos talpos "
-"apribojimu.<br />"
+"apribojimu."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Let op:</b> LXC-containers vereisen functies die niet beschikbaar zijn op "
-"OpenWrt-afbeeldingen voor apparaten met een kleine flitser.<br />"
+"OpenWrt-afbeeldingen voor apparaten met een kleine flitser."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Zwróć uwagę:</b>Kontenery LXC wymagają funkcji niedostępnych w OpenWrt "
-"dla urządzeń z małą pamięcią flash.<br />"
+"dla urządzeń z małą pamięcią flash."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Por favor note:</b> LXC Containers requerem recursos não disponíveis nas "
-"imagens do OpenWrt para aparelhos com flash pequeno.<br />"
+"imagens do OpenWrt para aparelhos com flash pequeno."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Atenção:</b> Os contêineres LXC requerem recursos não estão disponíveis "
"nas imagens do OpenWrt para os dispositivos que tenham uma memória flash "
-"pequena.<br />"
+"pequena."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Te rugăm să reții:</b> Containerele LXC necesită caracteristici care nu "
-"sunt disponibile pe imaginile OpenWrt pentru dispozitive cu flash mic.<br />"
+"sunt disponibile pe imaginile OpenWrt pentru dispozitive cu flash mic."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Пожалуйста, обратите внимание:</b> LXC-контейнеры требуют функций, "
-"недоступных в образах OpenWrt для устройств с небольшой флэш-памятью.<br />"
+"недоступных в образах OpenWrt для устройств с небольшой флэш-памятью."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Uvedomte si prosím:</b> Kontajnery LXC vyžadujú funkcie, ktoré nie sú k "
-"dispozícii na obrazoch OpenWrt pre zariadenia s malým flash priestorom.<br />"
+"dispozícii na obrazoch OpenWrt pre zariadenia s malým flash priestorom."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b> Obs! : </b> LXC-behållare kräver funktioner som inte är tillgängliga på "
-"OpenWrt-bilder för enheter med liten flash-minne. <br />"
+"OpenWrt-bilder för enheter med liten flash-minne. "
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b> தயவுசெய்து கவனிக்கவும்: </b> எல்எக்ச்சி கொள்கலன்களுக்கு சிறிய ஃபிளாச் கொண்ட "
-"சாதனங்களுக்கான ஓபன்ஆர்டி படங்களில் நற்பொருத்தங்கள் கிடைக்கவில்லை. <br />"
+"சாதனங்களுக்கான ஓபன்ஆர்டி படங்களில் நற்பொருத்தங்கள் கிடைக்கவில்லை. "
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b> Lütfen unutmayın: </b> LXC Kapsayıcıları, küçük flaşlı aygıtlar için "
-"OpenWrt görüntülerinde bulunmayan özellikler gerektirir. <br />"
+"OpenWrt görüntülerinde bulunmayan özellikler gerektirir. "
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b> Завбачте: </b> Контейнери LXC вимагають функцій, недоступних в образах "
-"OpenWrt для пристроїв з малою флеш-пам'ятю. <br />"
+"OpenWrt для пристроїв з малою флеш-пам'ятю. "
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Xin lưu ý:</b> Bộ chứa LXC yêu cầu các tính năng không khả dụng trên ảnh "
-"OpenWrt dành cho thiết bị có đèn flash nhỏ.<br />"
+"OpenWrt dành cho thiết bị có đèn flash nhỏ."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>Tenga en cuenta:</b> los contenedores LXC requieren funciones que no "
"están disponibles en las imágenes OpenWrt para dispositivos con poco "
-"almacenamiento.<br />"
+"almacenamiento."
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
-msgstr "<b>请注意: </b>LXC容器需要针对小闪存设备的OpenWrt镜像所不具备的功能。<br />"
+"images for devices with small flash."
+msgstr "<b>请注意: </b>LXC容器需要针对小闪存设备的OpenWrt镜像所不具备的功能。"
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
msgid ""
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
-"images for devices with small flash.<br />"
+"images for devices with small flash."
msgstr ""
"<b>請注意:</b> 對於具有較小快閃記憶體的裝置,LXC 容器需要 OpenWrt 映像檔上不"
-"提供的功能。<br />"
+"提供的功能。"
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
--- /dev/null
+{
+ "admin/services/lxccm": {
+ "title": "LXC Container mgr",
+ "order": 80,
+ "action": {
+ "type": "firstchild"
+ },
+ "depends": {
+ "uci": {
+ "lxc": true
+ }
+ }
+ },
+
+ "admin/services/lxccm/overview": {
+ "title": "Overview",
+ "order": 10,
+ "action": {
+ "type": "view",
+ "path": "lxc/overview"
+ },
+ "depends": {
+ "acl": [ "luci-app-lxc" ]
+ }
+ },
+
+ "admin/services/lxc/lxc_create/*": {
+ "action": {
+ "type": "function",
+ "module": "luci.controller.lxc",
+ "function": "lxc_create"
+ },
+ "auth": {
+ "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
+ "login": true
+ }
+ },
+
+ "admin/services/lxc/lxc_action/*": {
+ "action": {
+ "type": "function",
+ "module": "luci.controller.lxc",
+ "function": "lxc_action"
+ },
+ "auth": {
+ "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
+ "login": true
+ }
+ },
+
+ "admin/services/lxc/lxc_get_downloadable/*": {
+ "action": {
+ "type": "function",
+ "module": "luci.controller.lxc",
+ "function": "lxc_get_downloadable"
+ },
+ "auth": {
+ "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
+ "login": true
+ }
+ },
+
+ "admin/services/lxc/lxc_configuration_get/*": {
+ "action": {
+ "type": "function",
+ "module": "luci.controller.lxc",
+ "function": "lxc_configuration_get"
+ },
+ "auth": {
+ "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
+ "login": true
+ }
+ },
+
+ "admin/services/lxc/lxc_configuration_set/*": {
+ "action": {
+ "type": "function",
+ "module": "luci.controller.lxc",
+ "function": "lxc_configuration_set"
+ },
+ "auth": {
+ "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
+ "login": true
+ }
+ }
+}
--- /dev/null
+// LXC management endpoint
+// Copyright 2026 Paul Donald <newtwen+github@gmail.com>
+// Licensed to the public under the Apache License 2.0.
+// Built against the lxc API v6
+'use strict';
+
+import * as fs from 'fs';
+import { cursor } from 'uci';
+import { connect } from 'ubus';
+const ctx = cursor();
+const LXC_URL = ctx.get('lxc', 'lxc', 'url');
+
+function statfs(path) {
+ let p = fs.popen('df -kP ' + path);
+ p.read('line'); // header
+ let line = p.read('line'); // data line
+ p.close();
+
+ if (!line) return null;
+ let cols = split(trim(line), /\s+/);
+ // return {
+ // filesystem: cols[0],
+ // blocks_kb: int(cols[1],10),
+ // used_kb: int(cols[2],10),
+ // avail_kb: int(cols[3],10),
+ // used_pct: cols[4],
+ // mount: cols.slice(5).join(' ')
+ // };
+ return int(cols[3],10);
+}
+
+const LXCController = {
+
+ lxc_get_downloadable: function() {
+ let target = this.lxc_get_arch_target(LXC_URL);
+ let templates = [];
+ let content = fs.popen(`sh /usr/share/lxc/templates/lxc-download --list --server ${LXC_URL} 2>/dev/null`, 'r').read('all');
+ content = split(content, '\n');
+ for (let line in content) {
+ let arr = match(line, /^(\S+)\s+(\S+)\s+(\S+)\s+default\s+(\S+)\s*$/);
+ if(length(arr) < 3) continue;
+ let dist = trim(arr[1]);
+ let version = trim(arr[2]);
+ let dist_target = trim(arr[3]);
+ if (dist && version && dist_target && dist_target == target)
+ push(templates, `${ dist }:${ version }`);
+ }
+ // content.close();
+
+ http.prepare_content('application/json');
+ http.write_json(templates);
+ },
+
+ lxc_create: function(lxc_name, lxc_template) {
+ http.prepare_content('text/plain');
+ let path = this.lxc_get_config_path();
+ if (!path) return;
+ let arr = match(lxc_template, /^(.+):(.+)$/);
+ let lxc_dist = arr[1], lxc_release = arr[2];
+
+ system(`/usr/bin/lxc-create --quiet --name ${lxc_name} --bdev best --template download -- --dist ${lxc_dist} --release ${lxc_release} --arch ${this.lxc_get_arch_target(LXC_URL)} --server ${LXC_URL}`);
+
+ while (fs.access(path + lxc_name + '/partial')) {
+ sleep(1000);
+ }
+
+ http.write('0');
+ },
+
+ lxc_action: function(lxc_action, lxc_name) {
+ let ubus = connect();
+ let data = ubus.call('lxc', lxc_action, { name: lxc_name });
+
+ http.prepare_content('application/json');
+ http.write_json(data ? data : '');
+ },
+
+ lxc_get_config_path: function() {
+ let content = fs.readfile('/etc/lxc/lxc.conf');
+ let ret = match(content, /^\s*lxc.lxcpath\s*=\s*(\S*)/);
+ if (ret && length(ret) == 2) {
+ if (fs.access(ret[1])) {
+ let min_space = int(ctx.get('lxc', 'lxc', 'min_space')) || 100000;
+ let free_space = statfs(ret[1]);
+ if (free_space && free_space >= min_space) {
+ let min_temp = int(ctx.get('lxc', 'lxc', 'min_temp')) || 100000;
+ let free_temp = statfs('/tmp');
+ if (free_temp && free_temp >= min_temp)
+ return ret[1] + '/';
+ else
+ return 'lxc error: not enough temporary space (< ' + min_temp + ' KB)';
+ }
+ else
+ return 'lxc error: not enough space (< ' + min_space + ' KB)';
+ }
+ else
+ return 'lxc error: directory not found';
+ }
+ else
+ return 'lxc error: config path is empty';
+ },
+
+ lxc_configuration_get: function(lxc_name) {
+ let content = fs.readfile(this.lxc_get_config_path() + lxc_name + '/config');
+
+ http.prepare_content('text/plain');
+ http.write(content);
+ },
+
+ lxc_configuration_set: function(lxc_name) {
+ http.prepare_content('text/plain');
+
+ let lxc_configuration = http.formvalue('lxc_conf');
+ lxc_configuration = http.urldecode(lxc_configuration, true);
+ if (!lxc_configuration) {
+ return 'lxc error: config formvalue is empty';
+ }
+
+ fs.writefile(this.lxc_get_config_path() + lxc_name + '/config', lxc_configuration);
+
+ http.write('0');
+ },
+
+ lxc_get_arch_target: function(url) {
+ let target = split(fs.popen('uname -m', 'r').read('line'), '\n');
+ if (url && match(url, /images.linuxcontainers.org/)) {
+ let target_map = {
+ armv5: 'armel',
+ armv6: 'armel',
+ armv7: 'armhf',
+ armv8: 'arm64',
+ aarch64:'arm64',
+ i686 : 'i386',
+ x86_64: 'amd64',
+ };
+ for (let k, v in target_map) {
+ if (target[0] == k) {
+ return v;
+ }
+ }
+ }
+ return target[0];
+ },
+};
+
+// Export all handlers with automatic error wrapping
+let controller = LXCController;
+let exports = {};
+for (let k, v in controller) {
+ if (type(v) == 'function')
+ exports[k] = v;
+}
+
+return exports;