diff --git a/inventory.yml b/inventory.yml
new file mode 100644
index 0000000..845c75b
--- /dev/null
+++ b/inventory.yml
@@ -0,0 +1,152 @@
+$ANSIBLE_VAULT;1.1;AES256
+63633264306266356564663836343363656536353265653630623231313232316232373364396234
+3236316538656536366265323139396261613363653261620a336663633534646530613832646330
+37636232373730363966333835333935346534373737373366333137393336343333386638373236
+3761336362363136320a313937373361636337613464383266643763356436623163353164373133
+66653337346637633231663433653465353963656561633037633131643931363439323737303636
+37313630326337313333633632343232373562363662396161343866393031383564383239626461
+35316334333464393064643534626166333264663932316533303963343038653162656232656165
+33346162393633633736623532386333653464646335626236356466343439633130653836623462
+34613337623034313834663231343830623836313037313435306633356131326235346364643234
+65646361336234343937653139303034333438626238366531656230653639373636346565643035
+65306130386331643037633165613635393433653537333238393365343736623932363432663166
+36376266313936393931366333303863353033643962303861393536383966343932373233356234
+64663834663965373038333265616532313161656337613035393736653864393562656630326561
+66343634383139386133326463633333313164303638313636663361373036396235636332323563
+31383337636535313139636661306430396335323237653364333838616434363931333861656262
+35366431646161396234666135343663343266626538363833356666613932303766373238363030
+64353666363662633836383135313738376235653565323039323962326665323334303731393535
+35303161636133376631386437626432363463383536343530396533316330393537643036346438
+34373939373938356466363261643936323964313232313331623237643564326239353861666636
+31363462336137373365333366383635303639643263303633386637303839363531336133353035
+66643039313764353935346133396233373735626163383734333736383062663038643464323137
+30613239393864333365663331313634373338616238303735646532386135336664623635373934
+34633366303736316438613733316230343039373434653565313631623931646363333139363332
+66353934396632613330313061336336626531656334633237633536613138393938353364623137
+34636231643565316435356334663736326463643131393135636366373338326233636564326165
+31646632326430353036363163303839336638393962373665346237373831393639376231396362
+38366438656236646436653634356235386238366233666433656161316636643035353165373837
+30653036346236306162626536393937653235306236313036633135653139656261666361316336
+66333835343735333937396362353330383139663635386236373531653837383231356535326330
+64323735383833306463636461373061376133366261356166626466656365663730383133353062
+35356136623464656662613438313535316466323538313438313963383532616232336564346534
+64333736316238393232623763306539373034366566306561336161336630613133633762636131
+34653565353031373436666363613164343964626164366537646438363965396366623765383333
+39656630383564623735666663643836363963356563303137343064373434343336663536353562
+30636165623032343136363534373761626638346432323661653730633264323837356163656132
+66323533316130356364353536303536613365663536346431306634623938613531373465323738
+33336132663431613338653432613863323665393532346130313332316366633439323664363361
+61323233303732393637303366336336626638353931663365326266386632666235343164623336
+65653861353133353661646463383133613733653561333136656665333338636331616262346661
+66383862323664626632613865663933313338633839313864303366306631613565633734353564
+38333137633933323538313632393233373836653365333531356336616265396163353365356162
+66636637646436343834313165363830346339636532343362333133666437323862636439323438
+39363934623766666464623838396465323539366433653334636365653665356237373235646661
+63316135626430363637383637363366313563326165393263393539353738313936373365346364
+62393931353138656633343933313337306266623834653038383437313238643530333130623666
+37653532333966666564343131316463383836373831393964333664653538616662383733623462
+31393131666662323236623838343630316434303961323663346262656538313464393564323132
+39623037323334613632666536366363306139646431646465376535393331333366333537343561
+66333862353036393930623632313737333062363464353437663966313438636261306165353032
+32306533363463316661653334306261336236323366353037353461613632633934333734373038
+37356230613336353938633562343632633031616635343461316333373062646137396163653235
+65653661386637376330313438323630353432353064383263626339333262363566366633613531
+66636237363265313266323038376163356437376532656362626436366235393335646165323061
+61663162313463613431393762633165653035343061333035333439616165646237356633323631
+65333861663236633064646563346233653231656430623564666265363834633635336163313864
+39646538613164393161306137393065623935643937393935313438363434623538373963663461
+39636530373739306333346132636533633861363530613437663632303963336634376639366134
+31323032393135336136636338366634393236303334633664343865613839663966373339373239
+61643765393539626337303264373133366265643438333935666633343738653761626437323861
+38633030653931313762333764316134393566306435303338623832653035393766663539663164
+64643136656338343334396234363139363337323033333163323261356431356362636435363838
+64333866313031333937353034376337346631343338626364343034616635633138313039613464
+33336439356438616234306666646132623230646161373938656239323134653435663734653235
+64646338343161363364316639336538646439313939326337346339376466666136333165636161
+64393732313538373334363431363836626364643834333265653135653035636330656464613731
+36316266316430336166393564306462363232336335626335616165313734633466636534353035
+37626637633534656235363530633866633062626537383935313763373562633737313032643462
+61353561366639306437626332643633626463313663366561646331373735393032616332326562
+36626266316236363930616662636134623865356632303465316266616663353039616261396663
+34373037623763393263316166343131643531663134616165386636653562336135346333326461
+39623964656137323439316166666132616561666235633439326464383937653738336435646134
+64383661363962333838376135323864633234386662643864336132343231366266363632313666
+31356339633938316636616537633335333564396631336238383063383164393238656438646131
+31373164313062623930636438346437623736353830396533343730366162343164393661396263
+63383837633335366534343137663634646631366234656365383235353532626163393537376537
+37643661666464303133313362366230313132363738643765613735613462333861633736643664
+38303537303338626139323566306536616236353563613862323435636165373434316432663864
+39373033326134626530663932623230396531636433346432653262666430653866656330613861
+64646265363562623663353532616663383830383633366462323235383966383263396535363536
+30633266386339656230326365303330376138313432653132323138333863663961613538623566
+62636565613130633166656639653264376461663737356162336331313834613662353430656431
+35313237633837623562396539623336313562313765383035373036333233356461366336383838
+65616164623762303033633130636364666630633465633435616666313031343863386261373033
+39623638313761393434333431646638363062303437373765666435386531343339323862666132
+38666635363431613530313766386165633032643836663861653434656231383333666633643266
+32333336383334303763323237653934313032643261326233376335616230386234346161353431
+63643733643634313133336332663933656363393861633831346261353334376464633562326534
+31663066656130363965656361626464613463323764383036623063386166653839366637376135
+34646466313836353234383762376165356439626532646430313730326166313839356263633966
+39643731613363613564356337653931626666363337356138333335393463393865666636393532
+33353366633465303765373936316334633331323930653235313231343039616238663433613365
+31373866666466363965346637656435383433626563313330316132376364366532316331343534
+32353462646536343538346130383139663334313331323935396537313835366161613539653164
+38666265306664623666396530383464653661356562613037323362303361323537326239323661
+66383230386437656132653966333062343536623861373461326333646266366430613563306436
+32623132303966323638643639343537643135396566323630313038636465353830636439343465
+34333136613435653037303437646439623035643539623663633561616137656138373063653662
+35643830613235663931386634626463333331313864666662313534626464663635616437313061
+66373039396333336639623637353839656333386635363733636636306132333130643131656438
+64333561323464333631356465393665613635356265343764346531613538633137366231343032
+30323832346266653238633432383334393661333737383761303238613734623534646232633562
+63643066663363306432366662643362663665313431616665653239386666623864633639383431
+37393630636363303438383665326663323561653335396238353138616533323965353031336465
+39383132666564333661383831323539313334393632653865353662373439663438656534653133
+63353562373637323436316530666639613637353866633931343536306230323765643335376563
+33626661366339663161653334666232626464636134623537336237663662343833613564306366
+64613132313038663761626562323832343937316136363666393438656165653666326463656565
+39663032656537313563346138323731626562396566646535633830636638643463323334356365
+65396564633530386237626664353734366336363339336162373139373036666339313531316265
+63313266316238383363333262373038653062653561656466643837343863633562633530366433
+61333663366361306233353463646636386335343131396461613562653264306432326663313436
+37656538356534393065386263373264623664333738306363373465323663313831323665323862
+37613366313361376137363663393334623237326663383766663736333133353931356264663163
+65653439356463393938626331323835383232386638323531326661613433343833343130626531
+61656461333565666633353739626266653138316163326565336461333934376235376639623338
+66396662323338663966653731386633623331626339653664383366316466653661366630333966
+63343930363565343464373731386237396363353562633634663935373733373663356537333134
+38623362633936616533646535626533376366663535626364646536333333313664356532356130
+33366164303836643730656439386537633761653032333061306261646364373961323334623831
+35393832323830336633303030653963393532376466353966333039313661373263303136636534
+65623830646662396236626263313763623833346236343361373465633434366131326362663034
+64323161383832663765636566363932613434353031653031633262363233653630343736653436
+35333435663562303135646531663033373538376366323638333031653962306563383733393235
+64333763663163306537346638633763363932336337633039633865633931656136356537366466
+66373639303037623066383239383335643664666362303364396465633361396536383133653939
+31336438646531376364303731313430396464373432646234356637663966333665636136653761
+39396138343439353565343139643165313864643132386633643836366564636130623666303933
+36623563326434656439333962336132343662663936653866316163633339336361376633396530
+66356639366330613335353633613831303535633538633233306666383761633864386538666266
+61383861393863626434613333633430396636666433306533623432356464633764663433373931
+38636538313830366438313964666430353237656639633763386332613636636462653634343831
+62626638633863353165366438656465376466633966356133366263636666343632313761653766
+65336461346531356631376462623737316538373363353230333436303532333835396435646634
+39306438663436636532373063643138366531313936306135666466333765646630336532313238
+64623530626334303638626364343864613164353732386262323439343735383164333232363434
+34666165353366343566656163373738663065643132303734623533326634346238343630643838
+64393833646132613761356334306539303233323466336533363938313633343834356239613034
+64653935373761396331323936616334303561613762623733613637653031623564366538373630
+61313733333863303630633036353535393532326134636139343532396163346537383665663838
+61386637353566306134393430366633636437616565343465393333306138376462633131623332
+65343035653066396533336238393365663432393537323431366532353232333036353662386363
+35313162646432626632633032636162343764343463366236386438323131623766363937336436
+31333332373731643164626137643162393964393334643237393133366237323562623839356534
+31343163656366616638663265323939663765666563333239306563653265343765613432633430
+63613231326333313437313166326236346538656236373933313262616334656264376430626263
+65396261386439646562343235326630353265356362666131316664343430343534353062613232
+64313234323430396139373331356434363663646339653566303638623839383330623630613662
+33653863333534656665313362326338346338303434363932323635666463323536343865386235
+31366134643263303630653534306364616466323538626335366231333762643961656165316534
+34626532393730323034663434386234313939336262333266656134613938613433396337376531
+343631376663336639636437653165333163
diff --git a/lmn-client.yml b/lmn-client.yml
new file mode 100644
index 0000000..75edb58
--- /dev/null
+++ b/lmn-client.yml
@@ -0,0 +1,313 @@
+## This playbook deploys a client for LinuxMuster.
+#
+# Use the following in the installer's preseed file:
+#
+# d-i preseed/late_command string \
+# mkdir -p /target/home/ansible/.ssh && \
+# echo "ssh-ed25519 A...YOUR.KEY...Z" >> /target/home/ansible/.ssh/authorized_keys ; \
+# in-target chown -R ansible:ansible /home/ansible/.ssh/ ; \
+# in-target chmod -R og= /home/ansible/.ssh/ ; \
+# if [ -n "$playbook" ] ; then \
+# mkdir -v /target/dev/shm ; \
+# in-target mount -v -t tmpfs tmpfs /dev/shm ; \
+# echo "$vaultpw" > /target/dev/shm/vaultpw ; \
+# in-target ansible-pull --verbose --purge --extra-vars="run_in_installer=true" \
+# --vault-password-file /dev/shm/vaultpw \
+# -i localhost, --url=git://ansible.example.org/.git -C YOUR_BRANCH $playbook ; \
+# fi
+#
+---
+- name: Apply common configuration to the machines
+ hosts: all # desktop:laptop
+ remote_user: ansible
+ become: yes
+ pre_tasks:
+ - pause:
+ prompt: "Enter global-admin AD password. Leave empty to skip domain join"
+ echo: false
+ register: adpw
+ no_log: true
+ when: "ansible_cmdline.adpw is not defined"
+ - name: Preseed apparmor
+ debconf:
+ name: apparmor
+ question: apparmor/homedirs
+ value: >-
+ /srv/samba/schools/default-school/teachers/
+ /srv/samba/schools/default-school/students/*/
+ /srv/samba/schools/default-school/examusers/
+ vtype: string
+
+ vars_files: lmn-vault
+ vars:
+ domain: "{{ ansible_domain }}"
+ kerberize_uris: "{{ vault_kerberize_uris }}" ## example.org
+ apt_conf: "{{ vault_apt_conf }}" ## Acquire::http::Proxy "http://aptcache.example.org:3142/";
+ ntp_serv: "{{ vault_ntp_serv }}" ## ntp.example.org
+ proxy: "{{ vault_proxy }}" ## http://firewall.example.org:3128
+ no_proxy: "{{ vault_no_proxy }}" ## firewall.example.org,server.example.org,idam.example.org,dw.example.org
+ printservers: "{{ vault_printservers }}" ## ['10.0.0.1', '10.0.0.15']
+
+ ## PAM mount nextcloud, remove or leave empty to skip:
+ web_dav: "{{ vault_web_dav }}" ## https://nc.example.org/remote.php/dav/files/%(USER)
+
+ ## Local mirror for mscorefonts. Remove or leave empty to use no mirror:
+ mirror_msfonts: "{{ vault_mirror_msfonts }}" ## http://livebox.example.org/mscorefonts/
+
+ ## Local mirror for libdvdcss. Remove or leave empty to use no mirror:
+ mirror_dvdcss: "{{ vault_mirror_dvdcss }}" ## http://livebox.example.org/libdvdcss/
+
+ uploadseed_pwd: "{{ vault_uploadseed_pwd }}"
+ rsyncsecret: "{{ vault_rsyncsecret }}"
+ keys2deploy: "{{ vault_keys2deploy }}" ## ['ssh-ed25519 AAAAC…uYlnS0', 'ssh-ed25519 AAAA…KTM']
+ localuser: "{{ vault_localuser }}" ## needed here for the (universal) pam-mount configuration
+
+ ## Use grub-mkpasswd-pbkdf2 to calculate the password hash:
+ grub_pwd: "{{ vault_grub_pwd }}"
+ nfs4: false
+ extra_pkgs:
+ - vim
+ - mc
+ - tmux
+ - krb5-user
+ - debconf-utils
+ extra_pkgs_bpo: [] # [ linux-image-amd64 ]
+
+ roles:
+ - lmn_network
+ - role: up2date_debian
+ tags: upgrade
+ - lmn_sssd
+ - lmn_mount
+ - lmn_kde
+ - lmn_fvs ## school specific customization
+ - lmn_vm
+ - lmn_printer
+ - kerberize
+ - lmn_security
+
+ tasks:
+ ## Temporary fixes and quirks:
+ - name: Fix 8086:4909 external graphics card
+ replace:
+ dest: "/etc/default/grub"
+ regexp: 'GRUB_CMDLINE_LINUX=""$'
+ replace: 'GRUB_CMDLINE_LINUX="i915.force_probe=4909"'
+ notify: Run update-grub
+ when: ansible_board_vendor == "LENOVO" and ansible_board_name == "32CB"
+
+ - name: Fix sound on 312A
+ replace:
+ dest: "/etc/default/grub"
+ regexp: 'GRUB_CMDLINE_LINUX="snd-intel-dspcfg.dsp_driver=1"$'
+ replace: 'GRUB_CMDLINE_LINUX=""'
+ notify: Run update-grub
+ when: ansible_board_vendor == "LENOVO" and ansible_board_name == "312A"
+
+ - name: Fix sound on 312A and 312D
+ apt:
+ name: firmware-sof-signed
+ state: latest
+ when: >
+ ansible_board_vendor == "LENOVO" and
+ (ansible_board_name == "312D" or ansible_board_name == "312A")
+
+ - name: Install customized CodeBlocks packages
+ block:
+ - name: Check for old CodeBlocks
+ command:
+ cmd: dpkg -l codeblocks
+ register: codeblocks_version
+ changed_when: False
+
+ - name: Download codeblocks zip archive
+ ansible.builtin.get_url:
+ url: "http://livebox.pn.steinbeis.schule/codeblocks/CodeBlocks.zip"
+ dest: /tmp/CodeBlocks.zip
+ use_proxy: False
+ when: codeblocks_version.stdout is not search('svn13456')
+
+ - name: Unpack zip archive and install packages manually
+ shell:
+ cmd: unzip -d /tmp/cb/ CodeBlocks.zip && dpkg -i cb/*.deb
+ chdir: /tmp/
+ when: codeblocks_version.stdout is not search('svn13456')
+ when: groups.PCroom is defined and inventory_hostname in groups.PCroom
+
+ ## Temporarily fix boot order
+ - name: Check for the buggy kernel
+ stat:
+ path: /boot/vmlinuz-6.1.0-17-amd64
+ register: bug
+
+ - name: Check for the fixed kernel
+ stat:
+ path: /boot/vmlinuz-6.1.0-18-amd64
+ register: fix
+
+ - name: Work around kernel with CIFS regression
+ block:
+ - name: Make sure kernel package -16 is available
+ ansible.builtin.apt:
+ name: linux-image-6.1.0-16-amd64
+ state: present
+ - name: Set 6.1.0-16 as default kernel in grub
+ lineinfile:
+ dest: /etc/default/grub
+ regexp: '^(GRUB_DEFAULT=).*'
+ line: '\g<1>"Debian GNU/Linux, with Linux 6.1.0-16-amd64"'
+ backrefs: yes
+ notify: Run update-grub
+ when: bug.stat.exists and not fix.stat.exists
+
+ - name: Set latest kernel in grub
+ lineinfile:
+ dest: /etc/default/grub
+ regexp: '^(GRUB_DEFAULT=).*'
+ line: '\g<1>0'
+ backrefs: yes
+ when: fix.stat.exists or not bug.stat.exists
+ notify: Run update-grub
+
+## Clean up stuff from obsolete/faulty tasks:
+ - name: Remove packages we do not need anymore
+ ansible.builtin.apt:
+ name:
+ - unattended-upgrades
+ - cachefilesd
+ state: absent
+ purge: True
+
+ - name: Remove virtiofs service
+ file:
+ path: /etc/systemd/system/virtiofs@.service
+ state: absent
+
+ - name: Fix mount point permissions and owner
+ file:
+ path: "{{ item }}"
+ mode: '0755'
+ owner: root
+ group: root
+ loop:
+ - /srv/samba
+ - /srv/samba/schools
+
+ - name: Remove pam_mount sysvol mount
+ blockinfile:
+ dest: /etc/security/pam_mount.conf.xml
+ marker: ""
+ block: |
+ rootansibleDebian-gdmsddm{{ localuser }}
+
+ state: absent
+
+ - name: check if rmlpr.timer is installed
+ stat: path=/etc/systemd/system/rmlpr.timer
+ register: rmlpr
+
+ - name: disable rmlpr.timer
+ systemd:
+ name: rmlpr.timer
+ enabled: false
+ when: rmlpr.stat.exists
+
+ - name: check if vmimage-torrent.service is installed
+ stat: path=/etc/systemd/system/vmimage-torrent.service
+ register: vmimagetorrent
+
+ - name: disable vmimage-torrent.service
+ systemd:
+ name: vmimage-torrent.service
+ enabled: false
+ when: vmimagetorrent.stat.exists
+
+ - name: Remove deprecated files and directories
+ file:
+ path: "{{ item }}"
+ state: absent
+ with_items:
+ - /etc/linuxmuster-linuxclient7
+ - /usr/lib/python3/dist-packages/linuxmusterLinuxclient7
+ - /usr/share/linuxmuster-linuxclient7
+ - /usr/local/bin/onLogin
+ - /etc/sudoers.d/90-lmn-sudotools
+ - /etc/systemd/system/rmlpr.service
+ - /etc/systemd/system/rmlpr.timer
+ - /usr/local/bin/sync-vm.sh
+ - /usr/local/bin/run-vm.sh
+ - /usr/local/bin/rebase-vm.sh
+ - /usr/local/bin/create-vm.sh
+ - /usr/local/bin/upload-vm.sh
+ - /usr/local/bin/vmimage-torrent
+ - /etc/systemd/system/vmimage-torrent.service
+ - /usr/local/bin/linbo-torrenthelper.sh
+ - /usr/local/bin/link-images.sh
+ - /usr/local/bin/start-virtiofsd.sh
+ - /etc/sudoers.d/90-lmn-upload-vm
+ - /etc/sudoers.d/90-lmn-sync-vm
+ - /etc/sudoers.d/90-lmn-startvirtiofsd
+ - /etc/sudoers.d/90-lmn-link-images
+ - /etc/rsync.secret
+
+ - name: check if vm_usage_information.txt exists
+ stat: path=/lmn/vm/vm_usage_information.txt
+ register: vm_usage_information
+
+ - name: pre-fill vm_usage_information.txt
+ shell:
+ cmd: |
+ ls -tr *.qcow2 > vm_usage_information.txt || touchvm_usage_information.txt
+ chown lmnsynci:lmnsynci vm_usage_information.txt
+ chdir: /lmn/vm/
+ when: not vm_usage_information.stat.exists
+
+## bookworm fixes/hacks:
+ - name: Work around sddm hang on shutdown
+ ansible.builtin.lineinfile:
+ path: /etc/systemd/system.conf
+ line: DefaultTimeoutStopSec=5s
+ insertafter: '^#DefaultTimeoutStopSec=.*'
+
+#################
+
+ - name: Timestamp successfull run and send up-to-date report
+ ansible.builtin.shell:
+ cmd: date --iso-8601=seconds >> /root/.ansible/stamps && /usr/local/sbin/reporter
+ changed_when: False
+ tags: upgrade
+
+#################
+
+- name: Apply additional laptop configuration
+ hosts: laptop
+ remote_user: ansible
+ become: yes
+ vars_files: lmn-vault
+ vars:
+ ssid: "{{ vault_ssid }}"
+ wifipasswd: "{{ vault_wifipasswd }}"
+ localuser: "{{ vault_localuser }}"
+ localuser_pwd: "{{ vault_localuser_pwd }}"
+ roles:
+ - role: lmn_wlan_iwd
+ when: ansible_interfaces | select('search', 'wl.+') | first is defined
+ - lmn_networkd
+ - lmn_localuser
+ tasks:
+ - name: Remove deprecated files and directories (laptop-class)
+ file:
+ path: "{{ item }}"
+ state: absent
+ with_items:
+ - /etc/systemd/network/80-wlan-dhcp.network
+ - /etc/systemd/network/wlan-dhcp.network
+ - /etc/systemd/network/virbr1.netdev
+ - /etc/systemd/network/virbr1.network
+ - /etc/systemd/network/wlan-dhcp.network
diff --git a/lmn-vault b/lmn-vault
new file mode 100644
index 0000000..9479d19
--- /dev/null
+++ b/lmn-vault
@@ -0,0 +1,112 @@
+$ANSIBLE_VAULT;1.1;AES256
+65646637366132333430346461646331313431363233363736306264633633396665626332623934
+6439363764316132383635626137313764633162636362340a613832323934646431663632396361
+36323539663238363738393131363034333561343233383238396234613434633334323235626637
+6266326166333334650a353637383131313136613635333237616361353732663734613833306538
+35643831653332346634616139363032656433623032383832363837653231306465353766343961
+65313134303434333635306634633731313462396535383662616134653762343732366431373032
+65346564663335633936636662626162636134636339343463376166666333346133616136626665
+66373064303562323564363864366363663336383862336632373436666330373465636135623762
+36656632346664326463646666313663343662363865343166376363313866663536623234383561
+61616637653630316230626337653135396134323636303538346435663639643662646133383363
+39393234333934656166366633356663326162396431383362306339623534646162613339383836
+39666464666163633033653434306365393933383232653364363062356133356239626538633338
+32303030626538373637323533303964643838383331366365326465666530623965613731663261
+33626563386262353531353635356430333633633665393230613561633836653636636639313663
+37373736346234313134313232376164633332386563383863343266626231373237643063663533
+39343939393331653665633335653264376531383364376565646239616231343531336134626531
+38396630363865376161313835376261646637383438323537626433323232353632386439393933
+32306238643436653666333561643764633831343962643165356232663932633763396437383634
+35313763323633643439376333643836613637613339343731636633313064386231613135623832
+32643934376233663865326534313735633535316635393932646263313135373633636333333737
+33386365363830336139323763303734383966616165356462333734333666343830356234616662
+30616434623237653138623538643331373432663137323333376632343065316431313734333965
+64333237396236666664613466623039626634343238363136363438663730626132646562646536
+61653562666636613164373464663931356565623862306332653230396230326636363638393862
+62663765373436303831613435383866323138316633336532336632383065343537326332653235
+30383764396361396565323035346531396365623130636538396238613037353438346365363331
+64383636376566306136653033613638323865653266383136373231353063303434636332363166
+37373462353530326663386139333536616138386431373763383838336365336634366339636637
+32356263653964343461393162396539303534343562653032643461626235373339666363646637
+37633934393036356331363563303330316234393535376338646235663235383966396634383166
+37656633373562643530663037333735396638363963323837653831396233653962356536616236
+37323139306131323561303061633136303234316335363361633766623530303762663465353163
+33313733386265346333663065366536616533326364613231313330373137616130373539313131
+37316637653934653035373965636633626262626561313338646261313530356163356364663834
+64663037666133626261386266666633336666323362373237626639373535333937336331353039
+30313833353766626332376432326531616464643364313232386633613361623234653536313830
+61626666333037393564303738646333643534623138366264353339326331386433343733623837
+63646431646533383331356334656466316465623735613537646536623364636632323566626233
+31663263623539343562383836366134366236346539386532373735633237373363636438366632
+39613330336365626137363765313930623262666263393835626532333262343265313761356333
+34623663633636373734326662363865396635613933393464356436393161303132663564366437
+35323166643762333862656561306239343034643562316534316236636362363162306131633961
+31653736613732643930666338333131373634333166633466656663636163396266306538626666
+65326163646137373236633363663063383162393435356163366665653033666161333037303035
+35396334663135373863646664613137666565356161353865316164633939323037323664346331
+63313230333565336232663166616465363038326663633066623531363338623430313332333138
+34363532643036343831353463356665646363363239643835396661356665393035303561653337
+62663335636533366334643636653366303231353630633166343832313133303663393836623036
+33616563636266356130356635663538343236356632376461626532626436616334386330356430
+62323864323534333032643737656164343633636365623664336236626239633138626230383536
+39306534383933326638666130383139316334373530373335633238316238353038643136366533
+38396661626661373964363630633963373732343161663065386539306637313633303534663466
+66396361373163313865373131636239613930333963663462306636626431363934343136616330
+30643763343838316338643463323833666639616437336361303363393361356431356266353233
+63366366336231386530313961356538613136366530343338353063343332333165323763613566
+34653133633532613332376634616234383237666261363038613437646366356332636530623534
+30613735663666636232623230633161663064666436333161633334356336383038386535336133
+39663963333031383961643232636539313137346132336462336165313862653366303135353730
+63303834313462646633333232646661623731613439633434663266303834376635346438356438
+39303066663633656234633131366330356363636535373034613037363837326562306562663538
+61316666643230626662663266373330643865393938313232306130376333306536393930363037
+66636562396339633763656431653036646361313632313932636231626333303337366266623238
+39616336656537363439373231643132363264306135346437386465326265666137353032336261
+61323234643662653233353737346661373630376630343635383834313038373162643135343434
+37613634333330326132623437623834363539343037643764303631613463343863643065643063
+33326537376130346365323361343266663331343038663037623438666362656236613065366235
+65363130356133353739623733376531636438643535633731646431653837343531313531373436
+31356139393363646262623664663261613931636330663436336466633038643763336337336330
+33393433346332623538653262303462636363363338346538376463373838363036343634363131
+31636332343931643436393464656165616631373339336537623130343630346164383830313165
+36316364653739646330663762356332393262653931613933643963626433633532353766663632
+65616262666433383763363636303131373064636261616661613139373766336639376336393962
+65336332303164353763636332323031363363653262386331313038646564393131303366653834
+33303464303566656363343464336164363264626436306465633261386464663764636431353037
+31363034323331333235346137653139323835326135653337323339346239383038363861313638
+31363136353037396634326239306665303230616131363965653439656361356538623135613238
+66366639333331306337323562343934313532323633613034353863623839636135393465383832
+34323031326262306161613439323836646538363136336537313266343662383935373762666138
+64336132356662366436326664653234303034623066313736353439396334653630643136336431
+31386330326636303334313535363564383964623538656666376136366365633538386139333862
+65336662653965343035306534393962616438636366646664383231316365366435663763643663
+64383034336565663561626262636263616336303066396164633464313830363338303932356638
+32373162313330303935316137366435373532346363386461303933643237383830623335626639
+33383335653436353831303163656530613962303439383563376534663738383035346433303834
+31663863343864656463643433383938393464613865356134346261663333616537663066333965
+32366466373165633936323232333237383638313434366437376237653837363532393564323035
+64623234376538666237653938346634346532656135333165353864383739353737643965636539
+63626134376330346538656539333362633765363735656161323635323164323038633139653663
+64616466353137623937333237633163646266326437663833393437336662356465336566353832
+30653063666261613534393439663664326336353338393439336137386662316137666236636337
+32326336396430633136333064383164373033366230333832333564616364663931653233333233
+62353264343865663865323461643032633465336564646161303039356266303738306435353131
+30376431616631613463313666383664343962306265613361376365353361303162653834623631
+33643762306232636134666366373637353234353265303437306261383861333235383530383638
+62353338323535333438376335636339386161326564623037323861343134396637366362646335
+66633865386339666265396438353362333463376361306666313331353063313331636539343835
+66326661386532343865653365356531663365663865666439653039643333363363653838616436
+38333037353333373866316333613538373263386334626665363239353162376335373238613737
+31616465326138663934356530353263653133636232396134316163343131316664633964643437
+37353937653665326638663631383733646563336162643361643366633564663439396639373966
+66356163343731353430626537326466393538363939313134343464643666323037356639323538
+35626137303439316233313664326535396234326432396132646361663936636362626232383530
+37366334333035656638383161663732393864333562373761303031353262303666626436373065
+66396233393864373463363065373461353538626135663937656330326632663863353438643838
+63663438356663313039616135393833623838366530353735333161663739366431393139623737
+63306464353039323065623765363665663266393934653962303761383362646364373239313062
+38353737663434646138303562303835373439653137656234653333313234366436623963386636
+66313837393636373537663030393331613633306531306339306261636366343362333736363465
+32316662666664636437393736383130663235373266393263623131643339323266633633336334
+30643737396364303462363262653332346637643466323339633435323436366430626339393537
+633439343732646238343833663731646631
diff --git a/lmn-www-server.yml b/lmn-www-server.yml
new file mode 100644
index 0000000..fb751fc
--- /dev/null
+++ b/lmn-www-server.yml
@@ -0,0 +1,43 @@
+## This playbook deploys a FvS web server machine.
+---
+- name: apply configuration to the web server
+ hosts: all
+ remote_user: ansible
+ become: yes
+ pre_tasks:
+ - pause:
+ prompt: "Enter global-admin AD password. Leave empty to skip domain join"
+ echo: false
+ register: adpw
+ no_log: true
+ when: "ansible_cmdline.adpw is not defined"
+ vars:
+ domain: "pn.steinbeis.schule"
+ extra_pkgs:
+ - vim
+ - apache2
+ - python3-flask
+
+ extra_pkgs_bpo: [ ] # [ libreoffice ]
+
+ roles:
+ - up2date_debian
+ - lmn_sssd
+ - kerberize
+
+ tasks:
+ - name: Override home dir location
+ lineinfile:
+ dest: /etc/sssd/sssd.conf
+ line: override_homedir = /home/%u
+
+ - name: enable pam_mkhomedir.so
+ lineinfile:
+ dest: /etc/pam.d/common-session
+ line: "session optional pam_mkhomedir.so umask=0026"
+ insertbefore: "session optional pam_mount.so"
+
+ - name: enable apache mod userdir
+ apache2_module:
+ state: present
+ name: userdir
diff --git a/roles/kerberize/handlers/main.yml b/roles/kerberize/handlers/main.yml
new file mode 100644
index 0000000..3ac7e3e
--- /dev/null
+++ b/roles/kerberize/handlers/main.yml
@@ -0,0 +1,5 @@
+- name: reload sshd
+ systemd:
+ name: sshd
+ state: reloaded
+ when: not run_in_installer|default(false)|bool
diff --git a/roles/kerberize/tasks/main.yml b/roles/kerberize/tasks/main.yml
new file mode 100644
index 0000000..ee17a3e
--- /dev/null
+++ b/roles/kerberize/tasks/main.yml
@@ -0,0 +1,45 @@
+- name: Install kerberos packages
+ apt:
+ name: krb5-user
+ state: latest
+
+- name: Kerberize sshd server
+ ansible.builtin.copy:
+ dest: /etc/ssh/sshd_config.d/kerberize.conf
+ content: |
+ GSSAPIAuthentication yes
+ notify: "reload sshd"
+
+- name: Kerberize ssh client, authenticate and delegate credentials
+ ansible.builtin.copy:
+ dest: /etc/ssh/ssh_config.d/kerberize.conf
+ content: |
+ GSSAPIAuthentication yes
+ GSSAPIDelegateCredentials yes
+
+- name: Check if firefox is available
+ stat: path=/etc/firefox-esr/firefox-esr.js
+ register: firefox
+
+- name: Kerberize firefox for sites in the local domain
+ lineinfile:
+ dest: /etc/firefox-esr/firefox-esr.js
+ line: "{{ item }}"
+ with_items:
+ - '// kerberize for sites in the local domain:'
+ - 'pref("network.negotiate-auth.delegation-uris", "{{ kerberize_uris | default(ansible_domain) }}");'
+ - 'pref("network.negotiate-auth.trusted-uris", "{{ kerberize_uris | default(ansible_domain) }}");'
+ when: firefox.stat.exists
+
+- name: Ensures /etc/chromium/policies/managed dir exists
+ file:
+ path: "/etc/chromium/policies/managed"
+ state: directory
+
+- name: Kerberize chromium for sites in the local domain
+ copy:
+ dest: /etc/chromium/policies/managed/idam.json
+ content: |
+ {
+ "AuthServerAllowlist": "idam.steinbeis.schule"
+ }
diff --git a/roles/lmn_fvs/files/bootorder.sh b/roles/lmn_fvs/files/bootorder.sh
new file mode 100644
index 0000000..a0fb6cd
--- /dev/null
+++ b/roles/lmn_fvs/files/bootorder.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/bash
+#
+# fix boot order: first PXE, then Debian
+#
+set -eu
+
+cur="$(efibootmgr | grep -Ei 'BootOrder:' | \
+ sed -E 's/^BootOrder: ([[:xdigit:]]{4}),.+$/\1/')"
+pxeip4="$(efibootmgr | grep -Ei "IP.*4" | \
+ sed -E 's/^Boot([[:xdigit:]]{4}).+$/\1/')"
+debian="$(efibootmgr | grep -Ei "debian" | \
+ sed -E 's/^Boot([[:xdigit:]]{4}).+$/\1/')"
+
+if [[ "$cur" != "$pxeip4" ]] && [[ -n "$pxeip4" ]] && [[ -n "$debian" ]] ; then
+ efibootmgr -o $pxeip4,$debian
+else
+ echo "Nothing to do."
+fi
diff --git a/roles/lmn_fvs/files/fvs-config.js b/roles/lmn_fvs/files/fvs-config.js
new file mode 100644
index 0000000..c51c702
--- /dev/null
+++ b/roles/lmn_fvs/files/fvs-config.js
@@ -0,0 +1,113 @@
+// configure plasma defaults
+
+function forEachWidgetInContainmentList(containmentList, callback) {
+ for (var containmentIndex = 0; containmentIndex < containmentList.length; containmentIndex++) {
+ var containment = containmentList[containmentIndex];
+
+ var widgets = containment.widgets();
+ for (var widgetIndex = 0; widgetIndex < widgets.length; widgetIndex++) {
+ var widget = widgets[widgetIndex];
+ callback(widget, containment);
+ if (widget.type === "org.kde.plasma.systemtray") {
+ systemtrayId = widget.readConfig("SystrayContainmentId");
+ if (systemtrayId) {
+ forEachWidgetInContainmentList([desktopById(systemtrayId)], callback)
+ }
+ }
+ }
+ }
+}
+
+function forEachWidget(callback) {
+ forEachWidgetInContainmentList(desktops(), callback);
+ forEachWidgetInContainmentList(panels(), callback);
+}
+
+function forEachWidgetByType(type, callback) {
+ forEachWidget(function(widget, containment) {
+ if (widget.type == type) {
+ callback(widget, containment);
+ }
+ });
+}
+
+function widgetSetProperty(args) {
+ if (!(args.widgetType && args.configGroup && args.configKey)) {
+ return;
+ }
+
+ forEachWidgetByType(args.widgetType, function(widget){
+ widget.currentConfigGroup = [args.configGroup];
+/*
+ //--- Delete when done debugging
+ const oldValue = widget.readConfig(args.configKey);
+ print("" + widget.type + " (id: " + widget.id + "):");
+ print("\t[" + args.configGroup + "] " + args.configKey + ": " +
+ oldValue + " => " + args.configValue + "\n");
+ //--- End Debug
+*/
+ widget.writeConfig(args.configKey, args.configValue);
+ });
+}
+
+// configure task bar starters:
+widgetSetProperty({
+ widgetType: "org.kde.plasma.icontasks",
+ configGroup: "General",
+ configKey: "launchers",
+ configValue: [
+ "applications:systemsettings.desktop",
+ "preferred://browser",
+ "applications:thunderbird.desktop",
+ "applications:libreoffice-startcenter.desktop",
+ "preferred://filemanager"
+ //"applications:org.kde.konsole.desktop",
+ //"applications:org.kde.discover.desktop"
+ ],
+
+});
+
+// kickoff is the default menu:
+/* this does not work (anymore?)
+widgetSetProperty({
+ widgetType: "org.kde.plasma.kickoff",
+ configGroup: "General",
+ configKey: "favorites",
+ configValue: ["applications:libreoffice-startcenter.desktop",],
+});
+*/
+
+widgetSetProperty({
+ widgetType: "org.kde.plasma.kickoff",
+ configGroup: "General",
+ configKey: "systemFavorites",
+ configValue: ["reboot", "shutdown", "logout"],
+ //configValue: ["logout"],
+});
+
+
+// prepare a folder view on the desktop:
+/* 20230917 disabled for now
+var allDesktops = desktops();
+for (var desktopIndex = 0; desktopIndex < allDesktops.length; desktopIndex++) {
+ var d = allDesktops[desktopIndex];
+ d.addWidget("org.kde.plasma.folder", 50, 50, 456, 600)
+ print("Folder app generated!\n")
+}
+
+widgetSetProperty({
+ widgetType: "org.kde.plasma.folder",
+ configGroup: "General",
+ configKey: "url",
+ configValue: "/lmn/media/",
+});
+
+widgetSetProperty({
+ widgetType: "org.kde.plasma.folder",
+ configGroup: "General",
+ configKey: "labelMode",
+ configValue: "0",
+});
+*/
+
+// /usr/share/plasma/shells/org.kde.plasma.desktop/contents/updates/fvs-config.js
diff --git a/roles/lmn_fvs/files/lmn-dolphin.sh b/roles/lmn_fvs/files/lmn-dolphin.sh
new file mode 100644
index 0000000..7b5d3a3
--- /dev/null
+++ b/roles/lmn_fvs/files/lmn-dolphin.sh
@@ -0,0 +1,3 @@
+if [[ "$UID" -gt 10000 ]] && [[ ! -f ~/.local/share/user-places.xbel.lmn ]] ; then
+ (sleep 30 ; lmn-patch-dolphin.sh) &
+fi
diff --git a/roles/lmn_fvs/files/lmn-patch-dolphin.sh b/roles/lmn_fvs/files/lmn-patch-dolphin.sh
new file mode 100755
index 0000000..7e7c8e2
--- /dev/null
+++ b/roles/lmn_fvs/files/lmn-patch-dolphin.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# patch 'Tausch' and 'Nextcloud' into dolphin's bookmarks
+#
+set -eu
+
+file="${1:-$HOME/.local/share/user-places.xbel}"
+
+[[ -e "$file" ]] || exit 0
+
+if grep -q "Tausch\|Nextcloud" "$file" ; then
+ echo "Your Dolphin seems to already contain 'Tausch' and/or 'Nextcloud'." | tee "$file.lmn"
+ exit 0
+fi
+
+id="$(grep ID "$file" | sed -E "s|^.+ID>([[:digit:]]+)/([[:digit:]]+)true
+
+
+
++
++ Tausch
++
++
++
++
++
++ $IDENTITY/${NUM1}
++ true
++
++
++
++
++ Nextcloud
++
++
++
++
++
++ $IDENTITY/${NUM2}
++ true
++
++
++
+
+ Network
+
+
+
+"
+
+echo "$patch" | patch -z '.lmn' --fuzz=0 --backup "$file"
diff --git a/roles/lmn_fvs/files/lmn-reset-dolphin.sh b/roles/lmn_fvs/files/lmn-reset-dolphin.sh
new file mode 100755
index 0000000..10f6e75
--- /dev/null
+++ b/roles/lmn_fvs/files/lmn-reset-dolphin.sh
@@ -0,0 +1,222 @@
+#!/bin/bash
+
+sed -e "s|HOME|/${HOME##/srv/samba/schools/default-school/}|g" -e "s|USER|${USER}|g" > ~/.local/share/user-places.xbel <
+
+
+
+
+ 4
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ true
+ false
+
+
+
+ Home
+
+
+
+
+
+ 1682498425/0
+ true
+
+
+
+
+ Desktop
+
+
+
+
+
+ 1682498425/1
+ true
+
+
+
+
+ Documents
+
+
+
+
+
+ 1682498425/2
+ true
+
+
+
+
+ Downloads
+
+
+
+
+
+ 1682498425/3
+ true
+
+
+
+
+ Music
+
+
+
+
+
+ 1682498425/6
+ true
+
+
+
+
+ Pictures
+
+
+
+
+
+ 1682498425/7
+ true
+
+
+
+
+ Videos
+
+
+
+
+
+ 1682498425/8
+ true
+
+
+
+
+ Tausch
+
+
+
+
+
+ 1682498425/9
+ true
+
+
+
+
+ Nextcloud
+
+
+
+
+
+ 1682498425/10
+ true
+
+
+
+
+ Network
+
+
+
+
+
+ 1682498425/4
+ true
+
+
+
+
+ Trash
+
+
+
+
+
+ 1682498425/5
+ true
+
+
+
+
+ Recent Files
+
+
+
+
+
+ 1682498425/9
+ true
+
+
+
+
+ Recent Locations
+
+
+
+
+
+ 1682498425/10
+ true
+
+
+
+
+
+
+ /org/kde/fstab///server/default-school/:/srv/samba/schools/default-school
+ true
+ true
+
+
+
+
+
+
+ /org/kde/fstab///server/default-school/:/lmn/media/USER/home
+ true
+ true
+
+
+
+
+
+
+ /org/kde/fstab///server/sysvol/:/srv/samba/USER/sysvol
+ true
+ true
+
+
+
+
+
+
+ /org/kde/fstab///server/default-school/:/lmn/media/USER/share
+ true
+ true
+
+
+
+
+
+
+ /org/freedesktop/UDisks2/block_devices/sda2
+ true
+
+
+
+
+EOF
diff --git a/roles/lmn_fvs/files/policies.json b/roles/lmn_fvs/files/policies.json
new file mode 100644
index 0000000..08797b5
--- /dev/null
+++ b/roles/lmn_fvs/files/policies.json
@@ -0,0 +1,82 @@
+{
+ "policies": {
+ "Proxy": {
+ "Mode": "system"
+ },
+ "OverrideFirstRunPage": "https://www.steinbeisschule-reutlingen.de",
+ "Homepage": {
+ "URL": "https://www.debian.org",
+ "Locked": false,
+ "StartPage": "previous-session"
+ },
+ "DisplayBookmarksToolbar": true,
+ "ManagedBookmarks": [
+ {
+ "toplevel_name": "FvS-Reutlingen"
+ },
+ {
+ "url": "https://www.steinbeisschule-reutlingen.de",
+ "name": "FvS-Homepage"
+ },
+ {
+ "url": "https://server.pn.steinbeis.schule",
+ "name": "Schulkonsole/Passwort ändern"
+ },
+ {
+ "url": "https://dw.steinbeis.schule",
+ "name": "FvS-Hilfesystem"
+ },
+ {
+ "url": "https://mail.steinbeis.schule",
+ "name": "FvS-eMail"
+ },
+ {
+ "url": "https://nc.steinbeis.schule",
+ "name": "FvS-Nextcloud"
+ },
+ {
+ "url": "https://moodle.steinbeis.schule",
+ "name": "FvS-Moodle"
+ },
+ {
+ "name": "Debian",
+ "children": [
+ {
+ "url": "https://www.debian.org",
+ "name": "Debian Homepage"
+ },
+ {
+ "url": "https://wiki.debian.org",
+ "name": "Debian Wiki"
+ },
+ {
+ "name": "Debian LAN/Live",
+ "children": [
+ {
+ "url": "https://salsa.debian.org/andi/debian-lan-ansible",
+ "name": "Debian LAN Ansible"
+ },
+ {
+ "url": "https://wiki.debian.org/DebianLive",
+ "name": "Debian Live"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "SearchEngines": {
+ "Add": [
+ {
+ "Name": "Startpage",
+ "URLTemplate": "https://www.startpage.com/sp/search?query={searchTerms}",
+ "Method": "GET",
+ "IconURL": "https://www.startpage.com/sp/cdn/favicons/favicon--default.ico",
+ "Alias": "sp",
+ "Description": "Startpage Search Engine"
+ }
+ ],
+ "Default": "Startpage"
+ }
+ }
+}
diff --git a/roles/lmn_fvs/files/pwroff b/roles/lmn_fvs/files/pwroff
new file mode 100755
index 0000000..84ee525
--- /dev/null
+++ b/roles/lmn_fvs/files/pwroff
@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# logout idle users and shutdown machine
+#
+set -eu
+
+action="systemctl poweroff"
+uptime=$(cat /proc/uptime | cut -f1 -d.)
+maxidle=3600 ## seconds
+
+u=($(loginctl list-users --no-legend | sort -hr | head -1))
+una=${u[1]:-''}
+uid=${u[0]:-''}
+
+talk2dbus() {
+ local display=":$(ls /tmp/.X11-unix/* | sed 's#/tmp/.X11-unix/X##' | head -n 1)"
+ sudo -u $una DISPLAY=$display \
+ DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus "$@"
+}
+
+########
+
+## shutdown if nobody is loged in:
+if [[ -z "$una" ]] || [[ $uid -lt 1000 ]] ; then
+ exec $action
+fi
+
+# FIXME: find idle time independent of running screensaver
+if ! t=$(talk2dbus qdbus org.kde.screensaver /ScreenSaver GetActiveTime) ; then
+ echo "No graphical logins found."
+else
+ idle=$(( t / 1000 ))
+ if [[ $idle -gt $maxidle ]] && [[ ! -d "/srv/samba/schools/default-school/teachers/" ]] ; then
+ talk2dbus notify-send -i system-shutdown -u critical -a 'Important System Information' \
+ 'Please log out, the system will shut down soon!' \
+ 'There has been no activity for too long.'
+ ## shutdown:
+ #talk2dbus qdbus org.kde.ksmserver /KSMServer logout 1 2 0
+ ## logout:
+ talk2dbus qdbus org.kde.ksmserver /KSMServer logout 1 0 0 || \
+ loginctl terminate-user $una
+ echo "Log-out user $una after being idle for $idle seconds."
+ else
+ echo "The user $una has been idle for $idle seconds."
+ fi
+fi
+
+#w -s | grep tty | sed "s/[[:space:]]\+/ /g" | cut -f4 -d ' '
diff --git a/roles/lmn_fvs/files/pwroff.service b/roles/lmn_fvs/files/pwroff.service
new file mode 100644
index 0000000..1fe36d3
--- /dev/null
+++ b/roles/lmn_fvs/files/pwroff.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Run pwroff script
+
+[Service]
+Type=simple
+ExecStart=/usr/local/sbin/pwroff
diff --git a/roles/lmn_fvs/files/pwroff.timer b/roles/lmn_fvs/files/pwroff.timer
new file mode 100644
index 0000000..0d11a63
--- /dev/null
+++ b/roles/lmn_fvs/files/pwroff.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Run pwroff script every 15 min after 90 min uptime
+
+[Timer]
+OnBootSec=90min
+OnUnitActiveSec=15min
+
+[Install]
+WantedBy=timers.target
diff --git a/roles/lmn_fvs/files/reporter b/roles/lmn_fvs/files/reporter
new file mode 100755
index 0000000..f665a6f
--- /dev/null
+++ b/roles/lmn_fvs/files/reporter
@@ -0,0 +1,33 @@
+#!/usr/bin/bash
+#
+# Send stdout of some commands to monitoring server.
+# Collect the reports with 'nc -u -k -l 1234' on 'sendto'.
+# Use /bin/nc.openbsd, /bin/nc.traditional seems not to work.
+#
+set -eu
+
+sendto="collector.steinbeis.schule 1234"
+n=0
+
+cmds=(
+ 'uname -a'
+ 'tail -1 /root/.ansible/stamps'
+ 'ip route list default'
+ 'ip link show | \
+ sed -nE -e "s/^[2-9]: (\S+): .+/\1/p" -e "s/.+ether ([0-9a-f:]+) .+/\1/p" | \
+ paste - -'
+)
+# 'w'
+# 'uptime'
+# 'ls -d --full-time /home/ansible/.ansible/tmp/'
+# 'ip addr show'
+# 'apt list --upgradeable -o Apt::Cmd::Disable-Script-Warning=true'
+
+r="$HOSTNAME ------- $(date --rfc-3339=seconds) -------
+$(for c in "${cmds[@]}" ; do
+ n=$(( n + 1 ))
+ echo -n "$n"
+ eval "$c" | sed 's/^/\t/'
+done | sed "s/^/$HOSTNAME /")
+## -------------------------------------------------"
+echo "$r" | nc -w 1 -u $sendto
diff --git a/roles/lmn_fvs/files/reporter.service b/roles/lmn_fvs/files/reporter.service
new file mode 100644
index 0000000..09f6378
--- /dev/null
+++ b/roles/lmn_fvs/files/reporter.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Run reporting script
+
+[Service]
+Type=simple
+ExecStart=/usr/local/sbin/reporter
diff --git a/roles/lmn_fvs/files/reporter.timer b/roles/lmn_fvs/files/reporter.timer
new file mode 100644
index 0000000..dbfda49
--- /dev/null
+++ b/roles/lmn_fvs/files/reporter.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Run reporter script every 15 min
+
+[Timer]
+OnBootSec=5min
+OnUnitActiveSec=15min
+
+[Install]
+WantedBy=timers.target
diff --git a/roles/lmn_fvs/tasks/main.yml b/roles/lmn_fvs/tasks/main.yml
new file mode 100644
index 0000000..1dcfcc6
--- /dev/null
+++ b/roles/lmn_fvs/tasks/main.yml
@@ -0,0 +1,258 @@
+---
+- name: Preseed wireshark to allow users sniffing packets
+ ansible.builtin.debconf:
+ name: wireshark-common
+ question: wireshark-common/install-setuid
+ value: 'true'
+ vtype: boolean
+
+- name: Preseed ttf-mscorefonts-installer
+ ansible.builtin.debconf:
+ name: ttf-mscorefonts-installer
+ question: msttcorefonts/dlurl
+ value: "{{ mirror_msfonts }}"
+ vtype: string
+ when: mirror_msfonts is defined and mirror_msfonts | length > 0
+
+
+- name: Install desktop EDU packages and some more
+ apt:
+ name:
+ - atftp
+ - calligraplan
+ - cmake ## for kdevelop
+ - codelite
+ - codelite-plugins
+ - curl
+ - elpa-color-theme-modern
+ - elpa-magit
+ - emacs
+ - filezilla
+ - freeplane
+ - git
+ - gitg
+ - gitk
+ - htop
+ - jupyter
+ - kdevelop
+ - kdevelop-php
+ - kdevelop-python
+ - krita
+ - libnotify-bin ## needed for pwroff script
+ - links2
+ - minder
+ - neovim
+ - net-tools
+ - netcat-openbsd
+ - nmap
+ - pdf-presenter-console
+ - php-cli
+ - pipx
+ - planner
+ - pulseview
+ - python3-websockets
+ - qpdfview
+ - shellcheck
+ - sigrok
+ - sigrok-cli
+ - texlive-latex-recommended
+ - tmux
+ - tree
+ - ttf-mscorefonts-installer
+ - twinkle
+ - unison-gtk
+ - w3m
+ - wireshark
+ - zulucrypt-gui
+ autoremove: true
+ state: latest
+ environment:
+ http_proxy: '' # this is needed to avoid ttf-mscorefonts-installer picking up aptcacher
+
+- name: Remove update notifications from plasma-discover
+ apt:
+ name:
+ - plasma-discover
+ autoremove: true
+ state: absent
+
+- name: Make sure wireshark works for all users after installation and upgrades
+ ansible.builtin.copy:
+ dest: /etc/apt/apt.conf.d/92wireshark4all
+ content: |
+ ## Modify permissions after installation/upgrade to allow all
+ ## users dumping packages on network interfaces for wireshark
+ DPkg::Post-Invoke {"/usr/bin/chmod o+x /usr/bin/dumpcap || true"; };
+
+
+- name: Create firefox policies directory
+ ansible.builtin.file:
+ path: /etc/firefox-esr/policies
+ state: directory
+ mode: '0755'
+
+- name: Create a symbolic link firefox to firefox-esr
+ ansible.builtin.file:
+ src: /etc/firefox-esr
+ dest: /etc/firefox
+ state: link
+
+- name: Copy firefox policy
+ ansible.builtin.copy:
+ src: policies.json
+ dest: /etc/firefox-esr/policies/
+
+
+- name: Copy some scripts
+ copy:
+ src: "{{ item }}"
+ dest: /usr/local/sbin/
+ mode: 0755
+ loop:
+ - pwroff
+ - bootorder.sh
+ - reporter
+
+- name: Provide services and timers for some scripts
+ copy:
+ src: "{{ item }}"
+ dest: "/etc/systemd/system/{{ item }}"
+ mode: 0644
+ loop:
+ - pwroff.service
+ - pwroff.timer
+ - reporter.service
+ - reporter.timer
+
+- name: Enable pwroff.timer
+ systemd:
+ name: "{{ item }}"
+ enabled: true
+ loop:
+ - pwroff.timer
+ - reporter.timer
+
+- name: PXE first boot order
+ command: /usr/local/sbin/bootorder.sh
+ register: cmd_result
+ changed_when: cmd_result.stdout is not search('Nothing to do.')
+ when: groups.PCroom is defined and inventory_hostname in groups.PCroom
+
+- name: Copy dolphin config scripts
+ ansible.builtin.copy:
+ src: "{{ item }}"
+ dest: /usr/local/bin/
+ mode: 0755
+ loop:
+ - lmn-reset-dolphin.sh
+ - lmn-patch-dolphin.sh
+
+- name: Configure KDE dolphin menu
+ ansible.builtin.copy:
+ src: lmn-dolphin.sh
+ dest: /etc/profile.d/
+
+
+- name: Copy fvs-config.js to configure plasma
+ ansible.builtin.copy:
+ src: fvs-config.js
+ dest: /usr/share/plasma/shells/org.kde.plasma.desktop/contents/updates/fvs-config.js
+ mode: 0644
+
+
+- name: Configure some KDE aspects
+ blockinfile:
+ path: /etc/xdg/kdeglobals
+ create: true
+ block: |
+ [KDE]
+ SingleClick=false
+
+ [KDE Action Restrictions][$i]
+ action/start_new_session=false
+ #action/switch_user=false
+ #action/lock_screen=false
+
+- name: Shut down when idle for too long
+ ansible.builtin.copy:
+ dest: /etc/xdg/powermanagementprofilesrc
+ content: |
+ [AC][SuspendSession][$i]
+ idleTime=7200000
+ suspendType=8
+
+- name: Start with empty session by default
+ ansible.builtin.copy:
+ dest: /etc/xdg/ksmserverrc
+ content: |
+ [General]
+ loginMode=emptySession
+
+- name: Fix primary screen for class room PCs with projector
+ block:
+ - name: Switch projector off for login
+ lineinfile:
+ dest: /usr/share/sddm/scripts/Xsetup
+ line: 'xrandr --output {{ dual_screen[0] }} --off'
+ - name: Deploy fix-screen script
+ ansible.builtin.template:
+ src: lmn-fix-screen.j2
+ dest: /usr/local/bin/lmn-fix-screen
+ mode: '0755'
+ - name: Deploy fix-screen autostarter
+ ansible.builtin.copy:
+ dest: /etc/xdg/autostart/lmn-fix-screen.desktop
+ content: |
+ [Desktop Entry]
+ Name=fix-screen
+ Exec=lmn-fix-screen
+ Type=Application
+ NoDisplay=true
+ when: dual_screen is defined
+
+#- name: Avoid starting kscreen (confusing autodetection)
+# ansible.builtin.copy:
+# dest: /etc/xdg/kded5rc
+# content: |
+# [Module-kscreen]
+# autoload=false
+#
+#- name: Disable automatic lock screen and user specific modifications
+# ansible.builtin.copy:
+# path: /etc/xdg/kscreenlockerrc
+# content: |
+# [Daemon][$i]
+# Autolock=false
+# LockOnResume=false
+#
+
+- name: Download libdvdcss from mirror
+ ansible.builtin.get_url:
+ url: "{{ mirror_dvdcss }}/libdvdcss.so.2.2.0"
+ dest: /usr/lib/x86_64-linux-gnu/libdvdcss.so.2.2.0
+ use_proxy: False
+ when: mirror_dvdcss is defined and mirror_dvdcss | length > 0
+
+- name: Link library so name
+ ansible.builtin.file:
+ src: libdvdcss.so.2.2.0
+ dest: /usr/lib/x86_64-linux-gnu/libdvdcss.so.2
+ state: link
+ when: mirror_dvdcss is defined and mirror_dvdcss | length > 0
+
+- name: Patch sddm login screen to show hostname
+ blockinfile:
+ path: /usr/share/sddm/themes/debian-breeze/Main.qml
+ marker: // {mark} ANSIBLE MANAGED BLOCK
+ insertbefore: '\s+//Footer'
+ block: |
+ Text {
+ id: hostname
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.topMargin: 10
+ anchors.rightMargin: 15
+ color: "#ffffff"
+ text: sddm.hostName + " | <{{ ansible_date_time['date'] }}>"
+ font.pointSize: config.fontSize
+ }
diff --git a/roles/lmn_fvs/templates/lmn-fix-screen.j2 b/roles/lmn_fvs/templates/lmn-fix-screen.j2
new file mode 100644
index 0000000..2aa7cc2
--- /dev/null
+++ b/roles/lmn_fvs/templates/lmn-fix-screen.j2
@@ -0,0 +1,10 @@
+#!/usr/bin/bash
+#
+# Set the primary screen after login
+#
+set -eu
+
+sleep 5
+if [[ "$XDG_SESSION_TYPE" = wayland ]] ; then
+ kscreen-doctor output.{{ dual_screen[1] }}.priority.1
+fi
diff --git a/roles/lmn_kde/files/lmn-fix-dolphin.sh b/roles/lmn_kde/files/lmn-fix-dolphin.sh
new file mode 100755
index 0000000..b7ec7e3
--- /dev/null
+++ b/roles/lmn_kde/files/lmn-fix-dolphin.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/bash
+#
+# Dolphin keeps old paths after modifications.
+# Run with '--do-it' to really make the change.
+#
+set -eu
+
+do="${1:-}"
+
+bmk=".local/share/user-places.xbel"
+rt="/srv/samba/schools/default-school/students"
+
+extract() {
+ local grp="$1"
+ grp="${grp##*${rt}/}"
+ grp="${grp%%/*}"
+ echo $grp
+}
+
+for f in $(find $rt/*/*/$bmk) ; do
+ cor="$(extract $f)"
+ for l in "$(grep "$rt" "$f")" ; do
+ fnd="$(extract "$l")"
+ if [[ "$cor" != "$fnd" ]] ; then
+ echo "Check ${f##*${rt}/}: '$cor' != '$fnd'."
+ if [[ "$do" = "--do-it" ]] ; then
+ sed -i.lmn-fix-path "s|$rt/$fnd|$rt/$cor|g" "$f"
+ break
+ fi
+ fi
+ done
+done
diff --git a/roles/lmn_kde/handlers/main.yml b/roles/lmn_kde/handlers/main.yml
new file mode 100644
index 0000000..e2b8cdf
--- /dev/null
+++ b/roles/lmn_kde/handlers/main.yml
@@ -0,0 +1,2 @@
+- name: Run update-grub
+ command: update-grub
diff --git a/roles/lmn_kde/tasks/main.yml b/roles/lmn_kde/tasks/main.yml
new file mode 100644
index 0000000..99c5c84
--- /dev/null
+++ b/roles/lmn_kde/tasks/main.yml
@@ -0,0 +1,170 @@
+---
+- name: Install desktop and educational packages
+ apt:
+ name:
+ - akonadi-backend-sqlite
+ - arduino
+ - bluefish
+ - calligra
+ - codeblocks
+ - dia
+ - flameshot
+ - freecad
+ - fritzing
+ - ghex
+ - gimp
+ - inkscape
+ - kde-full
+ - keepassxc
+ - librecad
+ - mu-editor
+ - openboard
+ - qtcreator
+ - spyder
+ - sqlite3
+ - sqlitebrowser
+ - task-german-desktop
+ - task-german-kde-desktop
+ - task-kde-desktop
+ - thonny
+ - thunderbird-l10n-de
+ - vlc
+ - vym
+ - webext-privacy-badger
+ - webext-ublock-origin-chromium
+ - webext-ublock-origin-firefox
+ - xdg-desktop-portal-kde
+ - xdg-desktop-portal-wlr # share screen in browser
+ - xournalpp
+ autoremove: true
+ state: latest
+
+- name: Add {{ ansible_distribution_release }}-backports
+ apt_repository:
+ repo: deb http://deb.debian.org/debian/ {{ ansible_distribution_release }}-backports main non-free-firmware
+ state: present
+ update_cache: true
+
+- name: Install extra packages from backports
+ apt:
+ name:
+ - filius
+ - kicad
+ - kicad-doc-de
+ - libreoffice
+ - libreoffice-l10n-de
+ state: latest # noqa package-latest
+ autoremove: true
+ default_release: "{{ ansible_distribution_release }}-backports"
+
+
+- name: Create akonadi config dir
+ ansible.builtin.file:
+ path: /etc/xdg/akonadi/
+ state: directory
+ mode: '0755'
+
+- name: Use sqlite in akonadi
+ blockinfile:
+ path: /etc/xdg/akonadi/akonadiserverrc
+ create: true
+ block: |
+ [%General]
+ Driver=QSQLITE3
+
+## Akonadi complains if not set:
+- name: Add home dirs to apparmor
+ lineinfile:
+ dest: /etc/apparmor.d/tunables/home.d/ubuntu
+ line: >-
+ @{HOMEDIRS}+=/srv/samba/schools/default-school/teachers/
+ /srv/samba/schools/default-school/students/*/
+ /srv/samba/schools/default-school/examusers/
+
+
+- name: tune SDDM login
+ blockinfile:
+ path: /etc/sddm.conf
+ create: true
+ block: |
+ [Users]
+ MaximumUid=999
+ RememberLastUser=false
+ RememberLastSession=false
+
+- name: Enable wake-on-lan for all ethernet connections
+ ansible.builtin.copy:
+ dest: /etc/NetworkManager/conf.d/wake-on-lan.conf
+ content: |
+ [connection]
+ ethernet.wake-on-lan=64
+
+- name: Create directory to avoid suspend
+ ansible.builtin.file:
+ path: /etc/systemd/sleep.conf.d/
+ state: directory
+ mode: '0755'
+
+- name: Avoid suspending
+ blockinfile:
+ path: /etc/systemd/sleep.conf.d/nosuspend.conf
+ create: true
+ block: |
+ [Sleep]
+ AllowSuspend=no
+ AllowHibernation=no
+ AllowSuspendThenHibernate=no
+ AllowHybridSleep=no
+
+- name: Deploy dolphin script
+ copy:
+ src: lmn-fix-dolphin.sh
+ dest: /usr/local/bin/
+ mode: '0755'
+
+################# general settings ##################
+- name: Enable boot splash screen
+ replace:
+ dest: "/etc/default/grub"
+ regexp: '"quiet"$'
+ replace: '"quiet splash"'
+ notify: Run update-grub
+
+- name: Protect editing grub menu entries
+ blockinfile:
+ path: /etc/grub.d/40_custom
+ block: |
+ set superusers='root'
+ export superusers
+ password_pbkdf2 root {{ grub_pwd }}
+ notify: Run update-grub
+
+- name: Allow booting grub menu entries
+ lineinfile:
+ dest: /etc/grub.d/10_linux
+ line: CLASS="${CLASS} --unrestricted"
+ insertafter: '^CLASS=.*'
+ firstmatch: true
+ notify: Run update-grub
+
+- name: Disable Grub submenus
+ lineinfile:
+ dest: /etc/default/grub
+ line: 'GRUB_DISABLE_SUBMENU=true'
+ insertafter: '^GRUB_TIMEOUT=.*'
+ notify: Run update-grub
+
+- name: Grub timeout
+ lineinfile:
+ dest: /etc/default/grub
+ regexp: '^(GRUB_TIMEOUT=).*'
+ line: '\g<1>1'
+ backrefs: yes
+ notify: Run update-grub
+
+- name: Keyboard compose key
+ lineinfile:
+ dest: /etc/default/keyboard
+ regexp: '^(XKBOPTIONS=).*'
+ line: '\1"compose:caps"'
+ backrefs: yes
diff --git a/roles/lmn_localuser/tasks/main.yml b/roles/lmn_localuser/tasks/main.yml
new file mode 100644
index 0000000..6b619f6
--- /dev/null
+++ b/roles/lmn_localuser/tasks/main.yml
@@ -0,0 +1,28 @@
+---
+- name: Mount tmpfs on /home/{{ localuser }}
+ ansible.posix.mount:
+ name: /home/{{ localuser }}
+ src: tmpfs
+ fstype: tmpfs
+ opts: uid=1001,gid=1001,mode=755,size=4G
+ state: mounted
+
+- name: Add local guest user
+ ansible.builtin.user:
+ name: "{{ localuser }}"
+ comment: "Local Guest User,,,"
+ shell: /bin/bash
+ uid: 1001
+ password_expire_min: 99999
+ createhome: false
+ password: "{{ localuser_pwd }}"
+
+- name: Prepare generator for local guest user
+ ansible.builtin.copy:
+ dest: /etc/systemd/user-environment-generators/60-guest-user.sh
+ content: |
+ #!/usr/bin/bash
+ set -eu
+ [[ "$UID" -ne 1001 ]] && exit 0
+ cp -r -n /etc/skel/.* "$HOME"
+ mode: "0755"
diff --git a/roles/lmn_mount/defaults/main.yml b/roles/lmn_mount/defaults/main.yml
new file mode 100644
index 0000000..cf3a823
--- /dev/null
+++ b/roles/lmn_mount/defaults/main.yml
@@ -0,0 +1,2 @@
+smb_server: "server"
+smb_share: "default-school/"
diff --git a/roles/lmn_mount/files/lmn-linkhome.sh b/roles/lmn_mount/files/lmn-linkhome.sh
new file mode 100644
index 0000000..59e0800
--- /dev/null
+++ b/roles/lmn_mount/files/lmn-linkhome.sh
@@ -0,0 +1,4 @@
+if [[ "${UID}" -gt 60000 ]]; then
+ [[ -L "/lmn/media/${USER}/share" ]] || ln -s .default-school/share "/lmn/media/${USER}/share"
+ [[ -L "/lmn/media/${USER}/home" ]] || ln -s ".default-school/${HOME##/srv/samba/schools/default-school/}" "/lmn/media/${USER}/home"
+fi
diff --git a/roles/lmn_mount/files/lmn-mounthome.sh b/roles/lmn_mount/files/lmn-mounthome.sh
new file mode 100644
index 0000000..44f81e1
--- /dev/null
+++ b/roles/lmn_mount/files/lmn-mounthome.sh
@@ -0,0 +1,3 @@
+if [[ "${UID}" -gt 60000 ]]; then
+ sudo /usr/local/bin/mounthome.sh &
+fi
diff --git a/roles/lmn_mount/tasks/main.yml b/roles/lmn_mount/tasks/main.yml
new file mode 100644
index 0000000..c2cf63c
--- /dev/null
+++ b/roles/lmn_mount/tasks/main.yml
@@ -0,0 +1,102 @@
+---
+- name: Install needed packages
+ apt:
+ name:
+ - libpam-mount
+ - cifs-utils
+ - nfs-common
+ - hxtools
+ - davfs2
+ state: latest
+
+- name: Configure pam_mount for Webdav Nextcloud
+ blockinfile:
+ dest: /etc/security/pam_mount.conf.xml
+ marker: ""
+ block: |
+ rootansibleDebian-gdmsddm{{ localuser }}
+
+ insertafter: ""
+ when: web_dav is defined and web_dav | length > 0
+
+- name: Configure pam_mount for LMN homes
+ blockinfile:
+ dest: /etc/security/pam_mount.conf.xml
+ marker: ""
+ block: |
+ rootansibleDebian-gdmsddm{{ localuser }}
+
+ insertafter: ""
+
+- name: Prepare mount point for homes
+ ansible.builtin.file:
+ path: /srv/samba/schools/default-school/
+ state: directory
+ mode: '0755'
+
+- name: Prepare persistent user cache base directory
+ ansible.builtin.file:
+ path: /var/cache/user/
+ state: directory
+ mode: '1777'
+
+- name: Create user-environment-generator directory
+ ansible.builtin.file:
+ path: /etc/systemd/user-environment-generators/
+ state: directory
+
+- name: Prepare generator for persistent user cache directory
+ ansible.builtin.copy:
+ dest: /etc/systemd/user-environment-generators/50-xdg-cache-home.sh
+ content: |
+ #!/usr/bin/bash
+ set -eu
+ ## local users do not need the extra cache dir:
+ [[ "$UID" -le 60000 ]] && exit 0
+ cp -r -n /etc/skel/.* "$HOME"
+ DIR="/var/cache/user/${UID}/"
+ [[ -d "$DIR" ]] || mkdir -m 0700 "$DIR"
+ echo XDG_CACHE_HOME="$DIR"
+ echo JUPYTER_ALLOW_INSECURE_WRITES=1
+ mode: "0755"
+
+
+- name: Clean up all user processes after logout
+ ansible.builtin.replace:
+ path: /etc/security/pam_mount.conf.xml
+ regexp: '^()$'
+ replace: '\n'
+
+- name: Kill all user processes on logout
+ ansible.builtin.lineinfile:
+ path: /etc/systemd/logind.conf
+ line: KillUserProcesses=yes
+ insertafter: '#KillUserProcesses=no'
+
+- name: Bind mount /lmn/media with nosuid directory
+ ansible.posix.mount:
+ src: /lmn/media
+ path: /lmn/media
+ opts: nosuid,bind
+ state: present
+ fstype: none
+
+- name: Mount NFSv4 home directory
+ ansible.posix.mount:
+ src: server:/default-school
+ path: /srv/samba/schools/default-school
+ opts: sec=krb5p,_netdev,x-systemd.automount,x-systemd.idle-timeout=60
+ state: present
+ fstype: nfs4
+ when: nfs4
diff --git a/roles/lmn_network/tasks/main.yml b/roles/lmn_network/tasks/main.yml
new file mode 100644
index 0000000..0c7176a
--- /dev/null
+++ b/roles/lmn_network/tasks/main.yml
@@ -0,0 +1,29 @@
+---
+- name: Deploy http proxy config
+ copy:
+ dest: /etc/environment.d/10-lmn-proxy.conf
+ content: |
+ http_proxy="{{ proxy }}"
+ https_proxy="{{ proxy }}"
+ ftp_proxy="{{ proxy }}"
+ no_proxy="{{ no_proxy }}"
+
+- name: Set aptcache
+ ansible.builtin.copy:
+ dest: /etc/apt/apt.conf
+ content: >
+ {{ apt_conf }}
+
+- name: Set NTP server
+ ansible.builtin.lineinfile:
+ path: /etc/systemd/timesyncd.conf
+ insertafter: '^#NTP='
+ line: NTP={{ ntp_serv }}
+
+- name: Add proposed-updates repository
+ apt_repository:
+ repo: >
+ deb http://deb.debian.org/debian/ {{ ansible_distribution_release }}-proposed-updates
+ main non-free-firmware
+ state: present
+ when: groups.R202 is defined and inventory_hostname in groups.R202
diff --git a/roles/lmn_networkd/tasks/main.yml b/roles/lmn_networkd/tasks/main.yml
new file mode 100644
index 0000000..31e9742
--- /dev/null
+++ b/roles/lmn_networkd/tasks/main.yml
@@ -0,0 +1,90 @@
+---
+# temporary disable network manager
+- name: Use iwd but ignore interfaces managed by systemd-networkd (wlan0,en*)
+ blockinfile:
+ dest: /etc/NetworkManager/NetworkManager.conf
+ block: |
+ [device]
+ match-device=interface-name:wlx*
+ wifi.backend=iwd
+ [connection]
+ match-device=interface-name:wlx*
+ ipv4.route-metric=2048
+ [keyfile]
+ unmanaged-devices=interface-name:wlan0;interface-name:en*;interface-name:vm*
+
+- name: Enable Networkmanager
+ ansible.builtin.systemd:
+ name: NetworkManager.service
+ #state: started
+ enabled: true
+
+- name: Configure systemd-networkd virbr1.netdev
+ ansible.builtin.copy:
+ dest: "/etc/systemd/network/30-{{ item }}.netdev"
+ content: |
+ [NetDev]
+ Name={{ item }}
+ Kind=bridge
+ loop:
+ - virbr1
+ - virbr2
+
+- name: Set MAC-Address of virtio1 to ethernet nic
+ ansible.builtin.lineinfile:
+ path: /etc/systemd/network/30-virbr1.netdev
+ line: "MACAddress={{ ansible_facts[ansible_interfaces | select('search', '^en.*') | first].macaddress }}"
+ when: ansible_interfaces | select('search', '^en.*')
+
+- name: Configure systemd-networkd ethernet.network
+ ansible.builtin.copy:
+ dest: /etc/systemd/network/40-ethernet.network
+ content: |
+ [Match]
+ Name=enp*
+ [Network]
+ Bridge=virbr1
+
+- name: Configure systemd-networkd ethernet-usb.network
+ ansible.builtin.copy:
+ dest: /etc/systemd/network/40-ethernet-usb.network
+ content: |
+ [Match]
+ Name=enx*
+ [Network]
+ Bridge=virbr2
+
+- name: Configure systemd-networkd virbr1.network
+ ansible.builtin.copy:
+ dest: /etc/systemd/network/50-virbr1.network
+ content: |
+ [Match]
+ Name=virbr1
+ [Network]
+ DHCP=yes
+ [DHCPv4]
+ UseDomains=true
+ RouteMetric=512
+
+- name: Configure systemd-networkd virbr2.network
+ ansible.builtin.copy:
+ dest: /etc/systemd/network/50-virbr2.network
+ content: |
+ [Match]
+ Name=virbr2
+ [Network]
+ DHCP=yes
+ [DHCPv4]
+ UseDomains=false
+ RouteMetric=2048
+
+- name: Configure systemd-networkd wlan.network
+ ansible.builtin.copy:
+ dest: /etc/systemd/network/60-wlan0-dhcp.network
+ content: |
+ [Match]
+ Name=wlan0
+ [Network]
+ DHCP=yes
+ [DHCPv4]
+ UseDomains=true
diff --git a/roles/lmn_printer/files/90-lmn-install-printers b/roles/lmn_printer/files/90-lmn-install-printers
new file mode 100644
index 0000000..8b32a2b
--- /dev/null
+++ b/roles/lmn_printer/files/90-lmn-install-printers
@@ -0,0 +1,3 @@
+%examusers ALL=(root) NOPASSWD: /usr/local/bin/install-printers.sh
+%role-student ALL=(root) NOPASSWD: /usr/local/bin/install-printers.sh
+%role-teacher ALL=(root) NOPASSWD: /usr/local/bin/install-printers.sh
diff --git a/roles/lmn_printer/tasks/main.yml b/roles/lmn_printer/tasks/main.yml
new file mode 100644
index 0000000..419edd9
--- /dev/null
+++ b/roles/lmn_printer/tasks/main.yml
@@ -0,0 +1,59 @@
+---
+- name: Install cups
+ apt:
+ name:
+ - cups
+ state: latest
+
+- name: Disable cups printer browsing
+ lineinfile:
+ dest: /etc/cups/cupsd.conf
+ regexp: '^(Browsing ).*'
+ line: '\1No'
+ backrefs: yes
+
+- name: Listen on VMBridge
+ lineinfile:
+ dest: /etc/cups/cupsd.conf
+ line: 'Listen 192.168.122.1:631'
+ insertafter: 'Listen localhost:631'
+ state: present
+
+- name: Allow access from localhost and from VM
+ blockinfile:
+ dest: /etc/cups/cupsd.conf
+ block: |
+ Allow localhost
+ Allow 192.168.122.0/24
+ insertafter: ""
+ marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item }}"
+ state: present
+ loop:
+ - "/"
+ - "/admin"
+
+- name: Disable cups-browsed
+ ansible.builtin.systemd:
+ name: cups-browsed.service
+ state: stopped
+ enabled: no
+
+- name: Install install-printers.sh
+ template:
+ src: install-printers.sh.j2
+ dest: /usr/local/bin/install-printers.sh
+ mode: 0755
+
+- name: Install lmn-install-printers sudoers
+ copy:
+ src: 90-lmn-install-printers
+ dest: /etc/sudoers.d/
+ mode: 0660
+ owner: root
+ group: root
+
+- name: Run printer script from /etc/profile.d/
+ copy:
+ dest: /etc/profile.d/lmn-printer.sh
+ content: |
+ [[ "${UID}" -gt 10000 ]] && (sudo /usr/local/bin/install-printers.sh > /dev/null &)
diff --git a/roles/lmn_printer/templates/install-printers.sh.j2 b/roles/lmn_printer/templates/install-printers.sh.j2
new file mode 100644
index 0000000..3e6629c
--- /dev/null
+++ b/roles/lmn_printer/templates/install-printers.sh.j2
@@ -0,0 +1,48 @@
+#!/usr/bin/bash
+
+set -eu
+
+printservers="{{ printservers | join(' ') }}"
+hostgroup="$(id -Gn "${HOSTNAME^^}$")"
+usergroup="$(id -Gn "${SUDO_USER}")"
+installedprinters="$(lpstat -p | cut -f 2 -d" ")"
+
+cat < "/lmn/media/${SUDO_USER}/.printerlist.csv"
+
+## Add all printers needed:
+for ps in $printservers ; do
+ echo "Checking print server '$ps' for available printers:"
+ printers="$(lpstat -h "$ps" -U "${SUDO_USER}" -v | sed -E 's/^.+ (\w+): .+$/\1/')"
+ echo -e "$printers\n"
+ for p in $printers; do
+ if [[ "${hostgroup}" =~ "$p" ]] || [[ "${usergroup}" =~ "$p" ]] ; then
+ if [[ "$installedprinters" =~ "$p" ]] ; then
+ echo "Print queue '$p' already available."
+ else
+ echo "Adding print queue '$p'."
+ timeout 10 lpadmin -p "$p" -E -v \
+ "ipp://$ps/printers/$p" \
+ -m "driverless:ipp://$ps/printers/$p" || echo "Adding queue '$p' failed."
+ fi
+ echo "$p;ipp://192.168.122.1/printers/$p" >> "/lmn/media/${SUDO_USER}/.printerlist.csv"
+ fi
+ done
+done
diff --git a/roles/lmn_security/handlers/main.yml b/roles/lmn_security/handlers/main.yml
new file mode 100644
index 0000000..4a3b124
--- /dev/null
+++ b/roles/lmn_security/handlers/main.yml
@@ -0,0 +1,5 @@
+- name: Reload sshd
+ systemd:
+ name: sshd
+ state: reloaded
+ when: not run_in_installer|default(false)|bool
diff --git a/roles/lmn_security/tasks/main.yml b/roles/lmn_security/tasks/main.yml
new file mode 100644
index 0000000..3ce31d5
--- /dev/null
+++ b/roles/lmn_security/tasks/main.yml
@@ -0,0 +1,29 @@
+---
+- name: Deploy SSH keys
+ ansible.posix.authorized_key:
+ user: ansible
+ key: "{{ item }}"
+ loop: "{{ keys2deploy }}"
+
+- name: Allow sudo without password for ansible
+ ansible.builtin.lineinfile:
+ path: /etc/sudoers.d/95-lmn-ansible
+ line: 'ansible ALL=(root) NOPASSWD: ALL'
+ create: True
+ owner: root
+ group: root
+ mode: '0700'
+
+- name: Disable ansible user login
+ ansible.builtin.user:
+ name: ansible
+ password_lock: True
+
+- name: Limit SSH access to user ansible
+ ansible.builtin.blockinfile:
+ dest: /etc/ssh/sshd_config.d/local.conf
+ create: true
+ block: |
+ PasswordAuthentication no
+ AllowUsers ansible
+ notify: Reload sshd
diff --git a/roles/lmn_sssd/handlers/main.yml b/roles/lmn_sssd/handlers/main.yml
new file mode 100644
index 0000000..c7c508b
--- /dev/null
+++ b/roles/lmn_sssd/handlers/main.yml
@@ -0,0 +1,3 @@
+- name: restart sssd
+ service: name=sssd state=restarted enabled=yes
+ listen: "restart sssd"
diff --git a/roles/lmn_sssd/tasks/main.yml b/roles/lmn_sssd/tasks/main.yml
new file mode 100644
index 0000000..069f67c
--- /dev/null
+++ b/roles/lmn_sssd/tasks/main.yml
@@ -0,0 +1,25 @@
+---
+- name: Install needed packages
+ apt:
+ name:
+ - sssd-ad
+ - sssd-tools
+ - adcli
+ state: latest
+
+- name: Provide user identities from AD
+ template:
+ src: sssd.conf.j2
+ dest: /etc/sssd/sssd.conf
+ mode: 0600
+ notify: restart sssd
+
+ ## Either one of the variables is defined:
+- name: Join the domain
+ shell:
+ cmd: >
+ echo "{{ ansible_cmdline.adpw | default('') + adpw.user_input | default('') }}" |
+ adcli join --stdin-password -U global-admin {{ domain | upper }}
+ when: >
+ ansible_cmdline.adpw | default('') | length > 0 or
+ adpw.user_input | default('') | length > 0
diff --git a/roles/lmn_sssd/templates/sssd.conf.j2 b/roles/lmn_sssd/templates/sssd.conf.j2
new file mode 100644
index 0000000..0c10c3c
--- /dev/null
+++ b/roles/lmn_sssd/templates/sssd.conf.j2
@@ -0,0 +1,19 @@
+[sssd]
+domains = {{ domain }}
+config_file_version = 2
+implicit_pac_responder = False
+
+[domain/{{ domain }}]
+krb5_realm = {{ domain | upper }}
+ad_domain = {{ domain }}
+id_provider = ad
+access_provider = ad
+use_fully_qualified_names = False
+cache_credentials = True
+krb5_store_password_if_offline = True
+default_shell = /usr/bin/bash
+# default: # ldap_id_mapping = True
+ad_gpo_access_control = disabled
+ad_gpo_ignore_unreadable = True
+ad_maximum_machine_account_password_age = 0
+ignore_group_members = True
diff --git a/roles/lmn_vm/files/desktop-sync b/roles/lmn_vm/files/desktop-sync
new file mode 100644
index 0000000..580d43c
--- /dev/null
+++ b/roles/lmn_vm/files/desktop-sync
@@ -0,0 +1,14 @@
+#!/usr/bin/bash
+#
+# Synchronize desktop starters
+#
+set -eu
+
+source /etc/lmn/vm.conf
+RSYNC_COMMAND=$(rsync -ai --delete --exclude=mimeinfo.cache \
+ --chown=root:root --chmod=F644,D755 "${DESKTOPSTARTERDIR}" \
+ /usr/local/share/applications/ | sed '/ \.\//d')
+if [[ $? -eq 0 ]] && [[ -n "${RSYNC_COMMAND}" ]]; then
+ echo "${RSYNC_COMMAND}"
+ update-desktop-database /usr/local/share/applications
+fi
diff --git a/roles/lmn_vm/files/fvs.directory b/roles/lmn_vm/files/fvs.directory
new file mode 100644
index 0000000..359630a
--- /dev/null
+++ b/roles/lmn_vm/files/fvs.directory
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Type=Directory
+Name=FvS
+Icon=face-smile-big
+#X-KDE-BaseGroup=info
diff --git a/roles/lmn_vm/files/fvs.menu b/roles/lmn_vm/files/fvs.menu
new file mode 100644
index 0000000..b2fa6af
--- /dev/null
+++ b/roles/lmn_vm/files/fvs.menu
@@ -0,0 +1,12 @@
+
+
diff --git a/roles/lmn_vm/files/lmn-mounthome b/roles/lmn_vm/files/lmn-mounthome
new file mode 100644
index 0000000..062c4f7
--- /dev/null
+++ b/roles/lmn_vm/files/lmn-mounthome
@@ -0,0 +1,3 @@
+%examusers ALL=(root) NOPASSWD: /usr/local/bin/mounthome.sh
+%role-student ALL=(root) NOPASSWD: /usr/local/bin/mounthome.sh
+%role-teacher ALL=(root) NOPASSWD: /usr/local/bin/mounthome.sh
diff --git a/roles/lmn_vm/files/lmn-vm b/roles/lmn_vm/files/lmn-vm
new file mode 100644
index 0000000..7d4011e
--- /dev/null
+++ b/roles/lmn_vm/files/lmn-vm
@@ -0,0 +1,25 @@
+# vm-sync: Download and synchronize VM-Images and xml-Files
+%role-teacher ALL=(lmnsynci) NOPASSWD: /usr/local/bin/vm-sync
+%role-student ALL=(lmnsynci) NOPASSWD: /usr/local/bin/vm-sync
+%examusers ALL=(lmnsynci) NOPASSWD: /usr/local/bin/vm-sync
+
+# vm-aria2: Start/Stop aria2 as systemd-service for VM-Images
+lmnsynci ALL=(root) NOPASSWD: /usr/local/bin/vm-aria2
+
+# vm-link-images: Link VM-Images to User-tmp-directory
+%examusers ALL=(root) NOPASSWD: /usr/local/bin/vm-link-images
+%role-student ALL=(root) NOPASSWD: /usr/local/bin/vm-link-images
+%role-teacher ALL=(root) NOPASSWD: /usr/local/bin/vm-link-images
+
+# vm-virtiofsd: Start Virtiofsd as systemd-service
+%examusers ALL=(root) NOPASSWD: /usr/local/bin/vm-virtiofsd
+%role-student ALL=(root) NOPASSWD: /usr/local/bin/vm-virtiofsd
+%role-teacher ALL=(root) NOPASSWD: /usr/local/bin/vm-virtiofsd
+
+# desktop-sync:
+%examusers ALL=(root) NOPASSWD: /usr/local/bin/desktop-sync
+%role-student ALL=(root) NOPASSWD: /usr/local/bin/desktop-sync
+%role-teacher ALL=(root) NOPASSWD: /usr/local/bin/desktop-sync
+
+# vm-upload:
+%role-teacher ALL=(root) NOPASSWD: /usr/local/bin/vm-upload
diff --git a/roles/lmn_vm/files/mounthome.sh b/roles/lmn_vm/files/mounthome.sh
new file mode 100755
index 0000000..5cb0405
--- /dev/null
+++ b/roles/lmn_vm/files/mounthome.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/bash
+set -eu
+
+home="$(getent passwd "$SUDO_UID" | cut -d : -f 6 | sed 's|/srv/samba/schools/default-school/||')"
+
+exit_script() {
+ echo "unmounting media - terminated by trap!" >> "/tmp/${SUDO_UID}-exit-mount.log"
+ findmnt "/lmn/media/${SUDO_USER}/oldhome" && umount "/lmn/media/${SUDO_USER}/oldhome" && rmdir "/lmn/media/${SUDO_USER}/oldhome"
+ findmnt "/lmn/media/${SUDO_USER}/oldprojects" && umount "/lmn/media/${SUDO_USER}/oldprojects" && rmdir "/lmn/media/${SUDO_USER}/oldprojects"
+ findmnt "/lmn/media/${SUDO_USER}/linuxhome" && umount "/lmn/media/${SUDO_USER}/linuxhome" && rmdir "/lmn/media/${SUDO_USER}/linuxhome"
+ trap - SIGHUP SIGINT SIGTERM # clear the trap
+ kill -- -$$ # Sends SIGTERM to child/sub processes
+}
+
+exit_script_home() {
+ echo "unmounting media - terminated by trap!" >> "/tmp/${SUDO_UID}-exit-mount.log"
+ umount "/lmn/media/${SUDO_USER}/home"
+ trap - SIGHUP SIGINT SIGTERM # clear the trap
+ kill -- -$$ # Sends SIGTERM to child/sub processes
+}
+
+##########################
+
+if [[ "$#" -gt 0 ]] && [[ "$1" = '-u' ]]; then
+ findmnt "/lmn/media/${SUDO_USER}/home" && umount "/lmn/media/${SUDO_USER}/home" && rmdir "/lmn/media/${SUDO_USER}/home"
+ #findmnt "/lmn/media/${SUDO_USER}/share" && umount "/lmn/media/${SUDO_USER}/share" && rmdir "/lmn/media/${SUDO_USER}/share"
+ findmnt "/lmn/media/${SUDO_USER}/oldhome" && umount "/lmn/media/${SUDO_USER}/oldhome" && rmdir "/lmn/media/${SUDO_USER}/oldhome"
+ findmnt "/lmn/media/${SUDO_USER}/oldprojects" && umount "/lmn/media/${SUDO_USER}/oldprojects" && rmdir "/lmn/media/${SUDO_USER}/oldprojects"
+ findmnt "/lmn/media/${SUDO_USER}/linuxhome" && umount "/lmn/media/${SUDO_USER}/linuxhome" && rmdir "/lmn/media/${SUDO_USER}/linuxhome"
+elif [ "$#" -gt 0 ] && [ "$1" = '-o' ]; then
+ echo "Einbinden der Daten des alten/bisherigen Systems (PaedML Novell)."
+ echo "Bitte den Username und Passwort aus dem ALTEN System eingeben."
+ read -rp "Username: " username
+ read -srp "Passwort: " PASSWD
+ export PASSWD
+ echo
+ mkdir -p "/lmn/media/${SUDO_USER}/oldhome"
+ mkdir -p "/lmn/media/${SUDO_USER}/oldprojects"
+ #errcode=$(mount -t cifs -o "username=${username},uid=${SUDO_UID},gid=1010,file_mode=0770,dir_mode=0770,forceuid,forcegid" \
+ # "//192.168.1.2/DOCS/fvs" "/lmn/media/${SUDO_USER}/oldhome")
+ #if [[ ! "${errcode}" ]]; then
+ mount -t cifs -o "username=${username},uid=${SUDO_UID},gid=1010,file_mode=0770,dir_mode=0770,forceuid,forcegid,nobrl,mfsymlinks" \
+ "//192.168.1.2/DOCS/fvs" "/lmn/media/${SUDO_USER}/oldhome"
+ mount -t cifs -o "username=${username},uid=${SUDO_UID},gid=1010,file_mode=0770,dir_mode=0770,forceuid,forcegid,nobrl,mfsymlinks" \
+ "//192.168.1.2/DATA/fvs/projekte" "/lmn/media/${SUDO_USER}/oldprojects"
+ #echo "Mounting successfull!"
+ echo "Einbindung erfolgreich!"
+ echo "Dieses Fenster bitte nicht schließen!"
+ #echo "Um weiter zu arbeiten: + "
+ trap exit_script SIGHUP SIGINT SIGTERM
+ sleep infinity
+elif [ "$#" -gt 0 ] && [ "$1" = '-l' ]; then
+ echo "Einbinden des Netboot-Home-Verzeichnises. Daten des alten/bisherigen Systems (PaedML Novell)."
+ echo "Bitte den Username und Passwort aus dem ALTEN System (PaedML Novell) eingeben."
+ echo "Bitte auch Groß- und Kleinschreibung achten."
+ read -rp "Username: " username
+ mkdir -p "/lmn/media/${SUDO_USER}/linuxhome"
+ mount -t fuse -o "allow_other,uid=${SUDO_UID},gid=1010,reconnect" \
+ "sshfs#${username}@home.steinbeisschule-reutlingen.de:" "/lmn/media/${SUDO_USER}/linuxhome"
+ #echo "Mounting successfull!"
+ echo "Einbindung erfolgreich!"
+ echo "Dieses Fenster bitte nicht schließen!"
+ #echo "Um weiter zu arbeiten: + "
+ trap exit_script SIGHUP SIGINT SIGTERM
+ sleep infinity
+fi
diff --git a/roles/lmn_vm/files/pam-umount.sh b/roles/lmn_vm/files/pam-umount.sh
new file mode 100644
index 0000000..0d90dd5
--- /dev/null
+++ b/roles/lmn_vm/files/pam-umount.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/bash
+#
+# /usr/local/sbin/pam-umount.sh %(USER) %(USERUID) %(MNTPT)'
+
+set -eu
+
+usr="$1"
+uid="$2"
+mtp="$3"
+slce="system-virtiofs.slice"
+slp=false
+
+shutdownVMs(){
+ local VM
+ for VM in $(sudo -u $usr XDG_RUNTIME_DIR="/run/user/$uid" \
+ XDG_CONFIG_HOME="/tmp/$uid/.config/" \
+ XDG_CACHE_HOME="/var/cache/user/$uid/" \
+ virsh list --state-running | \
+ sed -nE "s/.*\s+(\S+)\s+running/\1/p") ; do
+ sudo -u $usr XDG_RUNTIME_DIR="/run/user/$uid" \
+ XDG_CONFIG_HOME="/tmp/$uid/.config/" \
+ XDG_CACHE_HOME="/var/cache/user/$uid/" \
+ virsh destroy "$VM" 2>&1 | systemd-cat || true
+ slp=true
+ done
+}
+
+######################
+
+## This is the first mount we need to get rid of:
+if [[ "$mtp" =~ "/lmn/media/$usr/share" ]] && [[ -d "/run/user/$uid" ]] ; then
+ shutdownVMs
+ [[ "$slp" = true ]] && sleep 5 # leave some time to write caches …
+ sudo -u ${usr} killall gvfsd | systemd-cat
+ sudo -u ${usr} killall dbus-daemon | systemd-cat
+ systemctl -q is-active "$slce" && systemctl kill "$slce"
+ # debug to find processes blocking umount:
+ # lsof >> /var/log/lsof.log
+fi
+
+## Just umount:
+exec umount "$mtp"
diff --git a/roles/lmn_vm/files/pulseaudio-override.conf b/roles/lmn_vm/files/pulseaudio-override.conf
new file mode 100644
index 0000000..5d7783e
--- /dev/null
+++ b/roles/lmn_vm/files/pulseaudio-override.conf
@@ -0,0 +1,2 @@
+[Service]
+Environment=HOME=/tmp/pulse.%u
diff --git a/roles/lmn_vm/files/sync-vm.sh b/roles/lmn_vm/files/sync-vm.sh
new file mode 100755
index 0000000..d18c3a2
--- /dev/null
+++ b/roles/lmn_vm/files/sync-vm.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/bash
+# Push VM-Disk-Image on server
+set -eu
+
+show_help() {
+ cat << EOF >&2
+Usage: $(basename "$0") [-d] [-a] [-t] [vmnames]"
+Images from vmnames-List will be synced from server. Default by torrent.
+Using flag -d VMs will be synced by rsync
+Using flag -a images from images.list and xml-directory will be synced from server.
+Using flag -t all torrents and xml-VM-Definitions will be synced
+EOF
+}
+
+download_image() {
+ rsync -av "rsync://server:/vmimages-download/${VM_NAME}.qcow2" \
+ /lmn/vm/
+ rsync -av "rsync://server:/vmimages-download/${VM_NAME}.xml" \
+ /lmn/vm/
+ rsync -av "rsync://server:/vmimages-download/${VM_NAME}.qcow2.torrent" \
+ /lmn/vm/
+ /usr/local/bin/vmimage-torrent restart "${VM_NAME}.qcow2"
+}
+
+torrent_image() {
+ if [[ ! -f "/lmn/vm/${VM_NAME}.qcow2.torrent" ]]; then
+ echo "No torrent-File found"
+ exit 1
+ fi
+ lockfile="/tmp/sync-vm-${VM_NAME}.lock"
+ if ! flock -n "$lockfile" echo "try to acquire lock"; then
+ echo torrent seems to be in process.
+ echo waiting for completion ...
+ flock -w 3600 "$lockfile" echo "...completed"
+ sleep 5
+ else
+ (
+ if ! flock -n 200; then
+ echo "failed to acquire lock"
+ echo "Bitte noch einmal starten."
+ echo "Beliebige Taste zum Beenden."
+ read -n 1
+ exit 1
+ fi
+ torrent="${VM_NAME}.qcow2.torrent"
+ session="${torrent//./_}"
+ if vmimage-torrent status | grep -qw ^"$session"; then
+ vmimage-torrent stop "${VM_NAME}.qcow2"
+ fi
+ cd /lmn/vm
+ ctorrent -e 0 "${VM_NAME}.qcow2.torrent"
+ /usr/local/bin/vmimage-torrent restart "${VM_NAME}.qcow2"
+ if ! flock -u 200; then
+ echo failed to drop lock
+ exit 1
+ fi
+ ) 200>"$lockfile"
+ fi
+}
+
+sync_all_images() {
+ rsync -av --files-from=/lmn/vm/images.list \
+ rsync://server:/vmimages-download/ /lmn/vm/
+ rsync -av rsync://server:/vmimages-download/*.xml \
+ /lmn/vm/
+}
+
+delete_old_qcows() {
+ cd /lmn/vm
+ for qcow2 in $(find . -maxdepth 1 -name "*.qcow2" -exec basename {} ';'); do
+ qcowsize=$(stat -c%s "${qcow2}")
+ if [[ -f "${qcow2}.size" ]] && [[ "${qcowsize}" != $(<"${qcow2}.size") ]]; then
+ torrent="${qcow2}.torrent"
+ session="${torrent//./_}"
+ if vmimage-torrent status | grep -qw ^"$session"; then
+ vmimage-torrent stop "${qcow2}"
+ fi
+ mv "${qcow2}" /tmp/
+ fi
+ done
+}
+
+sync_all_torrents() {
+ rsync -ai rsync://server:/vmimages-download/*.torrent /lmn/vm/
+ rsync -ai rsync://server:/vmimages-download/*.size /lmn/vm/
+ delete_old_qcows
+ rsync -ai rsync://server:/vmimages-download/*.xml /lmn/vm/
+ RSYNC_COMMAND=$(rsync -ai --delete --exclude=mimeinfo.cache rsync://server:/vmimages-download/desktop/ /usr/local/share/applications/ | sed '/ \.\//d')
+ if [[ $? -eq 0 ]] && [[ -n "${RSYNC_COMMAND}" ]]; then
+ echo "${RSYNC_COMMAND}"
+ update-desktop-database /usr/local/share/applications
+ fi
+}
+
+create_starter() {
+ if [[ ! -f "/usr/share/applications/VM_${VM_NAME}_starter.desktop" ]]; then
+ cat << EOF >"/usr/share/applications/VM_${VM_NAME}_starter.desktop"
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=VMstart: ${VM_NAME}
+GenericName=VM starter ${VM_NAME}
+Comment=Start VM ${VM_NAME}
+#TryExec=konsole
+Exec=/usr/local/bin/run-vm.sh ${VM_NAME}
+Icon=clementine
+Categories=VM;Engineering;
+MimeType=image/vnd.dxf;
+Keywords=design;VM;diagrams;graphics
+Terminal=true
+EOF
+ update-desktop-database /usr/share/applications
+ fi
+}
+
+if [[ "$(id -nu)" != "lmnsynci" ]]; then
+ echo "$(basename "$0") must be run as lmnsynci user"
+ show_help
+ exit 1
+fi
+
+while getopts ':dat' OPTION; do
+ case "$OPTION" in
+ d)
+ DOWNLOAD=1
+ ;;
+ a)
+ sync_all_images
+ exit 0
+ ;;
+ t)
+ sync_all_torrents
+ exit 0
+ ;;
+ ?)
+ show_help
+ exit 1
+ ;;
+ esac
+done
+
+shift "$((OPTIND -1))"
+
+# if less than one arguments supplied, display usage
+if [[ $# -lt 1 ]]; then
+ show_help
+ exit 1
+fi
+
+for VM_NAME in "$@"; do
+ if [[ -v "DOWNLOAD" ]]; then
+ echo "Downloading $VM_NAME"
+ download_image
+ else
+ echo "Torrenting $VM_NAME"
+ torrent_image
+ fi
+done
diff --git a/roles/lmn_vm/files/sync.desktop b/roles/lmn_vm/files/sync.desktop
new file mode 100644
index 0000000..5646f6b
--- /dev/null
+++ b/roles/lmn_vm/files/sync.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Sync Starters
+GenericName=Aktualisiert Info über vorhandene VMs
+Comment=Sync VM Image information
+#TryExec=konsole
+Exec=if sudo /usr/local/bin/desktop-sync; then echo 'sync erfolgreich.\nFenster schließt sich in 3 Sekunden.'; sleep 3; else echo "Fehler - sollte nicht vorkommen."; read; fi
+Icon=bittorrent-sync
+Categories=fvs;
+MimeType=image/vnd.dxf;
+Keywords=design;VM;diagrams;graphics
+Terminal=true
diff --git a/roles/lmn_vm/files/uploadseed b/roles/lmn_vm/files/uploadseed
new file mode 100755
index 0000000..f38d853
--- /dev/null
+++ b/roles/lmn_vm/files/uploadseed
@@ -0,0 +1,93 @@
+#!/usr/bin/python3
+
+import os, sys
+import subprocess
+import xmlrpc.client as xc
+import ssl
+import argparse
+
+parser = argparse.ArgumentParser(description='Upload a file to the bittorrent seeder.')
+parser.add_argument('--server', required=True,
+ help="the server address and RPC port like 'IPaddress:port'")
+parser.add_argument('--dht-port', required=True,
+ help='the DHT port the RPC server is listening on')
+pwgrp = parser.add_mutually_exclusive_group(required=True)
+pwgrp.add_argument('--passwd',
+ help='the RPC secret. Either this or --pwdfile needs to be ' \
+ 'provided')
+pwgrp.add_argument('--pwdfile',
+ help="file containing the RPC secret in the form " \
+ "'secret = \"token:SECRET\"'. " \
+ 'Either this or --secret needs to be provided')
+certgrp = parser.add_mutually_exclusive_group(required=True)
+certgrp.add_argument('--no-cert', action='store_true',
+ help='do not use SSL certificate')
+certgrp.add_argument('--cert', help='the certificate to use for verification')
+parser.add_argument('FILE', help='the file to upload')
+
+args = parser.parse_args()
+
+rpcseeder = 'https://' + args.server + '/rpc'
+dhtentry = args.server.split(':')[0] + ':' + args.dht_port
+file2send = args.FILE
+torrent = '/tmp/' + os.path.basename(file2send) + '.torrent'
+if args.passwd:
+ secret = 'token:' + args.passwd
+else:
+ exec(open(args.pwdfile).read())
+
+ssl_ctx = ssl.create_default_context()
+if args.no_cert:
+ ssl_ctx.check_hostname = False
+ ssl_ctx.verify_mode = ssl.CERT_NONE
+ print("Certificate verification disabled.")
+elif args.cert is not None:
+ ssl_ctx.load_verify_locations(args.cert)
+
+s = xc.ServerProxy(rpcseeder, context = ssl_ctx)
+
+def make_torrent():
+ if os.path.isfile(torrent):
+ print("Torrent file", torrent, "exists already, please (re)move it.")
+ sys.exit(1)
+
+ subprocess.run(["/usr/bin/mktorrent", "-l 24", "-v", "-o", torrent, file2send], check=True)
+ h = subprocess.check_output(["/usr/bin/aria2c", "-S ", torrent])
+ for line in h.decode().splitlines():
+ if "Info Hash" in line:
+ return line.split(': ')[1]
+
+def check_seeds(bthash):
+ active_seeds = s.aria2.tellActive(secret)
+ for seed in active_seeds:
+ f = seed['bittorrent']['info']['name']
+ gid = seed['gid']
+ ihash = seed['infoHash']
+ if f == os.path.basename(file2send):
+ print(file2send, "is already seeded with GID:", gid)
+ print("Info Hash is:", ihash)
+ if bthash == ihash:
+ print("The torrent file has not changed, exiting.")
+ return False
+ else:
+ print("The torrent file has changed, replacing torrent.")
+ s.aria2.remove(secret, gid)
+ return True
+ print("="*19, " Uploading new torrent with aria2 now. ", "="*19)
+ return True
+
+def upload_torrent():
+ s.aria2.addTorrent(secret, xc.Binary(open(torrent, mode='rb').read()))
+ subprocess.run(["/usr/bin/aria2c",
+ "--dht-entry-point=" + dhtentry,
+ "--check-integrity",
+ "--dir=" + os.path.dirname(file2send),
+ torrent])
+
+############################
+
+if __name__ == '__main__':
+ infoHash = make_torrent()
+ if check_seeds(infoHash):
+ upload_torrent()
+ print("Upload finished.")
diff --git a/roles/lmn_vm/files/virtiofsd b/roles/lmn_vm/files/virtiofsd
new file mode 100755
index 0000000..471c24d
Binary files /dev/null and b/roles/lmn_vm/files/virtiofsd differ
diff --git a/roles/lmn_vm/files/vm-aria2 b/roles/lmn_vm/files/vm-aria2
new file mode 100755
index 0000000..7be4ac2
--- /dev/null
+++ b/roles/lmn_vm/files/vm-aria2
@@ -0,0 +1,33 @@
+#!/usr/bin/bash
+
+set -eu
+
+# if less than one arguments supplied, display usage
+if [[ $# -ne 2 ]]; then
+ echo "This script takes as input the name of the VM " >&2
+ echo "Usage: $0 [start|stop] vm_name" >&2
+ exit 1
+fi
+
+COMMAND="$1"
+VM_NAME="$2"
+
+source /etc/lmn/vm.conf
+
+if [[ "${COMMAND}" = "start" ]]; then
+ systemd-run --unit=aria2-"${VM_NAME}" \
+ --slice=system-aria2 \
+ --uid="$(id -u lmnsynci)" \
+ --gid="$(id -g lmnsynci)" \
+ --nice=19 \
+ --working-directory="${VM_SYSDIR}" \
+ --collect \
+ --property=Type=exec \
+ --property=SuccessExitStatus=1 \
+ aria2c --bt-hash-check-seed=true --check-integrity=true --seed-ratio=0.0 \
+ --dht-entry-point="${SEEDBOX_HOST}:${SEEDBOX_PORT}" \
+ --dht-file-path=$DHTDAT \
+ "${VM_SYSDIR}/${VM_NAME}.qcow2.torrent"
+elif [[ "${COMMAND}" = "stop" ]] && systemctl is-active "aria2-${VM_NAME}.service"; then
+ systemctl stop "aria2-${VM_NAME}.service"
+fi
diff --git a/roles/lmn_vm/files/vm-create b/roles/lmn_vm/files/vm-create
new file mode 100755
index 0000000..4fb2a67
--- /dev/null
+++ b/roles/lmn_vm/files/vm-create
@@ -0,0 +1,47 @@
+#!/usr/bin/bash
+# create 1st level-Clones
+
+set -eu
+
+source /etc/lmn/vm.conf
+PERSISTENT=0
+
+while getopts ':p' OPTION; do
+ case "$OPTION" in
+ p)
+ PERSISTENT=1
+ VM_DIR="${VM_DIR_PERSISTENT}"
+ ;;
+ esac
+done
+
+shift "$((OPTIND -1))"
+
+# if less than two arguments supplied, display usage
+if [[ $# -ne 2 ]]; then
+ echo "This script takes as input the name of the VM to clone" >&2
+ echo "Usage: $0 vm_name_orig vm_name_clone" >&2
+ exit 1
+fi
+
+VM_NAME=$1
+VM_CLONE=$2
+
+# Create User-VM-Dir and link system VM-Images
+[[ -d "${VM_DIR}" ]] || mkdir -p "${VM_DIR}"
+if [[ "${PERSISTENT}" -eq 1 ]]; then
+ sudo /usr/local/bin/vm-link-images -p
+else
+ sudo /usr/local/bin/vm-link-images
+fi
+
+# change to image-directory
+cd "${VM_DIR}"
+
+if [[ ! -f "${VM_NAME}.qcow2" ]]; then
+ echo "qcow2 File does not exists." >&2
+ exit 1
+fi
+
+qemu-img create -f qcow2 -F qcow2 -b "${VM_NAME}.qcow2" "${VM_NAME}-${VM_CLONE}.qcow2"
+chmod a-w "${VM_NAME}-${VM_CLONE}.qcow2"
diff --git a/roles/lmn_vm/files/vm-link-images b/roles/lmn_vm/files/vm-link-images
new file mode 100755
index 0000000..e4c8618
--- /dev/null
+++ b/roles/lmn_vm/files/vm-link-images
@@ -0,0 +1,28 @@
+#!/usr/bin/bash
+# link VM in User-Dir in /tmp or /var/vm
+
+set -eu
+
+source /etc/lmn/vm.conf
+
+# change to image-directory
+cd "${VM_SYSDIR}"
+
+while getopts ':p' OPTION; do
+ case "$OPTION" in
+ p)
+ VM_DIR="${VM_DIR_PERSISTENT}"
+ ;;
+ esac
+done
+
+shift "$((OPTIND -1))"
+
+# link system-VM-Images to User VM Directory
+for i in *.qcow2; do
+ [[ -f "${VM_DIR}/${i}" ]] || ln "${i}" "${VM_DIR}/${i}"
+done
+
+# allow lmnsynci to remove old vm images
+chgrp lmnsynci "${VM_DIR}"
+chmod g+w "${VM_DIR}"
diff --git a/roles/lmn_vm/files/vm-netboot b/roles/lmn_vm/files/vm-netboot
new file mode 100755
index 0000000..2f4c062
--- /dev/null
+++ b/roles/lmn_vm/files/vm-netboot
@@ -0,0 +1,72 @@
+#!/usr/bin/bash
+#
+# Start a netboot VM connected to macvtap device and fraction of mem/cpus
+#
+set -eu
+
+## Imporant for all virsh libvirt calls:
+export XDG_CONFIG_HOME="/tmp/${UID}/.config"
+
+menu=(standard "CLI Standard Debian GNU/Linux NFS"
+ standard-ram "CLI Standard Debian GNU/Linux RAM"
+ kde-desktop "KDE Plasma Desktop Debian GNU/Linux NFS"
+ gnome-desktop "Gnome Desktop Debian GNU/Linux NFS")
+img=$(dialog --clear --backtitle "Virtual Machine Chooser" \
+ --title "Choose the Virtual Machine to Start" \
+ --menu "Start VM:" 12 70 6 "${menu[@]}" 2>&1 >/dev/tty)
+
+## If the menu is canceled, $0 stops here because of set -e
+
+mac="$(ip link | grep -A1 "vm-macvtap" | \
+ sed -nE "s%\s+link/ether ([[:xdigit:]:]{17}) .+%\1%p")"
+
+if [[ $# -eq 0 ]] ; then
+ mem=$(sed -En "s/^MemAvailable:\s+([0-9]+)\s+kB/\1/p" /proc/meminfo)
+ cpu=$(sed -En "0,/^cpu cores/s/^cpu cores\s+:\s+([0-9]+)/\1/p" /proc/cpuinfo)
+ arg=("--memory=$((mem/2048))" "--vcpu=$((cpu/2))")
+ echo Set options: "${arg[@]}"
+else
+ arg=("$@")
+fi
+
+kernel="http://livebox/d-i/n-live/${img%-ram}/live/vmlinuz"
+initrd="http://livebox/d-i/n-live/${img%-ram}/live/initrd.img"
+kargs=(boot=live components splash locales=de_DE.UTF-8 keyboard-layouts=de
+ swap=true live-config.timezone=Europe/Berlin)
+
+case "$img" in
+ standard*)
+ arg+=(--autoconsole=text)
+ kargs+=(console=ttyS0)
+ ;;&
+ *-ram)
+ kargs+=("fetch=http://10.190.1.2/d-i/n-live/${img%-ram}/live/filesystem.squashfs")
+ ;;
+ *)
+ kargs+=(netboot=nfs "nfsroot=10.190.1.2:/srv/nfs/debian-live/${img%-ram}")
+ ;;
+esac
+
+type="ethernet,mac=${mac},target.dev=vm-macvtap,xpath1.set=./target/@managed=no"
+for vm in $(virsh --connect qemu:///session list --all --name) ; do
+ if virsh domiflist "$vm" | grep -q "$mac" ; then
+ type="user"
+ virt-manager &
+ break
+ fi
+done
+
+## FIXME: use passt, needs more settings for correct DNS/gateway
+# type=user,xpath1.create=./backend,xpath2.set=./backend/@type=passt,xpath3.create=./ip,xpath4.set=./ip/@family=ipv4,xpath5.set=./ip/@address=172.16.1.1,xpath6.set=./ip/@prefix=24,xpath7.create=./portForward,xpath8.set=./portForward/@proto=tcp,xpath9.set=./portForward/range/@start=2001,xpath10.set=./portForward/range/@end=2500,xpath11.set=./portForward/range/@to=1
+
+http_proxy='' \
+ exec virt-install \
+ --name "$img" \
+ --osinfo debiantesting \
+ --nodisks --import --noreboot --transient \
+ --controller type=scsi,model=virtio-scsi \
+ --install kernel="$kernel",initrd="$initrd",kernel_args="${kargs[*]}" \
+ --network "type=$type" "${arg[@]}"
+
+# --filesystem "$HOME",share
+# mount -t 9p share /mnt
diff --git a/roles/lmn_vm/files/vm-rebase b/roles/lmn_vm/files/vm-rebase
new file mode 100755
index 0000000..4182390
--- /dev/null
+++ b/roles/lmn_vm/files/vm-rebase
@@ -0,0 +1,80 @@
+#!/usr/bin/bash
+# Rebase one level down
+set -eu
+
+show_help() {
+ cat << EOF >&2
+Usage: $(basename "$0") [-n newname] vmname"
+This script takes as input the name of the VM to rebase one level down
+ -n new name of the rebased image
+EOF
+}
+
+source /etc/lmn/vm.conf
+
+while getopts ':n:p' OPTION; do
+ case "$OPTION" in
+ n)
+ NEWNAME=$OPTARG
+ ;;
+ p)
+ VM_DIR="${VM_DIR_PERSISTENT}"
+ ;;
+ ?)
+ show_help
+ exit 1
+ ;;
+ esac
+done
+
+shift "$((OPTIND -1))"
+
+# if less or more than one arguments supplied, display usage
+if [[ $# -ne 1 ]]; then
+ show_help
+ exit 1
+fi
+
+# change to Images directory
+cd "${VM_DIR}"
+
+VM_NAME="$1"
+
+# check if VM-Diskimage exists
+if [[ ! -f "${VM_NAME}.qcow2" ]]; then
+ echo "File not found ${VM_NAME}.qcow2" >&2
+ exit 1
+fi
+
+# check if new VM-Diskimage exists
+if [[ -v NEWNAME ]] && [[ -f "${NEWNAME}.qcow2" ]]; then
+ echo "New Base already exists: ${NEWNAME}.qcow2" >&2
+ exit 1
+fi
+
+NUMBASES=$(qemu-img info --backing-chain "${VM_NAME}.qcow2" | grep -c image)
+NEWBASE=$(qemu-img info --backing-chain "${VM_NAME}.qcow2" | grep image | head -n 3 | tail -n 1 | cut -d' ' -f2)
+CURRENTBASE=$(qemu-img info --backing-chain "${VM_NAME}.qcow2" | grep image | head -n 2 | tail -n 1 | cut -d' ' -f2)
+
+if [[ ! "${NUMBASES}" -ge 3 ]]; then
+ echo "Image must have at least 2 backing-files" >&2
+ exit 1
+fi
+
+# check if base Diskimage exists
+if [[ ! -f "${NEWBASE}" ]] || [[ ! -f "${CURRENTBASE}" ]]; then
+ echo "Backingfiles not found ${CURRENTBASE}, ${NEWBASE}" >&2
+ exit 1
+fi
+
+# rebasing disk image
+qemu-img rebase -f qcow2 -b "${NEWBASE}" -F qcow2 "${VM_NAME}.qcow2"
+if [[ -v NEWNAME ]]; then
+ NEWNAME="${NEWNAME}.qcow2"
+else
+ rm -f "${CURRENTBASE}"
+ NEWNAME="${CURRENTBASE}"
+fi
+
+mv "${VM_NAME}.qcow2" "${NEWNAME}"
+chmod a-w "${NEWNAME}"
diff --git a/roles/lmn_vm/files/vm-run b/roles/lmn_vm/files/vm-run
new file mode 100755
index 0000000..5ef6e2a
--- /dev/null
+++ b/roles/lmn_vm/files/vm-run
@@ -0,0 +1,256 @@
+#!/usr/bin/bash
+# create and run clone
+
+set -eu
+
+show_help() {
+ cat << EOF >&2
+Usage: $(basename "$0") [options] vmname"
+Create a new clone, start the vm (if not yet running) and run virt-viewer.
+Squid-Proxy will be started too.
+options:
+ -n|--new new clone will be created, even if exists
+ -p|--persistent new clone will be created persistent, so available after reboot too
+ -s|--system qemu:///system instead of default qemu:///session
+ --no-viewer start without viewer
+ --heads n number of displays
+ --memory sizeMB memory size in MB
+ --cpu num number of CPUs
+ --os OS operating system (win10|linux|..)
+ --data-disk size additional data-disk
+ --bridge virbrX additional network interface on bridge virbrX
+ --options options additional options for virt-install command
+EOF
+}
+
+exit_script() {
+ echo "run-vm.sh ${VM_NAME} terminated by trap!" >> "/tmp/${UID}/exit-run-vm.log"
+ virsh --connect="${QEMU}" destroy "${VM_NAME}-clone"
+ trap - SIGHUP SIGINT SIGTERM # clear the trap
+ kill -- -$$ # Sends SIGTERM to child/sub processes
+}
+
+check_images() {
+ # sync vm-torrents and machine definition file
+ sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${VM_NAME}.qcow2.torrent"
+ [[ -f "${VM_SYSDIR}/${VM_NAME}.qcow2" ]] && sudo -u lmnsynci /usr/local/bin/vm-sync delete_outdated_image "${VM_NAME}.qcow2"
+
+ BACKINGARRAY=()
+ imgfile="${VM_SYSDIR}/${VM_NAME}.qcow2" && [[ -f "${VM_DIR}/${VM_NAME}.qcow2" ]] && imgfile="${VM_DIR}/${VM_NAME}.qcow2"
+ BACKINGARRAY+=("${imgfile}")
+ echo "Imgfile=$imgfile"
+ if [[ ! -f "${imgfile}" ]] || ! qemu-img info -U "${imgfile}" | grep "file format: qcow2"; then
+ if [[ ! -f "${VM_SYSDIR}/${VM_NAME}.qcow2.torrent" ]]; then
+ echo "no base VM disk '${VM_NAME}.qcow2' found and/or ${VM_NAME} not found on server" >&2
+ exit 1
+ fi
+ # sync vm-disk image by torrent
+ echo "Try to sync VM ${VM_NAME} by torrent"
+ sudo -u lmnsynci /usr/local/bin/vm-sync get_image "${VM_NAME}"
+ fi
+
+ backingfile=$(qemu-img info -U "${imgfile}" | grep "^backing file:" | cut -d ' ' -f 3)
+ while [[ -n "${backingfile}" ]]; do
+ echo "Backingfile required: ${backingfile}"
+ imgfile="${VM_SYSDIR}/${backingfile}" && [[ -f "${VM_DIR}/${backingfile}" ]] && imgfile="${VM_DIR}/${backingfile}"
+ BACKINGARRAY+=("${imgfile}")
+ sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${backingfile}.torrent"
+ [[ -f "${VM_SYSDIR}/${backingfile}" ]] && sudo -u lmnsynci /usr/local/bin/vm-sync delete_outdated_image "${backingfile}"
+ if [[ ! -f "${imgfile}" ]] || ! qemu-img info -U "${imgfile}" | grep "file format: qcow2"; then
+ # sync vm-disk image by torrent
+ echo "Try to sync backingfile ${backingfile} by torrent"
+ sudo -u lmnsynci /usr/local/bin/vm-sync get_image "${backingfile%.qcow2}"
+ fi
+ backingfile=$(qemu-img info -U "${imgfile}" | grep "^backing file:" | cut -d ' ' -f 3)
+ done
+
+ echo "VM-Image and required backingfiles available"
+ echo "Now, let's check the images."
+
+ # Check VM-Images in reverse order
+ for ((i=${#BACKINGARRAY[@]}-1; i>=0; i--))
+ do
+ echo "Checking ${BACKINGARRAY[$i]}"
+ if ! qemu-img check -U "${BACKINGARRAY[$i]}" 2>/dev/null; then
+ echo "check failed!"
+ echo "sync ${BACKINGARRAY[$i]} again"
+ sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${BACKINGARRAY[$i]}.torrent"
+ sudo -u lmnsynci /usr/local/bin/vm-sync get_image "$(basename "${BACKINGARRAY[$i]}" .qcow2)"
+ fi
+ done
+ echo "VM-Image and required backingfiles available and checked"
+
+ sudo -u lmnsynci /usr/local/bin/vm-sync update_usage_information ${BACKINGARRAY[*]}
+}
+
+create_clone() {
+ local VM_NAME="$1"
+
+ if ! [[ -f "${VM_SYSDIR}/${VM_NAME}.qcow2" || -f "${VM_DIR}/${VM_NAME}.qcow2" ]]; then
+ echo "qcow2 File does not exists." >&2
+ exit 1
+ fi
+
+ # Create User-VM-Dir and link system VM-Images
+ [[ -d "${VM_DIR}" ]] || mkdir -p "${VM_DIR}"
+ if [[ "${PERSISTENT}" -eq 1 ]]; then
+ sudo /usr/local/bin/vm-link-images -p
+ else
+ sudo /usr/local/bin/vm-link-images
+ fi
+
+ # Create backing file
+ cd "${VM_DIR}"
+ qemu-img create -f qcow2 -F qcow2 -b "${VM_NAME}.qcow2" "${VM_NAME}-clone.qcow2"
+
+}
+
+QEMU='qemu:///session'
+
+NEWCLONE=0
+PERSISTENT=0
+LIBVIRTOSINFO="win10"
+LIBVIRTOPTS=""
+NO_VIEWER=0
+
+source /etc/lmn/vm.conf
+
+TEMP=$(getopt -o no:ps --long new,no-viewer,options:,persistent,system,memory:,data-disk:,heads:,cpu:,bridge:,os:,help -n $0 -- "$@")
+if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
+
+eval set -- "$TEMP"
+
+while true; do
+ case "$1" in
+ -p | --persistent )
+ PERSISTENT=1;
+ VM_DIR="${VM_DIR_PERSISTENT}"
+ shift
+ ;;
+ -n | --new )
+ NEWCLONE=1
+ shift
+ ;;
+ -s | --system )
+ QEMU='qemu:///system'
+ shift
+ ;;
+ -o | --options )
+ LIBVIRTOPTS=$2
+ shift 2
+ ;;
+ --no-viewer )
+ NO_VIEWER=1
+ shift
+ ;;
+ --data-disk )
+ LIBVIRTOPTS="${LIBVIRTOPTS} --disk ${VM_DIR}/data.qcow2,size=$2,sparse=yes"
+ shift 2
+ ;;
+ --heads )
+ for i in $(seq $2)
+ do
+ LIBVIRTOPTS="${LIBVIRTOPTS} --video model.heads=$i"
+ done
+ shift 2
+ ;;
+ --memory )
+ mem_avail=$(sed -En "s/^MemAvailable:\s+([0-9]+)\s+kB/\1/p" /proc/meminfo)
+ mem_avail=$((mem_avail / 1024 - 2048))
+ if (( $2 < mem_avail )); then
+ LIBVIRTOPTS="${LIBVIRTOPTS} --memory $2"
+ else
+ LIBVIRTOPTS="${LIBVIRTOPTS} --memory ${mem_avail}"
+ fi
+ shift 2
+ ;;
+ --cpu )
+ #cpu=$(sed -En "0,/^cpu cores/s/^cpu cores\s+:\s+([0-9]+)/\1/p" /proc/cpuinfo)
+ cpu=$(lscpu | grep "^CPU(s):" | sed 's/.* //g')
+ if (( $2 < cpu )); then
+ LIBVIRTOPTS="${LIBVIRTOPTS} --vcpu $2"
+ else
+ LIBVIRTOPTS="${LIBVIRTOPTS} --vcpu ${cpu}"
+ fi
+ shift 2
+ ;;
+ --bridge )
+ if ip link | grep $2; then
+ LIBVIRTOPTS="${LIBVIRTOPTS} --network=bridge=$2,model.type=virtio"
+ fi
+ shift 2
+ ;;
+ --os )
+ LIBVIRTOSINFO=$2
+ shift 2
+ ;;
+ --help )
+ show_help
+ exit 1
+ ;;
+ -- ) shift; break ;;
+ * ) break ;;
+ esac
+done
+
+# if less than one arguments supplied, display usage
+if [[ $# -ne 1 ]] ; then
+ show_help
+ exit 1
+fi
+
+VM_NAME=$1
+
+# check, if we have to start squid
+if ! killall -s 0 squid; then
+ echo "starting squid."
+ /usr/sbin/squid -f /etc/squid/squid-usermode.conf
+fi
+
+# because virsh has problems with long pathnames, using diffent configdir
+export XDG_CONFIG_HOME="/tmp/${UID}/.config"
+
+if ! virsh --connect="${QEMU}" list | grep "${VM_NAME}-clone"; then
+ echo "VM not yet running."
+ sudo /usr/local/bin/desktop-sync
+ check_images
+ if [[ "${NEWCLONE}" = 1 ]] || [[ ! -f "${VM_DIR}/${VM_NAME}-clone.qcow2" ]]; then
+ create_clone "${VM_NAME}"
+ fi
+ # delete the old vm
+ virsh --connect=qemu:///session undefine "${VM_NAME}-clone" || echo "${VM_NAME}-clone did not exist"
+ #trap exit_script SIGHUP SIGINT SIGTERM
+
+ # start virtiofsd-service
+ [[ "${QEMU}" = 'qemu:///session' ]] && sudo /usr/local/bin/vm-virtiofsd "${VM_NAME}"
+
+ # finally, create the new vm
+
+ virt-install \
+ --osinfo "${LIBVIRTOSINFO}" \
+ --name "${VM_NAME}-clone" \
+ --import \
+ --clock hpet_present=yes,hypervclock_present=yes \
+ --features hyperv.synic.state=on,xpath1.set=./hyperv/vpindex/@state=on,xpath2.set=./hyperv/stimer/@state=on \
+ --memorybacking source.type=memfd,access.mode=shared \
+ --disk "${VM_DIR}/${VM_NAME}-clone.qcow2",driver.discard=unmap,target.bus=scsi,cache=writeback \
+ --network=bridge=virbr0,model.type=virtio \
+ --filesystem driver.type=virtiofs,accessmode=passthrough,target.dir=virtiofs,xpath1.set=./source/@socket="/run/user/${UID}/virtiofs-${VM_NAME}.sock" \
+ --controller type=scsi,model=virtio-scsi \
+ --check path_in_use=off \
+ --connect="${QEMU}" \
+ --noautoconsole \
+ ${LIBVIRTOPTS}
+
+# --dry-run \
+# --print-xml \
+# > /tmp/vm.xml
+# --features hyperv.synic.state=on,xpath1.set=./hyperv/vpindex/@state=on,xpath2.set=./hyperv/stimer/@state=on \
+# --network type=ethernet,target.dev=vm-macvtap,xpath1.set=./target/@managed=no \
+# virsh --connect="${QEMU}" start "${VM_NAME}-clone"
+fi
+if [[ $NO_VIEWER == 0 ]] ; then
+ echo "starting viewer"
+ trap exit_script SIGHUP SIGINT SIGTERM
+ virt-viewer --connect="${QEMU}" --full-screen "${VM_NAME}-clone"
+fi
diff --git a/roles/lmn_vm/files/vm-sync b/roles/lmn_vm/files/vm-sync
new file mode 100755
index 0000000..a9713ed
--- /dev/null
+++ b/roles/lmn_vm/files/vm-sync
@@ -0,0 +1,168 @@
+#!/usr/bin/bash
+# Push/Pull VM-Disk-Image and Infos from server
+set -eu
+
+show_help() {
+ cat << EOF >&2
+Usage: $(basename "$0") command [args]"
+command:
+ push_file
+ get_file
+ get_image
+ delete_outdated_image
+ update_usage_information
+EOF
+}
+
+get_torrent() {
+ if [[ ! -f "${VM_SYSDIR}/${VM_NAME}.qcow2.torrent" ]]; then
+ echo "No torrent-File found"
+ exit 1
+ fi
+ cd "${VM_SYSDIR}"
+ echo Size needed: $(get_image_size "${VM_NAME}.qcow2.torrent")
+ echo Size available: $(df --block-size=1 --output=avail "${VM_SYSDIR}" | sed 1d)
+ while [[ $(get_image_size "${VM_NAME}.qcow2.torrent") -gt $(df --block-size=1 --output=avail "${VM_SYSDIR}" | sed 1d) ]]; do
+ echo "Not enough space to get ${VM_NAME}."
+ FILENAME="$(head -1 vm_usage_information.txt)"
+ if [[ -z $FILENAME ]]; then
+ echo "No more old VM-files to delete. Unable to get ${VM_NAME}. Please contact system administrator."
+ exit 1
+ fi
+ echo "Deleting $FILENAME"
+ sudo vm-aria2 stop "$(basename "${FILENAME}" .qcow2)"
+ rm -f "${FILENAME}"
+ [[ -f "${VM_DIR}/${FILENAME}" ]] && rm -f "${VM_DIR}/${FILENAME}"
+ sed -i -e 1d vm_usage_information.txt
+ done
+ lockfile="/tmp/sync-vm-${VM_NAME}.lock"
+ if ! flock -n "$lockfile" echo "try to acquire lock"; then
+ echo torrent seems to be in process.
+ echo waiting for completion ...
+ flock -w 3600 "$lockfile" echo "...completed"
+ sleep 5
+ else
+ (
+ if ! flock -n 200; then
+ echo "failed to acquire lock"
+ echo "Bitte noch einmal starten."
+ echo "Beliebige Taste zum Beenden."
+ read -n 1
+ exit 1
+ fi
+ # stop aria2-seeding if running
+ sudo vm-aria2 stop "${VM_NAME}"
+ cd "${VM_SYSDIR}"
+ # get image
+ aria2c --seed-time=0 --dht-file-path="$DHTDAT" \
+ --dht-entry-point="${SEEDBOX_HOST}:${SEEDBOX_PORT}" \
+ "${VM_SYSDIR}/${VM_NAME}.qcow2.torrent"
+ # and seed
+ sudo vm-aria2 start "${VM_NAME}"
+ if ! flock -u 200; then
+ echo failed to drop lock
+ exit 1
+ fi
+ ) 200>"$lockfile"
+ fi
+}
+
+
+get_image_size() {
+ torrentfile=$1
+ length=$(aria2c -S "${torrentfile}" | grep "Total Length" | \
+ sed -E -e 's/.*\(([0-9,]*)\)/\1/' -e 's/,//g')
+ echo "$length"
+}
+
+delete_outdated_image() {
+ cd "${VM_SYSDIR}"
+ qcowsize=$(stat -c%s "${FILENAME}")
+ if [[ -f "${FILENAME}.torrent" ]] && [[ "${qcowsize}" != $(get_image_size "${FILENAME}.torrent") ]]; then
+ sudo vm-aria2 stop "${FILENAME%.qcow2}"
+ rm -f "${FILENAME}"
+ fi
+}
+
+update_usage_information() {
+ cd "${VM_SYSDIR}"
+ [[ -f vm_usage_information.txt ]] || ls -tr *.qcow2 > vm_usage_information.txt || touch vm_usage_information.txt
+ FILENAME="$(basename $FILENAME)"
+ echo sed -i "/${FILENAME}/d" vm_usage_information.txt
+ sed -i "/${FILENAME}/d" vm_usage_information.txt
+ echo "${FILENAME}" >> vm_usage_information.txt
+}
+
+get_file() {
+ cd "${VM_SYSDIR}"
+ curl --fail --noproxy "${SEEDBOX_HOST}" -o "${FILENAME}" \
+ "http://${SEEDBOX_HOST}/aria2/${FILENAME}" || echo "File not found on seedbox"
+}
+
+push_file() {
+ cd "${VM_SYSDIR}"
+ uploadseed --server "${SEEDBOX_HOST}:${SEEDBOX_RPC_PORT}" --dht-port "${SEEDBOX_PORT}" \
+ --pwdfile "${SEEDBOX_PWFILE}" --no-cert "${FILENAME}"
+}
+
+########################
+
+if [[ "$(id -nu)" != "lmnsynci" ]]; then
+ echo "$(basename "$0") must be run as lmnsynci user"
+ show_help
+ exit 1
+fi
+
+source /etc/lmn/vm.conf
+
+while getopts ':' OPTION; do
+ case "$OPTION" in
+ ?)
+ show_help
+ exit 1
+ ;;
+ esac
+done
+
+shift "$((OPTIND -1))"
+
+# if less than one arguments supplied, display usage
+if [[ $# -lt 1 ]]; then
+ show_help
+ exit 1
+fi
+
+command=$1
+shift
+
+case "$command" in
+ push_file)
+ for FILENAME in "$@"; do
+ push_file
+ done
+ ;;
+ get_file)
+ for FILENAME in "$@"; do
+ get_file
+ done
+ ;;
+ get_image)
+ for VM_NAME in "$@"; do
+ get_torrent
+ done
+ ;;
+ delete_outdated_image)
+ for FILENAME in "$@"; do
+ delete_outdated_image
+ done
+ ;;
+ update_usage_information)
+ for FILENAME in "$@"; do
+ update_usage_information
+ done
+ ;;
+ *)
+ show_help
+ exit 1
+ ;;
+esac
diff --git a/roles/lmn_vm/files/vm-upload b/roles/lmn_vm/files/vm-upload
new file mode 100755
index 0000000..1fbdda3
--- /dev/null
+++ b/roles/lmn_vm/files/vm-upload
@@ -0,0 +1,60 @@
+#!/usr/bin/bash
+# Push VM-Disk-Image on server
+set -eu
+
+show_help() {
+ cat << EOF >&2
+Usage: $(basename "$0") vmname"
+Create torrent and upload disk on server.
+EOF
+}
+
+
+upload_image() {
+ # check if VM-Diskimage exists
+ if [[ ! (-f "${VM_SYSDIR}/${VM_NAME}.qcow2" || -f "${VM_DIR}/${VM_NAME}.qcow2") ]]; then
+ echo "File not found ${VM_NAME}.qcow2" >&2
+ exit 1
+ fi
+ vm-aria2 stop "${VM_NAME}" || echo "VMImage-torrent not running"
+ # link private VM-Diskimage to system-Dir
+ if [[ -f "${VM_DIR}/${VM_NAME}.qcow2" \
+ && ( -f "${VM_SYSDIR}/${VM_NAME}.qcow2" && ("${VM_DIR}/${VM_NAME}.qcow2" -nt "${VM_SYSDIR}/${VM_NAME}.qcow2") \
+ || ! -f "${VM_SYSDIR}/${VM_NAME}.qcow2") ]]; then
+ echo "copy private VM-Diskimage to system-dir"
+ chown lmnsynci:lmnsynci "${VM_DIR}/${VM_NAME}.qcow2"
+ ln -f "${VM_DIR}/${VM_NAME}.qcow2" "${VM_SYSDIR}/${VM_NAME}.qcow2"
+ fi
+ cd "${VM_SYSDIR}"
+ if [[ -f "/tmp/${VM_NAME}.qcow2.torrent" ]]; then
+ rm -f "/tmp/${VM_NAME}.qcow2.torrent"
+ fi
+ uploadseed --server "${SEEDBOX_HOST}:${SEEDBOX_RPC_PORT}" --dht-port "${SEEDBOX_PORT}" \
+ --pwdfile "${SEEDBOX_PWFILE}" --no-cert "${VM_NAME}.qcow2"
+}
+
+source /etc/lmn/vm.conf
+
+while getopts ':p' OPTION; do
+ case "$OPTION" in
+ p)
+ VM_DIR="${VM_DIR_PERSISTENT}"
+ ;;
+ ?)
+ show_help
+ exit 1
+ ;;
+ esac
+done
+
+shift "$((OPTIND -1))"
+
+# if less than one arguments supplied, display usage
+if [[ $# -ne 1 ]] ; then
+ show_help
+ exit 1
+fi
+
+VM_NAME=$1
+
+upload_image
diff --git a/roles/lmn_vm/files/vm-virtiofsd b/roles/lmn_vm/files/vm-virtiofsd
new file mode 100755
index 0000000..9326a5f
--- /dev/null
+++ b/roles/lmn_vm/files/vm-virtiofsd
@@ -0,0 +1,50 @@
+#!/usr/bin/bash
+
+set -eu
+
+# if less than one arguments supplied, display usage
+if [[ $# -ne 1 ]]; then
+ echo "This script takes as input the name of the VM " >&2
+ echo "Usage: $0 vm_name" >&2
+ exit 1
+fi
+
+VM_NAME="$1"
+
+## Make sure VMs can read the base directory:
+chgrp 1010 "/lmn/media/${SUDO_USER}"
+chmod 0775 "/lmn/media/${SUDO_USER}"
+
+socket="/run/user/$(id -u $SUDO_USER)/virtiofs-${VM_NAME}.sock"
+
+# FIXME: This does not work. In windows, there is no virtiofs device.
+# In GNU/Linux it's only readable.
+#
+#if ! systemctl -q is-active virtiofs-${VM_NAME}.socket ; then
+# systemd-run --unit=virtiofs-${VM_NAME} \
+# --slice=system-virtiofs \
+# --collect \
+# --socket-property=ListenStream="$socket" \
+# --socket-property=Accept=no \
+# --socket-property=SocketMode=0700 \
+# --socket-property=SocketUser=${SUDO_USER} \
+# --property=Type=exec \
+# --property=StandardInput=socket \
+# /usr/local/bin/virtiofsd --log-level debug --sandbox none \
+# --syslog --fd=0 --shared-dir "/lmn/media/${SUDO_USER}"
+#else
+# systemctl restart virtiofs-${VM_NAME}.socket
+#fi
+
+if [[ ! -S "$socket" ]] ; then
+ systemd-run --unit=virtiofs-${VM_NAME} \
+ --slice=system-virtiofs \
+ --collect \
+ --property=Type=exec \
+ --property=SuccessExitStatus=1 \
+ --property="ExecStopPost=rm $socket" \
+ /usr/local/bin/virtiofsd --socket-path "$socket" \
+ --shared-dir "/lmn/media/${SUDO_USER}"
+fi
+sleep 1
+chown "${SUDO_USER}" "$socket"
diff --git a/roles/lmn_vm/files/vm.conf b/roles/lmn_vm/files/vm.conf
new file mode 100644
index 0000000..8bd4189
--- /dev/null
+++ b/roles/lmn_vm/files/vm.conf
@@ -0,0 +1,17 @@
+# variables for LMN VM submodule
+
+SEEDBOX_HOST="seedbox.pn.steinbeis.schule"
+SEEDBOX_PORT=6789
+SEEDBOX_RPC_PORT=6800
+SEEDBOX_PWFILE="/etc/lmn/uploadseed.conf"
+DHTDAT="/var/cache/aria2/dht.dat"
+DESKTOPSTARTERDIR="/srv/samba/schools/default-school/share/school/AdminIT/desktop/"
+
+VM_SYSDIR="/lmn/vm"
+if [[ -v SUDO_UID ]]; then
+ VM_DIR="/tmp/${SUDO_UID}/vm"
+ VM_DIR_PERSISTENT="/var/vm/${SUDO_UID}"
+else
+ VM_DIR="/tmp/${UID}/vm"
+ VM_DIR_PERSISTENT="/var/vm/${UID}"
+fi
diff --git a/roles/lmn_vm/handlers/main.yml b/roles/lmn_vm/handlers/main.yml
new file mode 100644
index 0000000..6af3160
--- /dev/null
+++ b/roles/lmn_vm/handlers/main.yml
@@ -0,0 +1,11 @@
+- name: Reload libvirtd
+ systemd:
+ name: libvirtd.service
+ listen: reload libvirtd
+
+- name: Run update-desktop-database
+ command: update-desktop-database "{{ item }}"
+ loop:
+ - /usr/local/share/applications
+ - /usr/local/share/desktop-directories
+ - /etc/xdg/menus/applications-merged
diff --git a/roles/lmn_vm/tasks/main.yml b/roles/lmn_vm/tasks/main.yml
new file mode 100644
index 0000000..0d87a4e
--- /dev/null
+++ b/roles/lmn_vm/tasks/main.yml
@@ -0,0 +1,277 @@
+---
+# FIXME #691138, better: prepare interfaces ready to use, c.f. down below, macvtap.
+# This task needs to be run before the last apt run to provide a ready-to-use installation.
+- name: Allow users to attach to bridge
+ ansible.builtin.copy:
+ dest: /etc/apt/apt.conf.d/94qemu-bridge-suid
+ content: |
+ ## Modify permissions after installation/upgrade
+ ## to run qemu-bridge as root
+ DPkg::Post-Invoke {"/usr/bin/chmod 4755 /usr/lib/qemu/qemu-bridge-helper || true"; };
+
+
+- name: install libvirt packages
+ apt:
+ name:
+ - aria2
+ - mktorrent
+ - libvirt-daemon-system
+ - virt-manager
+ - dialog # for vm-netboot menu
+ state: latest
+ autoremove: true
+
+ #- name: allow all users to use VMs
+ # lineinfile:
+ # dest: /etc/libvirt/libvirtd.conf
+ # line: 'auth_unix_rw = "none"'
+ # insertafter: '#auth_unix_rw = "polkit"'
+ # notify: reload libvirtd
+
+- name: Configure pam_mount for VM bind mounts
+ blockinfile:
+ dest: /etc/security/pam_mount.conf.xml
+ marker: ""
+ block: |
+
+ rootansibleDebian-gdmsddm{{ localuser }}
+
+ rootansibleDebian-gdmsddm{{ localuser }}
+
+ insertafter: ""
+
+- name: Use umount script for proper cleanup
+ blockinfile:
+ dest: /etc/security/pam_mount.conf.xml
+ marker: ""
+ block: |
+
+ /usr/local/sbin/pam-umount.sh %(USER) %(USERUID) %(MNTPT)
+ insertafter: '^'
+ insertafter: ''
+
+- name: Autostart default network for VMs
+ file:
+ src: /etc/libvirt/qemu/networks/default.xml
+ dest: /etc/libvirt/qemu/networks/autostart/default.xml
+ state: link
+
+- name: Create system-user syncing VM-files and others
+ ansible.builtin.user:
+ name: lmnsynci
+ comment: lmn sync user
+ system: true
+ create_home: false
+
+- name: Create /etc/lmn directory
+ file:
+ path: /etc/lmn
+ state: directory
+
+- name: Create /lmn directory
+ file:
+ path: /lmn
+ state: directory
+
+- name: Create /lmn/media directory
+ file:
+ path: /lmn/media
+ state: directory
+ mode: '1777'
+
+- name: Create /var/vm directory
+ file:
+ path: /var/vm
+ state: directory
+ mode: '1777'
+
+- name: Create vm directory
+ file:
+ path: /lmn/vm
+ state: directory
+ owner: lmnsynci
+ group: lmnsynci
+ mode: 0755
+
+- name: Install squid
+ apt:
+ name:
+ - squid
+ state: latest
+ autoremove: true
+
+- name: Disable squid
+ systemd:
+ name: squid
+ enabled: false
+ state: stopped
+
+- name: Deploy squid user mode configuration
+ template:
+ src: squid-usermode.conf.j2
+ dest: /etc/squid/squid-usermode.conf
+ mode: '0644'
+
+- name: Deploy sudo configurations
+ copy:
+ src: "{{ item }}"
+ dest: "/etc/sudoers.d/90-{{ item }}"
+ owner: root
+ group: root
+ mode: '0700'
+ loop:
+ - lmn-mounthome
+ - lmn-vm
+
+- name: Deploy vmimages scripts
+ copy:
+ src: "{{ item }}"
+ dest: /usr/local/bin/
+ owner: root
+ group: root
+ mode: '0755'
+ loop:
+ - mounthome.sh
+ - vm-create
+ - vm-rebase
+ - vm-run
+ - vm-upload
+ - vm-sync
+ - vm-link-images
+ - vm-virtiofsd
+ - virtiofsd
+ - vm-aria2
+ - uploadseed
+ - desktop-sync
+
+- name: Deploy vm configuration file vm.conf
+ ansible.builtin.copy:
+ src: vm.conf
+ dest: /etc/lmn/vm.conf
+ owner: root
+ group: root
+
+- name: Deploy aria2 RPC password file
+ ansible.builtin.copy:
+ dest: /etc/lmn/uploadseed.conf
+ owner: root
+ group: lmnsynci
+ mode: '0640'
+ content: |
+ {{ uploadseed_pwd }}
+
+- name: Prepare directory for aria2 dht.dat
+ ansible.builtin.file:
+ path: /var/cache/aria2/
+ state: directory
+ owner: lmnsynci
+ group: lmnsynci
+
+- name: Prepare directory for qemu bridge config
+ ansible.builtin.file:
+ path: /etc/qemu/
+ state: directory
+
+- name: Deploy bridge.conf needed for qemu session mode
+ ansible.builtin.copy:
+ dest: /etc/qemu/bridge.conf
+ content: |
+ allow virbr0
+ allow virbr1
+ allow virbr2
+
+- name: Configure macvtap interface
+ ansible.builtin.copy:
+ dest: /etc/NetworkManager/system-connections/macvlan-vm-macvtap.nmconnection
+ mode: '0600'
+ content: |
+ [connection]
+ id=macvlan-vm-macvtap
+ type=macvlan
+ interface-name=vm-macvtap
+ [macvlan]
+ mode=2
+ parent={{ ansible_default_ipv4['interface'] }}
+ tap=true
+ [ipv4]
+ method=disabled
+ [ipv6]
+ method=disabled
+ [proxy]
+
+- name: Adjust interface permissions for user mode VMs
+ ansible.builtin.copy:
+ dest: /etc/udev/rules.d/80-macvlan.rules
+ content: |
+ SUBSYSTEMS=="net", KERNELS=="vm-macvtap", MODE="0666"
+
+
+- name: Create directory for local .desktop-Files
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - /usr/local/share/applications
+ - /usr/local/share/desktop-directories
+ notify: Run update-desktop-database
+
+- name: Copy fvs.directory
+ ansible.builtin.copy:
+ src: fvs.directory
+ dest: /usr/local/share/desktop-directories/
+ notify: Run update-desktop-database
+
+- name: Copy fvs.menu
+ ansible.builtin.copy:
+ src: fvs.menu
+ dest: /etc/xdg/menus/applications-merged/
+ notify: Run update-desktop-database
+
+- name: check if sync.desktop is installed
+ stat: path=/usr/local/share/applications/sync.desktop
+ register: syncdesktop
+
+- name: remove deprecated desktop-files
+ ansible.builtin.shell: rm -f /usr/local/share/applications/*.desktop
+ when: not syncdesktop.stat.exists
+ notify: Run update-desktop-database
+
+- name: Copy initial sync starter
+ ansible.builtin.copy:
+ src: sync.desktop
+ dest: /usr/local/share/applications/
+ notify: Run update-desktop-database
+
+- name: Start virt-manager in session mode by default
+ ansible.builtin.copy:
+ dest: /usr/local/bin/virt-manager
+ content: |
+ #!/usr/bin/sh
+ exec /usr/bin/virt-manager --connect qemu:///session $@
+ mode: '0755'
+
+- name: Copy vm-netboot script
+ ansible.builtin.copy:
+ src: vm-netboot
+ dest: /usr/local/bin/
+ mode: '0755'
diff --git a/roles/lmn_vm/templates/squid-usermode.conf.j2 b/roles/lmn_vm/templates/squid-usermode.conf.j2
new file mode 100644
index 0000000..586dbb1
--- /dev/null
+++ b/roles/lmn_vm/templates/squid-usermode.conf.j2
@@ -0,0 +1,11 @@
+acl local-servers dstdomain .{{ domain }}
+cache_peer firewall.{{ domain }} parent 3128 0 no-query default login=NEGOTIATE auth-no-keytab
+never_direct deny local-servers
+never_direct allow all
+#access_log stdio:/tmp/access.log squid
+access_log none
+cache_log /dev/null
+logfile_rotate 0
+pid_filename none
+http_port 192.168.122.1:3128
+http_access allow all
diff --git a/roles/lmn_wlan_iwd/tasks/main.yml b/roles/lmn_wlan_iwd/tasks/main.yml
new file mode 100644
index 0000000..c8ff1c1
--- /dev/null
+++ b/roles/lmn_wlan_iwd/tasks/main.yml
@@ -0,0 +1,60 @@
+## Make sure to use an initrd providing firmware:
+## wget https://cdimage.debian.org/cdimage/firmware/testing/current/firmware.cpio.gz
+## cat initrd.gz firmware.cpio.gz > initrd-fw.gz
+---
+- name: Install packages related to iwd and wifi
+ ansible.builtin.apt:
+ name:
+ - iwd
+ - systemd-resolved
+ - firmware-realtek # for our wifi sticks
+ - rfkill
+ state: latest
+
+- name: Disable wpa-supplicant
+ ansible.builtin.systemd:
+ name: wpa_supplicant.service
+ enabled: False
+
+- name: Enable iwd
+ ansible.builtin.systemd:
+ name: iwd.service
+ enabled: True
+
+- name: Prepare directory for iwd
+ file:
+ path: /var/lib/iwd/
+ state: directory
+
+- name: Configure iwd for wifi device
+ ansible.builtin.copy:
+ dest: /var/lib/iwd/{{ ssid }}.psk
+ content: |
+ [Security]
+ Passphrase={{ wifipasswd }}
+
+- name: Enable systemd-networkd
+ ansible.builtin.systemd:
+ name: systemd-networkd.service
+ enabled: True
+
+
+- name: Provide service to enable WiFi on boot
+ ansible.builtin.copy:
+ dest: /etc/systemd/system/enable-wifi.service
+ content: |
+ [Unit]
+ Description=Switch WiFi on
+
+ [Service]
+ Type=oneshot
+ ExecStart=/usr/sbin/rfkill enable wifi
+
+ [Install]
+ WantedBy=multi-user.target
+
+- name: Enable the enable-wifi service
+ ansible.builtin.systemd:
+ name: enable-wifi.service
+ enabled: True
+ daemon_reload: True
diff --git a/roles/lmn_wlan_nm/tasks/main.yml b/roles/lmn_wlan_nm/tasks/main.yml
new file mode 100644
index 0000000..7122c7f
--- /dev/null
+++ b/roles/lmn_wlan_nm/tasks/main.yml
@@ -0,0 +1,24 @@
+---
+- name: Configure WLAN for devices
+ community.general.nmcli:
+ conn_name: "{{ ssid }}"
+ type: wifi
+ ssid: "{{ ssid }}"
+ ifname: "{{ ansible_interfaces | select('search', 'wl.+') | first }}"
+ wifi_sec:
+ key-mgmt: wpa-psk
+ psk: "{{ wifipasswd }}"
+ autoconnect: true
+ state: present
+ when: |
+ not run_in_installer|default(false)|bool and
+ ansible_interfaces | select('search', 'wl.+') | first is defined
+
+- name: Provide WLAN config during installation
+ template:
+ src: ssid.nmconnection.j2
+ dest: "/etc/NetworkManager/system-connections/{{ ssid }}.nmconnection"
+ mode: '0600'
+ when: |
+ run_in_installer|default(false)|bool and
+ ansible_interfaces | select('search', 'wl.+') | first is defined
diff --git a/roles/lmn_wlan_nm/templates/ssid.nmconnection.j2 b/roles/lmn_wlan_nm/templates/ssid.nmconnection.j2
new file mode 100644
index 0000000..d99a2c3
--- /dev/null
+++ b/roles/lmn_wlan_nm/templates/ssid.nmconnection.j2
@@ -0,0 +1,21 @@
+[connection]
+id=FVS-devices
+type=wifi
+interface-name={{ ansible_interfaces | select('search', 'wl.+') | first }}
+
+[wifi]
+mode=infrastructure
+ssid=FVS-devices
+
+[wifi-security]
+key-mgmt=wpa-psk
+psk={{ wifipasswd }}
+
+[ipv4]
+method=auto
+
+[ipv6]
+addr-gen-mode=default
+method=auto
+
+[proxy]
diff --git a/roles/up2date_debian/defaults/main.yml b/roles/up2date_debian/defaults/main.yml
new file mode 100644
index 0000000..97d8c9b
--- /dev/null
+++ b/roles/up2date_debian/defaults/main.yml
@@ -0,0 +1,2 @@
+extra_pkgs: ""
+extra_pkgs_bpo: ""
diff --git a/roles/up2date_debian/tasks/main.yml b/roles/up2date_debian/tasks/main.yml
new file mode 100644
index 0000000..b71d0d8
--- /dev/null
+++ b/roles/up2date_debian/tasks/main.yml
@@ -0,0 +1,49 @@
+# Update lists and upgrade packages.
+
+- name: update apt package lists
+ apt:
+ update_cache: true
+ cache_valid_time: 86400
+
+- block:
+ - name: upgrade packages
+ apt:
+ upgrade: dist
+ autoremove: true
+ autoclean: true
+ rescue:
+ - name: Looks like dpkg was interrupted, configure manually
+ command:
+ cmd: dpkg --configure -a
+ - name: Try again to upgrade packages
+ apt:
+ upgrade: dist
+ autoremove: true
+ autoclean: true
+
+- name: install etckeeper
+ apt:
+ name: etckeeper
+ state: latest # noqa package-latest
+
+- name: install extra packages from stable
+ apt:
+ name: "{{ extra_pkgs }}"
+ state: latest # noqa package-latest
+ when: extra_pkgs|length
+
+- name: add {{ ansible_distribution_release }}-backports
+ apt_repository:
+ repo: >
+ deb http://deb.debian.org/debian/ {{ ansible_distribution_release }}-backports
+ main non-free-firmware
+ state: present
+ update_cache: true
+ when: extra_pkgs_bpo|length
+
+- name: install extra packages from backports
+ apt:
+ name: "{{ extra_pkgs_bpo }}"
+ state: latest # noqa package-latest
+ default_release: "{{ ansible_distribution_release }}-backports"
+ when: extra_pkgs_bpo|length
diff --git a/tools/collector b/tools/collector
new file mode 100755
index 0000000..fc855c8
--- /dev/null
+++ b/tools/collector
@@ -0,0 +1,24 @@
+#!/usr/bin/bash
+#
+# collect messages from reporter and drop them into log files
+#
+set -eu
+
+port=1234
+#logdir="/var/log/collector"
+logdir="/tmp/collector"
+
+mkdir -vp "$logdir"
+
+nc -k -l -u -p "$port" | while read line ; do
+ sndr="${line%% *}"
+ msg="${line#* }"
+ if [[ "$sndr" =~ [a-z0-9]+ ]] ; then
+ if [[ "$msg" =~ ^-------\ .+\ -------$ ]] ; then
+ echo "$(date --rfc-3339=seconds) → Report from '$sndr' received."
+ echo "$msg" > "$logdir/$sndr"
+ else
+ echo "$msg" >> "$logdir/$sndr"
+ fi
+ fi
+done
diff --git a/tools/emitter b/tools/emitter
new file mode 100755
index 0000000..be950c1
--- /dev/null
+++ b/tools/emitter
@@ -0,0 +1,93 @@
+#!/usr/bin/bash
+#
+# Run ansible on all hosts older than the latest git commit.
+# Use argument "$(date)" to update all machines independent
+# of the last ansible run.
+#
+set -eu
+
+## maximal age of file in minutes:
+age="15"
+
+pbook="lmn-client"
+logdir="/tmp/collector"
+debug=false
+
+## date of latest git commit in ansible repository:
+git_date="$(date --iso-8601=seconds --date="$(git log --date=iso-strict | \
+ head -3 | sed -nE "s/^Date:\s+(.+)$/\1/p")")"
+echo "Latest commit in git at: $git_date."
+
+if [[ $# = 0 ]] ; then
+ timestamp="$git_date"
+else
+ timestamp="$(date --iso-8601=seconds --date="$1")"
+fi
+echo "Time stamp at: $timestamp."
+
+#dir="$(mktemp -d)"
+dir="/tmp/emitter"
+mkdir -vp "$dir"
+
+hlist=""
+n=0
+running=0
+ansible_arg=""
+
+find_outdated(){
+ hlist=""
+ n=0
+ running=0
+ ansible_arg="--tags=upgrade"
+ while IFS= read -r -d '' file ; do
+ running=$(( running + 1 ))
+ $debug && echo -n "Processing host '$file' with IP address "
+ d="$(sed -nE "s/^2\s+(\S.+)$/\1/p" "$file")"
+ if [[ -z "$d" ]] || \
+ [[ $(date --date="$d" +%s) -lt $(date --date="$timestamp" +%s) ]] ; then
+ r='([0-9]{1,3}\.){3}[0-9]{1,3}'
+ ipa="$(sed -nE "s/^3\s+default via.+ src ($r) metric.+/\1/p" "$file")"
+ if [[ -z "$ipa" ]] ; then
+ # FIXME: Outdated report format, try fallback:
+ ipa="$(sed -nE "s|^.+default via.+ src ($r) metric.+|\1|p" "$file" | head -1)"
+ echo -ne "\n Outdated '$ipa': $file"
+ fi
+ hlist="$hlist,$ipa"
+ n=$(( n + 1 ))
+ if [[ $(date --date="$d" +%s) -lt $(date --date="$git_date" +%s) ]] ; then
+ ## ansible run needed at least on one machine, run it on all:
+ echo -n "✗"
+ ansible_arg=""
+ else
+ echo -n "U"
+ fi
+ else
+ echo -n '✓'
+ fi
+ done < <(find "$logdir" -maxdepth 1 -type f -mmin -$age -print0)
+ hlist="${hlist#,}"
+ echo -n " $n/$running "
+}
+
+run_ansible(){
+ local hsts="$1"
+ if [[ -n "$hsts" ]] ; then
+ if ! echo | eval ANSIBLE_RETRY_FILES_ENABLED=1 ANSIBLE_RETRY_FILES_SAVE_PATH="$dir" \
+ ansible-playbook --vault-password-file ~/.vaultpwd \
+ -bi inventory.yml "$pbook.yml" "$ansible_arg" -l "$hsts" ; then
+ while IFS= read -r ipa ; do
+ echo "Ansible for IP address '$ipa' failed."
+ done < "$dir/$pbook.retry"
+ fi
+ fi
+}
+
+#################
+while true ; do
+ date +%H:%M | tr '\n' ' '
+ find_outdated
+ run_ansible "$hlist"
+ t=$(( 600/(n*n+1) ))
+ echo "sleeping ${t}s "
+ sleep $t
+done
diff --git a/tools/wol-generator.sh b/tools/wol-generator.sh
new file mode 100755
index 0000000..02ebd44
--- /dev/null
+++ b/tools/wol-generator.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/bash
+#
+# Pipe the '--list-hosts' output of ansible into this program to wake up all corresponding hosts:
+#
+# ansible-playbook [...] -i inventory/inventory.yml -l R317 --list-hosts | ./wol-generator.sh
+#
+set -eu
+
+tmpf="$(mktemp)"
+devs='devices.csv'
+
+while read -r line ; do
+ sed -nE -e "s%.*(..:..:..:..:..:..);(${line//./\\.});.*%\1 \2%p" "$devs" >> "$tmpf"
+done < <(cat - | grep -E "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
+
+wakeonlan -f "$tmpf"
+rm "$tmpf"