# example lxc_host.cfg # cfg data => ' # { # "bridge": "lxcbr0", # "netmask": "255.255.255.0", # "network": "10.0.1.0/24", # "addr": "10.0.1.1", # "dhcp_range": "10.0.1.200,10.0.1.254" # "dhcp_max": 253, # }'; bundle agent lxc_host( cfg ) { vars: # var ip wird in mergedata verwendet, und mergedata mag keine flachen Variablen, # sondern braucht Arrays auf die eine oder andere Weise 'ip[thirdoctet]' string => format( "%02x", nth( splitstring( $(cfg[addr]), '\.', 4 ), 2 ) ); "cfg_ip" data => mergedata( cfg, ip ); debian|ubuntu:: 'lxc_path' string => '/etc/lxc'; 'lxc_net_path' string => '/etc/default/lxc-net'; 'lxc_net_tmpl' string => 'debian.lxc-net.mustache'; 'lxc_default_path' string => '$(lxc_path)/default.conf'; 'lxc_default_tmpl' string => 'default.conf.mustache'; 'lxc_hosts_d' string => '$(lxc_path)/hosts.d'; debian:: 'pkg_list' slist => { 'lxc','lxc-templates','debian-archive-keyring' }; 'lxc_nft_path' string => '/usr/local/bin'; 'lxc_down_nft' string => '$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/lxc-net-down.nft.txt'; 'lxc_net_override' string => '$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/lxc-net.override.conf'; debian_11:: 'lxc_up_nft' string => '$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/debian11.lxc-net-up.nft.txt'; 'lxc_hook_net' string => '$(sys.workdir)/inputs/$(def.wmde_libdir)/scripts/lxc_host/debian11.lxc-hook-net.sh'; debian_12|debian_13:: 'lxc_up_nft' string => '$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/debian12.lxc-net-up.nft.txt'; 'lxc_hook_net' string => '$(sys.workdir)/inputs/$(def.wmde_libdir)/scripts/lxc_host/debian12.lxc-hook-net.sh'; ubuntu:: 'pkg_list' slist => { 'lxc','lxc-templates','ubuntu-archive-keyring' }; fedora|centos|redhat:: 'lxc_net_path' string => '/etc/lxc-net'; methods: 'Ensure the packages required for LXC are installed' usebundle => wmde_install_packages(@(pkg_list),'lxc'), comment => "The packages to install are defined in var `pkg_list`.", handle => "lxc_installed"; reports: "The agent $(default:def.agent_name) has been prepared as a host for lx containers."; "The host provides the network on $(cfg[bridge]) with address $(cfg[addr])."; "It will configure LXC via DHCP in the range $(cfg[dhcp_range])."; "Static mappings are in /etc/lxc/hosts.d."; "This is the third octet: $(ip[thirdoctet])."; files: "$(lxc_path)/." acl => lxc_dnsmasq, depends_on => { "lxc_installed" }; "$(lxc_path)/dnsmasq.conf" perms => mog('644','root','root'), content => "dhcp-hostsdir=$(lxc_hosts_d)", depends_on => { "lxc_installed" }; "$(lxc_hosts_d)/." create => "true", perms => mog('755','root','root'), handle => "lxchostsdir", depends_on => { "lxc_installed" }; "/usr/local/bin/lxc-hooks" perms => mog('700','root','root'), copy_from => local_cp("$(sys.workdir)/inputs/wmdelib/scripts/lxc_host/lxc-hooks.sh"), depends_on => { "lxc_installed" }, handle => "lxchookscript"; debian_11|debian_12|debian_13:: '$(lxc_nft_path)/lxc-net-up.nft' perms => mog('700','root','root'), copy_from => local_cp('$(lxc_up_nft)'), depends_on => { 'lxc_installed' }, handle => 'lxc_up_nft_copied'; '$(lxc_nft_path)/lxc-net-down.nft' perms => mog('700','root','root'), copy_from => local_cp('$(lxc_down_nft)'), depends_on => { 'lxc_installed' }, handle => 'lxc_down_nft_copied'; '/etc/systemd/system/lxc-net.service.d/.' perms => mog('755','root','root'), create => 'true', depends_on => { 'lxc_installed' }, handle => 'lxc_net_override_dir'; '/etc/systemd/system/lxc-net.service.d/override.conf' perms => mog('644','root','root'), copy_from => local_cp('$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/lxc-net.override.conf'), depends_on => { 'lxc_installed' }, classes => results('namespace','lxc_net'), handle => 'lxc_net_override'; "/usr/local/bin/lxc-hook-net" perms => mog('700','root','root'), copy_from => local_cp("$(lxc_hook_net)"), depends_on => { "lxc_installed", "lxchookscript" }, handle => "lxchooknet"; debian|ubuntu:: "$(lxc_net_path)" perms => mog('644','root', 'root'), template_data => @(cfg_ip), template_method => 'mustache', edit_template => "$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/$(lxc_net_tmpl)", depends_on => { "lxc_installed" }; "$(lxc_default_path)" perms => mog('644','root', 'root'), template_data => @(cfg_ip), template_method => 'mustache', edit_template => "$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/$(lxc_default_tmpl)", depends_on => { "lxc_installed" }; commands: lxc_net_repaired:: "/usr/bin/systemctl" args => "daemon-reload", handle => "lxc_net_reloaded"; services: lxc_net_repaired:: "lxc-net.service" service_policy => "restart", depends_on => { 'lxc_net_reloaded' }; } body acl lxc_dnsmasq { acl_method => "append"; acl_type => "posix"; aces => { "user:dnsmasq:rx:allow" }; } # example lxc.cfg # cfg data => ' # { # "name": "proxy", # "dist": "debian", # "release": "bookworm", # "arch": "amd64", # "bridge": "lxcbr0", # "ip": "10.0.11.80", # "group": "", # "autostart": true, # "policy": "absent|present", # "state": "stopped|running|manual", # }'; # name # lxc.container.conf: # # Parameters passed to the template: --dist $(cfg[dist]) --arch $(cfg[arch]) --release $(cfg[release]) bundle agent lxc( cfg ) { classes: "cfg_array" expression => strcmp( type( "cfg", "true" ), "data array" ); vars: cfg_array:: "index" slist => getindices( @(cfg) ); methods: cfg_array:: "Iterate over config array: $(index)" usebundle => _lxc( @(cfg[$(index)]) ); !cfg_array:: "Forward config to" usebundle => _lxc( @(cfg) ); reports: cfg_array:: "Iterated over cfg array."; } # Although all attributes are passed in one data object there are # qualitative differences. All need the name element. # lxc-create needs dist, arch and release. # lxc config file needs autostart and group # dnsmasq needs the ip bundle agent _lxc( cfg ) { classes: "lxc_exists" expression => returnszero( "/usr/bin/lxc-info $(cfg[name]) 2> /dev/null", "noshell" ), scope => "bundle"; "lxc_host_file_exists" expression => fileexists( "$(lxc_host_file)" ), scope => "bundle"; "lxc_policy_exists" expression => isvariable( "cfg[policy]" ); "lxc_state_exists" expression => isvariable( "cfg[state]" ); "lxc_ports_exists" expression => isvariable( "cfg[ports]" ); "lxc_raw_lines_exists" expression => isvariable( "cfg[raw_lines]" ); lxc_policy_exists:: "lxc_policy_valid" expression => regcmp( "(absent|present)", "$(cfg[policy])" ); lxc_policy_valid:: 'present' expression => strcmp( "present", "$(cfg[policy])" ); !lxc_policy_exists:: 'present'; lxc_state_exists:: "lxc_state_valid" expression => regcmp( "(stopped|running|manual)", "$(cfg[state])" ); lxc_state_valid:: 'running' expression => strcmp( "running", "$(cfg[state])" ); lxc_state_valid:: 'manual' expression => strcmp( "manual", "$(cfg[state])" ); !lxc_state_exists:: 'running'; lxc_exists:: "lxc_correct_distribution" expression => regline( '^# Parameters passed to the template: --dist $(cfg[dist]) --arch $(cfg[arch]) --release $(cfg[release])$', "$(lxc_dir)/config" ), scope => "bundle"; lxc_ports_exists:: "ports_array" expression => strcmp( type( "cfg[ports]", "false" ), "data" ); vars: "lxc_host_file" string => "$(lxc_host.lxc_hosts_d)/$(cfg[name])"; "lxc_dir" string => "/var/lib/lxc/$(cfg[name])"; "lxc_rootfs" string => "$(lxc_dir)/rootfs"; 'autostart' string => $(cfg[autostart]); 'group' string => $(cfg[group]); files: ports_array:: "/var/lib/lxc/$(cfg[name])/ports" edit_template => "$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/port_forwarding.mustache", template_data => @(cfg), template_method => "mustache", # depends_on => { "lxc_$(cfg[name])_created" }, handle => "$(cfg[name])_ports_created"; !lxc_exists & present:: "/var/lib/lxc/$(cfg[name])/config" edit_line => lxc_config( "$(autostart)", "$(group)", @(cfg[raw_lines]) ), depends_on => { "lxc_$(cfg[name])_created" }, handle => "$(cfg[name])_config_created"; methods: !lxc_exists & present:: "Ensure existence of container ($(cfg[name]))" usebundle => lxc_create( @(cfg) ), handle => "lxc_$(cfg[name])_created"; "Ensure static mapping in dnsmasq" usebundle => lxc_add_static_mapping( @(cfg) ), depends_on => { "lxc_$(cfg[name])_created" }, handle => "lxc_$(cfg[name])_mapped_statically"; lxc_exists & !present:: "Ensure absence of container ($cfg[name])" usebundle => lxc_destroy( @(cfg) ), handle => "lxc_$(cfg[name])_destroyed"; "Ensure absence of static mapping" usebundle => lxc_remove_static_mapping( @(cfg) ), depends_on => { "lxc_$(cfg[name])_destroyed" }, handle => "lxc_$(cfg[name])_unmapped_statically"; "Ensure dnsmasq picks up current lxc host configs" usebundle => reload_dnsmasq, depends_on => { "lxc_$(cfg[name])_unmapped_statically" }, handle => "reloaded_dnsmasq_for_$(cfg[name])"; present & running:: "Ensure running state of container ($(cfg[name]))" usebundle => lxc_start( @(cfg[name]) ), handle => "lxc_$(cfg[name])_started"; present & !(running | manual):: "Ensure stopped state of container ($(cfg[name]))" usebundle => lxc_stop( @(cfg[name]) ), handle => "lxc_$(cfg[name])_stopped"; reports: "_lxc: raw_lines_$(cfg[name]) [$(lxc_raw_type)] enthält $(cfg[raw_lines])"; "_lxc: raw_lines_arg_$(cfg[name]) [$(lxc_raw_type)] enthält ($-Notation) $(raw_lines_arg_$(cfg[name]))"; lxc_exists & lxc_correct_distribution:: "LX Container $(cfg[name]) already configured, nothing to do"; !lxc_exists & present:: "LXC $(cfg[name]) did not exist and should have been created."; lxc_exists & !present:: "LXC $(cfg[name]) did exist and should have been destroyed."; present & running:: "LXC $(cfg[name]) should now be in state RUNNING."; present & !running:: "LXC $(cfg[name]) should now be in state STOPPED."; } bundle edit_line lxc_config( autostart, group, raw ) { classes: "autostart_true" expression => some( $(autostart_lc), true_statements ), depends_on => { "$(autostart)_lowercased" }; "group_provided" expression => isgreaterthan( $(group_length), 0 ), depends_on => { "group_length_measured" }; vars: "autostart_lc" string => string_downcase( $(autostart) ), handle => "$(autostart)_lowercased"; "group_length" int => string_length( $(group) ), handle => "group_length_measured"; "true_statements" slist => { "yes", "true", "on", "1" }, handle => "truth"; insert_lines: "# This file is managed by CFEngine. Manual changes will be overwritten." location => first_line, handle => "warning"; "$(raw)" depends_on => { "warning" }; autostart_true:: "lxc.start.auto = 1"; !autostart_true:: "lxc.start.auto = 0"; group_provided:: "lxc.group = $(group)"; reports: "lxc_config 1: autostart ist $(autostart)"; "lxc_config 2: group ist $(group)"; "lxc_config 3: raw enthält $(raw)"; } body location first_line { before_after => "before"; first_last => "first"; select_line_matching => ".*"; } body location last_line { before_after => "after"; first_last => "last"; select_line_matching => ".*"; } bundle agent lxc_add_static_mapping( cfg ) { files: "$(lxc_host.lxc_hosts_d)/$(cfg[name])" perms => mog( '644', 'root', 'root' ), content => "$(cfg[name]),$(cfg[ip])", handle => "mapped_$(cfg[name])"; reports: "mapped $(cfg[name]) to $(cfg[ip])" depends_on => { "mapped_$(cfg[name])" }; } bundle agent lxc_remove_static_mapping( cfg ) { files: "$(lxc_host.lxc_hosts_d)/$(cfg[name])" delete => tidy, classes => if_repaired(dnsmasq_reload); reports: dnsmasq_reload:: "mapped $(cfg[name]) to $(cfg[ip])"; } bundle agent lxc_add_port_forwarding_rule_config( cfg ) { } # When files for static mappings are added dnsmasq automatically loads # them. But dnsmasq doesn't remove them automatically again when the # file gets removed. bundle agent reload_dnsmasq { processes: dnsmasq_reload:: "dnsmasq" signals => { "hup" }; } # DOWNLOAD_KEYSERVER="keyserver.ubuntu.com" lxc-create -n manual -t download -- -d debian -a amd64 -r bookworm bundle agent lxc_create( cfg ) { classes: "lxc_dir_btrfs" expression => strcmp( execresult( "/usr/bin/stat -f -c %T /var/lib/lxc" , "noshell", "stdout" ), "btrfs" ); vars: !lxc_dir_btrfs:: "create_args" slist => { "-n", $(cfg[name]), "-t", "download", "--", "-d", $(cfg[dist]), "-a", $(cfg[arch]), "-r", $(cfg[release]), }; lxc_dir_btrfs:: "create_args" slist => { "-n", $(cfg[name]), "-t", "download", "-B", "btrfs", "--", "-d", $(cfg[dist]), "-a", $(cfg[arch]), "-r", $(cfg[release]), }; commands: "/usr/bin/lxc-create" arglist => { @(create_args) }, contain => lxc_commands, handle => "lxc_$(cfg[name])_created"; reports: "LXC $(cfg[name]) has been created" depends_on => { "lxc_$(cfg[name])_created" }; } bundle agent lxc_destroy( cfg ) { methods: "Ensure LXC is stopped" usebundle => lxc_stop( $(cfg[name]) ), handle => "stopped_$(cfg[name])"; commands: "/usr/bin/lxc-destroy" arglist => { "-n", $(cfg[name]) }, depends_on => { "stopped_$(cfg[name])" }, handle => "destroyed_$(cfg[name])"; } bundle agent lxc_start( name ) { classes: "lxc_running" expression => strcmp( execresult( "/usr/bin/lxc-info -n $(name) -s -H", "noshell", "stdout" ), "RUNNING" ), scope => "bundle"; commands: !lxc_running:: "/usr/bin/lxc-start" arglist => { "-n", $(name) }, handle => "lxc_$(name)_started"; reports: !lxc_running:: "$(name) has been started" depends_on => { "lxc_$(name)_started" }; } bundle agent lxc_stop( name ) { classes: "lxc_running" expression => strcmp( execresult( "/usr/bin/lxc-info -n $(name) -s -H", "noshell", "stdout" ), "RUNNING" ), scope => "bundle"; commands: lxc_running:: "/usr/bin/lxc-stop" arglist => { "-n", $(name) }, handle => "lxc_$(name)_stopped"; reports: lxc_running:: "$(name) has been stopped" depends_on => { "lxc_$(name)_stopped" }; } body contain lxc_commands { useshell => "noshell"; no_output => "true"; }