#!/usr/bin/env python3 # # lmn-appliance # thomas@linuxmuster.net # 20250315 # 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('-l, --pvdevice= : Initially sets up lvm on the given device (server') print(' profile only). can be a partition or an') print(' entire disk.') print('-v, --volumes= : List of lvm volumes to create (to be used together') print(' with -l/--pvdevice). Syntax (size in GiB):') print(' :,:,...') 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:hl:n:p:t:uv:w:", [ "reboot", "domain=", "firewall=", "help", "volumes=", "swapsize=", "pvdevice=", "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 ("-v", "--volumes"): volumes = ' -v ' + a elif o in ("-w", "--swapsize"): swapsize = ' -w ' + a elif o in ("-l", "--pvdevice"): pvdevice = ' -l ' + 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 lmnkey_remote = 'https://deb.linuxmuster.net/pub.gpg' lmnkey_local = '/etc/apt/trusted.gpg.d/linuxmuster.net.gpg' lmn_list = '/etc/apt/sources.list.d/lmn.list' lmn72_deb = 'deb https://deb.linuxmuster.net/ lmn72 main' lmn73_deb = 'deb https://deb.linuxmuster.net/ lmn73 main' 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'] # install repos id, version = os_release() if id != 'ubuntu': print(id + ' is not the expected os.') sys.exit(1) if version != '22.04' and version != '24.04': print(version + ' is not the expected version.') sys.exit(1) print('## install linuxmuster.net repos') urllib.request.urlretrieve(lmnkey_remote, lmnkey_local) if version == '24.04': writeTextfile(lmn_list, lmn73_deb, 'w') else: writeTextfile(lmn_list, lmn72_deb, '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 + volumes + pvdevice + swapsize, shell=True)