From d72dfae79dbc3be4f7e098263085ae8241c98d66 Mon Sep 17 00:00:00 2001 From: Fabio Boi Date: Sun, 4 Oct 2026 11:40:25 +0200 Subject: [PATCH] Correct source file transfer encoding --- lxc-enroll.sh | 439 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 438 insertions(+), 1 deletion(-) diff --git a/lxc-enroll.sh b/lxc-enroll.sh index dd2a481..f0860a4 100644 --- a/lxc-enroll.sh +++ b/lxc-enroll.sh @@ -1 +1,438 @@ -IyEvdXNyL2Jpbi9lbnYgYmFzaAojIFJ1biBhcyByb290IG9uIGEgUHJveG1veCBob3N0LiBObyBjcmVkZW50aWFscyBvciBzaXRlLXNwZWNpZmljIGFkZHJlc3NlcyBoZXJlLgpzZXQgLUVldW8gcGlwZWZhaWwKc2V0ICt4CnVtYXNrIDA3NwoKZmFpbCgpIHsgcHJpbnRmICdGRUhMRVI6ICVzXG4nICIkKiIgPiYyOyBleGl0IDE7IH0Kc2F5KCkgeyBwcmludGYgJyVzXG4nICIkKiI7IH0KYXNrKCkgeyBwcmludGYgJyVzICcgIiQxIiA+JjI7IElGUz0gcmVhZCAtciBSRVBMWSA8JjMgfHwgZmFpbCAnRWluZ2FiZSBhYmdlYnJvY2hlbi4nOyB9CnNlY3JldCgpIHsKICBwcmludGYgJyVzICcgIiQxIiA+JjIKICBJRlM9IHJlYWQgLXIgLXMgUkVQTFkgPCYzIHx8IGZhaWwgJ0VpbmdhYmUgYWJnZWJyb2NoZW4uJwogIHByaW50ZiAnXG4nID4mMgp9Cmluc3RhbGxlZF9sYWJlbCgpIHsgaWYgW1sgJDEgPT0gMSBdXTsgdGhlbiBwcmludGYgJ3ZvcmhhbmRlbic7IGVsc2UgcHJpbnRmICdmZWhsdCc7IGZpOyB9CgpMSVNUPTA7IERSWT0wOyBNQU5BR0VSPScnOyBWRVJTSU9OPScnOyBMT0dJTl9TRVJWRVI9Jyc7IFNFTEVDVElPTj0nJzsgVkVSU0lPTl9FWFBMSUNJVD0wClNTSF9FTkFCTEVEPTE7IFNTSF9VU0VSPScnOyBTU0hfU1VETz0wOyBTU0hfTk9QQVNTV0Q9MDsgU0FWRV9TRVRUSU5HUz0xClZBUlNfRklMRT0vZXRjL2x4Yy1lbnJvbGwvLnZhcnMKd2hpbGUgW1sgJCMgLWd0IDAgXV07IGRvCiAgY2FzZSAiJDEiIGluCiAgICAtLWxpc3QpIExJU1Q9MTsgc2hpZnQgOzsKICAgIC0tZHJ5LXJ1bikgRFJZPTE7IHNoaWZ0IDs7CiAgICAtLW1hbmFnZXJ8LS1hZ2VudC12ZXJzaW9ufC0tbG9naW4tc2VydmVyfC0tc2VsZWN0KQogICAgICBbWyAkIyAtZ2UgMiAmJiAtbiAkMiBdXSB8fCBmYWlsICJXZXJ0IGZlaGx0OiAkMSIKICAgICAgY2FzZSAiJDEiIGluCiAgICAgICAgLS1tYW5hZ2VyKSBNQU5BR0VSPSQyOzsgLS1hZ2VudC12ZXJzaW9uKSBWRVJTSU9OPSQyOyBWRVJTSU9OX0VYUExJQ0lUPTE7OwogICAgICAgIC0tbG9naW4tc2VydmVyKSBMT0dJTl9TRVJWRVI9JDI7OyAtLXNlbGVjdCkgU0VMRUNUSU9OPSQyOzsKICAgICAgZXNhYwogICAgICBzaGlmdCAyIDs7CiAgICAtLWhlbHB8LWgpCiAgICAgIGNhdCA8PCdIRUxQJwpBdWYgZGVtIFByb3htb3gtSG9zdCBhbHMgcm9vdCBzdGFydGVuOgogIGJhc2ggbHhjLWVucm9sbC5zaAogIGJhc2ggbHhjLWVucm9sbC5zaCAtLWxpc3QKICBiYXNoIGx4Yy1lbnJvbGwuc2ggLS1kcnktcnVuIC0tc2VsZWN0ICcxMDYgMTA3JwpPcHRpb25lbjogLS1tYW5hZ2VyIElQL05BTUUgLS1hZ2VudC12ZXJzaW9uIFZFUlNJT04gLS1sb2dpbi1zZXJ2ZXIgSFRUUFMtVVJMCiAgICAgICAgICAtLXNlbGVjdCAnQ1RJRCBDVElEJyAtLWxpc3QgLS1kcnktcnVuCk51ciBsYXVmZW5kZSBEZWJpYW4tL1VidW50dS1MWENzIG1pdCBzeXN0ZW1kLiBWb3JoYW5kZW5lIFRhaWxzY2FsZS0vV2F6dWgtClJlZ2lzdHJpZXJ1bmdlbiBibGVpYmVuIHVudmVyYWVuZGVydC4gT3B0aW9uYWwgVGFpbHNjYWxlIFNTSC9OdXR6ZXIvU3VkbyBmdWVyIGRpZSBBdXN3YWhsLgpMb2thbGUgRWluc3RlbGx1bmdlbjogL2V0Yy9seGMtZW5yb2xsLy52YXJzIChKU09OLCBvaG5lIEdlaGVpbW5pc3NlKS4KS2VpbmUgSG9zdC1BZ2VudC1JbnN0YWxsYXRpb24uIEdlaGVpbW5pc3NlIHdlcmRlbiBsb2thbCBhYmdlZnJhZ3QuCkhFTFAKICAgICAgZXhpdCAwIDs7CiAgICAqKSBmYWlsICJVbmJla2FubnRlIE9wdGlvbjogJDEiIDs7CiAgZXNhYwpkb25lCltbICRFVUlEID09IDAgXV0gfHwgZmFpbCAnQml0dGUgYXVmIGRlbSBQcm94bW94LUhvc3QgYWxzIHJvb3Qgc3RhcnRlbi4nCmZvciB0b29sIGluIHBjdCB0aW1lb3V0IHB5dGhvbjM7IGRvIGNvbW1hbmQgLXYgIiR0b29sIiA+L2Rldi9udWxsIHx8IGZhaWwgIiR0b29sIGZlaGx0IGF1ZiBkZW0gSG9zdC4iOyBkb25lCgpkZWNsYXJlIC1hIElEUz0oKSBOQU1FUz0oKSBUUz0oKSBXWj0oKSBUVU49KCkgTUFOQUdFUlM9KCkgU0VMRUNURUQ9KCkgUkVNT1RFX0RJUlM9KCkKVE1QPScnCmNsZWFudXAoKSB7CiAgbG9jYWwgaWQgZGlyCiAgZm9yIGlkIGluICIke1NFTEVDVEVEW0BdfSI7IGRvCiAgICBkaXI9JHtSRU1PVEVfRElSU1skaWRdOi19CiAgICBpZiBbWyAkZGlyID1+IF4vcnVuL2x4Yy1lbnJvbGxcLltBLVphLXowLTldKyQgXV07IHRoZW4KICAgICAgdGltZW91dCAxNSBwY3QgZXhlYyAiJGlkIiAtLSBybSAtcmYgLS0gIiRkaXIiID4vZGV2L251bGwgMj4mMSB8fCB0cnVlCiAgICBmaQogIGRvbmUKICBbWyAteiAkVE1QIF1dIHx8IHJtIC1yZiAtLSAiJFRNUCIKfQp0cmFwIGNsZWFudXAgRVhJVAp0cmFwICdleGl0IDEzMCcgSU5UCnRyYXAgJ2V4aXQgMTQzJyBURVJNCgp5ZXNfbm8oKSB7CiAgbG9jYWwgcHJvbXB0PSQxIGRlZmF1bHQ9JDIKICBhc2sgIiRwcm9tcHQiCiAgY2FzZSAiJFJFUExZIiBpbiAnJyApIFJFUExZPSRkZWZhdWx0OzsganxKfGphfEphfHl8WXx5ZXN8WWVzKSBSRVBMWT0xOzsgbnxOfG5laW58TmVpbnxub3xObykgUkVQTFk9MDs7ICopIGZhaWwgJ0JpdHRlIGogb2RlciBuIGVpbmdlYmVuLic7OyBlc2FjCn0KCmxvYWRfc2V0dGluZ3MoKSB7CiAgbG9jYWwgZmllbGRzIGtleSB2YWx1ZQogIFtbICEgLWUgJFZBUlNfRklMRSAmJiAhIC1MICRWQVJTX0ZJTEUgXV0gJiYgcmV0dXJuIDAKICB5ZXNfbm8gJ0dlc3BlaWNoZXJ0ZSBFaW5zdGVsbHVuZ2VuIGF1cyAvZXRjL2x4Yy1lbnJvbGwvLnZhcnMgdmVyd2VuZGVuPyBbSi9uXScgMQogIFtbICRSRVBMWSA9PSAxIF1dIHx8IHJldHVybiAwCiAgIyBKU09OIGRhdGEsIG5ldmVyIHNvdXJjZS9ldmFsLiBSZWplY3Qgbm9uLXJvb3Qtb3duZWQgb3Igd3JpdGFibGUgc2V0dGluZ3MuCiAgZmllbGRzPSQocHl0aG9uMyAtICIkVkFSU19GSUxFIiA8PCdQWScKaW1wb3J0IGpzb24sIG9zLCBzdGF0LCBzeXMKcD1zeXMuYXJndlsxXTsgcz1vcy5sc3RhdChwKQppZiBub3Qgc3RhdC5TX0lTUkVHKHMuc3RfbW9kZSkgb3Igcy5zdF91aWQgIT0gMCBvciBzdGF0LlNfSU1PREUocy5zdF9tb2RlKSAmIDBvMDc3OgogICAgc3lzLmV4aXQoJ1Vuc2ljaGVyZSBSZWNodGUgZGVyIC52YXJzOiByZWd1bGFlcmUgRGF0ZWksIHJvb3QsIE1vZHVzIDYwMCBlcmZvcmRlcmxpY2guJykKZD1qc29uLmxvYWQob3BlbihwKSkKZm9yIGsgaW4gKCdtYW5hZ2VyJywnYWdlbnRfdmVyc2lvbicsJ2xvZ2luX3NlcnZlcicsJ3NzaF9lbmFibGVkJywnc3NoX3VzZXInLCdzc2hfc3VkbycsJ3NzaF9ub3Bhc3N3ZCcpOgogICAgdj1kLmdldChrLCcnKQogICAgaWYgbm90IGlzaW5zdGFuY2UodixzdHIpIG9yIGFueShjIGluIHYgZm9yIGMgaW4gJ1xuXHJcdFwwJykgb3IgbGVuKHYpPjE2Mzg0OgogICAgICAgIHN5cy5leGl0KCdVbmd1ZWx0aWdlIC52YXJzLicpCiAgICBwcmludChrKydcdCcrdikKUFkKICApIHx8IGZhaWwgJ0dlc3BlaWNoZXJ0ZSBFaW5zdGVsbHVuZ2VuIGtvbm50ZW4gbmljaHQgc2ljaGVyIGdlbGVzZW4gd2VyZGVuLicKICB3aGlsZSBJRlM9JCdcdCcgcmVhZCAtciBrZXkgdmFsdWU7IGRvCiAgICBjYXNlICIka2V5IiBpbgogICAgICBtYW5hZ2VyKSBbWyAtbiAkTUFOQUdFUiBdXSB8fCBNQU5BR0VSPSR2YWx1ZTs7CiAgICAgIGFnZW50X3ZlcnNpb24pIFtbIC1uICRWRVJTSU9OIF1dIHx8IFZFUlNJT049JHZhbHVlOzsKICAgICAgbG9naW5fc2VydmVyKSBbWyAtbiAkTE9HSU5fU0VSVkVSIF1dIHx8IExPR0lOX1NFUlZFUj0kdmFsdWU7OwogICAgICBzc2hfZW5hYmxlZCkgW1sgJHZhbHVlID1+IF5bMDFdJCBdXSB8fCBmYWlsICdVbmd1ZWx0aWdlIFNTSC1FaW5zdGVsbHVuZy4nOyBTU0hfRU5BQkxFRD0kdmFsdWU7OwogICAgICBzc2hfdXNlcikgU1NIX1VTRVI9JHZhbHVlOzsKICAgICAgc3NoX3N1ZG8pIFtbICR2YWx1ZSA9fiBeWzAxXSQgXV0gfHwgZmFpbCAnVW5ndWVsdGlnZSBTdWRvLUVpbnN0ZWxsdW5nLic7IFNTSF9TVURPPSR2YWx1ZTs7CiAgICAgIHNzaF9ub3Bhc3N3ZCkgW1sgJHZhbHVlID1+IF5bMDFdJCBdXSB8fCBmYWlsICdVbmd1ZWx0aWdlIFN1ZG8tRWluc3RlbGx1bmcuJzsgU1NIX05PUEFTU1dEPSR2YWx1ZTs7CiAgICBlc2FjCiAgZG9uZSA8PDwiJGZpZWxkcyIKfQoKc2F2ZV9zZXR0aW5ncygpIHsKICBbWyAkU0FWRV9TRVRUSU5HUyA9PSAxIF1dIHx8IHJldHVybiAwCiAgaW5zdGFsbCAtZCAtbSA3MDAgL2V0Yy9seGMtZW5yb2xsCiAgcHl0aG9uMyAtICIkVkFSU19GSUxFIiAiJE1BTkFHRVIiICIkVkVSU0lPTiIgIiRMT0dJTl9TRVJWRVIiICIkU1NIX0VOQUJMRUQiICIkU1NIX1VTRVIiICIkU1NIX1NVRE8iICIkU1NIX05PUEFTU1dEIiA8PCdQWScKaW1wb3J0IGpzb24sIG9zLCBzeXMsIHRlbXBmaWxlCnA9c3lzLmFyZ3ZbMV0Ka2V5cz0oJ21hbmFnZXInLCdhZ2VudF92ZXJzaW9uJywnbG9naW5fc2VydmVyJywnc3NoX2VuYWJsZWQnLCdzc2hfdXNlcicsJ3NzaF9zdWRvJywnc3NoX25vcGFzc3dkJykKZmQsdG1wPXRlbXBmaWxlLm1rc3RlbXAocHJlZml4PScudmFycy4nLGRpcj1vcy5wYXRoLmRpcm5hbWUocCkpCnRyeToKICAgIG9zLmZjaG1vZChmZCwwbzYwMCkKICAgIHdpdGggb3MuZmRvcGVuKGZkLCd3JykgYXMgZjoganNvbi5kdW1wKGRpY3QoemlwKGtleXMsc3lzLmFyZ3ZbMjpdKSksZixpbmRlbnQ9Mik7IGYud3JpdGUoJ1xuJykKICAgIG9zLnJlcGxhY2UodG1wLHApCmZpbmFsbHk6CiAgICBpZiBvcy5wYXRoLmV4aXN0cyh0bXApOiBvcy51bmxpbmsodG1wKQpQWQogIHNheSAnRWluc3RlbGx1bmdlbiBvaG5lIEdlaGVpbW5pc3NlIGdlc3BlaWNoZXJ0OiAvZXRjL2x4Yy1lbnJvbGwvLnZhcnMnCn0KCnNjYW4oKSB7CiAgbG9jYWwgcm93cyBpZCBzdGF0dXMgcmVzdCBjb25maWcgbWV0YSBuYW1lIG9zIGNvZGVuYW1lIHRzIHd6IG1nciB0dW4gaW5pdAogIHJvd3M9JChwY3QgbGlzdCkgfHwgZmFpbCAnQ29udGFpbmVybGlzdGUga29ubnRlIG5pY2h0IGdlbGVzZW4gd2VyZGVuLicKICB3aGlsZSByZWFkIC1yIGlkIHN0YXR1cyByZXN0OyBkbwogICAgW1sgJGlkID1+IF5bMS05XVswLTldKiQgXV0gfHwgY29udGludWUKICAgIGlmIFtbICRzdGF0dXMgIT0gcnVubmluZyBdXTsgdGhlbiBzYXkgIlVlYmVyc3BydW5nZW46IExYQyAkaWQgKCRzdGF0dXMpLiI7IGNvbnRpbnVlOyBmaQogICAgY29uZmlnPSQocGN0IGNvbmZpZyAiJGlkIikgfHwgeyBzYXkgIlVlYmVyc3BydW5nZW46IExYQyAkaWQgKEtvbmZpZ3VyYXRpb24gbmljaHQgbGVzYmFyKS4iOyBjb250aW51ZTsgfQogICAgaWYgZ3JlcCAtcSAnXmxvY2s6JyA8PDwiJGNvbmZpZyI7IHRoZW4gc2F5ICJVZWJlcnNwcnVuZ2VuOiBMWEMgJGlkIChnZXNwZXJydCkuIjsgY29udGludWU7IGZpCiAgICBtZXRhPSQodGltZW91dCAyMCBwY3QgZXhlYyAiJGlkIiAtLSBzaCAtcyA8PCdQUk9CRScKc2V0IC1ldQppc19pbnN0YWxsZWQoKSB7IFsgIiQoZHBrZy1xdWVyeSAtVyAtZj0nJHtTdGF0dXN9JyAiJDEiIDI+L2Rldi9udWxsIHx8IHRydWUpIiA9ICdpbnN0YWxsIG9rIGluc3RhbGxlZCcgXTsgfQouIC9ldGMvb3MtcmVsZWFzZQp0cz0wOyB3ej0wOyBtZ3I9MDsgdHVuPTA7IGluaXQ9MAppZiBjb21tYW5kIC12IHRhaWxzY2FsZSA+L2Rldi9udWxsIDI+JjEgfHwgaXNfaW5zdGFsbGVkIHRhaWxzY2FsZTsgdGhlbiB0cz0xOyBmaQppZiBbIC14IC92YXIvb3NzZWMvYmluL3dhenVoLWFnZW50ZCBdIHx8IGlzX2luc3RhbGxlZCB3YXp1aC1hZ2VudDsgdGhlbiB3ej0xOyBmaQppZiBbIC14IC92YXIvb3NzZWMvYmluL3dhenVoLW1hbmFnZXJkIF0gfHwgaXNfaW5zdGFsbGVkIHdhenVoLW1hbmFnZXI7IHRoZW4gbWdyPTE7IHd6PTE7IGZpClsgISAtYyAvZGV2L25ldC90dW4gXSB8fCB0dW49MQpbICEgLWQgL3J1bi9zeXN0ZW1kL3N5c3RlbSBdIHx8IGluaXQ9MQpwcmludGYgJyVzfCVzfCVzfCVzfCVzfCVzfCVzfCVzfCVzXG4nICIkKGhvc3RuYW1lKSIgIiRJRCIgIiR7VkVSU0lPTl9DT0RFTkFNRTotdW5rbm93bn0iICIkdHMiICIkd3oiICIkbWdyIiAiJHR1biIgIiRpbml0IiBkb25lClBST0JFCiAgICApIHx8IHsgc2F5ICJVZWJlcnNwcnVuZ2VuOiBMWEMgJGlkIChQcnVlZnVuZyBmZWhsZ2VzY2hsYWdlbikuIjsgY29udGludWU7IH0KICAgIElGUz0nfCcgcmVhZCAtciBuYW1lIG9zIGNvZGVuYW1lIHRzIHd6IG1nciB0dW4gaW5pdCByZXN0IDw8PCIkbWV0YSIKICAgIFtbICRtZ3IgPT0gMCBdXSB8fCBNQU5BR0VSUys9KCIkaWQiKQogICAgaWYgW1sgJHJlc3QgIT0gZG9uZSB8fCAkaW5pdCAhPSAxIHx8ICEgJG9zID1+IF4oZGViaWFufHVidW50dSkkIHx8ICEgJGNvZGVuYW1lID1+IF5bYS16XSskIF1dOyB0aGVuCiAgICAgIHNheSAiVWViZXJzcHJ1bmdlbjogTFhDICRpZCAodW50ZXJzdHVldHp0OiBEZWJpYW4vVWJ1bnR1IG1pdCBzeXN0ZW1kIHVuZCBWZXJzaW9ucy1Db2RlbmFtZSkuIjsgY29udGludWUKICAgIGZpCiAgICBpZiBbWyAkdHMgPT0gMSAmJiAkd3ogPT0gMSBdXTsgdGhlbgogICAgICBzYXkgIkF1c2dlYmxlbmRldDogTFhDICRpZCAkbmFtZSAoYmVpZGUgS29tcG9uZW50ZW4gdm9yaGFuZGVuOyBWZXJiaW5kdW5nIG5pY2h0IGdlcHJ1ZWZ0KS4iOyBjb250aW51ZQogICAgZmkKICAgIFtbICRuYW1lID1+IF5bQS1aYS16MC05XVtBLVphLXowLTkuXy1dezAsNjJ9JCBdXSB8fCB7IHNheSAiVWViZXJzcHJ1bmdlbjogTFhDICRpZCAoSG9zdG5hbWUgdW5ndWVsdGlnKS4iOyBjb250aW51ZTsgfQogICAgSURTKz0oIiRpZCIpOyBOQU1FU1skaWRdPSRuYW1lOyBUU1skaWRdPSR0czsgV1pbJGlkXT0kd3o7IFRVTlskaWRdPSR0dW4KICBkb25lIDw8PCIkcm93cyIKICBzYXkgJyc7IHNheSAnQXVzd2FlaGxiYXJlIExYQ3MgKG51ciBmZWhsZW5kZSBLb21wb25lbnRlbiB3ZXJkZW4gaW5zdGFsbGllcnQpOicKICBwcmludGYgJyUtOHMgJS0yNHMgJS0xMnMgJS0xMnMgJXNcbicgJ0NUSUQnICdOYW1lJyAnVGFpbHNjYWxlJyAnV2F6dWgnICdUVU4nCiAgZm9yIGlkIGluICIke0lEU1tAXX0iOyBkbwogICAgcHJpbnRmICclLThzICUtMjRzICUtMTJzICUtMTJzICVzXG4nICIkaWQiICIke05BTUVTWyRpZF19IiAiJChpbnN0YWxsZWRfbGFiZWwgIiR7VFNbJGlkXX0iKSIgIiQoaW5zdGFsbGVkX2xhYmVsICIke1daWyRpZF19IikiICIkKGluc3RhbGxlZF9sYWJlbCAiJHtUVU5bJGlkXX0iKSIKICBkb25lCn0KCmVuc3VyZV90dW4oKSB7CiAgbG9jYWwgaWQ9JDEgY2ZnIHNsb3Q9LTEgbgogIGNmZz0kKHBjdCBjb25maWcgIiRpZCIpIHx8IHJldHVybiAxCiAgaWYgISBncmVwIC1FcSAnXmRldlswLTldKzouKigvZGV2L25ldC90dW4pKCx8JCknIDw8PCIkY2ZnIjsgdGhlbgogICAgZm9yIG4gaW4gezAuLjI1NX07IGRvCiAgICAgIGlmICEgZ3JlcCAtcSAiXmRldiRuOiIgPDw8IiRjZmciOyB0aGVuIHNsb3Q9JG47IGJyZWFrOyBmaQogICAgZG9uZQogICAgW1sgJHNsb3QgIT0gLTEgXV0gfHwgeyBzYXkgIkxYQyAkaWQ6IEtlaW4gZnJlaWVyIERldmljZS1TbG90LiI7IHJldHVybiAxOyB9CiAgICBwcmludGYgJyVzXG4nICIkY2ZnIiA+IiRUTVAvbHhjLSRpZC5jb25mLmJlZm9yZSIgfHwgcmV0dXJuIDEKICAgICMgQmFja3VwcyBzdXJ2aXZlIHRoaXMgcnVuOyBubyBzZWNyZXRzIGFyZSBzdG9yZWQgaW4gdGhlc2UgZmlsZXMuCiAgICBpbnN0YWxsIC1kIC1tIDcwMCAvdmFyL2JhY2t1cHMvbHhjLWVucm9sbCB8fCByZXR1cm4gMQogICAgaW5zdGFsbCAtbSA2MDAgIiRUTVAvbHhjLSRpZC5jb25mLmJlZm9yZSIgIi92YXIvYmFja3Vwcy9seGMtZW5yb2xsLyRpZC0kKGRhdGUgKyVZJW0lZC0lSCVNJVMpLmNvbmYiIHx8IHJldHVybiAxCiAgICBwY3Qgc2V0ICIkaWQiICItLWRldiRzbG90IiAvZGV2L25ldC90dW4gfHwgcmV0dXJuIDEKICBmaQogICMgTmV2ZXIgZm9yY2libHkgc3RvcCBhIHdvcmtsb2FkLiBJZiBjbGVhbiBzaHV0ZG93biBmYWlscywgc3RvcCB0aGlzIENUJ3Mgd29yay4KICBwY3Qgc2h1dGRvd24gIiRpZCIgLS10aW1lb3V0IDkwIHx8IHJldHVybiAxCiAgcGN0IHN0YXJ0ICIkaWQiIHx8IHJldHVybiAxCiAgZm9yIG4gaW4gezEuLjMwfTsgZG8KICAgIGlmIHBjdCBleGVjICIkaWQiIC0tIHRlc3QgLWMgL2Rldi9uZXQvdHVuIDI+L2Rldi9udWxsOyB0aGVuIHJldHVybiAwOyBmaQogICAgc2xlZXAgMgogIGRvbmUKICBzYXkgIkxYQyAkaWQ6IFRVTiBuYWNoIE5ldXN0YXJ0IG5pY2h0IHZlcmZ1ZWdiYXIuIjsgcmV0dXJuIDEKfQoKd3JpdGVfd29ya2VyKCkgewogIGNhdCA+IiRUTVAvd29ya2VyLnNoIiA8PCdXT1JLRVInCiMhL3Vzci9iaW4vZW52IGJhc2gKc2V0IC1FZXVvIHBpcGVmYWlsCnNldCAreAp1bWFzayAwNzcKRElSPSQxOyBNQU5BR0VSPSQyOyBBR0VOVF9WRVJTSU9OPSQzOyBMT0dJTj0kNDsgTkFNRT0kNTsgV0FOVF9UUz0kNjsgV0FOVF9XWj0kNwpTU0hfRU5BQkxFRD0kODsgU1NIX1VTRVI9JDk7IFNTSF9TVURPPSR7MTB9OyBTU0hfTk9QQVNTV0Q9JHsxMX0KdHJhcCAncm0gLWYgLS0gIiRESVIvaGVhZHNjYWxlLmtleSIgIiRESVIvZW5yb2xsbWVudC5wYXNzIiAiJERJUi9zdWRvLnBhc3MiJyBFWElUCmV4cG9ydCBERUJJQU5fRlJPTlRFTkQ9bm9uaW50ZXJhY3RpdmUKcHJlc2VudCgpIHsgW1sgJChkcGtnLXF1ZXJ5IC1XIC1mPScke1N0YXR1c30nICIkMSIgMj4vZGV2L251bGwgfHwgdHJ1ZSkgPT0gJ2luc3RhbGwgb2sgaW5zdGFsbGVkJyBdXTsgfQpkb3dubG9hZCgpIHsgY3VybCAtLWZhaWwgLS1zaWxlbnQgLS1zaG93LWVycm9yIC0tbG9jYXRpb24gLS1wcm90byAnPWh0dHBzJyAtLXRsc3YxLjIgLS1jb25uZWN0LXRpbWVvdXQgMTAgLS1tYXgtdGltZSA2MCAtLXJldHJ5IDIgIiQxIiAtbyAiJDIiOyB9Cmhhc19yZXBvKCkgeyBncmVwIC1ScXMgLS0gIiQxIiAvZXRjL2FwdC9zb3VyY2VzLmxpc3QuZCAyPi9kZXYvbnVsbCB8fCBncmVwIC1xcyAtLSAiJDEiIC9ldGMvYXB0L3NvdXJjZXMubGlzdCAyPi9kZXYvbnVsbDsgfQoKIyBSZWNoZWNrIGJlZm9yZSBpbnN0YWxsaW5nOyBuZXZlciBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgY29tcG9uZW50IG9yIGVucm9sbG1lbnQuCmlmIHByZXNlbnQgdGFpbHNjYWxlIHx8IGNvbW1hbmQgLXYgdGFpbHNjYWxlID4vZGV2L251bGw7IHRoZW4gV0FOVF9UUz0wOyBmaQppZiBwcmVzZW50IHdhenVoLWFnZW50IHx8IHByZXNlbnQgd2F6dWgtbWFuYWdlciB8fCBbWyAteCAvdmFyL29zc2VjL2Jpbi93YXp1aC1hZ2VudGQgXV07IHRoZW4gV0FOVF9XWj0wOyBmaQppZiBbWyAkV0FOVF9XWiA9PSAxICYmIC1lIC92YXIvb3NzZWMvZXRjL29zc2VjLmNvbmYgXV07IHRoZW4KICBlY2hvICdXYXp1aC1Lb25maWd1cmF0aW9uIGJlcmVpdHMgdm9yaGFuZGVuOiBiaXR0ZSB1bnZvbGxzdGFlbmRpZ2UgSW5zdGFsbGF0aW9uIHp1ZXJzdCByZXBhcmllcmVuLicgPiYyOyBleGl0IDEKZmkKW1sgJFdBTlRfVFMgPT0gMSB8fCAkV0FOVF9XWiA9PSAxIHx8ICRTU0hfRU5BQkxFRCA9PSAxIF1dIHx8IHsgZWNobyAnS29tcG9uZW50ZW4gaW56d2lzY2hlbiB2b3JoYW5kZW47IG5pY2h0cyBnZWFlbmRlcnQuJzsgZXhpdCAwOyB9CgojIENoZWNrIG1hbmFnZXIgcmVhY2hhYmlsaXR5IGJlZm9yZSBpbnN0YWxsaW5nIGEgbmV3IGFnZW50LgppZiBbWyAkV0FOVF9XWiA9PSAxIF1dOyB0aGVuCiAgZm9yIHBvcnQgaW4gMTUxNCAxNTE1OyBkbwogICAgdGltZW91dCA1IGJhc2ggLWMgJ2V4ZWMgNzw+L2Rldi90Y3AvJDEvJDInIC0tICIkTUFOQUdFUiIgIiRwb3J0IiB8fCB7CiAgICAgIGVjaG8gIldhenVoLU1hbmFnZXIgYXVmIFBvcnQgJHBvcnQgbmljaHQgZXJyZWljaGJhcjsgSW5zdGFsbGF0aW9uIGFiZ2Vicm9jaGVuLiIgPiYyOyBleGl0IDE7CiAgICB9CiAgZG9uZQpmaQphcHQtZ2V0IC1vIERQa2c6OkxvY2s6OlRpbWVvdXQ9MTIwIHVwZGF0ZQphcHQtZ2V0IC1vIERQa2c6OkxvY2s6OlRpbWVvdXQ9MTIwIGluc3RhbGwgLXkgLS1uby1pbnN0YWxsLXJlY29tbWVuZHMgY2EtY2VydGlmaWNhdGVzIGN1cmwgZ251cGcKaW5zdGFsbCAtZCAtbSA3NTUgL3Vzci9zaGFyZS9rZXlyaW5ncyAvZXRjL2FwdC9zb3VyY2VzLmxpc3QuZAoKaWYgW1sgJFdBTlRfVFMgPT0gMSBdXTsgdGhlbgogIFtbIC1jIC9kZXYvbmV0L3R1biAmJiAtcyAkRElSL2hlYWRzY2FsZS5rZXkgXV0gfHwgeyBlY2hvICdUVU4gb2RlciBIZWFkc2NhbGUtU2NobHVlc3NlbCBmZWhsdC4nID4mMjsgZXhpdCAxOyB9CiAgLiAvZXRjL29zLXJlbGVhc2UKICBpZiAhIGhhc19yZXBvICdwa2dzLnRhaWxzY2FsZS5jb20nOyB0aGVuCiAgICBbWyAhIC1lIC9ldGMvYXB0L3NvdXJjZXMubGlzdC5kL3RhaWxzY2FsZS5saXN0IF1dIHx8IHsgZWNobyAnVm9yaGFuZGVuZSB0YWlsc2NhbGUubGlzdCBiaXR0ZSB6dWVyc3QgcHJ1ZWZlbi4nID4mMjsgZXhpdCAxOyB9CiAgICBkb3dubG9hZCAiaHR0cHM6Ly9wa2dzLnRhaWxzY2FsZS5jb20vc3RhYmxlLyRJRC8kVkVSU0lPTl9DT0RFTkFNRS5ub2FybW9yLmdwZyIgIiRESVIvdGFpbHNjYWxlLmdwZyIKICAgIGRvd25sb2FkICJodHRwczovL3BrZ3MudGFpbHNjYWxlLmNvbS9zdGFibGUvJElELyRWRVJTSU9OX0NPREVOQU1FLnRhaWxzY2FsZS1rZXlyaW5nLmxpc3QiICIkRElSL3RhaWxzY2FsZS5saXN0IgogICAgaW5zdGFsbCAtbSA2NDQgIiRESVIvdGFpbHNjYWxlLmdwZyIgL3Vzci9zaGFyZS9rZXlyaW5ncy90YWlsc2NhbGUtYXJjaGl2ZS1rZXlyaW5nLmdwZwogICAgaW5zdGFsbCAtbSA2NDQgIiRESVIvdGFpbHNjYWxlLmxpc3QiIC9ldGMvYXB0L3NvdXJjZXMubGlzdC5kL3RhaWxzY2FsZS5saXN0CiAgZmkKICBhcHQtZ2V0IC1vIERQa2c6OkxvY2s6OlRpbWVvdXQ9MTIwIHVwZGF0ZQogIGFwdC1nZXQgLW8gRFBrZzo6TG9jazo6VGltZW91dD0xMjAgaW5zdGFsbCAteSAtLW5vLWluc3RhbGwtcmVjb21tZW5kcyB0YWlsc2NhbGUKICBzeXN0ZW1jdGwgZW5hYmxlIC0tbm93IHRhaWxzY2FsZWQKICAjIEZpbGUgcmVmZXJlbmNlIGtlZXBzIHRoZSBrZXkgb3V0IG9mIGNvbW1hbmQgYXJndW1lbnRzL2hpc3RvcnkuIEtlZXAgbG9jYWwgRE5TLgogIHRpbWVvdXQgOTAgdGFpbHNjYWxlIHVwIC0tbG9naW4tc2VydmVyPSIkTE9HSU4iIC0tYXV0aC1rZXk9ImZpbGU6JERJUi9oZWFkc2NhbGUua2V5IiAtLWhvc3RuYW1lPSIkTkFNRSIgLS1hY2NlcHQtZG5zPWZhbHNlIC0tdGltZW91dD02MHMKICBybSAtZiAtLSAiJERJUi9oZWFkc2NhbGUua2V5IgogIHRhaWxzY2FsZSBpcCAtNAogIGVjaG8gJ1RhaWxzY2FsZSBpbnN0YWxsaWVydCB1bmQgYW5nZW1lbGRldC4nCmZpCgppZiBbWyAkV0FOVF9XWiA9PSAxIF1dOyB0aGVuCiAgaWYgISBoYXNfcmVwbyAncGFja2FnZXMud2F6dWguY29tJzsgdGhlbgogICAgW1sgISAtZSAvZXRjL2FwdC9zb3VyY2VzLmxpc3QuZC9seGMtZW5yb2xsLXdhenVoLmxpc3QgXV0gfHwgZXhpdCAxCiAgICBkb3dubG9hZCBodHRwczovL3BhY2thZ2VzLndhenVoLmNvbS9rZXkvR1BHLUtFWS1XQVpVSCAiJERJUi93YXp1aC5hc2MiCiAgICBncGcgLS1iYXRjaCAtLXllcyAtLWRlYXJtb3IgLW8gIiRESVIvd2F6dWguZ3BnIiAiJERJUi93YXp1aC5hc2MiCiAgICBpbnN0YWxsIC1tIDY0NCAiJERJUi93YXp1aC5ncGciIC91c3Ivc2hhcmUva2V5cmluZ3MvbHhjLWVucm9sbC13YXp1aC5ncGcKICAgIHByaW50ZiAnJXNcbicgJ2RlYiBbc2lnbmVkLWJ5PS91c3Ivc2hhcmUva2V5cmluZ3MvbHhjLWVucm9sbC13YXp1aC5ncGddIGh0dHBzOi8vcGFja2FnZXMud2F6dWguY29tLzQueC9hcHQvIHN0YWJsZSBtYWluJyA+L2V0Yy9hcHQvc291cmNlcy5saXN0LmQvbHhjLWVucm9sbC13YXp1aC5saXN0CiAgICBjaG1vZCA2NDQgL2V0Yy9hcHQvc291cmNlcy5saXN0LmQvbHhjLWVucm9sbC13YXp1aC5saXN0CiAgZmkKICBhcHQtZ2V0IC1vIERQa2c6OkxvY2s6OlRpbWVvdXQ9MTIwIHVwZGF0ZQogIGFwdC1jYWNoZSBtYWRpc29uIHdhenVoLWFnZW50IHwgYXdrICd7cHJpbnQgJDN9JyB8IGdyZXAgLUZ4ICIkQUdFTlRfVkVSU0lPTiIgPi9kZXYvbnVsbCB8fCB7CiAgICBlY2hvICJXYXp1aC1WZXJzaW9uICRBR0VOVF9WRVJTSU9OIG5pY2h0IHZlcmZ1ZWdiYXI7IGtlaW4gbmV1ZXJlciBBZ2VudCB3aXJkIGluc3RhbGxpZXJ0LiIgPiYyOyBleGl0IDE7CiAgfQogIGV4cG9ydCBXQVpVSF9NQU5BR0VSPSIkTUFOQUdFUiIgV0FaVUhfUkVHSVNUUkFUSU9OX1NFUlZFUj0iJE1BTkFHRVIiIFdBWlVIX0FHRU5UX05BTUU9IiROQU1FIgogIGlmIFtbIC1zICRESVIvZW5yb2xsbWVudC5wYXNzIF1dOyB0aGVuIGV4cG9ydCBXQVpVSF9SRUdJU1RSQVRJT05fUEFTU1dPUkQ7IFdBWlVIX1JFR0lTVFJBVElPTl9QQVNTV09SRD0kKGNhdCAiJERJUi9lbnJvbGxtZW50LnBhc3MiKTsgZmkKICBhcHQtZ2V0IC1vIERQa2c6OkxvY2s6OlRpbWVvdXQ9MTIwIGluc3RhbGwgLXkgLS1uby1pbnN0YWxsLXJlY29tbWVuZHMgIndhenVoLWFnZW50PSRBR0VOVF9WRVJTSU9OIgogIHVuc2V0IFdBWlVIX1JFR0lTVFJBVElPTl9QQVNTV09SRAogIHJtIC1mIC0tICIkRElSL2Vucm9sbG1lbnQucGFzcyIKICBhcHQtbWFyayBob2xkIHdhenVoLWFnZW50CiAgc3lzdGVtY3RsIGRhZW1vbi1yZWxvYWQKICBzeXN0ZW1jdGwgZW5hYmxlIC0tbm93IHdhenVoLWFnZW50CiAgY29ubmVjdGVkPTAKICBmb3IgYXR0ZW1wdCBpbiB7MS4uMzB9OyBkbwogICAgaWYgZ3JlcCAtcSAiXnN0YXR1cz0nY29ubmVjdGVkJyIgL3Zhci9vc3NlYy92YXIvcnVuL3dhenVoLWFnZW50ZC5zdGF0ZSAyPi9kZXYvbnVsbDsgdGhlbgogICAgICBjb25uZWN0ZWQ9MTsgYnJlYWsKICAgIGZpCiAgICBzbGVlcCAyCiAgZG9uZQogIGlmIFtbICRjb25uZWN0ZWQgPT0gMCBdXTsgdGhlbgogICAgZWNobyAnV2F6dWggaW5zdGFsbGllcnQsIFZlcmJpbmR1bmcgbmljaHQgYmVzdGFldGlndC4gQWdlbnQtS29uZmlndXJhdGlvbi9Mb2dzIHBydWVmZW47IGtlaW5lIGF1dG9tYXRpc2NoZSBOZXVyZWdpc3RyaWVydW5nLicgPiYyCiAgICBleGl0IDEKICBmaQogIGVjaG8gJ1dhenVoIGluc3RhbGxpZXJ0IHVuZCBtaXQgZGVtIE1hbmFnZXIgdmVyYnVuZGVuLicKZmkKCmlmIFtbICRTU0hfRU5BQkxFRCA9PSAxIF1dOyB0aGVuCiAgaWYgW1sgJFNTSF9TVURPID09IDEgXV07IHRoZW4gYXB0LWdldCAtbyBEUGtnOjpMb2NrOjpUaW1lb3V0PTEyMCBpbnN0YWxsIC15IC0tbm8taW5zdGFsbC1yZWNvbW1lbmRzIHN1ZG87IGZpCiAgbmV3X3VzZXI9MAogIGlmIGlkICIkU1NIX1VTRVIiID4vZGV2L251bGwgMj4mMTsgdGhlbgogICAgW1sgJChpZCAtdSAiJFNTSF9VU0VSIikgIT0gMCBdXSB8fCB7IGVjaG8gJ1NTSC1aaWVsIGRhcmYga2VpbiByb290LUtvbnRvIHNlaW4uJyA+JjI7IGV4aXQgMTsgfQogIGVsc2UKICAgIHVzZXJhZGQgLW0gLVUgLXMgL2Jpbi9iYXNoICIkU1NIX1VTRVIiCiAgICBuZXdfdXNlcj0xCiAgZmkKICBpZiBbWyAkU1NIX1NVRE8gPT0gMSBdXTsgdGhlbgogICAgdXNlcm1vZCAtYUcgc3VkbyAiJFNTSF9VU0VSIgogICAgaWYgW1sgJFNTSF9OT1BBU1NXRCA9PSAxIF1dOyB0aGVuCiAgICAgIHN1ZG9fcnVsZT0iL2V0Yy9zdWRvZXJzLmQvbHhjLWVucm9sbC0kU1NIX1VTRVIiCiAgICAgIHByaW50ZiAnJXMgQUxMPShBTEw6QUxMKSBOT1BBU1NXRDogQUxMXG4nICIkU1NIX1VTRVIiID4iJERJUi9zdWRvZXJzIgogICAgICB2aXN1ZG8gLWNmICIkRElSL3N1ZG9lcnMiCiAgICAgIGlmIFtbIC1lICRzdWRvX3J1bGUgXV0gJiYgISBjbXAgLXMgIiRESVIvc3Vkb2VycyIgIiRzdWRvX3J1bGUiOyB0aGVuCiAgICAgICAgZWNobyAnQWJ3ZWljaGVuZGUgdm9yaGFuZGVuZSBTdWRvLVJlZ2VsOiBiaXR0ZSBtYW51ZWxsIHBydWVmZW4uJyA+JjI7IGV4aXQgMQogICAgICBmaQogICAgICBpbnN0YWxsIC1tIDQ0MCAiJERJUi9zdWRvZXJzIiAiJHN1ZG9fcnVsZSIKICAgIGVsaWYgW1sgJG5ld191c2VyID09IDEgXV07IHRoZW4KICAgICAgW1sgLXMgJERJUi9zdWRvLnBhc3MgXV0gfHwgeyBlY2hvICdQYXNzd29ydCBmdWVyIG5ldWVuIFN1ZG8tTnV0emVyIGZlaGx0LicgPiYyOyBleGl0IDE7IH0KICAgICAgeyBwcmludGYgJyVzOicgIiRTU0hfVVNFUiI7IGNhdCAiJERJUi9zdWRvLnBhc3MiOyBwcmludGYgJ1xuJzsgfSB8IGNocGFzc3dkCiAgICAgIGVjaG8gJ1Bhc3N3b3J0IGZ1ZXIgbmV1ZW4gTnV0emVyIGdlc2V0enQ7IGJlc3RlaGVuZGUgTnV0emVycGFzc3dvZXJ0ZXIgYmxlaWJlbiB1bnZlcmFlbmRlcnQuJwogICAgZmkKICBmaQogIHJtIC1mIC0tICIkRElSL3N1ZG8ucGFzcyIKICB0YWlsc2NhbGUgc2V0IC0tc3NoPXRydWUKICBlY2hvICJUYWlsc2NhbGUgU1NIIGZ1ZXIgJFNTSF9VU0VSIGFrdGl2aWVydC4gSGVhZHNjYWxlLU5ldHp3ZXJrLSBVTkQgU1NILVBvbGljeSBtdWVzc2VuIGRpZXNlbiBadWdyaWZmIGVybGF1YmVuLiIKICBlY2hvICdLZWluIE9wZW5TU0gtU2VydmVyIGluc3RhbGxpZXJ0LiBWb3JoYW5kZW5lIE51dHplci0vU3Vkby1SZWNodGUgd2VyZGVuIG5pY2h0IGVudHpvZ2VuLicKZmkKV09SS0VSCn0KCmRlcGxveV9vbmUoKSB7CiAgbG9jYWwgaWQ9JDEgZGlyIHRzIHd6CiAgW1sgJChwY3Qgc3RhdHVzICIkaWQiKSA9PSAnc3RhdHVzOiBydW5uaW5nJyBdXSB8fCByZXR1cm4gMQogIGlmIHBjdCBjb25maWcgIiRpZCIgfCBncmVwIC1xICdebG9jazonOyB0aGVuIHNheSAiTFhDICRpZCBpbnp3aXNjaGVuIGdlc3BlcnJ0LiI7IHJldHVybiAxOyBmaQogIHRzPSQoKDEtJHtUU1skaWRdfSkpOyB3ej0kKCgxLSR7V1pbJGlkXX0pKQogIGlmIFtbICR0cyA9PSAxICYmICR7VFVOWyRpZF19ID09IDAgXV07IHRoZW4gZW5zdXJlX3R1biAiJGlkIiB8fCByZXR1cm4gMTsgZmkKICBkaXI9JChwY3QgZXhlYyAiJGlkIiAtLSBta3RlbXAgLWQgL3J1bi9seGMtZW5yb2xsLlhYWFhYWFhYKSB8fCByZXR1cm4gMQogIFtbICRkaXIgPX4gXi9ydW4vbHhjLWVucm9sbFwuW0EtWmEtejAtOV0rJCBdXSB8fCByZXR1cm4gMQogIFJFTU9URV9ESVJTWyRpZF09JGRpcgogIHBjdCBwdXNoICIkaWQiICIkVE1QL3dvcmtlci5zaCIgIiRkaXIvd29ya2VyLnNoIiAtLXVzZXIgMCAtLWdyb3VwIDAgLS1wZXJtcyA2MDAgfHwgcmV0dXJuIDEKICBpZiBbWyAkdHMgPT0gMSBdXTsgdGhlbiBwY3QgcHVzaCAiJGlkIiAiJFRNUC9oZWFkc2NhbGUua2V5IiAiJGRpci9oZWFkc2NhbGUua2V5IiAtLXVzZXIgMCAtLWdyb3VwIDAgLS1wZXJtcyA2MDAgfHwgcmV0dXJuIDE7IGZpCiAgaWYgW1sgJHd6ID09IDEgJiYgLXMgJFRNUC9lbnJvbGxtZW50LnBhc3MgXV07IHRoZW4gcGN0IHB1c2ggIiRpZCIgIiRUTVAvZW5yb2xsbWVudC5wYXNzIiAiJGRpci9lbnJvbGxtZW50LnBhc3MiIC0tdXNlciAwIC0tZ3JvdXAgMCAtLXBlcm1zIDYwMCB8fCByZXR1cm4gMTsgZmkKICBpZiBbWyAkU1NIX0VOQUJMRUQgPT0gMSBdXTsgdGhlbgogICAgaWYgW1sgLXMgJFRNUC9zdWRvLnBhc3MgXV07IHRoZW4gcGN0IHB1c2ggIiRpZCIgIiRUTVAvc3Vkby5wYXNzIiAiJGRpci9zdWRvLnBhc3MiIC0tdXNlciAwIC0tZ3JvdXAgMCAtLXBlcm1zIDYwMCB8fCByZXR1cm4gMTsgZmkKICBmaQogICMgQ3JlZGVudGlhbHMgYXJlIGZpbGVzLCBuZXZlciBwY3QgYXJndW1lbnRzLiBiYXNoIHJlY2VpdmVzIG9ubHkgbm9uLXNlY3JldCBzZXR0aW5ncy4KICBwY3QgZXhlYyAiJGlkIiAtLSBiYXNoICIkZGlyL3dvcmtlci5zaCIgIiRkaXIiICIkTUFOQUdFUiIgIiRWRVJTSU9OIiAiJExPR0lOX1NFUlZFUiIgIiR7TkFNRVNbJGlkXX0iICIkdHMiICIkd3oiICIkU1NIX0VOQUJMRUQiICIkU1NIX1VTRVIiICIkU1NIX1NVRE8iICIkU1NIX05PUEFTU1dEIiB8fCByZXR1cm4gMQogIGlmIFtbICR0cyA9PSAxIF1dOyB0aGVuCiAgICBwY3QgZXhlYyAiJGlkIiAtLSB0YWlsc2NhbGUgc3RhdHVzIC0tanNvbiB8IHB5dGhvbjMgLWMgJ2ltcG9ydCBqc29uLHN5czsgZD1qc29uLmxvYWQoc3lzLnN0ZGluKTsgc3lzLmV4aXQoMCBpZiBkLmdldCgiQmFja2VuZFN0YXRlIik9PSJSdW5uaW5nIiBhbmQgZC5nZXQoIlNlbGYiLHt9KS5nZXQoIk9ubGluZSIpIGlzIFRydWUgZWxzZSAxKScgfHwgewogICAgICBzYXkgIkxYQyAkaWQ6IEhlYWRzY2FsZS1WZXJiaW5kdW5nIG5pY2h0IGFscyBvbmxpbmUgYmVzdGFldGlndC4iOyByZXR1cm4gMTsKICAgIH0KICBmaQogIHBjdCBleGVjICIkaWQiIC0tIHJtIC1yZiAtLSAiJGRpciIgfHwgcmV0dXJuIDEKICBSRU1PVEVfRElSU1skaWRdPScnCn0KCm1haW4oKSB7CiAgbG9jYWwgaWQgaW5wdXQgd2FudGVkIGZvdW5kIG5lZWRfdHM9MCBuZWVkX3d6PTAgcmVzdGFydF9pZHM9Jycgb2s9JycgZmFpbGVkPScnIGF1dG9fdmVyc2lvbiBwYXNzd29yZAogIHNjYW4KICBbWyAkTElTVCA9PSAwIF1dIHx8IHJldHVybiAwCiAgW1sgJHsjSURTW0BdfSAtZ3QgMCBdXSB8fCB7IHNheSAnS2VpbmUgcGFzc2VuZGVuIExYQ3MgZ2VmdW5kZW4uJzsgcmV0dXJuIDA7IH0KICBleGVjIDM8Pi9kZXYvdHR5IHx8IGZhaWwgJ0ludGVyYWt0aXZlcyBUZXJtaW5hbCBmZWhsdC4gLS1saXN0IGthbm4gb2huZSBUZXJtaW5hbCBsYXVmZW4uJwogIGxvYWRfc2V0dGluZ3MKICB5ZXNfbm8gIlRhaWxzY2FsZSBTU0ggZnVlciBhbGxlIGF1c2dld2FlaGx0ZW4gTFhDcyBha3RpdmllcmVuPyAoU3RhbmRhcmQ6ICRTU0hfRU5BQkxFRDsgMT1KYSwgMD1OZWluKSBbai9uXSIgIiRTU0hfRU5BQkxFRCIKICBTU0hfRU5BQkxFRD0kUkVQTFkKICBpZiBbWyAkU1NIX0VOQUJMRUQgPT0gMSBdXTsgdGhlbgogICAgYXNrICJTU0gtTnV0emVybmFtZSR7U1NIX1VTRVI6KyBbJFNTSF9VU0VSXX06IgogICAgU1NIX1VTRVI9JHtSRVBMWTotJFNTSF9VU0VSfQogICAgW1sgJFNTSF9VU0VSID1+IF5bYS16X11bYS16MC05Xy1dezAsMzB9JCAmJiAkU1NIX1VTRVIgIT0gcm9vdCBdXSB8fCBmYWlsICdCaXR0ZSBlaW5lbiBub3JtYWxlbiBMaW51eC1OdXR6ZXJuYW1lbiB2ZXJ3ZW5kZW4uJwogICAgeWVzX25vICJOdXR6ZXIgenUgc3VkbyBoaW56dWZ1ZWdlbj8gKFN0YW5kYXJkOiAkU1NIX1NVRE8pIFtqL25dIiAiJFNTSF9TVURPIjsgU1NIX1NVRE89JFJFUExZCiAgICBpZiBbWyAkU1NIX1NVRE8gPT0gMSBdXTsgdGhlbgogICAgICB5ZXNfbm8gIlN1ZG8gT0hORSBQYXNzd29ydCBlcmxhdWJlbj8gKFN0YW5kYXJkOiAkU1NIX05PUEFTU1dEOyAwPVBhc3N3b3J0IGVyZm9yZGVybGljaCkgW2ovbl0iICIkU1NIX05PUEFTU1dEIjsgU1NIX05PUEFTU1dEPSRSRVBMWQogICAgZmkKICBmaQogIHllc19ubyAnRWluc3RlbGx1bmdlbiBvaG5lIEdlaGVpbW5pc3NlIGxva2FsIGluIC52YXJzIHNwZWljaGVybj8gW0ovbl0nIDE7IFNBVkVfU0VUVElOR1M9JFJFUExZCiAgaWYgW1sgLXogJFNFTEVDVElPTiBdXTsgdGhlbiBhc2sgJ0NUSURzIGF1c3dhZWhsZW4gKExlZXJ6ZWljaGVuLCBvZGVyIGFsbDsgbGVlciA9IGFiYnJlY2hlbik6JzsgU0VMRUNUSU9OPSRSRVBMWTsgZmkKICBbWyAtbiAkU0VMRUNUSU9OIF1dIHx8IHJldHVybiAwCiAgaWYgW1sgJFNFTEVDVElPTiA9PSBhbGwgXV07IHRoZW4gU0VMRUNURUQ9KCIke0lEU1tAXX0iKTsgZWxzZQogICAgU0VMRUNUSU9OPSR7U0VMRUNUSU9OLy8sLyB9CiAgICBmb3Igd2FudGVkIGluICRTRUxFQ1RJT047IGRvCiAgICAgIFtbICR3YW50ZWQgPX4gXlsxLTldWzAtOV0qJCBdXSB8fCBmYWlsICdCaXR0ZSBudXIgQ1RJRHMgZWluZ2ViZW4uJwogICAgICBmb3VuZD0wCiAgICAgIGZvciBpZCBpbiAiJHtJRFNbQF19IjsgZG8gW1sgJGlkICE9ICIkd2FudGVkIiBdXSB8fCBmb3VuZD0xOyBkb25lCiAgICAgIFtbICRmb3VuZCA9PSAxIF1dIHx8IGZhaWwgIkxYQyAkd2FudGVkIGlzdCBuaWNodCBhdXN3YWVobGJhci4iCiAgICAgIGZvciBpZCBpbiAiJHtTRUxFQ1RFRFtAXX0iOyBkbyBbWyAkaWQgIT0gIiR3YW50ZWQiIF1dIHx8IGZhaWwgIkxYQyAkd2FudGVkIGRvcHBlbHQgYXVzZ2V3YWVobHQuIjsgZG9uZQogICAgICBTRUxFQ1RFRCs9KCIkd2FudGVkIikKICAgIGRvbmUKICBmaQogIHNheSAnJzsgc2F5ICdHZXBsYW50ZSBBZW5kZXJ1bmdlbjonCiAgZm9yIGlkIGluICIke1NFTEVDVEVEW0BdfSI7IGRvCiAgICBzYXkgIkxYQyAkaWQgJHtOQU1FU1skaWRdfTogVGFpbHNjYWxlICQoaW5zdGFsbGVkX2xhYmVsICIke1RTWyRpZF19IiksIFdhenVoICQoaW5zdGFsbGVkX2xhYmVsICIke1daWyRpZF19IikuIgogICAgaWYgW1sgJHtUU1skaWRdfSA9PSAwIF1dOyB0aGVuCiAgICAgIG5lZWRfdHM9MQogICAgICBpZiBbWyAke1RVTlskaWRdfSA9PSAwIF1dOyB0aGVuIHJlc3RhcnRfaWRzKz0iICRpZCI7IGZpCiAgICBmaQogICAgW1sgJHtXWlskaWRdfSAhPSAwIF1dIHx8IG5lZWRfd3o9MQogIGRvbmUKICBpZiBbWyAtbiAkcmVzdGFydF9pZHMgXV07IHRoZW4gc2F5ICJUVU4tWnVncmlmZiBlcmdhZW56ZW4gdW5kIHNhdWJlciBuZXUgc3RhcnRlbjokcmVzdGFydF9pZHMiOyBmaQogIGlmIFtbICRTU0hfRU5BQkxFRCA9PSAxIF1dOyB0aGVuCiAgICBzYXkgIlRhaWxzY2FsZSBTU0ggZnVlciBkaWUgQXVzd2FobDogTnV0emVyICRTU0hfVVNFUiwgU3Vkbz0kU1NIX1NVRE8sIFN1ZG8tb2huZS1QYXNzd29ydD0kU1NIX05PUEFTU1dELiIKICAgIHNheSAnSGVhZHNjYWxlIG11c3MgWnVncmlmZiBhdWYgUG9ydCAyMiB1bmQgZGVuIGdld2FlaGx0ZW4gTGludXgtTnV0emVyIGluIHNlaW5lciBTU0gtUG9saWN5IGVybGF1YmVuLicKICAgIHNheSAnQmVzdGVoZW5kZSBTU0gtU2l0enVuZ2VuIHVlYmVyIFRhaWxzY2FsZSBrb2VubmVuIGJlaW0gQWt0aXZpZXJlbiB1bnRlcmJyb2NoZW4gd2VyZGVuLicKICBmaQogIFtbICREUlkgPT0gMCBdXSB8fCB7IHNheSAnVm9yc2NoYXUgYWJnZXNjaGxvc3Nlbi4gS2VpbmUgQWVuZGVydW5nZW4gdm9yZ2Vub21tZW4uJzsgcmV0dXJuIDA7IH0KICBjb21tYW5kIC12IGZsb2NrID4vZGV2L251bGwgfHwgZmFpbCAnZmxvY2sgZmVobHQgYXVmIGRlbSBIb3N0LicKICBleGVjIDg+L3J1bi9seGMtZW5yb2xsLmxvY2sKICBmbG9jayAtbiA4IHx8IGZhaWwgJ0VpbiBhbmRlcmVyIEVucm9sbG1lbnQtTGF1ZiBpc3QgYmVyZWl0cyBha3Rpdi4nCiAgaWYgW1sgJG5lZWRfd3ogPT0gMSBdXTsgdGhlbgogICAgaWYgW1sgLXogJE1BTkFHRVIgXV07IHRoZW4gYXNrICdFcnJlaWNoYmFyZSBXYXp1aC1NYW5hZ2VyLUFkcmVzc2UgKExBTi1JUCBvZGVyIEhlYWRzY2FsZS1JUCk6JzsgTUFOQUdFUj0kUkVQTFk7IGZpCiAgICBbWyAkTUFOQUdFUiA9fiBeW0EtWmEtejAtOV1bQS1aYS16MC05LjotXSokIF1dIHx8IGZhaWwgJ01hbmFnZXItQWRyZXNzZSB1bmd1ZWx0aWcuJwogICAgaWYgW1sgJHsjTUFOQUdFUlNbQF19ID09IDEgXV07IHRoZW4KICAgICAgYXV0b192ZXJzaW9uPSQocGN0IGV4ZWMgIiR7TUFOQUdFUlNbMF19IiAtLSBkcGtnLXF1ZXJ5IC1XICctZj0ke1ZlcnNpb259JyB3YXp1aC1tYW5hZ2VyKSB8fCBmYWlsICdNYW5hZ2VyLVZlcnNpb24gbmljaHQgbGVzYmFyLicKICAgICAgaWYgW1sgJFZFUlNJT05fRVhQTElDSVQgPT0gMSAmJiAkVkVSU0lPTiAhPSAiJGF1dG9fdmVyc2lvbiIgXV07IHRoZW4gZmFpbCAnQWdlbnQtVmVyc2lvbiBtdXNzIGRlciBsb2thbGVuIE1hbmFnZXItVmVyc2lvbiBlbnRzcHJlY2hlbi4nOyBmaQogICAgICBWRVJTSU9OPSRhdXRvX3ZlcnNpb24KICAgIGVsaWYgW1sgLXogJFZFUlNJT04gXV07IHRoZW4gYXNrICdFeGFrdGUgTWFuYWdlci1QYWtldHZlcnNpb24gKHouQi4gNC4xNC44LTEpOic7IFZFUlNJT049JFJFUExZOyBmaQogICAgW1sgJFZFUlNJT04gPX4gXlswLTldK1wuWzAtOV0rXC5bMC05XSstWzAtOV0rJCBdXSB8fCBmYWlsICdQYWtldHZlcnNpb24gdW5ndWVsdGlnLicKICAgIHNheSAiTmV1ZSBXYXp1aC1BZ2VudHM6ICRWRVJTSU9OIChhbnNjaGxpZXNzZW5kIGF1ZiBob2xkOyBnZW1laW5zYW0gbWl0IE1hbmFnZXIgYWt0dWFsaXNpZXJlbikuIgogIGZpCiAgaWYgW1sgJG5lZWRfdHMgPT0gMSBdXTsgdGhlbgogICAgaWYgW1sgLXogJExPR0lOX1NFUlZFUiBdXTsgdGhlbiBhc2sgJ0hlYWRzY2FsZS1Mb2dpbi1TZXJ2ZXIgKHZvbGxzdGFlbmRpZ2UgSFRUUFMtVVJMKTonOyBMT0dJTl9TRVJWRVI9JFJFUExZOyBmaQogICAgW1sgJExPR0lOX1NFUlZFUiA9fiBeaHR0cHM6Ly9bQS1aYS16MC05XVtBLVphLXowLTkuOi1dKi8/JCBdXSB8fCBmYWlsICdCaXR0ZSBlaW5lIEhUVFBTLVNlcnZlci1VUkwgb2huZSBadWdhbmdzZGF0ZW4vUXVlcnkgZWluZ2ViZW4uJwogICAgc2F5ICdOZXVlIFRhaWxzY2FsZS1DbGllbnRzIHZlcndlbmRlbiBIZWFkc2NhbGU7IHZvcmhhbmRlbmUgbG9rYWxlIEROUy1FaW5zdGVsbHVuZ2VuIGJsZWliZW4gYWt0aXYuJwogIGZpCiAgYXNrICdOdXIgZGllc2UgZmVobGVuZGVuIEtvbXBvbmVudGVuIGluc3RhbGxpZXJlbj8gQml0dGUgSU5TVEFMTCBlaW5nZWJlbjonCiAgW1sgJFJFUExZID09IElOU1RBTEwgXV0gfHwgeyBzYXkgJ0FiZ2Vicm9jaGVuLic7IHJldHVybiAwOyB9CiAgaWYgW1sgLW4gJHJlc3RhcnRfaWRzIF1dOyB0aGVuCiAgICBhc2sgIkRpZXNlIENvbnRhaW5lciB3ZXJkZW4ga3VyeiB1bnRlcmJyb2NoZW46JHJlc3RhcnRfaWRzLiBCaXR0ZSBORVVTVEFSVCBlaW5nZWJlbjoiCiAgICBbWyAkUkVQTFkgPT0gTkVVU1RBUlQgXV0gfHwgeyBzYXkgJ0FiZ2Vicm9jaGVuOyBuaWNodHMgaW5zdGFsbGllcnQuJzsgcmV0dXJuIDA7IH0KICBmaQogIFRNUD0kKG1rdGVtcCAtZCAvcnVuL2x4Yy1lbnJvbGwuWFhYWFhYWFgpCiAgaWYgW1sgJFNTSF9FTkFCTEVEID09IDEgXV07IHRoZW4KICAgIGlmIFtbICRTU0hfU1VETyA9PSAxICYmICRTU0hfTk9QQVNTV0QgPT0gMCBdXTsgdGhlbgogICAgICBzZWNyZXQgJ1Bhc3N3b3J0IGZ1ZXIgTkVVIGFuZ2VsZWd0ZSBTdWRvLU51dHplciAoYmVzdGVoZW5kZSBQYXNzd29lcnRlciBibGVpYmVuIHVudmVyYWVuZGVydCk6JzsgcGFzc3dvcmQ9JFJFUExZCiAgICAgIFtbIC1uICRwYXNzd29yZCAmJiAkcGFzc3dvcmQgIT0gKjoqICYmICRwYXNzd29yZCAhPSAqJCdccicqIF1dIHx8IGZhaWwgJ1Bhc3N3b3J0IGxlZXIgb2RlciBlbnRoYWVsdCB1bmd1ZWx0aWdlIFplaWNoZW4uJwogICAgICBzZWNyZXQgJ1Bhc3N3b3J0IHdpZWRlcmhvbGVuOic7IFtbICRSRVBMWSA9PSAiJHBhc3N3b3JkIiBdXSB8fCBmYWlsICdQYXNzd29lcnRlciBzdGltbWVuIG5pY2h0IHVlYmVyZWluLicKICAgICAgcHJpbnRmICclcycgIiRwYXNzd29yZCIgPiIkVE1QL3N1ZG8ucGFzcyI7IHBhc3N3b3JkPScnOyBSRVBMWT0nJwogICAgZmkKICBmaQogIHNhdmVfc2V0dGluZ3MKICBpZiBbWyAkbmVlZF90cyA9PSAxIF1dOyB0aGVuCiAgICBzZWNyZXQgJ1dpZWRlcnZlcndlbmRiYXJlbiwga3VyeiBndWVsdGlnZW4gSGVhZHNjYWxlLVByZWF1dGgtS2V5IGVpbmdlYmVuICh3aXJkIG5pY2h0IGFuZ2V6ZWlndCk6JwogICAgW1sgLW4gJFJFUExZICYmICRSRVBMWSAhPSAqJCdccicqIF1dIHx8IGZhaWwgJ1NjaGx1ZXNzZWwgZmVobHQvdW5ndWVsdGlnLicKICAgIHByaW50ZiAnJXMnICIkUkVQTFkiID4iJFRNUC9oZWFkc2NhbGUua2V5IjsgUkVQTFk9JycKICBmaQogIGlmIFtbICRuZWVkX3d6ID09IDEgXV07IHRoZW4KICAgIHNlY3JldCAnT3B0aW9uYWxlcyBXYXp1aC1FbnJvbGxtZW50LVBhc3N3b3J0IChsZWVyLCBmYWxscyBrZWluZXMgdmVybGFuZ3Qgd2lyZCk6JwogICAgcHJpbnRmICclcycgIiRSRVBMWSIgPiIkVE1QL2Vucm9sbG1lbnQucGFzcyI7IFJFUExZPScnCiAgZmkKICB3cml0ZV93b3JrZXIKICBmb3IgaWQgaW4gIiR7U0VMRUNURURbQF19IjsgZG8KICAgIHNheSAnJzsgc2F5ICJCZWFyYmVpdGUgTFhDICRpZCAke05BTUVTWyRpZF19IC4uLiIKICAgICMgQWxsIGRlcGxveW1lbnQgb3BlcmF0aW9ucyBleHBsaWNpdGx5IHByb3BhZ2F0ZSBlcnJvcnMuIFBhcmVudCBvd25zIGNsZWFudXAuCiAgICBpZiBkZXBsb3lfb25lICIkaWQiOyB0aGVuIG9rKz0iICRpZCI7IGVsc2UgZmFpbGVkKz0iICRpZCI7IGZpCiAgZG9uZQogIHNheSAnJzsgc2F5ICJFcmZvbGdyZWljaCBhYmdlc2NobG9zc2VuOiR7b2s6LSBrZWluZX0iCiAgc2F5ICJOaWNodCBhYmdlc2NobG9zc2VuOiR7ZmFpbGVkOi0ga2VpbmV9IgogIHNheSAnQmVyZWl0cyB2b3JoYW5kZW5lIEtvbXBvbmVudGVuIHd1cmRlbiBuaWNodCB1bWtvbmZpZ3VyaWVydDsgZGVyZW4gVmVyYmluZHVuZyBiaXR0ZSBzZXBhcmF0IHBydWVmZW4uJwogIHNheSAnRW5yb2xsbWVudCByaWNodGV0IG5vY2gga2VpbmUgRGlzY29yZC1SZWdlbG4gb2RlciB2b2xsc3RhZW5kaWdlIFNjaHdhY2hzdGVsbGVuYWJkZWNrdW5nIGVpbi4nCiAgW1sgLXogJGZhaWxlZCBdXQp9Cm1haW4K \ No newline at end of file +#!/usr/bin/env bash +# Run as root on a Proxmox host. No credentials or site-specific addresses here. +set -Eeuo pipefail +set +x +umask 077 + +fail() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; } +say() { printf '%s\n' "$*"; } +ask() { printf '%s ' "$1" >&2; IFS= read -r REPLY <&3 || fail 'Eingabe abgebrochen.'; } +secret() { + printf '%s ' "$1" >&2 + IFS= read -r -s REPLY <&3 || fail 'Eingabe abgebrochen.' + printf '\n' >&2 +} +installed_label() { if [[ $1 == 1 ]]; then printf 'vorhanden'; else printf 'fehlt'; fi; } + +LIST=0; DRY=0; MANAGER=''; VERSION=''; LOGIN_SERVER=''; SELECTION=''; VERSION_EXPLICIT=0 +SSH_ENABLED=1; SSH_USER=''; SSH_SUDO=0; SSH_NOPASSWD=0; SAVE_SETTINGS=1 +VARS_FILE=/etc/lxc-enroll/.vars +while [[ $# -gt 0 ]]; do + case "$1" in + --list) LIST=1; shift ;; + --dry-run) DRY=1; shift ;; + --manager|--agent-version|--login-server|--select) + [[ $# -ge 2 && -n $2 ]] || fail "Wert fehlt: $1" + case "$1" in + --manager) MANAGER=$2;; --agent-version) VERSION=$2; VERSION_EXPLICIT=1;; + --login-server) LOGIN_SERVER=$2;; --select) SELECTION=$2;; + esac + shift 2 ;; + --help|-h) + cat <<'HELP' +Auf dem Proxmox-Host als root starten: + bash lxc-enroll.sh + bash lxc-enroll.sh --list + bash lxc-enroll.sh --dry-run --select '106 107' +Optionen: --manager IP/NAME --agent-version VERSION --login-server HTTPS-URL + --select 'CTID CTID' --list --dry-run +Nur laufende Debian-/Ubuntu-LXCs mit systemd. Vorhandene Tailscale-/Wazuh- +Registrierungen bleiben unveraendert. Optional Tailscale SSH/Nutzer/Sudo fuer die Auswahl. +Lokale Einstellungen: /etc/lxc-enroll/.vars (JSON, ohne Geheimnisse). +Keine Host-Agent-Installation. Geheimnisse werden lokal abgefragt. +HELP + exit 0 ;; + *) fail "Unbekannte Option: $1" ;; + esac +done +[[ $EUID == 0 ]] || fail 'Bitte auf dem Proxmox-Host als root starten.' +for tool in pct timeout python3; do command -v "$tool" >/dev/null || fail "$tool fehlt auf dem Host."; done + +declare -a IDS=() NAMES=() TS=() WZ=() TUN=() MANAGERS=() SELECTED=() REMOTE_DIRS=() +TMP='' +cleanup() { + local id dir + for id in "${SELECTED[@]}"; do + dir=${REMOTE_DIRS[$id]:-} + if [[ $dir =~ ^/run/lxc-enroll\.[A-Za-z0-9]+$ ]]; then + timeout 15 pct exec "$id" -- rm -rf -- "$dir" >/dev/null 2>&1 || true + fi + done + [[ -z $TMP ]] || rm -rf -- "$TMP" +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +yes_no() { + local prompt=$1 default=$2 + ask "$prompt" + case "$REPLY" in '' ) REPLY=$default;; j|J|ja|Ja|y|Y|yes|Yes) REPLY=1;; n|N|nein|Nein|no|No) REPLY=0;; *) fail 'Bitte j oder n eingeben.';; esac +} + +load_settings() { + local fields key value + [[ ! -e $VARS_FILE && ! -L $VARS_FILE ]] && return 0 + yes_no 'Gespeicherte Einstellungen aus /etc/lxc-enroll/.vars verwenden? [J/n]' 1 + [[ $REPLY == 1 ]] || return 0 + # JSON data, never source/eval. Reject non-root-owned or writable settings. + fields=$(python3 - "$VARS_FILE" <<'PY' +import json, os, stat, sys +p=sys.argv[1]; s=os.lstat(p) +if not stat.S_ISREG(s.st_mode) or s.st_uid != 0 or stat.S_IMODE(s.st_mode) & 0o077: + sys.exit('Unsichere Rechte der .vars: regulaere Datei, root, Modus 600 erforderlich.') +d=json.load(open(p)) +for k in ('manager','agent_version','login_server','ssh_enabled','ssh_user','ssh_sudo','ssh_nopasswd'): + v=d.get(k,'') + if not isinstance(v,str) or any(c in v for c in '\n\r\t\0') or len(v)>16384: + sys.exit('Ungueltige .vars.') + print(k+'\t'+v) +PY + ) || fail 'Gespeicherte Einstellungen konnten nicht sicher gelesen werden.' + while IFS=$'\t' read -r key value; do + case "$key" in + manager) [[ -n $MANAGER ]] || MANAGER=$value;; + agent_version) [[ -n $VERSION ]] || VERSION=$value;; + login_server) [[ -n $LOGIN_SERVER ]] || LOGIN_SERVER=$value;; + ssh_enabled) [[ $value =~ ^[01]$ ]] || fail 'Ungueltige SSH-Einstellung.'; SSH_ENABLED=$value;; + ssh_user) SSH_USER=$value;; + ssh_sudo) [[ $value =~ ^[01]$ ]] || fail 'Ungueltige Sudo-Einstellung.'; SSH_SUDO=$value;; + ssh_nopasswd) [[ $value =~ ^[01]$ ]] || fail 'Ungueltige Sudo-Einstellung.'; SSH_NOPASSWD=$value;; + esac + done <<<"$fields" +} + +save_settings() { + [[ $SAVE_SETTINGS == 1 ]] || return 0 + install -d -m 700 /etc/lxc-enroll + python3 - "$VARS_FILE" "$MANAGER" "$VERSION" "$LOGIN_SERVER" "$SSH_ENABLED" "$SSH_USER" "$SSH_SUDO" "$SSH_NOPASSWD" <<'PY' +import json, os, sys, tempfile +p=sys.argv[1] +keys=('manager','agent_version','login_server','ssh_enabled','ssh_user','ssh_sudo','ssh_nopasswd') +fd,tmp=tempfile.mkstemp(prefix='.vars.',dir=os.path.dirname(p)) +try: + os.fchmod(fd,0o600) + with os.fdopen(fd,'w') as f: json.dump(dict(zip(keys,sys.argv[2:])),f,indent=2); f.write('\n') + os.replace(tmp,p) +finally: + if os.path.exists(tmp): os.unlink(tmp) +PY + say 'Einstellungen ohne Geheimnisse gespeichert: /etc/lxc-enroll/.vars' +} + +scan() { + local rows id status rest config meta name os codename ts wz mgr tun init + rows=$(pct list) || fail 'Containerliste konnte nicht gelesen werden.' + while read -r id status rest; do + [[ $id =~ ^[1-9][0-9]*$ ]] || continue + if [[ $status != running ]]; then say "Uebersprungen: LXC $id ($status)."; continue; fi + config=$(pct config "$id") || { say "Uebersprungen: LXC $id (Konfiguration nicht lesbar)."; continue; } + if grep -q '^lock:' <<<"$config"; then say "Uebersprungen: LXC $id (gesperrt)."; continue; fi + meta=$(timeout 20 pct exec "$id" -- sh -s <<'PROBE' +set -eu +is_installed() { [ "$(dpkg-query -W -f='${Status}' "$1" 2>/dev/null || true)" = 'install ok installed' ]; } +. /etc/os-release +ts=0; wz=0; mgr=0; tun=0; init=0 +if command -v tailscale >/dev/null 2>&1 || is_installed tailscale; then ts=1; fi +if [ -x /var/ossec/bin/wazuh-agentd ] || is_installed wazuh-agent; then wz=1; fi +if [ -x /var/ossec/bin/wazuh-managerd ] || is_installed wazuh-manager; then mgr=1; wz=1; fi +[ ! -c /dev/net/tun ] || tun=1 +[ ! -d /run/systemd/system ] || init=1 +printf '%s|%s|%s|%s|%s|%s|%s|%s|%s\n' "$(hostname)" "$ID" "${VERSION_CODENAME:-unknown}" "$ts" "$wz" "$mgr" "$tun" "$init" done +PROBE + ) || { say "Uebersprungen: LXC $id (Pruefung fehlgeschlagen)."; continue; } + IFS='|' read -r name os codename ts wz mgr tun init rest <<<"$meta" + [[ $mgr == 0 ]] || MANAGERS+=("$id") + if [[ $rest != done || $init != 1 || ! $os =~ ^(debian|ubuntu)$ || ! $codename =~ ^[a-z]+$ ]]; then + say "Uebersprungen: LXC $id (unterstuetzt: Debian/Ubuntu mit systemd und Versions-Codename)."; continue + fi + if [[ $ts == 1 && $wz == 1 ]]; then + say "Ausgeblendet: LXC $id $name (beide Komponenten vorhanden; Verbindung nicht geprueft)."; continue + fi + [[ $name =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$ ]] || { say "Uebersprungen: LXC $id (Hostname ungueltig)."; continue; } + IDS+=("$id"); NAMES[$id]=$name; TS[$id]=$ts; WZ[$id]=$wz; TUN[$id]=$tun + done <<<"$rows" + say ''; say 'Auswaehlbare LXCs (nur fehlende Komponenten werden installiert):' + printf '%-8s %-24s %-12s %-12s %s\n' 'CTID' 'Name' 'Tailscale' 'Wazuh' 'TUN' + for id in "${IDS[@]}"; do + printf '%-8s %-24s %-12s %-12s %s\n' "$id" "${NAMES[$id]}" "$(installed_label "${TS[$id]}")" "$(installed_label "${WZ[$id]}")" "$(installed_label "${TUN[$id]}")" + done +} + +ensure_tun() { + local id=$1 cfg slot=-1 n + cfg=$(pct config "$id") || return 1 + if ! grep -Eq '^dev[0-9]+:.*(/dev/net/tun)(,|$)' <<<"$cfg"; then + for n in {0..255}; do + if ! grep -q "^dev$n:" <<<"$cfg"; then slot=$n; break; fi + done + [[ $slot != -1 ]] || { say "LXC $id: Kein freier Device-Slot."; return 1; } + printf '%s\n' "$cfg" >"$TMP/lxc-$id.conf.before" || return 1 + # Backups survive this run; no secrets are stored in these files. + install -d -m 700 /var/backups/lxc-enroll || return 1 + install -m 600 "$TMP/lxc-$id.conf.before" "/var/backups/lxc-enroll/$id-$(date +%Y%m%d-%H%M%S).conf" || return 1 + pct set "$id" "--dev$slot" /dev/net/tun || return 1 + fi + # Never forcibly stop a workload. If clean shutdown fails, stop this CT's work. + pct shutdown "$id" --timeout 90 || return 1 + pct start "$id" || return 1 + for n in {1..30}; do + if pct exec "$id" -- test -c /dev/net/tun 2>/dev/null; then return 0; fi + sleep 2 + done + say "LXC $id: TUN nach Neustart nicht verfuegbar."; return 1 +} + +write_worker() { + cat >"$TMP/worker.sh" <<'WORKER' +#!/usr/bin/env bash +set -Eeuo pipefail +set +x +umask 077 +DIR=$1; MANAGER=$2; AGENT_VERSION=$3; LOGIN=$4; NAME=$5; WANT_TS=$6; WANT_WZ=$7 +SSH_ENABLED=$8; SSH_USER=$9; SSH_SUDO=${10}; SSH_NOPASSWD=${11} +trap 'rm -f -- "$DIR/headscale.key" "$DIR/enrollment.pass" "$DIR/sudo.pass"' EXIT +export DEBIAN_FRONTEND=noninteractive +present() { [[ $(dpkg-query -W -f='${Status}' "$1" 2>/dev/null || true) == 'install ok installed' ]]; } +download() { curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 --connect-timeout 10 --max-time 60 --retry 2 "$1" -o "$2"; } +has_repo() { grep -Rqs -- "$1" /etc/apt/sources.list.d 2>/dev/null || grep -qs -- "$1" /etc/apt/sources.list 2>/dev/null; } + +# Recheck before installing; never overwrite an existing component or enrollment. +if present tailscale || command -v tailscale >/dev/null; then WANT_TS=0; fi +if present wazuh-agent || present wazuh-manager || [[ -x /var/ossec/bin/wazuh-agentd ]]; then WANT_WZ=0; fi +if [[ $WANT_WZ == 1 && -e /var/ossec/etc/ossec.conf ]]; then + echo 'Wazuh-Konfiguration bereits vorhanden: bitte unvollstaendige Installation zuerst reparieren.' >&2; exit 1 +fi +[[ $WANT_TS == 1 || $WANT_WZ == 1 || $SSH_ENABLED == 1 ]] || { echo 'Komponenten inzwischen vorhanden; nichts geaendert.'; exit 0; } + +# Check manager reachability before installing a new agent. +if [[ $WANT_WZ == 1 ]]; then + for port in 1514 1515; do + timeout 5 bash -c 'exec 7<>/dev/tcp/$1/$2' -- "$MANAGER" "$port" || { + echo "Wazuh-Manager auf Port $port nicht erreichbar; Installation abgebrochen." >&2; exit 1; + } + done +fi +apt-get -o DPkg::Lock::Timeout=120 update +apt-get -o DPkg::Lock::Timeout=120 install -y --no-install-recommends ca-certificates curl gnupg +install -d -m 755 /usr/share/keyrings /etc/apt/sources.list.d + +if [[ $WANT_TS == 1 ]]; then + [[ -c /dev/net/tun && -s $DIR/headscale.key ]] || { echo 'TUN oder Headscale-Schluessel fehlt.' >&2; exit 1; } + . /etc/os-release + if ! has_repo 'pkgs.tailscale.com'; then + [[ ! -e /etc/apt/sources.list.d/tailscale.list ]] || { echo 'Vorhandene tailscale.list bitte zuerst pruefen.' >&2; exit 1; } + download "https://pkgs.tailscale.com/stable/$ID/$VERSION_CODENAME.noarmor.gpg" "$DIR/tailscale.gpg" + download "https://pkgs.tailscale.com/stable/$ID/$VERSION_CODENAME.tailscale-keyring.list" "$DIR/tailscale.list" + install -m 644 "$DIR/tailscale.gpg" /usr/share/keyrings/tailscale-archive-keyring.gpg + install -m 644 "$DIR/tailscale.list" /etc/apt/sources.list.d/tailscale.list + fi + apt-get -o DPkg::Lock::Timeout=120 update + apt-get -o DPkg::Lock::Timeout=120 install -y --no-install-recommends tailscale + systemctl enable --now tailscaled + # File reference keeps the key out of command arguments/history. Keep local DNS. + timeout 90 tailscale up --login-server="$LOGIN" --auth-key="file:$DIR/headscale.key" --hostname="$NAME" --accept-dns=false --timeout=60s + rm -f -- "$DIR/headscale.key" + tailscale ip -4 + echo 'Tailscale installiert und angemeldet.' +fi + +if [[ $WANT_WZ == 1 ]]; then + if ! has_repo 'packages.wazuh.com'; then + [[ ! -e /etc/apt/sources.list.d/lxc-enroll-wazuh.list ]] || exit 1 + download https://packages.wazuh.com/key/GPG-KEY-WAZUH "$DIR/wazuh.asc" + gpg --batch --yes --dearmor -o "$DIR/wazuh.gpg" "$DIR/wazuh.asc" + install -m 644 "$DIR/wazuh.gpg" /usr/share/keyrings/lxc-enroll-wazuh.gpg + printf '%s\n' 'deb [signed-by=/usr/share/keyrings/lxc-enroll-wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main' >/etc/apt/sources.list.d/lxc-enroll-wazuh.list + chmod 644 /etc/apt/sources.list.d/lxc-enroll-wazuh.list + fi + apt-get -o DPkg::Lock::Timeout=120 update + apt-cache madison wazuh-agent | awk '{print $3}' | grep -Fx "$AGENT_VERSION" >/dev/null || { + echo "Wazuh-Version $AGENT_VERSION nicht verfuegbar; kein neuerer Agent wird installiert." >&2; exit 1; + } + export WAZUH_MANAGER="$MANAGER" WAZUH_REGISTRATION_SERVER="$MANAGER" WAZUH_AGENT_NAME="$NAME" + if [[ -s $DIR/enrollment.pass ]]; then export WAZUH_REGISTRATION_PASSWORD; WAZUH_REGISTRATION_PASSWORD=$(cat "$DIR/enrollment.pass"); fi + apt-get -o DPkg::Lock::Timeout=120 install -y --no-install-recommends "wazuh-agent=$AGENT_VERSION" + unset WAZUH_REGISTRATION_PASSWORD + rm -f -- "$DIR/enrollment.pass" + apt-mark hold wazuh-agent + systemctl daemon-reload + systemctl enable --now wazuh-agent + connected=0 + for attempt in {1..30}; do + if grep -q "^status='connected'" /var/ossec/var/run/wazuh-agentd.state 2>/dev/null; then + connected=1; break + fi + sleep 2 + done + if [[ $connected == 0 ]]; then + echo 'Wazuh installiert, Verbindung nicht bestaetigt. Agent-Konfiguration/Logs pruefen; keine automatische Neuregistrierung.' >&2 + exit 1 + fi + echo 'Wazuh installiert und mit dem Manager verbunden.' +fi + +if [[ $SSH_ENABLED == 1 ]]; then + if [[ $SSH_SUDO == 1 ]]; then apt-get -o DPkg::Lock::Timeout=120 install -y --no-install-recommends sudo; fi + new_user=0 + if id "$SSH_USER" >/dev/null 2>&1; then + [[ $(id -u "$SSH_USER") != 0 ]] || { echo 'SSH-Ziel darf kein root-Konto sein.' >&2; exit 1; } + else + useradd -m -U -s /bin/bash "$SSH_USER" + new_user=1 + fi + if [[ $SSH_SUDO == 1 ]]; then + usermod -aG sudo "$SSH_USER" + if [[ $SSH_NOPASSWD == 1 ]]; then + sudo_rule="/etc/sudoers.d/lxc-enroll-$SSH_USER" + printf '%s ALL=(ALL:ALL) NOPASSWD: ALL\n' "$SSH_USER" >"$DIR/sudoers" + visudo -cf "$DIR/sudoers" + if [[ -e $sudo_rule ]] && ! cmp -s "$DIR/sudoers" "$sudo_rule"; then + echo 'Abweichende vorhandene Sudo-Regel: bitte manuell pruefen.' >&2; exit 1 + fi + install -m 440 "$DIR/sudoers" "$sudo_rule" + elif [[ $new_user == 1 ]]; then + [[ -s $DIR/sudo.pass ]] || { echo 'Passwort fuer neuen Sudo-Nutzer fehlt.' >&2; exit 1; } + { printf '%s:' "$SSH_USER"; cat "$DIR/sudo.pass"; printf '\n'; } | chpasswd + echo 'Passwort fuer neuen Nutzer gesetzt; bestehende Nutzerpasswoerter bleiben unveraendert.' + fi + fi + rm -f -- "$DIR/sudo.pass" + tailscale set --ssh=true + echo "Tailscale SSH fuer $SSH_USER aktiviert. Headscale-Netzwerk- UND SSH-Policy muessen diesen Zugriff erlauben." + echo 'Kein OpenSSH-Server installiert. Vorhandene Nutzer-/Sudo-Rechte werden nicht entzogen.' +fi +WORKER +} + +deploy_one() { + local id=$1 dir ts wz + [[ $(pct status "$id") == 'status: running' ]] || return 1 + if pct config "$id" | grep -q '^lock:'; then say "LXC $id inzwischen gesperrt."; return 1; fi + ts=$((1-${TS[$id]})); wz=$((1-${WZ[$id]})) + if [[ $ts == 1 && ${TUN[$id]} == 0 ]]; then ensure_tun "$id" || return 1; fi + dir=$(pct exec "$id" -- mktemp -d /run/lxc-enroll.XXXXXXXX) || return 1 + [[ $dir =~ ^/run/lxc-enroll\.[A-Za-z0-9]+$ ]] || return 1 + REMOTE_DIRS[$id]=$dir + pct push "$id" "$TMP/worker.sh" "$dir/worker.sh" --user 0 --group 0 --perms 600 || return 1 + if [[ $ts == 1 ]]; then pct push "$id" "$TMP/headscale.key" "$dir/headscale.key" --user 0 --group 0 --perms 600 || return 1; fi + if [[ $wz == 1 && -s $TMP/enrollment.pass ]]; then pct push "$id" "$TMP/enrollment.pass" "$dir/enrollment.pass" --user 0 --group 0 --perms 600 || return 1; fi + if [[ $SSH_ENABLED == 1 ]]; then + if [[ -s $TMP/sudo.pass ]]; then pct push "$id" "$TMP/sudo.pass" "$dir/sudo.pass" --user 0 --group 0 --perms 600 || return 1; fi + fi + # Credentials are files, never pct arguments. bash receives only non-secret settings. + pct exec "$id" -- bash "$dir/worker.sh" "$dir" "$MANAGER" "$VERSION" "$LOGIN_SERVER" "${NAMES[$id]}" "$ts" "$wz" "$SSH_ENABLED" "$SSH_USER" "$SSH_SUDO" "$SSH_NOPASSWD" || return 1 + if [[ $ts == 1 ]]; then + pct exec "$id" -- tailscale status --json | python3 -c 'import json,sys; d=json.load(sys.stdin); sys.exit(0 if d.get("BackendState")=="Running" and d.get("Self",{}).get("Online") is True else 1)' || { + say "LXC $id: Headscale-Verbindung nicht als online bestaetigt."; return 1; + } + fi + pct exec "$id" -- rm -rf -- "$dir" || return 1 + REMOTE_DIRS[$id]='' +} + +main() { + local id input wanted found need_ts=0 need_wz=0 restart_ids='' ok='' failed='' auto_version password + scan + [[ $LIST == 0 ]] || return 0 + [[ ${#IDS[@]} -gt 0 ]] || { say 'Keine passenden LXCs gefunden.'; return 0; } + exec 3<>/dev/tty || fail 'Interaktives Terminal fehlt. --list kann ohne Terminal laufen.' + load_settings + yes_no "Tailscale SSH fuer alle ausgewaehlten LXCs aktivieren? (Standard: $SSH_ENABLED; 1=Ja, 0=Nein) [j/n]" "$SSH_ENABLED" + SSH_ENABLED=$REPLY + if [[ $SSH_ENABLED == 1 ]]; then + ask "SSH-Nutzername${SSH_USER:+ [$SSH_USER]}:" + SSH_USER=${REPLY:-$SSH_USER} + [[ $SSH_USER =~ ^[a-z_][a-z0-9_-]{0,30}$ && $SSH_USER != root ]] || fail 'Bitte einen normalen Linux-Nutzernamen verwenden.' + yes_no "Nutzer zu sudo hinzufuegen? (Standard: $SSH_SUDO) [j/n]" "$SSH_SUDO"; SSH_SUDO=$REPLY + if [[ $SSH_SUDO == 1 ]]; then + yes_no "Sudo OHNE Passwort erlauben? (Standard: $SSH_NOPASSWD; 0=Passwort erforderlich) [j/n]" "$SSH_NOPASSWD"; SSH_NOPASSWD=$REPLY + fi + fi + yes_no 'Einstellungen ohne Geheimnisse lokal in .vars speichern? [J/n]' 1; SAVE_SETTINGS=$REPLY + if [[ -z $SELECTION ]]; then ask 'CTIDs auswaehlen (Leerzeichen, oder all; leer = abbrechen):'; SELECTION=$REPLY; fi + [[ -n $SELECTION ]] || return 0 + if [[ $SELECTION == all ]]; then SELECTED=("${IDS[@]}"); else + SELECTION=${SELECTION//,/ } + for wanted in $SELECTION; do + [[ $wanted =~ ^[1-9][0-9]*$ ]] || fail 'Bitte nur CTIDs eingeben.' + found=0 + for id in "${IDS[@]}"; do [[ $id != "$wanted" ]] || found=1; done + [[ $found == 1 ]] || fail "LXC $wanted ist nicht auswaehlbar." + for id in "${SELECTED[@]}"; do [[ $id != "$wanted" ]] || fail "LXC $wanted doppelt ausgewaehlt."; done + SELECTED+=("$wanted") + done + fi + say ''; say 'Geplante Aenderungen:' + for id in "${SELECTED[@]}"; do + say "LXC $id ${NAMES[$id]}: Tailscale $(installed_label "${TS[$id]}"), Wazuh $(installed_label "${WZ[$id]}")." + if [[ ${TS[$id]} == 0 ]]; then + need_ts=1 + if [[ ${TUN[$id]} == 0 ]]; then restart_ids+=" $id"; fi + fi + [[ ${WZ[$id]} != 0 ]] || need_wz=1 + done + if [[ -n $restart_ids ]]; then say "TUN-Zugriff ergaenzen und sauber neu starten:$restart_ids"; fi + if [[ $SSH_ENABLED == 1 ]]; then + say "Tailscale SSH fuer die Auswahl: Nutzer $SSH_USER, Sudo=$SSH_SUDO, Sudo-ohne-Passwort=$SSH_NOPASSWD." + say 'Headscale muss Zugriff auf Port 22 und den gewaehlten Linux-Nutzer in seiner SSH-Policy erlauben.' + say 'Bestehende SSH-Sitzungen ueber Tailscale koennen beim Aktivieren unterbrochen werden.' + fi + [[ $DRY == 0 ]] || { say 'Vorschau abgeschlossen. Keine Aenderungen vorgenommen.'; return 0; } + command -v flock >/dev/null || fail 'flock fehlt auf dem Host.' + exec 8>/run/lxc-enroll.lock + flock -n 8 || fail 'Ein anderer Enrollment-Lauf ist bereits aktiv.' + if [[ $need_wz == 1 ]]; then + if [[ -z $MANAGER ]]; then ask 'Erreichbare Wazuh-Manager-Adresse (LAN-IP oder Headscale-IP):'; MANAGER=$REPLY; fi + [[ $MANAGER =~ ^[A-Za-z0-9][A-Za-z0-9.:-]*$ ]] || fail 'Manager-Adresse ungueltig.' + if [[ ${#MANAGERS[@]} == 1 ]]; then + auto_version=$(pct exec "${MANAGERS[0]}" -- dpkg-query -W '-f=${Version}' wazuh-manager) || fail 'Manager-Version nicht lesbar.' + if [[ $VERSION_EXPLICIT == 1 && $VERSION != "$auto_version" ]]; then fail 'Agent-Version muss der lokalen Manager-Version entsprechen.'; fi + VERSION=$auto_version + elif [[ -z $VERSION ]]; then ask 'Exakte Manager-Paketversion (z.B. 4.14.8-1):'; VERSION=$REPLY; fi + [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$ ]] || fail 'Paketversion ungueltig.' + say "Neue Wazuh-Agents: $VERSION (anschliessend auf hold; gemeinsam mit Manager aktualisieren)." + fi + if [[ $need_ts == 1 ]]; then + if [[ -z $LOGIN_SERVER ]]; then ask 'Headscale-Login-Server (vollstaendige HTTPS-URL):'; LOGIN_SERVER=$REPLY; fi + [[ $LOGIN_SERVER =~ ^https://[A-Za-z0-9][A-Za-z0-9.:-]*/?$ ]] || fail 'Bitte eine HTTPS-Server-URL ohne Zugangsdaten/Query eingeben.' + say 'Neue Tailscale-Clients verwenden Headscale; vorhandene lokale DNS-Einstellungen bleiben aktiv.' + fi + ask 'Nur diese fehlenden Komponenten installieren? Bitte INSTALL eingeben:' + [[ $REPLY == INSTALL ]] || { say 'Abgebrochen.'; return 0; } + if [[ -n $restart_ids ]]; then + ask "Diese Container werden kurz unterbrochen:$restart_ids. Bitte NEUSTART eingeben:" + [[ $REPLY == NEUSTART ]] || { say 'Abgebrochen; nichts installiert.'; return 0; } + fi + TMP=$(mktemp -d /run/lxc-enroll.XXXXXXXX) + if [[ $SSH_ENABLED == 1 ]]; then + if [[ $SSH_SUDO == 1 && $SSH_NOPASSWD == 0 ]]; then + secret 'Passwort fuer NEU angelegte Sudo-Nutzer (bestehende Passwoerter bleiben unveraendert):'; password=$REPLY + [[ -n $password && $password != *:* && $password != *$'\r'* ]] || fail 'Passwort leer oder enthaelt ungueltige Zeichen.' + secret 'Passwort wiederholen:'; [[ $REPLY == "$password" ]] || fail 'Passwoerter stimmen nicht ueberein.' + printf '%s' "$password" >"$TMP/sudo.pass"; password=''; REPLY='' + fi + fi + save_settings + if [[ $need_ts == 1 ]]; then + secret 'Wiederverwendbaren, kurz gueltigen Headscale-Preauth-Key eingeben (wird nicht angezeigt):' + [[ -n $REPLY && $REPLY != *$'\r'* ]] || fail 'Schluessel fehlt/ungueltig.' + printf '%s' "$REPLY" >"$TMP/headscale.key"; REPLY='' + fi + if [[ $need_wz == 1 ]]; then + secret 'Optionales Wazuh-Enrollment-Passwort (leer, falls keines verlangt wird):' + printf '%s' "$REPLY" >"$TMP/enrollment.pass"; REPLY='' + fi + write_worker + for id in "${SELECTED[@]}"; do + say ''; say "Bearbeite LXC $id ${NAMES[$id]} ..." + # All deployment operations explicitly propagate errors. Parent owns cleanup. + if deploy_one "$id"; then ok+=" $id"; else failed+=" $id"; fi + done + say ''; say "Erfolgreich abgeschlossen:${ok:- keine}" + say "Nicht abgeschlossen:${failed:- keine}" + say 'Bereits vorhandene Komponenten wurden nicht umkonfiguriert; deren Verbindung bitte separat pruefen.' + say 'Enrollment richtet noch keine Discord-Regeln oder vollstaendige Schwachstellenabdeckung ein.' + [[ -z $failed ]] +} +main