Skip to content

Instantly share code, notes, and snippets.

@max1220
Created March 9, 2021 14:14
Show Gist options
  • Save max1220/c8afcee0815832ca82fe364c8e694339 to your computer and use it in GitHub Desktop.
Save max1220/c8afcee0815832ca82fe364c8e694339 to your computer and use it in GitHub Desktop.
Simple terminal GUI LXC container manager
#!/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