#!/usr/bin/env python3 # # lmn-appliance # thomas@linuxmuster.net # 20260618 # import configparser import getopt import os import subprocess import sys import urllib.request # check for root user and go on if not os.geteuid() == 0: sys.exit('Please start the script as root user.\n You can use the command "sudo -i" to achieve this.') lvmvols_default = 'var:10,linbo:40,global:10,default-school:100%FREE' # help def usage(rc): print('\nInstalls linuxmuster.net package repo, downloads and installs linuxmuster-prepare package.') print('If profile option is set it initially prepares a linuxmuster.net ubuntu appliance.\n') print('\nUsage: lmn-appliance [options]') print('\n[options] are:\n') print('-t, --hostname= : Hostname to apply (optional, works only with') print(' server profile).') print('-n, --ipnet= : Ip address and bitmask assigned to the host') print(' (optional, default is 10.0.0.x/16, depending') print(' on the profile).') print('-p, --profile= : Host profile to apply, mandatory. Expected') print(' values are "server" or "ubuntu".') print(' Profile name is also used as hostname, except for') print(' "server" if set with -t.') print('-w, --swapsize= : Swapfile size in GB (default "2").') print('-f, --firewall= : Firewall/gateway ip (default *.254).') print('-g, --gateway= : Gateway ip address (default is firewall ip).') print('-d, --domain= : Domainname (default linuxmuster.lan).') print('-u, --unattended : Unattended mode, do not ask, use defaults.') print('-b, --reboot : Reboots finally (only in unattended mode).') print('-h, --help : Print this help.') print('\nNote on lvm volumes:') print('- Default value for volumes is:') print(' "' + lvmvols_default + '".') print('- At least "linbo", "global" and "default-school" must be given.') print('- "nn%FREE" means "use nn% of the remaining free space of the device for this volume".') print(' Example: "linbo:50%FREE,global:10,default-school:100%FREE" means "linbo" takes 50% of') print(' the volume, "global" takes 10G and "default-school" finally takes all of the rest.') print('- "global" and "default-school" are mounted with quota options.') sys.exit(rc) # get cli args try: opts, args = getopt.getopt(sys.argv[1:], "bd:f:g:hl:n:p:t:uv:w:", [ "reboot", "domain=", "firewall=", "gateway=", "help", "swapsize=", "ipnet=", "profile=", "hostname=", "unattended"]) except getopt.GetoptError as err: # print help information and exit: print(err) # will print something like "option -a not recognized" usage(2) # default values unattended = '' reboot = '' profile = '' hostname = '' domainname = '' firewallip = '' gateway = '' ipnet = '' volumes = '' pvdevice = '' swapsize = '' profile_list = ['server', 'ubuntu'] # evaluate options for o, a in opts: if o in ("-u", "--unattended"): unattended = ' -u' elif o in ("-b", "--reboot"): reboot = ' -b' elif o in ("-p", "--profile"): profile = a elif o in ("-t", "--hostname"): hostname = ' -t ' + a elif o in ("-w", "--swapsize"): swapsize = ' -w ' + a elif o in ("-d", "--domain"): domainname = ' -d ' + a elif o in ("-n", "--ipnet"): ipnet = ' -n ' + a elif o in ("-f", "--firewall"): firewallip = ' -f ' + a elif o in ("-g", "--gateway"): gateway = ' -g ' + a elif o in ("-h", "--help"): usage(0) else: assert False, "unhandled option" usage(1) if profile != '' and profile not in profile_list: print('\nUnknown profile: ' + profile + '.') usage(1) # repo data lmn_url = 'https://deb.linuxmuster.net/' lmnkey_remote = lmn_url + 'pub.gpg' lmnkey_local = '/usr/share/keyrings/linuxmuster.net.gpg' lmn_list_pre = '/etc/apt/sources.list.d/lmn.' lmn74_def = 'Types: deb\nURIs: ' + lmn_url + '\nSuites: lmn74\nComponents: main\nSigned-By: ' + lmnkey_local lmn74_sources = lmn_list_pre + 'sources' lmn73_def = 'deb [arch=amd64 signed-by=' + lmnkey_local + '] ' + lmn_url + ' lmn73 main' lmn73_list = lmn_list_pre + 'list' pkgname = 'linuxmuster-prepare' # return content of text file def readTextfile(tfile): if not os.path.isfile(tfile): return False, None try: infile = open(tfile, 'r') content = infile.read() infile.close() return True, content except: print('Cannot read ' + tfile + '!') return False, None # write textfile def writeTextfile(tfile, content, flag): try: outfile = open(tfile, flag) outfile.write(content) outfile.close() return True except: print('Failed to write ' + tfile + '!') return False # get os release def os_release(): # read data rc, content = readTextfile('/etc/os-release') # insert a section content = "[OS]\n" + content content = content.replace('"', '') # read the os data os_data = configparser.ConfigParser() os_data.read_string(content) return os_data['OS']['ID'], os_data['OS']['VERSION_ID'] # check ubuntu version id, version = os_release() if id != 'ubuntu': print(id + ' is not the expected os.') sys.exit(1) if version != '26.04' and version != '24.04': print(version + ' is not the expected version.') sys.exit(1) print('## install linuxmuster.net repos') # install repo key urllib.request.urlretrieve(lmnkey_remote, lmnkey_local) # install repos if version == '26.04': writeTextfile(lmn74_sources, lmn74_def, 'w') else: writeTextfile(lmn73_list, lmn73_def, 'w') # get updates print('## install software updates') subprocess.call('apt clean', shell=True) subprocess.call('apt update', shell=True) subprocess.call('DEBIAN_FRONTEND=noninteractive apt -y dist-upgrade', shell=True) # get lmn packages print('## install ' + pkgname + ' package') subprocess.call('DEBIAN_FRONTEND=noninteractive echo -e "y\ny\ny\n" | apt -y install ' + pkgname, shell=True) # invoke prepare script if profile is set if profile != '': print('## invoke lmn-prepare') profile = ' -p ' + profile subprocess.call('lmn-prepare -i' + unattended + reboot + profile + hostname + domainname + firewallip + gateway + ipnet + swapsize, shell=True)