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:]]+) ++ 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 @@ + + + Applications + + FvS + fvs.directory + + fvs + + + 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"