Created
March 9, 2021 14:14
-
-
Save max1220/c8afcee0815832ca82fe364c8e694339 to your computer and use it in GitHub Desktop.
Simple terminal GUI LXC container manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env lua5.1 | |
--[[ | |
This file provides a simple menu for managing LXC containers | |
by calling the associated lxc-* programms. | |
It uses the dialog util to provide interactive menus. | |
Currently, it supports: | |
* create/start/stop/delete/clone/rename containers | |
* list running/stopped containers | |
* show info/statistics about container | |
* create root filesystem tarball backup | |
]] | |
local function quote(str) | |
str = tostring(str) | |
return "\""..str:gsub("%c", function(char) | |
if char == "\n" then | |
return "\\n" | |
end | |
return "\\"..char:byte() | |
end).."\"" | |
end | |
function trim(str) | |
return (str:gsub("^%s*(.-)%s*$", "%1")) | |
end | |
local function exec(cmd) | |
return os.execute(cmd) | |
end | |
local confirm_mode = 0.5 --"confirm"/timeout/nil | |
local function lxc_confirm(cmd) | |
print("LXC command:", cmd) | |
if confirm_mode == "confirm" then | |
print("Press enter to confirm, ^c to abort") | |
io.read("*l") | |
elseif tonumber(confirm_mode) then | |
print("(abort using ^c)") | |
exec("sleep " .. tonumber(confirm_mode)) | |
end | |
print("running...") | |
end | |
local function lxc_exec(cmd) | |
lxc_confirm(cmd) | |
return exec(cmd) | |
end | |
local function lxc_popen(cmd) | |
lxc_confirm(cmd) | |
return io.popen(cmd) | |
end | |
local function dialog_exec(cmd) | |
local ret = exec("dialog --backtitle \"Manage LXC containers\" "..cmd) | |
end | |
local function dialog_popen(cmd) | |
return io.popen("dialog --stdout --backtitle \"Manage LXC containers\" "..cmd) | |
end | |
local function show_menu(title, list) | |
local cmd = ("--menu %s 0 0 0 "):format(quote(title)) | |
for k,v in ipairs(list) do | |
cmd = cmd .. quote(v[1]) .. " " .. quote(v[2]) | |
if k~= #list then | |
cmd = cmd .. " " | |
end | |
end | |
local p = dialog_popen(cmd) | |
local data = p:read("*a") | |
if data and data ~= "" then | |
return data | |
end | |
end | |
local function msgbox(text) | |
local cmd = ("--msgbox %s 0 0"):format(quote(text)) | |
dialog_exec(cmd) | |
end | |
local function text_input(prompt, init) | |
local p = dialog_popen(("--inputbox %s 0 0 %s"):format(quote(prompt), init or "")) | |
return p:read("*a") | |
end | |
local function get_container_info(container) | |
local f = lxc_popen("lxc-info -n " .. quote(container)) | |
local container_info = {} | |
for line in f:lines() do | |
local k,v = line:match("^(.*):(.*)$") | |
assert(k and v, "info match failed: \"" .. line .. "\"") | |
k,v = trim(k), trim(v) | |
container_info[k] = v | |
end | |
return container_info | |
end | |
local function get_container_list() | |
local f = lxc_popen("lxc-ls -1 --active") | |
local active = {} | |
for line in f:lines() do | |
table.insert(active, line) | |
end | |
f = lxc_popen("lxc-ls -1 --stopped") | |
local stopped = {} | |
for line in f:lines() do | |
table.insert(stopped, line) | |
end | |
return active,stopped | |
end | |
local function show_container_info(container) | |
local container_info = get_container_info(container) | |
local info_str = ([[ | |
Name: %s | |
State: %s | |
IP: %s | |
Link: %s | |
PID: %s | |
CPU use: %s | |
Memory use: %s | |
BlkIO use: %s | |
KMem use: %s | |
RX bytes: %s | |
TX bytes: %s | |
]]):format( | |
assert(container_info["Name"]), | |
assert(container_info["State"]), | |
assert(container_info["IP"] or "none"), | |
assert(container_info["Link"] or "none"), | |
assert(container_info["PID"] or "none"), | |
assert(container_info["CPU use"] or "none"), | |
assert(container_info["Memory use"] or "none"), | |
assert(container_info["BlkIO use"] or "none"), | |
assert(container_info["KMem use"] or "none"), | |
assert(container_info["RX bytes"] or "none"), | |
assert(container_info["TX bytes"] or "none") | |
) | |
msgbox(info_str) | |
end | |
local function menu_stopped_container(container) | |
local menu = { | |
{"info", "Show container information"}, | |
{"edit", "Edit container configuration"}, | |
{"start", "Start the container"}, | |
{"create_tar", "Create a tarball backup of the container root filesystem"}, | |
{"destroy", "Destroy the container"}, | |
{"clone", "Clone the container"}, | |
{"rename", "Rename the container"}, | |
} | |
repeat | |
local selected = show_menu("Container " .. container, menu) | |
if selected == "info" then | |
show_container_info(container) | |
elseif selected == "edit" then | |
exec("nano /var/lib/lxc/" .. container .. "/config") | |
elseif selected == "start" then | |
lxc_exec("lxc-start -n " .. quote(container)) | |
break | |
elseif selected == "create_tar" then | |
lxc_exec("lxc-execute -n " .. container .. " -- /bin/sh -c \"tar -vc --xattrs -S -f /tmp/backup.tar --exclude=/dev --exclude=/tmp --exclude=/run --exclude=/proc --exclude=/sys /\"") | |
local backup_name = ("backup_%s_%s.tar"):format(container, os.date("%d_%m_%Y-%H_%M_%S")) | |
lxc_exec("lxc-execute -n " .. container .. " -- /bin/sh -c \"cat /tmp/backup.tar\" > " .. backup_name) | |
elseif selected == "destroy" then | |
local input_name = text_input("This can not be undone.\nPlease type the container name to confirm deleting it") | |
assert(container==input_name) | |
lxc_exec("lxc-destroy -n " .. container) | |
break | |
elseif selected == "clone" then | |
local new_name = text_input("Please enter a name for the new container:") | |
lxc_exec("lxc-copy -n " .. container .. " -N " .. new_name) | |
elseif selected == "rename" then | |
local new_name = text_input("Please enter the new name for the new container:") | |
lxc_exec("lxc-copy -R -n " .. container .. " -N " .. new_name) | |
break | |
else | |
break | |
end | |
until not selected | |
end | |
local function menu_running_container(container) | |
local menu = { | |
{"info", "Show container information"}, | |
{"shell", "Get a shell in this container"}, | |
{"console", "Get a console in this container"}, | |
{"stop", "Stop the container"}, | |
} | |
repeat | |
local selected = show_menu("Container "..container, menu) | |
if selected == "info" then | |
show_container_info(container) | |
elseif selected == "shell" then | |
lxc_exec("lxc-attach -n " .. container .. " /bin/bash") | |
elseif selected == "console" then | |
lxc_exec("lxc-console -t 0 -n " .. container) | |
elseif selected == "stop" then | |
lxc_exec("lxc-stop -n " .. container) | |
break | |
end | |
until not selected | |
--menu_running_container(container) | |
end | |
local function menu_running() | |
local menu = {} | |
local active = get_container_list() | |
if #active == 0 then | |
return msgbox("No running containers!") | |
end | |
for k,v in ipairs(active) do | |
table.insert(menu, {tostring(k),tostring(v)}) | |
end | |
local selected = show_menu("Running containers", menu) | |
if tonumber(selected) then | |
return menu_running_container(active[tonumber(selected)]) | |
end | |
end | |
local function menu_stopped() | |
local menu = {} | |
local _,stopped = get_container_list() | |
if #stopped == 0 then | |
return msgbox("No stopped containers!") | |
end | |
for k,v in ipairs(stopped) do | |
table.insert(menu, {tostring(k),tostring(v)}) | |
end | |
local selected = show_menu("Stopped containers", menu) | |
if tonumber(selected) then | |
return menu_stopped_container(stopped[tonumber(selected)]) | |
end | |
end | |
local function menu_create() | |
local container_name = text_input("Please enter a name for the new container:") | |
assert(container_name and container_name~="") | |
assert(container_name:match("^[a-zA-Z-_0-9]+$")) | |
local default_template = "debian" | |
local template = text_input("Enter the template to use\nDefault: " .. default_template .. "") | |
if not template or template=="" then | |
template = default_template | |
end | |
local default_template_options = "-r stable" | |
local template_options = text_input("Enter template options\nDefault: "..default_template_options) | |
if not template_options or template_options=="" then | |
template_options = default_template_options | |
end | |
lxc_exec("lxc-create -n " .. container_name .. " -t " .. template .. " -- " .. template_options) | |
io.read() | |
end | |
local function menu_main() | |
local menu = { | |
{"running", "Manage running contaners"}, | |
{"stopped", "Manage stopped contaners"}, | |
{"create", "Create a new container"}, | |
{"clone", "Clone a container"}, | |
} | |
repeat | |
local selected = show_menu("Main menu", menu) | |
if selected == "running" then | |
menu_running() | |
elseif selected == "stopped" then | |
menu_stopped() | |
elseif selected == "create" then | |
menu_create() | |
elseif selected == "noconfirm" then | |
msgbox("No further confirm dialogs will be shown!") | |
confirm_mode = nil | |
elseif selected == "timeout" then | |
msgbox("Will show command abort messge for 3 seconds!") | |
confirm_mode = 3 | |
elseif selected == "confirm" then | |
msgbox("Will require command confirm for every LXC command!") | |
confirm_mode = "confirm" | |
end | |
until not selected | |
end | |
menu_main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment