ConfigLMM 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -0
- data/CNAME +1 -0
- data/Examples/.lmm.state.yaml +159 -0
- data/Examples/ConfigLMM.mm.yaml +32 -0
- data/Examples/Implemented.mm.yaml +252 -4
- data/Examples/SmallBusiness.mm.yaml +492 -0
- data/Plugins/Apps/Answer/answer.lmm.rb +165 -0
- data/Plugins/Apps/Answer/answer@.service +40 -0
- data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.conf.erb +0 -3
- data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.lmm.rb +0 -1
- data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +20 -0
- data/Plugins/Apps/Authentik/Authentik-Server.container +7 -1
- data/Plugins/Apps/Authentik/Authentik-Worker.container +7 -1
- data/Plugins/Apps/Authentik/Authentik.conf.erb +18 -6
- data/Plugins/Apps/Authentik/Authentik.lmm.rb +232 -45
- data/Plugins/Apps/BookStack/BookStack.conf.erb +38 -0
- data/Plugins/Apps/BookStack/BookStack.container +20 -0
- data/Plugins/Apps/BookStack/BookStack.lmm.rb +91 -0
- data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +9 -19
- data/Plugins/Apps/ClickHouse/ClickHouse.container +28 -0
- data/Plugins/Apps/ClickHouse/ClickHouse.lmm.rb +113 -0
- data/Plugins/Apps/ClickHouse/Config/listen.yaml +2 -0
- data/Plugins/Apps/ClickHouse/Config/logger.yaml +8 -0
- data/Plugins/Apps/ClickHouse/Config/zookeepers.yaml +5 -0
- data/Plugins/Apps/ClickHouse/Connection.rb +96 -0
- data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +22 -0
- data/Plugins/Apps/Discourse/Discourse.conf.erb +38 -0
- data/Plugins/Apps/Discourse/Discourse.container +21 -0
- data/Plugins/Apps/Discourse/Discourse.lmm.rb +156 -0
- data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +87 -52
- data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +24 -0
- data/Plugins/Apps/ERPNext/ERPNext-Queue.container +22 -0
- data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +22 -0
- data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +24 -0
- data/Plugins/Apps/ERPNext/ERPNext.container +23 -0
- data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +204 -0
- data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
- data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
- data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
- data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
- data/Plugins/Apps/GitLab/GitLab.container +9 -2
- data/Plugins/Apps/GitLab/GitLab.lmm.rb +52 -33
- data/Plugins/Apps/Homepage/Homepage.conf.erb +86 -0
- data/Plugins/Apps/Homepage/Homepage.container +19 -0
- data/Plugins/Apps/Homepage/Homepage.lmm.rb +54 -0
- data/Plugins/Apps/IPFS/IPFS.conf.erb +0 -3
- data/Plugins/Apps/IPFS/IPFS.lmm.rb +0 -1
- data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +0 -3
- data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +0 -1
- data/Plugins/Apps/Jackett/Jackett.conf.erb +0 -3
- data/Plugins/Apps/Jackett/Jackett.lmm.rb +0 -1
- data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +0 -3
- data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +0 -1
- data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +78 -0
- data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
- data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
- data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
- data/Plugins/Apps/LibreTranslate/LibreTranslate.container +21 -0
- data/Plugins/Apps/LibreTranslate/LibreTranslate.lmm.rb +34 -0
- data/Plugins/Apps/Lobsters/Containerfile +81 -0
- data/Plugins/Apps/Lobsters/Lobsters-Tasks.container +26 -0
- data/Plugins/Apps/Lobsters/Lobsters.conf.erb +99 -0
- data/Plugins/Apps/Lobsters/Lobsters.container +27 -0
- data/Plugins/Apps/Lobsters/Lobsters.lmm.rb +196 -0
- data/Plugins/Apps/Lobsters/crontab +3 -0
- data/Plugins/Apps/Lobsters/database.yml +26 -0
- data/Plugins/Apps/Lobsters/entrypoint.sh +30 -0
- data/Plugins/Apps/Lobsters/generateCredentials.rb +19 -0
- data/Plugins/Apps/Lobsters/lobsters-cron.sh +25 -0
- data/Plugins/Apps/Lobsters/lobsters-daily.sh +23 -0
- data/Plugins/Apps/Lobsters/puma.rb +49 -0
- data/Plugins/Apps/MariaDB/Connection.rb +55 -0
- data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +122 -0
- data/Plugins/Apps/Mastodon/Mastodon-Sidekiq.container +22 -0
- data/Plugins/Apps/Mastodon/Mastodon-Streaming.container +20 -0
- data/Plugins/Apps/Mastodon/Mastodon.conf.erb +34 -45
- data/Plugins/Apps/Mastodon/Mastodon.container +28 -0
- data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +240 -5
- data/Plugins/Apps/Mastodon/configlmm.rake +30 -0
- data/Plugins/Apps/Mastodon/entrypoint.sh +16 -0
- data/Plugins/Apps/Matrix/Element.container +19 -0
- data/Plugins/Apps/Matrix/Matrix.conf.erb +47 -9
- data/Plugins/Apps/Matrix/Matrix.lmm.rb +119 -5
- data/Plugins/Apps/Matrix/Synapse.container +22 -0
- data/Plugins/Apps/Matrix/config.json +50 -0
- data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
- data/Plugins/Apps/Matrix/log.config +30 -0
- data/Plugins/Apps/Netdata/Netdata.conf.erb +0 -3
- data/Plugins/Apps/Netdata/Netdata.lmm.rb +0 -1
- data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +3 -4
- data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +155 -48
- data/Plugins/Apps/Nextcloud/autoconfig.php +13 -0
- data/Plugins/Apps/Nextcloud/config.php +10 -1
- data/Plugins/Apps/Nextcloud/nextcloudcron.service +8 -0
- data/Plugins/Apps/Nextcloud/nextcloudcron.timer +10 -0
- data/Plugins/Apps/Nginx/Connection.rb +93 -0
- data/Plugins/Apps/Nginx/conf.d/configlmm.conf +54 -4
- data/Plugins/Apps/Nginx/conf.d/languages.conf +21 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +33 -22
- data/Plugins/Apps/Nginx/config-lmm/gateway-errors.conf +20 -0
- data/Plugins/Apps/Nginx/config-lmm/proxy.conf +6 -2
- data/Plugins/Apps/Nginx/main.conf.erb +7 -3
- data/Plugins/Apps/Nginx/nginx.conf +2 -2
- data/Plugins/Apps/Nginx/nginx.lmm.rb +103 -81
- data/Plugins/Apps/Nginx/proxy.conf.erb +24 -6
- data/Plugins/Apps/Odoo/Odoo.conf.erb +0 -3
- data/Plugins/Apps/Odoo/Odoo.container +7 -1
- data/Plugins/Apps/Odoo/Odoo.lmm.rb +4 -5
- data/Plugins/Apps/Ollama/Ollama.container +26 -0
- data/Plugins/Apps/Ollama/Ollama.lmm.rb +73 -0
- data/Plugins/Apps/OpenTelemetry/Config/config.yaml +704 -0
- data/Plugins/Apps/OpenTelemetry/OpenTelemetry.lmm.rb +154 -0
- data/Plugins/Apps/OpenVidu/Ingress.container +23 -0
- data/Plugins/Apps/{GitLab/GitLab.conf.erb → OpenVidu/OpenVidu.conf.erb} +8 -3
- data/Plugins/Apps/OpenVidu/OpenVidu.container +21 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +94 -0
- data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +32 -0
- data/Plugins/Apps/OpenVidu/OpenViduCall.container +20 -0
- data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
- data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
- data/Plugins/Apps/PHP-FPM/Connection.rb +91 -0
- data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +31 -4
- data/Plugins/Apps/Peppermint/Peppermint.conf.erb +2 -9
- data/Plugins/Apps/Peppermint/Peppermint.container +7 -1
- data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +29 -33
- data/Plugins/Apps/Perplexica/Perplexica.container +25 -0
- data/Plugins/Apps/Perplexica/Perplexica.lmm.rb +92 -0
- data/Plugins/Apps/Perplexica/config.toml +26 -0
- data/Plugins/Apps/Podman/Connection.rb +24 -0
- data/Plugins/Apps/Podman/Podman.lmm.rb +80 -0
- data/Plugins/Apps/Podman/storage.conf +6 -0
- data/Plugins/Apps/Postfix/Postfix.lmm.rb +249 -145
- data/Plugins/Apps/PostgreSQL/Connection.rb +97 -0
- data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +204 -99
- data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +0 -3
- data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +0 -2
- data/Plugins/Apps/Pterodactyl/Wings.conf.erb +0 -3
- data/Plugins/Apps/RVM/RVM.lmm.rb +57 -0
- data/Plugins/Apps/Roundcube/Roundcube.conf.erb +72 -0
- data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +141 -0
- data/Plugins/Apps/SSH/SSH.lmm.rb +9 -15
- data/Plugins/Apps/SearXNG/SearXNG.container +22 -0
- data/Plugins/Apps/SearXNG/SearXNG.lmm.rb +79 -0
- data/Plugins/Apps/SearXNG/limiter.toml +40 -0
- data/Plugins/Apps/SearXNG/settings.yml +2 -0
- data/Plugins/Apps/SigNoz/Config/alerts.yml +11 -0
- data/Plugins/Apps/SigNoz/Config/otel-collector-config.yaml +110 -0
- data/Plugins/Apps/SigNoz/Config/otel-collector-opamp-config.yaml +1 -0
- data/Plugins/Apps/SigNoz/Config/prometheus.yml +18 -0
- data/Plugins/Apps/SigNoz/SigNoz-Collector.container +23 -0
- data/Plugins/Apps/SigNoz/SigNoz-Migrator.container +17 -0
- data/Plugins/Apps/SigNoz/SigNoz.conf.erb +61 -0
- data/Plugins/Apps/SigNoz/SigNoz.container +26 -0
- data/Plugins/Apps/SigNoz/SigNoz.lmm.rb +319 -0
- data/Plugins/Apps/Solr/log4j2.xml +89 -0
- data/Plugins/Apps/Solr/solr.lmm.rb +82 -0
- data/Plugins/Apps/Sunshine/Sunshine.conf.erb +0 -3
- data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +0 -1
- data/Plugins/Apps/Tunnel/tunnel.lmm.rb +59 -0
- data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
- data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
- data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
- data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
- data/Plugins/Apps/UVdesk/UVdesk.conf.erb +0 -3
- data/Plugins/Apps/Umami/Umami.container +19 -0
- data/Plugins/Apps/Umami/Umami.lmm.rb +108 -0
- data/Plugins/Apps/Valkey/Valkey.lmm.rb +64 -20
- data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +9 -6
- data/Plugins/Apps/Vaultwarden/Vaultwarden.container +7 -1
- data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +67 -28
- data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +39 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.container +20 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +55 -0
- data/Plugins/Apps/YaCy/YaCy.conf.erb +93 -0
- data/Plugins/Apps/YaCy/YaCy.container +21 -0
- data/Plugins/Apps/YaCy/YaCy.lmm.rb +160 -0
- data/Plugins/Apps/ZooKeeper/ZooKeeper.container +24 -0
- data/Plugins/Apps/ZooKeeper/ZooKeeper.lmm.rb +68 -0
- data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +0 -3
- data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +0 -1
- data/Plugins/Apps/gollum/gollum.conf.erb +40 -4
- data/Plugins/Apps/gollum/gollum.container +10 -1
- data/Plugins/Apps/gollum/gollum.lmm.rb +56 -47
- data/Plugins/Apps/llama.cpp/llama.cpp.container +28 -0
- data/Plugins/Apps/llama.cpp/llama.cpp.lmm.rb +90 -0
- data/Plugins/Apps/vLLM/vLLM.container +32 -0
- data/Plugins/Apps/vLLM/vLLM.lmm.rb +89 -0
- data/Plugins/OS/General/Utils.lmm.rb +26 -0
- data/Plugins/OS/Linux/Connection.rb +472 -0
- data/Plugins/OS/Linux/Debian/preseed.cfg.erb +81 -0
- data/Plugins/OS/Linux/Distributions.yaml +32 -0
- data/Plugins/OS/Linux/Flavours.yaml +24 -0
- data/Plugins/OS/Linux/Grub/grub.cfg +10 -0
- data/Plugins/OS/Linux/HTTP.rb +32 -0
- data/Plugins/OS/Linux/Linux.lmm.rb +708 -174
- data/Plugins/OS/Linux/Packages.yaml +67 -3
- data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
- data/Plugins/OS/Linux/Services.yaml +8 -0
- data/Plugins/OS/Linux/Shell.rb +70 -0
- data/Plugins/OS/Linux/Syslinux/default +8 -0
- data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +93 -40
- data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +3 -0
- data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +29 -3
- data/Plugins/OS/Linux/systemd/systemd.lmm.rb +13 -11
- data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +6 -5
- data/Plugins/Platforms/GitHub.lmm.rb +73 -28
- data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +10 -7
- data/Plugins/Platforms/Proxmox/Proxmox.lmm.rb +402 -0
- data/Plugins/Platforms/Proxmox/XTerm.rb +321 -0
- data/Plugins/Platforms/libvirt/libvirt.lmm.rb +41 -15
- data/Plugins/Platforms/porkbun.lmm.rb +12 -2
- data/Plugins/Platforms/porkbun_spec.rb +2 -2
- data/Plugins/Services/DNS/AmberBit.lmm.rb +1 -1
- data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +1 -1
- data/Plugins/Services/DNS/NICLV.lmm.rb +1 -1
- data/Plugins/Services/DNS/PowerDNS.lmm.rb +130 -41
- data/Plugins/Services/DNS/tonic.lmm.rb +22 -12
- data/bootstrap.sh +41 -3
- data/lib/ConfigLMM/Framework/plugins/dns.rb +4 -3
- data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +187 -144
- data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +54 -6
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +68 -140
- data/lib/ConfigLMM/Framework/plugins/store.rb +4 -4
- data/lib/ConfigLMM/Framework/variables.rb +75 -0
- data/lib/ConfigLMM/Framework.rb +1 -0
- data/lib/ConfigLMM/cli.rb +13 -5
- data/lib/ConfigLMM/commands/cleanup.rb +1 -0
- data/lib/ConfigLMM/commands/configsCommand.rb +38 -5
- data/lib/ConfigLMM/commands/diff.rb +33 -9
- data/lib/ConfigLMM/context.rb +22 -3
- data/lib/ConfigLMM/io/configList.rb +85 -7
- data/lib/ConfigLMM/io/connection.rb +143 -0
- data/lib/ConfigLMM/io/dhcp.rb +330 -0
- data/lib/ConfigLMM/io/http.rb +78 -0
- data/lib/ConfigLMM/io/local.rb +207 -0
- data/lib/ConfigLMM/io/pxe.rb +92 -0
- data/lib/ConfigLMM/io/ssh.rb +156 -0
- data/lib/ConfigLMM/io/tftp.rb +105 -0
- data/lib/ConfigLMM/io.rb +2 -0
- data/lib/ConfigLMM/secrets/envStore.rb +39 -0
- data/lib/ConfigLMM/secrets/fileStore.rb +43 -0
- data/lib/ConfigLMM/state.rb +12 -3
- data/lib/ConfigLMM/version.rb +2 -1
- data/lib/ConfigLMM.rb +1 -0
- data/{Examples → scripts}/configlmmAuth.sh +7 -5
- metadata +257 -9
@@ -1,154 +1,396 @@
|
|
1
1
|
|
2
|
+
require_relative 'Connection'
|
3
|
+
|
2
4
|
require 'addressable/uri'
|
3
5
|
require 'http'
|
4
6
|
require 'securerandom'
|
5
7
|
require 'shellwords'
|
8
|
+
require 'ipaddr'
|
6
9
|
|
7
10
|
module ConfigLMM
|
8
11
|
module LMM
|
9
|
-
class Linux < Framework::
|
12
|
+
class Linux < Framework::Plugin
|
10
13
|
|
11
|
-
|
14
|
+
IMAGE_LOCATION = '~/.cache/configlmm/images/'
|
12
15
|
HOSTS_FILE = '/etc/hosts'
|
13
16
|
FSTAB_FILE = '/etc/fstab'
|
17
|
+
SUBUID_FILE = '/etc/subuid'
|
18
|
+
SUBGID_FILE = '/etc/subgid'
|
14
19
|
SSH_CONFIG = '~/.ssh/config'
|
15
20
|
SYSCTL_FILE = '/etc/sysctl.d/90-configlmm.conf'
|
16
21
|
FIREWALL_PACKAGE = 'firewalld'
|
17
22
|
FIREWALL_SERVICE = 'firewalld'
|
18
23
|
|
24
|
+
ARCH_NAME = 'Arch Linux'
|
25
|
+
SUSE_NAME = 'openSUSE Leap'
|
26
|
+
PROXMOXVE_NAME = 'Proxmox VE'
|
27
|
+
DEBIAN_NAME = 'Debian'
|
28
|
+
|
19
29
|
def actionLinuxBuild(id, target, activeState, context, options)
|
20
|
-
prepareConfig(target)
|
30
|
+
prepareConfig(target, context)
|
21
31
|
buildHostsFile(id, target, options)
|
22
32
|
buildSSHConfig(id, target, options)
|
23
|
-
|
33
|
+
buildAutoInstall(id, target, options)
|
24
34
|
end
|
25
35
|
|
26
36
|
def actionLinuxDeploy(id, target, activeState, context, options)
|
27
|
-
prepareConfig(target)
|
37
|
+
prepareConfig(target, context)
|
28
38
|
if target['Location'] && target['Location'] != '@me'
|
29
39
|
uri = Addressable::URI.parse(target['Location'])
|
30
40
|
case uri.scheme
|
31
41
|
when 'qemu'
|
32
42
|
deployOverLibvirt(id, target, activeState, context, options)
|
43
|
+
when 'proxmox'
|
44
|
+
deployOverProxmox(id, target, activeState, context, options)
|
45
|
+
when 'pxe', 'pxe+http'
|
46
|
+
deployOverPXE(uri, id, target, activeState, context, options)
|
33
47
|
when 'ssh'
|
34
|
-
|
48
|
+
self.withConnection(uri, target) do |connection|
|
49
|
+
self.class.withConnection(connection) do |connection|
|
50
|
+
deployOverConnection(connection, id, target, activeState, context, options)
|
51
|
+
end
|
52
|
+
end
|
35
53
|
else
|
36
54
|
raise Framework::PluginProcessError.new("#{id}: Unknown protocol: #{uri.scheme}!")
|
37
55
|
end
|
38
56
|
else
|
39
|
-
|
57
|
+
self.class.withConnection(Local.new(prompt, logger)) do |connection|
|
58
|
+
deployLocal(connection, target, options)
|
59
|
+
end
|
40
60
|
end
|
41
61
|
if target['AlternativeLocation']
|
42
|
-
|
43
|
-
|
44
|
-
|
62
|
+
self.withConnection(target['AlternativeLocation'], target) do |connection|
|
63
|
+
self.class.withConnection(connection) do |connection|
|
64
|
+
deployOverConnection(connection, id, target, activeState, context, options)
|
65
|
+
end
|
66
|
+
end
|
45
67
|
end
|
46
68
|
end
|
47
69
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
70
|
+
def deployOverConnection(connection, id, target, activeState, context, options)
|
71
|
+
if target['Domain'] || target['Hosts']
|
72
|
+
hostsLines = []
|
73
|
+
if target['Domain']
|
74
|
+
envs = connection.exec("env", false, { **options, 'dry' => false }).split("\n")
|
75
|
+
envVars = Hash[envs.map { |vars| vars.split('=', 2) }]
|
76
|
+
if envVars['SSH_CONNECTION']
|
55
77
|
ipAddr = envVars['SSH_CONNECTION'].split[-2]
|
56
|
-
hostsLines << ipAddr.
|
78
|
+
hostsLines << getHostsLine(ipAddr, [Addressable::IDNA.to_ascii(target['Domain']), target['Name']]) + "\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
target['Hosts'].to_a.each do |ip, entries|
|
82
|
+
hostsLines << getHostsLine(ip, entries) + "\n"
|
83
|
+
end
|
84
|
+
connection.updateFile(HOSTS_FILE, options, false) do |fileLines|
|
85
|
+
fileLines + hostsLines
|
86
|
+
end
|
87
|
+
end
|
88
|
+
distroInfo = connection.distroInfo
|
89
|
+
convertFlavour(distroInfo, target, connection, options)
|
90
|
+
configureNetwork(distroInfo, target, connection, options)
|
91
|
+
if target['Tmpfs']
|
92
|
+
connection.exec("sed -i '/ \\/tmp /d' #{FSTAB_FILE}", false, options)
|
93
|
+
connection.updateFile(FSTAB_FILE, options, false) do |fileLines|
|
94
|
+
fileLines << "tmpfs /tmp tmpfs nodev,nosuid,size=#{target['Tmpfs']} 0 0\n"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
if target['Sysctl']
|
98
|
+
connection.updateFile(SYSCTL_FILE, options, false) do |fileLines|
|
99
|
+
target['Sysctl'].each do |name, value|
|
100
|
+
fileLines << "#{name} = #{value}\n"
|
101
|
+
connection.exec("sysctl #{name}=#{value}", false, options)
|
102
|
+
end
|
103
|
+
fileLines
|
104
|
+
end
|
105
|
+
end
|
106
|
+
if target['Users']
|
107
|
+
target['Users'].each do |name, info|
|
108
|
+
userId = connection.exec("id -u #{name} 2>/dev/null", true, { **options, 'dry' => false }).strip
|
109
|
+
if userId.empty?
|
110
|
+
shell = ''
|
111
|
+
if info['Shell']
|
112
|
+
result = connection.exec("which #{info['Shell']}", true, { **options, 'dry' => false }).strip
|
113
|
+
if !result.empty? && !result.include?("no #{info['Shell']}")
|
114
|
+
shell = "--shell '#{result}'"
|
115
|
+
else
|
116
|
+
prompt.say("Shell '#{info['Shell']}' not found! Skipping setting!", :color => :red)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
params = '--create-home --user-group'
|
120
|
+
badname = '--badname'
|
121
|
+
badname = '--badnames' if distroInfo['Name'] == 'openSUSE Leap'
|
122
|
+
if info['System']
|
123
|
+
params += ' --system'
|
124
|
+
end
|
125
|
+
connection.exec("useradd #{badname} #{params} #{shell} #{name}", false, options)
|
126
|
+
elsif info['Shell']
|
127
|
+
result = connection.exec("which #{info['Shell']}", true, { **options, 'dry' => false }).strip
|
128
|
+
if !result.empty? && !result.include?("no #{info['Shell']}")
|
129
|
+
shell = "--shell '#{result}'"
|
130
|
+
connection.exec("chsh #{shell} #{name}")
|
131
|
+
else
|
132
|
+
prompt.say("Shell '#{info['Shell']}' not found! Skipping setting!", :color => :red)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
if info['Subuids']
|
136
|
+
connection.exec("sed -i '/^#{name}:.*/d' #{SUBUID_FILE}", false, options)
|
137
|
+
info['Subuids'].each do |id|
|
138
|
+
connection.exec("#{distroInfo['ModifyUser']} --add-subuids #{id} #{name}", false, options)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
if info['Subgids']
|
142
|
+
connection.exec("sed -i '/^#{name}:.*/d' #{SUBGID_FILE}", false, options)
|
143
|
+
info['Subgids'].each do |id|
|
144
|
+
connection.exec("#{distroInfo['ModifyUser']} --add-subgids #{id} #{name}", false, options)
|
145
|
+
end
|
57
146
|
end
|
58
|
-
|
59
|
-
|
147
|
+
homeDir = connection.exec("getent passwd #{name} | cut -d ':' -f 6", false, { **options, 'dry' => false }).strip
|
148
|
+
hostname = connection.exec("hostname", false, { **options, 'dry' => false }).strip
|
149
|
+
keyFile = homeDir + "/.ssh/id_ed25519"
|
150
|
+
if info['SSH'].to_h['Key'] && !connection.filePresent?(keyFile, options)
|
151
|
+
connection.exec("mkdir -p #{homeDir}/.ssh", false, options)
|
152
|
+
connection.exec("ssh-keygen -t ed25519 -f #{keyFile} -P '' -C '#{name}@#{hostname}'", false, options)
|
153
|
+
connection.exec("chown -R #{name}:#{name} #{homeDir}/.ssh", false, options)
|
60
154
|
end
|
61
|
-
|
62
|
-
|
155
|
+
if !info['SSH'].to_h['Config'].to_h.empty?
|
156
|
+
connection.exec("mkdir -p #{homeDir}/.ssh", false, options)
|
157
|
+
deploySSHConfig(connection, info['SSH']['Config'], "#{homeDir}/.ssh/config", target, options)
|
158
|
+
connection.exec("chown -R #{name}:#{name} #{homeDir}/.ssh", false, options)
|
63
159
|
end
|
64
160
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
161
|
+
end
|
162
|
+
if target['Firewall'] && target['Firewall'] != 'no'
|
163
|
+
connection.ensurePackage(FIREWALL_PACKAGE, options)
|
164
|
+
connection.ensureServiceAutoStart(FIREWALL_SERVICE, options)
|
165
|
+
connection.startService(FIREWALL_SERVICE, options)
|
166
|
+
end
|
167
|
+
if !target['Packages'].to_a.empty?
|
168
|
+
connection.ensurePackages(target['Packages'], options)
|
169
|
+
end
|
170
|
+
target['Services'].to_a.each do |service|
|
171
|
+
connection.ensureServiceAutoStart(service, options)
|
172
|
+
connection.startService(service, options)
|
173
|
+
end
|
174
|
+
self.executeCommands(target['Execute'], connection)
|
175
|
+
end
|
176
|
+
|
177
|
+
def convertFlavour(distroInfo, target, connection, options)
|
178
|
+
if target['Flavour']
|
179
|
+
if target['Flavour'] == PROXMOXVE_NAME
|
180
|
+
if distroInfo['Name'] != DEBIAN_NAME
|
181
|
+
raise 'Can\'t convert flavour!'
|
182
|
+
end
|
183
|
+
if connection.filePresent?('/etc/apt/sources.list.d/pve-install-repo.list', { **options, 'dry' => false })
|
184
|
+
needInstall = connection.exec('dpkg --status proxmox-ve 2>/dev/null | grep Status | grep installed | wc -l', false, { **options, 'dry' => false }).strip.to_i.zero?
|
185
|
+
if needInstall
|
186
|
+
connection.exec('DEBIAN_FRONTEND=noninteractive apt install --assume-yes proxmox-ve postfix open-iscsi chrony', false, options)
|
187
|
+
connection.exec("apt remove --assume-yes os-prober linux-image-amd64 'linux-image-*'", false, options)
|
188
|
+
connection.exec('update-grub', false, options)
|
189
|
+
end
|
190
|
+
else
|
191
|
+
connection.exec('echo "deb [arch=amd64] http://download.proxmox.com/debian/pve bookworm pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list', false, options)
|
192
|
+
File.write(options['output'] + 'proxmox-release-bookworm.gpg', HTTP.follow.get('https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg').body)
|
193
|
+
connection.upload(options['output'] + 'proxmox-release-bookworm.gpg', '/etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg', options)
|
194
|
+
connection.exec('apt update && apt full-upgrade --assume-yes', false, options)
|
195
|
+
connection.exec('apt install --assume-yes proxmox-default-kernel', false, options)
|
196
|
+
connection.exec('systemctl reboot', false, options)
|
197
|
+
end
|
198
|
+
target['Network'] = {} unless target['Network'].is_a?(Hash)
|
199
|
+
target['Network']['Interfaces'] = {} unless target['Network']['Interfaces'].is_a?(Hash)
|
200
|
+
if !target['Network']['Interfaces'].key?('vmbr0')
|
201
|
+
if target['Network']['IP']
|
202
|
+
target['Network']['Interfaces']['vmbr0'] = {}
|
203
|
+
target['Network']['Interfaces']['vmbr0']['Type'] = 'Bridge'
|
204
|
+
target['Network']['Interfaces']['vmbr0']['IP'] = target['Network']['IP']
|
205
|
+
target['Network']['Interfaces']['vmbr0']['Gateway'] = target['Network']['Gateway']
|
206
|
+
target['Network']['Interfaces']['vmbr0']['DNS'] = target['Network']['DNS']
|
71
207
|
else
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
208
|
+
target['Network']['Interfaces']['vmbr0'] = 'dhcp'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
else
|
212
|
+
raise 'Unimplemented flavour!'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def configureNetwork(distroInfo, target, connection, options)
|
218
|
+
if target['Network']
|
219
|
+
if distroInfo['Name'] == 'openSUSE Leap'
|
220
|
+
updateNetworkInterface(target['Network'], 'eth0', connection, options)
|
221
|
+
if target['Network']['Interfaces']
|
222
|
+
target['Network']['Interfaces'].each do |interface, config|
|
223
|
+
updateNetworkInterface(config, interface, connection, options)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
if target['Network']['DNS']
|
227
|
+
configFile = '/etc/sysconfig/network/config'
|
228
|
+
dns = target['Network']['DNS']
|
229
|
+
dns = [dns] unless dns.is_a?(Array)
|
230
|
+
connection.exec("sed -i 's|^NETCONFIG_DNS_STATIC_SERVERS=.*|NETCONFIG_DNS_STATIC_SERVERS=\"#{dns.join(' ')}\"|' #{configFile}", false, options)
|
231
|
+
end
|
232
|
+
if target['Network']['Gateway']
|
233
|
+
routesFile = '/etc/sysconfig/network/routes'
|
234
|
+
connection.exec("sed -i 's|^default |#default |' #{routesFile}", false, options)
|
235
|
+
connection.updateFile(routesFile, options) do |fileLines|
|
236
|
+
fileLines << "default #{target['Network']['Gateway']}\n"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
elsif distroInfo['Name'] == 'Debian'
|
240
|
+
links = self.networkLinks(connection)
|
241
|
+
raise 'Didn\'t find network links!' if links.empty?
|
242
|
+
linkType = nil
|
243
|
+
dnsSearch = connection.exec('cat /etc/resolv.conf | grep search', false, { **options, 'dry' => false }).strip.split(' ').last
|
244
|
+
if target['Network'].is_a?(String)
|
245
|
+
linkType = target['Network']
|
246
|
+
target['Network'] = {}
|
247
|
+
end
|
248
|
+
links.each do |link|
|
249
|
+
target['Network']['Interfaces'][link] = 'manual' unless target['Network']['Interfaces'].key?(link)
|
250
|
+
end
|
251
|
+
if !target['Network'].key?('Interfaces') ||
|
252
|
+
target['Network']['Interfaces'].to_h.empty? ||
|
253
|
+
!target['Network']['Interfaces'].key?(links.first)
|
254
|
+
target['Network']['Interfaces'] ||= {}
|
255
|
+
if !linkType.nil?
|
256
|
+
target['Network']['Interfaces'][links.first] = linkType
|
257
|
+
elsif target['Network']['IP']
|
258
|
+
target['Network']['Interfaces'][links.first] = {}
|
259
|
+
target['Network']['Interfaces'][links.first]['IP'] = target['Network']['IP']
|
260
|
+
target['Network']['Interfaces'][links.first]['Gateway'] = target['Network']['Gateway'] if target['Network']['Gateway']
|
261
|
+
target['Network']['Interfaces'][links.first]['DNS'] = target['Network']['DNS'] if target['Network']['DNS']
|
262
|
+
end
|
263
|
+
end
|
264
|
+
if target['Network']['Interfaces'].key?('vmbr0')
|
265
|
+
if !target['Network']['Interfaces']['vmbr0'].is_a?(Hash)
|
266
|
+
target['Network']['Interfaces']['vmbr0'] = { }
|
267
|
+
end
|
268
|
+
if target['Network']['Interfaces']['vmbr0']['Ports'].nil?
|
269
|
+
target['Network']['Interfaces']['vmbr0']['Ports'] = [links.first]
|
270
|
+
target['Network']['Interfaces'][links.first] = 'manual'
|
271
|
+
end
|
272
|
+
end
|
273
|
+
interfacesFile = '/etc/network/interfaces'
|
274
|
+
localFile = options['output'] + '/' + SecureRandom.alphanumeric(10)
|
275
|
+
connection.download(interfacesFile, localFile)
|
276
|
+
fileLines = File.read(localFile).lines
|
277
|
+
if fileLines.index(IO::Local::CONFIGLMM_SECTION_BEGIN).nil?
|
278
|
+
lines = []
|
279
|
+
iface = false
|
280
|
+
fileLines.each do |line|
|
281
|
+
if line.start_with?('iface')
|
282
|
+
if line.strip.split(' ')[1].start_with?('enp')
|
283
|
+
iface = true
|
284
|
+
else
|
285
|
+
lines << line
|
286
|
+
end
|
287
|
+
elsif iface && (line.start_with?(' ') || line.start_with?("\t"))
|
288
|
+
# Drop line
|
289
|
+
else
|
290
|
+
iface = false
|
291
|
+
lines << line
|
292
|
+
end
|
293
|
+
end
|
294
|
+
fileWrite(localFile, lines.join(), options[:dry])
|
295
|
+
connection.upload(localFile, interfacesFile)
|
296
|
+
end
|
297
|
+
connection.updateFile(interfacesFile, options) do |fileLines|
|
298
|
+
target['Network']['Interfaces'].each do |name, data|
|
299
|
+
fileLines << "auto #{name}\n"
|
300
|
+
data = 'manual' if data.nil?
|
301
|
+
if data.is_a?(String)
|
302
|
+
fileLines << "iface #{name} inet #{data}\n"
|
303
|
+
else
|
304
|
+
if data['IP']
|
305
|
+
fileLines << "iface #{name} inet static\n"
|
306
|
+
fileLines << " address #{data['IP']}\n"
|
307
|
+
fileLines << " gateway #{data['Gateway']}\n" if data['Gateway']
|
308
|
+
else
|
309
|
+
fileLines << "iface #{name} inet manual\n"
|
310
|
+
end
|
311
|
+
if data.key?('Ports')
|
312
|
+
if data['Ports']
|
313
|
+
fileLines << " bridge-ports #{data['Ports'].join(' ')}\n"
|
78
314
|
else
|
79
|
-
|
80
|
-
|
315
|
+
fileLines << " bridge-ports none\n"
|
316
|
+
end
|
317
|
+
fileLines << " bridge-stp off\n"
|
318
|
+
fileLines << " bridge-fd 0\n"
|
319
|
+
if data['VLANFiltering']
|
320
|
+
fileLines << " bridge-vlan-aware yes\n"
|
321
|
+
end
|
322
|
+
if data['VLANS']
|
323
|
+
fileLines << " bridge-vids #{data['VLANS']}\n"
|
81
324
|
end
|
82
325
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
fileLines << "
|
326
|
+
if data['DNS']
|
327
|
+
fileLines << " # dns-* options are implemented by the resolvconf package, if installed\n"
|
328
|
+
fileLines << " dns-nameservers #{data['DNS']}\n"
|
329
|
+
fileLines << " dns-search #{dnsSearch}\n" if dnsSearch
|
330
|
+
end
|
331
|
+
if data['NAT']
|
332
|
+
addr = IPAddr.new(data['IP'])
|
333
|
+
sourceAddr = "#{addr.to_s}/#{addr.prefix}"
|
334
|
+
outputInterface = ''
|
335
|
+
if data['NAT'].is_a?(String)
|
336
|
+
outputInterface = " -o #{data['NAT']}"
|
337
|
+
end
|
338
|
+
fileLines << " post-up iptables -t nat -A POSTROUTING -s #{sourceAddr}#{outputInterface} -j MASQUERADE\n"
|
339
|
+
fileLines << " post-down iptables -t nat -D POSTROUTING -s #{sourceAddr}#{outputInterface} -j MASQUERADE\n"
|
96
340
|
end
|
97
341
|
end
|
98
|
-
|
99
|
-
else
|
100
|
-
# TODO
|
101
|
-
raise 'Not Unimplemented!'
|
102
|
-
end
|
103
|
-
end
|
104
|
-
if target['Tmpfs']
|
105
|
-
self.class.sshExec!(ssh, "sed -i '/ \\/tmp /d' #{FSTAB_FILE}")
|
106
|
-
updateRemoteFile(ssh, FSTAB_FILE, options, false) do |fileLines|
|
107
|
-
fileLines << "tmpfs /tmp tmpfs nodev,nosuid,size=#{target['Tmpfs']} 0 0\n"
|
108
|
-
end
|
109
|
-
end
|
110
|
-
if target['Sysctl']
|
111
|
-
updateRemoteFile(ssh, SYSCTL_FILE, options, false) do |fileLines|
|
112
|
-
target['Sysctl'].each do |name, value|
|
113
|
-
fileLines << "#{name} = #{value}\n"
|
114
|
-
self.class.sshExec!(ssh, "sysctl #{name}=#{value}")
|
342
|
+
fileLines << "\n"
|
115
343
|
end
|
116
344
|
fileLines
|
117
345
|
end
|
346
|
+
else
|
347
|
+
# TODO
|
348
|
+
raise 'Not Unimplemented!'
|
118
349
|
end
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def updateNetworkInterface(config, interface, connection, options)
|
354
|
+
baseFile = '/etc/sysconfig/network/ifcfg-'
|
355
|
+
networkFile = baseFile + interface
|
356
|
+
connection.exec("touch #{networkFile}", false, options)
|
357
|
+
connection.exec("sed -i \"/^BOOTPROTO=.*/d\" #{networkFile}", false, options)
|
358
|
+
connection.exec("sed -i \"/^STARTMODE=.*/d\" #{networkFile}", false, options)
|
359
|
+
connection.exec("sed -i \"/^ZONE=.*/d\" #{networkFile}", false, options)
|
360
|
+
if config['IP']
|
361
|
+
connection.exec("sed -i 's|^IPADDR=|#IPADDR=|' #{networkFile}", false, options)
|
362
|
+
end
|
363
|
+
connection.updateFile(networkFile, options, false) do |fileLines|
|
364
|
+
fileLines << "STARTMODE=auto\n"
|
365
|
+
fileLines << "ZONE=public\n"
|
366
|
+
if config == 'dhcp'
|
367
|
+
fileLines << "BOOTPROTO=dhcp\n"
|
368
|
+
else
|
369
|
+
fileLines << "BOOTPROTO=static\n"
|
370
|
+
fileLines << "\n"
|
371
|
+
if config['IP']
|
372
|
+
if config['IP'].is_a?(Array)
|
373
|
+
config['IP'].each_with_index do |ip, i|
|
374
|
+
c = "_#{i}"
|
375
|
+
c = '' if i.zero?
|
376
|
+
fileLines << "IPADDR#{c}=#{ip}\n"
|
126
377
|
end
|
127
|
-
|
128
|
-
|
129
|
-
self.class.sshExec!(ssh, "useradd #{badname} --create-home --user-group #{shell} #{name}")
|
130
|
-
end
|
131
|
-
homeDir = self.class.sshExec!(ssh, "getent passwd #{name} | cut -d ':' -f 6").strip
|
132
|
-
keyFile = homeDir + "/.ssh/id_ed25519"
|
133
|
-
if info['SSHKey'] && !self.class.remoteFilePresent?(keyFile, ssh)
|
134
|
-
self.class.sshExec!(ssh, "mkdir -p #{homeDir}/.ssh")
|
135
|
-
self.class.sshExec!(ssh, "ssh-keygen -t ed25519 -f #{keyFile} -P ''")
|
136
|
-
self.class.sshExec!(ssh, "chown -R #{name}:#{name} #{homeDir}/.ssh")
|
378
|
+
else
|
379
|
+
fileLines << "IPADDR=#{config['IP']}\n"
|
137
380
|
end
|
138
381
|
end
|
139
382
|
end
|
140
|
-
|
141
|
-
end
|
142
|
-
if target['Firewall'] && target['Firewall'] != 'no'
|
143
|
-
self.ensurePackage(FIREWALL_PACKAGE, locationUri)
|
144
|
-
self.ensureServiceAutoStart(FIREWALL_SERVICE, locationUri)
|
145
|
-
self.startService(FIREWALL_SERVICE, locationUri)
|
383
|
+
fileLines
|
146
384
|
end
|
147
385
|
end
|
148
386
|
|
149
|
-
def
|
387
|
+
def networkLinks(connection)
|
388
|
+
connection.exec("ls /sys/class/net/").strip.split("\n").select { |name| name.start_with?('enp') }
|
389
|
+
end
|
390
|
+
|
391
|
+
def deployLocal(connection, target, options)
|
150
392
|
deployLocalHostsFile(target, options)
|
151
|
-
deployLocalSSHConfig(target, options)
|
393
|
+
deployLocalSSHConfig(connection, target, options)
|
152
394
|
if target['Sysctl']
|
153
395
|
updateLocalFile(SYSCTL_FILE, options) do |fileLines|
|
154
396
|
target['Sysctl'].each do |name, value|
|
@@ -160,35 +402,34 @@ module ConfigLMM
|
|
160
402
|
end
|
161
403
|
if target['Users']
|
162
404
|
target['Users'].each do |name, info|
|
163
|
-
userId =
|
405
|
+
userId = connection.exec("id -u #{name} 2>/dev/null", true, { **options, 'dry' => false }).strip
|
164
406
|
if userId.empty?
|
165
407
|
shell = ''
|
166
408
|
if info['Shell']
|
167
409
|
shell = "--shell '/usr/bin/#{info['Shell']}'"
|
168
410
|
end
|
169
|
-
distroInfo = self.class.currentDistroInfo(nil)
|
170
411
|
badname = '--badname'
|
171
|
-
badname = '--badnames' if
|
172
|
-
|
412
|
+
badname = '--badnames' if connection.distroName == 'openSUSE Leap'
|
413
|
+
connection.exec("useradd #{badname} --create-home --user-group #{shell} #{name}", false, options)
|
173
414
|
end
|
174
|
-
homeDir =
|
415
|
+
homeDir = connection.exec("getent passwd #{name} | cut -d ':' -f 6", false, options).strip
|
175
416
|
keyFile = homeDir + "/.ssh/id_ed25519"
|
176
|
-
if info['
|
177
|
-
|
178
|
-
|
179
|
-
|
417
|
+
if info['SSH'].to_h['Key'] && !connection.filePresent?(keyFile, options)
|
418
|
+
connection.exec("mkdir -p #{homeDir}/.ssh", false, options)
|
419
|
+
connection.exec("ssh-keygen -t ed25519 -f #{keyFile} -P ''", false, options)
|
420
|
+
connection.exec("chown -R #{name}:#{name} #{homeDir}/.ssh", false, options)
|
180
421
|
end
|
181
422
|
end
|
182
423
|
end
|
183
424
|
if target['Firewall'] && target['Firewall'] != 'no'
|
184
|
-
|
185
|
-
|
186
|
-
|
425
|
+
connection.ensurePackage(FIREWALL_PACKAGE, options)
|
426
|
+
connection.ensureServiceAutoStart(FIREWALL_SERVICE, options)
|
427
|
+
connection.startService(FIREWALL_SERVICE, options)
|
187
428
|
end
|
188
429
|
self.executeCommands(target['Execute'])
|
189
430
|
end
|
190
431
|
|
191
|
-
def executeCommands(commands,
|
432
|
+
def executeCommands(commands, connection)
|
192
433
|
return unless commands
|
193
434
|
|
194
435
|
commands.each do |type, data|
|
@@ -196,7 +437,7 @@ module ConfigLMM
|
|
196
437
|
when 'sh'
|
197
438
|
data = [data] unless data.is_a?(Array)
|
198
439
|
data.each do |cmd|
|
199
|
-
|
440
|
+
connection.exec(cmd)
|
200
441
|
end
|
201
442
|
else
|
202
443
|
raise 'Unimplemented!'
|
@@ -206,9 +447,71 @@ module ConfigLMM
|
|
206
447
|
|
207
448
|
def deployOverLibvirt(id, target, activeState, context, options)
|
208
449
|
location = Libvirt.getLocation(target['Location'])
|
209
|
-
iso = installationISO(target['Distro'], location)
|
210
|
-
iso =
|
211
|
-
plugins[:Libvirt].createVM(target['Name'], target, target['Location'], iso, activeState)
|
450
|
+
iso = installationISO(target['Distro'], target['Flavour'], location)
|
451
|
+
iso = buildAutoInstallISO(id, iso, target, options)
|
452
|
+
if plugins[:Libvirt].createVM(target['Name'], target, target['Location'], iso, activeState)
|
453
|
+
context.secrets.print('Root password', target['Users']['root']['Password']) if target['Users']['root'].key?('Password')
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def deployOverProxmox(id, target, activeState, context, options)
|
458
|
+
if target['LXC']
|
459
|
+
info = flavourInfo(target['Distro'], target['Flavour'])
|
460
|
+
if plugins[:Proxmox].createContainer(target, target['Location'], info, activeState, context)
|
461
|
+
context.secrets.print('Root password', target['Users']['root']['Password']) if target['Users']['root'].key?('Password')
|
462
|
+
end
|
463
|
+
else
|
464
|
+
location = Proxmox.getLocation(target['Location'])
|
465
|
+
iso = installationISO(target['Distro'], target['Flavour'], location)
|
466
|
+
iso = buildAutoInstallISO(id, iso, target, options)
|
467
|
+
if plugins[:Proxmox].createVM(target['Name'], target, target['Location'], iso, activeState, context)
|
468
|
+
context.secrets.print('Root password', target['Users']['root']['Password']) if target['Users']['root'].key?('Password')
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def findNetworkIP(ipaddr)
|
474
|
+
addrs = Socket.getifaddrs.select { |ifaddr| ifaddr.addr.ipv4? && !ifaddr.addr.ipv4_loopback? }
|
475
|
+
addrs.each do |addr|
|
476
|
+
ip = addr.addr.ip_unpack.first
|
477
|
+
netmask = addr.netmask.ip_unpack.first
|
478
|
+
prefix = netmask.split('.').map(&:to_i).map { |octet| octet.to_s(2).count('1') }.sum
|
479
|
+
if IPAddr.new("#{ip}/#{prefix}") == IPAddr.new(ipaddr)
|
480
|
+
return ip
|
481
|
+
end
|
482
|
+
end
|
483
|
+
nil
|
484
|
+
end
|
485
|
+
|
486
|
+
def deployOverPXE(uri, id, target, activeState, context, options)
|
487
|
+
if target['AlternativeLocation']
|
488
|
+
return if self.ping(target['AlternativeLocation'], target)
|
489
|
+
end
|
490
|
+
networkOptions = target['DefaultNetwork'].dup
|
491
|
+
networkOptions['ID'] = id
|
492
|
+
clientIp = networkOptions['IP']
|
493
|
+
if clientIp == 'dhcp'
|
494
|
+
networkOptions['IP'] = nil
|
495
|
+
networkOptions['ClientIP'] = nil
|
496
|
+
else
|
497
|
+
networkOptions['ClientIP'] = clientIp.split('/').first
|
498
|
+
networkOptions['IP'] = findNetworkIP(clientIp)
|
499
|
+
end
|
500
|
+
dir = preparePXE(id, target['Distro'], target['Flavour'], options)
|
501
|
+
bootFileResolver = Proc.new do |clientArch|
|
502
|
+
bootFile = 'lpxelinux.0'
|
503
|
+
bootFile = 'pxelinux.0' unless File.exist?(dir + bootFile)
|
504
|
+
if [0x0007, 0x0010].include?(clientArch) # EFI x64 and x64 UEFI HTTP
|
505
|
+
if target['Distro'] == SUSE_NAME
|
506
|
+
# Because we reuse Debian netboot archive...
|
507
|
+
bootFile = 'debian-installer/amd64/bootnetx64.efi'
|
508
|
+
elsif target['Distro'] == DEBIAN_NAME
|
509
|
+
bootFile = 'debian-installer/amd64/bootnetx64.efi'
|
510
|
+
end
|
511
|
+
end
|
512
|
+
bootFile
|
513
|
+
end
|
514
|
+
IO::PXE.boot(dir, uri, networkOptions, bootFileResolver, options, logger)
|
212
515
|
end
|
213
516
|
|
214
517
|
def buildHostsFile(id, target, options)
|
@@ -219,11 +522,11 @@ module ConfigLMM
|
|
219
522
|
hosts += "#<ip-address> <hostname.domain.org> <hostname>\n"
|
220
523
|
hosts += "127.0.0.1 localhost\n"
|
221
524
|
hosts += "::1 localhost\n\n"
|
222
|
-
hosts += CONFIGLMM_SECTION_BEGIN
|
525
|
+
hosts += IO::Local::CONFIGLMM_SECTION_BEGIN
|
223
526
|
target['Hosts'].each do |ip, entries|
|
224
|
-
hosts += ip
|
527
|
+
hosts += getHostsLine(ip, entries) + "\n"
|
225
528
|
end
|
226
|
-
hosts += CONFIGLMM_SECTION_END
|
529
|
+
hosts += IO::Local::CONFIGLMM_SECTION_END
|
227
530
|
|
228
531
|
path = options['output'] + '/' + id
|
229
532
|
mkdir(path + '/etc', options[:dry])
|
@@ -234,16 +537,16 @@ module ConfigLMM
|
|
234
537
|
def buildSSHConfig(id, target, options)
|
235
538
|
if !target['SSH']['Config'].empty?
|
236
539
|
sshConfig = "\n"
|
237
|
-
sshConfig += CONFIGLMM_SECTION_BEGIN
|
540
|
+
sshConfig += IO::Local::CONFIGLMM_SECTION_BEGIN
|
238
541
|
target['SSH']['Config'].each do |name, info|
|
239
542
|
sshConfig += "Host #{name} #{info['HostName']}\n"
|
240
|
-
sshConfig += " HostName " + info['HostName'] + "\n" if info['HostName']
|
241
|
-
sshConfig += " Port " + info['Port'] + "\n" if info['Port']
|
543
|
+
sshConfig += " HostName " + Addressable::IDNA.to_ascii(info['HostName']) + "\n" if info['HostName']
|
544
|
+
sshConfig += " Port " + info['Port'].to_s + "\n" if info['Port']
|
242
545
|
sshConfig += " User " + info['User'] + "\n" if info['User']
|
243
546
|
sshConfig += " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
|
244
547
|
sshConfig += "\n"
|
245
548
|
end
|
246
|
-
sshConfig += CONFIGLMM_SECTION_END
|
549
|
+
sshConfig += IO::Local::CONFIGLMM_SECTION_END
|
247
550
|
sshConfig += "\n"
|
248
551
|
|
249
552
|
configPath = options['output'] + '/' + id
|
@@ -252,66 +555,192 @@ module ConfigLMM
|
|
252
555
|
end
|
253
556
|
end
|
254
557
|
|
255
|
-
def
|
256
|
-
|
558
|
+
def buildAutoInstall(id, target, options)
|
559
|
+
config = prepareAutoInstallConfig(target)
|
560
|
+
if config['Flavour'] == PROXMOXVE_NAME
|
561
|
+
outputFolder = options['output'] + '/' + id + '/'
|
562
|
+
template = ERB.new(File.read(__dir__ + '/Proxmox/answer.toml.erb'))
|
563
|
+
renderTemplate(template, config, outputFolder + 'answer.toml', options)
|
564
|
+
File.write("#{outputFolder}/auto-installer-mode.toml", 'mode = "iso"')
|
565
|
+
elsif config['Distro'] == SUSE_NAME
|
257
566
|
outputFolder = options['output'] + '/' + id + '/'
|
258
567
|
template = ERB.new(File.read(__dir__ + '/openSUSE/autoinst.xml.erb'))
|
259
|
-
renderTemplate(template,
|
568
|
+
renderTemplate(template, config, outputFolder + 'autoinst.xml', options)
|
569
|
+
elsif config['Distro'] == DEBIAN_NAME
|
570
|
+
variables = prepareDebianStorage(config, options)
|
571
|
+
outputFolder = options['output'] + '/' + id + '/'
|
572
|
+
template = ERB.new(File.read(__dir__ + '/Debian/preseed.cfg.erb'))
|
573
|
+
renderTemplate(template, variables, outputFolder + 'preseed.cfg', options)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
def prepareAutoInstallConfig(target)
|
578
|
+
config = target.dup
|
579
|
+
if config['Apps'].to_a.include?('sshd')
|
580
|
+
config['Services'] << :sshd
|
581
|
+
config['Services'].uniq!
|
582
|
+
end
|
583
|
+
config['Apps'] = Framework::LinuxApp.mapPackages(config['Apps'], config['Distro']) if config['Distro']
|
584
|
+
config['Apps'].delete_if { |app| app.include?('|') }
|
585
|
+
config
|
586
|
+
end
|
587
|
+
|
588
|
+
def prepareDebianStorage(target, options)
|
589
|
+
variables = target.dup
|
590
|
+
variables['AutoPartition'] = true
|
591
|
+
variables['Disks'] = []
|
592
|
+
if target.key?('StorageDevices')
|
593
|
+
if target['StorageDevices'].length == 1
|
594
|
+
variables['Disks'] = [target['StorageDevices'].first['Device']]
|
595
|
+
if !target['StorageDevices'].first['Partitions'].to_a.empty?
|
596
|
+
variables['AutoPartition'] = false
|
597
|
+
end
|
598
|
+
elsif !target['StorageDevices'].empty?
|
599
|
+
variables['AutoPartition'] = false
|
600
|
+
end
|
601
|
+
end
|
602
|
+
if target.key?('Mounts') && !target['Mounts'].empty?
|
603
|
+
variables['AutoPartition'] = false
|
604
|
+
end
|
605
|
+
if !variables['AutoPartition']
|
606
|
+
logger.warn('Specified disk/partition configuration is not implemented! You will have to configure it manually!')
|
260
607
|
end
|
608
|
+
variables
|
609
|
+
end
|
610
|
+
|
611
|
+
def getHostsLine(ip, entries)
|
612
|
+
entries = entries.map { |entry| Addressable::IDNA.to_ascii(entry) }
|
613
|
+
# Hostnames should be case-insensitive but some implementations like in Alpine aren't
|
614
|
+
# so in case someone specified UpperCase hostname we also add lowercase one so that both would resolve
|
615
|
+
entries = (entries + entries.map(&:downcase)).uniq
|
616
|
+
ip.ljust(16) + entries.join(' ')
|
261
617
|
end
|
262
618
|
|
263
619
|
def deployLocalHostsFile(target, options)
|
264
620
|
if target['Hosts']
|
265
621
|
updateLocalFile(HOSTS_FILE, options) do |hostsLines|
|
266
622
|
target['Hosts'].each do |ip, entries|
|
267
|
-
hostsLines << ip
|
623
|
+
hostsLines << getHostsLine(ip, entries) + "\n"
|
268
624
|
end
|
269
625
|
hostsLines
|
270
626
|
end
|
271
627
|
end
|
272
628
|
end
|
273
629
|
|
274
|
-
def deployLocalSSHConfig(target, options)
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
configLines << " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
|
283
|
-
end
|
284
|
-
configLines
|
630
|
+
def deployLocalSSHConfig(connection, target, options)
|
631
|
+
deploySSHConfig(connection, target['SSH']['Config'], File.expand_path(SSH_CONFIG), target, options)
|
632
|
+
end
|
633
|
+
|
634
|
+
def deploySSHConfig(connection, config, path, target, options)
|
635
|
+
if !config.to_h.empty?
|
636
|
+
connection.updateFile(path, options) do |configLines|
|
637
|
+
processSSHConfig(config, configLines)
|
285
638
|
end
|
286
639
|
end
|
287
640
|
end
|
288
641
|
|
289
|
-
def
|
642
|
+
def processSSHConfig(config, lines)
|
643
|
+
config.each do |name, info|
|
644
|
+
lines << "Host #{name} #{info['HostName']}\n"
|
645
|
+
lines << " HostName " + Addressable::IDNA.to_ascii(info['HostName']) + "\n" if info['HostName']
|
646
|
+
lines << " Port " + info['Port'].to_s + "\n" if info['Port']
|
647
|
+
lines << " User " + info['User'] + "\n" if info['User']
|
648
|
+
lines << " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
|
649
|
+
end
|
650
|
+
lines
|
651
|
+
end
|
652
|
+
|
653
|
+
def flavourInfo(distro, flavour)
|
290
654
|
url = nil
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
655
|
+
flavour = distro unless flavour
|
656
|
+
flavourInfo = YAML.load_file(__dir__ + '/Flavours.yaml')[flavour]
|
657
|
+
if flavourInfo.nil?
|
658
|
+
raise Framework::PluginProcessError.new("#{id}: Unknown Linux Distro: #{flavour}!")
|
659
|
+
end
|
660
|
+
flavourInfo
|
661
|
+
end
|
662
|
+
|
663
|
+
def downloadImage(url)
|
664
|
+
local.remoteDownload(url, IMAGE_LOCATION)
|
665
|
+
end
|
666
|
+
|
667
|
+
def installationISO(distro, flavour, location)
|
668
|
+
info = flavourInfo(distro, flavour)
|
669
|
+
downloadImage(info['ISO'])
|
670
|
+
end
|
671
|
+
|
672
|
+
def preparePXE(id, distro, flavour, options)
|
673
|
+
outputFolder = options['output'] + '/pxe/'
|
674
|
+
local.mkdir(outputFolder, false)
|
675
|
+
info = flavourInfo(distro, flavour)
|
676
|
+
image = nil
|
677
|
+
if info['PXE']
|
678
|
+
image = downloadImage(info['PXE'])
|
679
|
+
local.exec("tar --extract --file=#{image.shellescape} --directory=#{outputFolder}", false)
|
680
|
+
if distro == DEBIAN_NAME
|
681
|
+
local.copy(options['output'] + '/' + id + '/preseed.cfg', outputFolder, false)
|
682
|
+
local.exec("sed -i 's|default .*|default auto|' #{outputFolder}debian-installer/amd64/boot-screens/syslinux.cfg", false)
|
683
|
+
local.exec("sed -i 's|--- quiet|file=/preseed.cfg --- quiet|' #{outputFolder}debian-installer/amd64/boot-screens/adtxt.cfg", false)
|
684
|
+
local.exec("echo \"set default='... Automated install'\" >> #{outputFolder}debian-installer/amd64/grub/grub.cfg", false)
|
685
|
+
local.exec("echo 'set timeout=1' >> #{outputFolder}debian-installer/amd64/grub/grub.cfg", false)
|
686
|
+
local.exec("gunzip #{outputFolder}debian-installer/amd64/initrd.gz", false)
|
687
|
+
local.exec("cd #{options['output'] + '/' + id} && echo preseed.cfg | cpio -H newc -o -O #{outputFolder}debian-installer/amd64/initrd --append", false)
|
688
|
+
local.exec("gzip #{outputFolder}debian-installer/amd64/initrd", false)
|
298
689
|
end
|
690
|
+
elsif distro == SUSE_NAME
|
691
|
+
# openSUSE doesn't provide netboot archive
|
692
|
+
# and grub.efi from it's ISO doesn't work
|
693
|
+
# so let's just reuse Debian archive
|
694
|
+
debianInfo = flavourInfo('Debian', nil)
|
695
|
+
debianImage = downloadImage(debianInfo['PXE'])
|
696
|
+
local.exec("tar --extract --file=#{debianImage.shellescape} --directory=#{outputFolder}", false)
|
697
|
+
local.exec("rm -rf #{outputFolder}pxelinux.cfg", false)
|
698
|
+
|
699
|
+
syslinux = downloadImage(flavourInfo('Syslinux', nil)['Archive'])
|
700
|
+
syslinuxFolder = options['output'] + '/syslinux'
|
701
|
+
local.mkdir(syslinuxFolder, false)
|
702
|
+
local.mkdir(outputFolder + 'pxelinux.cfg', false)
|
703
|
+
local.copy(options['output'] + '/' + id + '/autoinst.xml', outputFolder, false)
|
704
|
+
local.exec("tar --extract --file=#{syslinux.shellescape} --directory=#{syslinuxFolder}", false)
|
705
|
+
local.exec("cp #{syslinuxFolder}/*/bios/core/lpxelinux.0 #{outputFolder}", false)
|
706
|
+
local.exec("cp #{syslinuxFolder}/*/bios/com32/elflink/ldlinux/ldlinux.c32 #{outputFolder}", false)
|
707
|
+
local.exec("cp #{__dir__ + '/Syslinux/default'} #{outputFolder}pxelinux.cfg/", false)
|
708
|
+
local.exec("sed -i 's|$OPTIONS|install=#{info['FILES']} autoyast=/autoinst.xml|' #{outputFolder}pxelinux.cfg/default", false)
|
709
|
+
|
710
|
+
# If you have Network/IP configured in UEFI firmware which is different than what we use
|
711
|
+
# then Syslinux will use that IP instead of ours so you need to reset those settings
|
712
|
+
#
|
713
|
+
# Not using Syslinux.efi because for some reason it doesn't work - reboots after loading initrd
|
714
|
+
#local.exec("cp #{syslinuxFolder}/*/efi64/efi/syslinux.efi #{outputFolder}", false)
|
715
|
+
#local.exec("cp #{syslinuxFolder}/*/efi64/com32/elflink/ldlinux/ldlinux.e64 #{outputFolder}", false)
|
716
|
+
|
717
|
+
local.remoteDownload(info['FILES'] + 'boot/x86_64/loader/initrd', outputFolder)
|
718
|
+
local.remoteDownload(info['FILES'] + 'boot/x86_64/loader/linux', outputFolder)
|
719
|
+
|
720
|
+
# Not using grub.efi because UEFI Firemware says "Unsupported"
|
721
|
+
#local.remoteDownload(info['FILES'] + 'EFI/BOOT/bootx64.efi', outputFolder)
|
722
|
+
#local.remoteDownload(info['FILES'] + 'EFI/BOOT/grub.efi', outputFolder)
|
723
|
+
#local.remoteDownload(info['FILES'] + 'EFI/BOOT/grub.cfg', outputFolder)
|
724
|
+
|
725
|
+
local.exec("cp #{__dir__ + '/Grub/grub.cfg'} #{outputFolder}debian-installer/amd64/grub/", false)
|
726
|
+
local.exec("sed -i 's|$OPTIONS|install=#{info['FILES']} autoyast=/autoinst.xml|' #{outputFolder}debian-installer/amd64/grub/grub.cfg", false)
|
727
|
+
|
728
|
+
local.exec("xz --decompress --stdout #{outputFolder}initrd > #{outputFolder}initrd.decompressed", false)
|
729
|
+
local.exec("cd #{options['output'] + '/' + id} && echo autoinst.xml | cpio -H newc -o -O #{outputFolder}initrd.decompressed --append", false)
|
730
|
+
local.exec("xz --compress --stdout --check=crc32 #{outputFolder}initrd.decompressed > #{outputFolder}initrd", false)
|
299
731
|
else
|
300
|
-
raise
|
732
|
+
raise 'Not implemented!'
|
301
733
|
end
|
734
|
+
outputFolder
|
735
|
+
end
|
302
736
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
File.open(iso, 'wb') do |file|
|
311
|
-
response.body.each do |chunk|
|
312
|
-
file.write(chunk)
|
313
|
-
end
|
314
|
-
end
|
737
|
+
def buildAutoInstallISO(id, iso, target, options)
|
738
|
+
if target['Flavour'] == PROXMOXVE_NAME
|
739
|
+
iso = buildISOAutoProxmox(id, iso, target, options)
|
740
|
+
elsif target['Distro'] == SUSE_NAME
|
741
|
+
iso = buildISOAutoYaST(id, iso, target, options)
|
742
|
+
elsif target['Distro'] == DEBIAN_NAME
|
743
|
+
iso = buildISOPreseed(id, iso, target, options)
|
315
744
|
end
|
316
745
|
iso
|
317
746
|
end
|
@@ -319,40 +748,92 @@ module ConfigLMM
|
|
319
748
|
def buildISOAutoYaST(id, iso, target, options)
|
320
749
|
outputFolder = options['output'] + '/iso/'
|
321
750
|
mkdir(outputFolder, false)
|
322
|
-
|
751
|
+
local.exec("xorriso -osirrox on -indev #{iso} -extract / #{outputFolder}")
|
323
752
|
FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
|
324
753
|
copy(options['output'] + '/' + id + '/autoinst.xml', outputFolder, false)
|
325
754
|
|
326
755
|
cfg = outputFolder + "boot/x86_64/loader/isolinux.cfg"
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
756
|
+
local.exec("sed -i 's|default harddisk|default linux|' #{cfg}")
|
757
|
+
local.exec("sed -i 's|append initrd=initrd splash=silent showopts|append initrd=initrd splash=silent autoyast=device://sr0/autoinst.xml|' #{cfg}")
|
758
|
+
local.exec("sed -i 's|prompt 1|prompt 0|' #{cfg}")
|
759
|
+
local.exec("sed -i 's|timeout 600|timeout 1|' #{cfg}")
|
760
|
+
|
761
|
+
ifcfg = ''
|
762
|
+
if target['DefaultNetwork']['IP'] != 'dhcp'
|
763
|
+
ifcfg = "ifcfg=\"eth*=#{target['DefaultNetwork']['IP']}"
|
764
|
+
if target['DefaultNetwork']['Gateway'] || target['DefaultNetwork']['DNS']
|
765
|
+
ifcfg += ',' + target['DefaultNetwork']['Gateway'].to_s
|
766
|
+
if target['DefaultNetwork']['DNS']
|
767
|
+
ifcfg += ',' + target['DefaultNetwork']['DNS']
|
768
|
+
ifcfg += ',' + Addressable::IDNA.to_ascii(target['Domain']) if target['Domain']
|
769
|
+
end
|
770
|
+
end
|
771
|
+
ifcfg += '"'
|
772
|
+
end
|
773
|
+
|
774
|
+
cfg = outputFolder + "EFI/BOOT/grub.cfg"
|
775
|
+
local.exec("sed -i 's|timeout=.*|timeout=1|' #{cfg}")
|
776
|
+
local.exec("sed -i 's|linux splash=silent|linux splash=silent #{ifcfg} autoyast=device://sr0/autoinst.xml|' #{cfg}")
|
331
777
|
|
332
778
|
patchedIso = File.dirname(iso) + '/patched.iso'
|
333
|
-
|
779
|
+
local.exec("xorriso -as mkisofs -no-emul-boot -boot-info-table -boot-load-size 4 -iso-level 4 -b boot/x86_64/loader/isolinux.bin -c boot/x86_64/loader/boot.cat -eltorito-alt-boot -no-emul-boot -e boot/x86_64/efi -o #{patchedIso} #{outputFolder}")
|
334
780
|
patchedIso
|
335
781
|
end
|
336
782
|
|
337
|
-
def
|
783
|
+
def buildISOPreseed(id, iso, target, options)
|
784
|
+
outputFolder = options['output'] + '/iso/'
|
785
|
+
mkdir(outputFolder, false)
|
786
|
+
local.exec("xorriso -osirrox on -indev #{iso} -extract / #{outputFolder}", false)
|
787
|
+
FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
|
788
|
+
local.copy(options['output'] + '/' + id + '/preseed.cfg', outputFolder, false)
|
789
|
+
|
790
|
+
local.exec("sed -i 's|vga=788 --- quiet|auto=true file=/cdrom/preseed.cfg vga=788 --- quiet|' #{outputFolder}boot/grub/grub.cfg")
|
791
|
+
local.exec("echo \"set default='... Automated install'\" >> #{outputFolder}boot/grub/grub.cfg", false)
|
792
|
+
local.exec("echo 'set timeout=1' >> #{outputFolder}boot/grub/grub.cfg", false)
|
793
|
+
local.exec("sed -i 's|--- quiet|file=/cdrom/preseed.cfg --- quiet|' #{outputFolder + "isolinux/adgtk.cfg"}")
|
794
|
+
local.exec("sed -i 's|default .*|default autogui|' #{outputFolder + "isolinux/isolinux.cfg"}")
|
795
|
+
|
796
|
+
patchedIso = File.dirname(iso) + '/patched.iso'
|
797
|
+
local.exec("xorriso -as mkisofs -no-emul-boot -boot-info-table -boot-load-size 4 -iso-level 4 -b isolinux/isolinux.bin -c isolinux/boot.cat -eltorito-alt-boot -e boot/grub/efi.img -no-emul-boot -o #{patchedIso} #{outputFolder}")
|
798
|
+
patchedIso
|
799
|
+
end
|
800
|
+
|
801
|
+
def buildISOAutoProxmox(id, iso, target, options)
|
802
|
+
outputFolder = options['output'] + '/iso/'
|
803
|
+
patchedIso = File.dirname(iso) + '/patched.iso'
|
804
|
+
|
805
|
+
copy(iso, patchedIso, false)
|
806
|
+
|
807
|
+
self.class.exec("xorriso -boot_image any keep -dev #{patchedIso} -map #{options['output'] + '/' + id + '/auto-installer-mode.toml'} /auto-installer-mode.toml")
|
808
|
+
self.class.exec("xorriso -boot_image any keep -dev #{patchedIso} -map #{options['output'] + '/' + id + '/answer.toml'} /answer.toml")
|
809
|
+
patchedIso
|
810
|
+
end
|
811
|
+
|
812
|
+
def self.withConnection(connection)
|
813
|
+
yield(LinuxConnection.new(connection))
|
814
|
+
end
|
815
|
+
|
816
|
+
def prepareConfig(target, context)
|
338
817
|
target['SSH'] ||= {}
|
339
818
|
target['SSH']['Config'] ||= {}
|
340
819
|
target['Users'] ||= {}
|
341
820
|
target['HostName'] = target['Name'] unless target['HostName']
|
342
821
|
|
343
|
-
if
|
822
|
+
if target['SecretId'] && context.secrets.load(target['SecretId'], 'ROOT_PASSWORD_HASH')
|
344
823
|
target['Users']['root'] ||= {}
|
345
|
-
target['Users']['root']['PasswordHash'] =
|
346
|
-
elsif
|
824
|
+
target['Users']['root']['PasswordHash'] = context.secrets.load(target['SecretId'], 'ROOT_PASSWORD_HASH')
|
825
|
+
elsif target['SecretId'] && context.secrets.load(target['SecretId'], 'ROOT_PASSWORD')
|
347
826
|
target['Users']['root'] ||= {}
|
348
|
-
target['Users']['root']['
|
827
|
+
target['Users']['root']['Password'] = context.secrets.load(target['SecretId'], 'ROOT_PASSWORD')
|
828
|
+
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(target['Users']['root']['Password'])
|
349
829
|
elsif target['Users'].key?('root')
|
350
|
-
if !target['Users']['root']
|
351
|
-
!target['Users']['root']
|
352
|
-
|
353
|
-
|
354
|
-
target['Users']['root']['
|
355
|
-
|
830
|
+
if !target['Users']['root'].key?('Password') &&
|
831
|
+
!target['Users']['root'].key?('PasswordHash')
|
832
|
+
password = SecureRandom.urlsafe_base64(20)
|
833
|
+
context.secrets.store(target['SecretId'], 'ROOT_PASSWORD', password) if target['SecretId']
|
834
|
+
target['Users']['root']['Password'] = password
|
835
|
+
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(password)
|
836
|
+
elsif target['Users']['root']['Password'] == false
|
356
837
|
target['Users']['root'].delete('Password')
|
357
838
|
end
|
358
839
|
end
|
@@ -360,7 +841,7 @@ module ConfigLMM
|
|
360
841
|
target['Users'].each do |user, info|
|
361
842
|
newKeys = []
|
362
843
|
info['AuthorizedKeys'].to_a.each do |key|
|
363
|
-
if key.start_with?('/') || key.start_with?('~')
|
844
|
+
if key.start_with?('/') || key.start_with?('~') || key.start_with?('.') || key.end_with?('.pub')
|
364
845
|
newKeys << File.read(File.expand_path(key)).strip
|
365
846
|
else
|
366
847
|
newKeys << key
|
@@ -369,14 +850,67 @@ module ConfigLMM
|
|
369
850
|
info['AuthorizedKeys'] = newKeys
|
370
851
|
end
|
371
852
|
|
372
|
-
packages = YAML.load_file(__dir__ + '/Packages.yaml')
|
373
853
|
newApps = []
|
374
854
|
target['Services'] ||= []
|
375
|
-
|
376
|
-
|
377
|
-
|
855
|
+
target['Packages'] = target['Apps'].dup
|
856
|
+
prepareDefaultNetwork(target)
|
857
|
+
end
|
858
|
+
|
859
|
+
def prepareDefaultNetwork(target)
|
860
|
+
target['DefaultNetwork'] = {}
|
861
|
+
target['DefaultNetwork']['IP'] = 'dhcp'
|
862
|
+
target['DefaultNetwork']['Interface'] = 'enp1s0'
|
863
|
+
target['DefaultNetwork']['VLAN'] = nil
|
864
|
+
|
865
|
+
if target['Network'].is_a?(Hash)
|
866
|
+
ipaddr = nil
|
867
|
+
if target['Network']['IP']
|
868
|
+
ipaddr = target['Network']['IP']
|
869
|
+
end
|
870
|
+
gateway = nil
|
871
|
+
if target['Network']['Gateway']
|
872
|
+
gateway = target['Network']['Gateway']
|
873
|
+
end
|
874
|
+
dns = nil
|
875
|
+
if target['Network']['DNS']
|
876
|
+
dns = target['Network']['DNS']
|
877
|
+
end
|
878
|
+
interface = nil
|
879
|
+
vlan = nil
|
880
|
+
if gateway.nil? && target['Network']['Interfaces'].is_a?(Hash)
|
881
|
+
cadidates = target['Network']['Interfaces'].select { |name, data| name[0] == 'e' }
|
882
|
+
target['DefaultNetwork']['Interface'] = cadidates.first unless cadidates.empty?
|
883
|
+
target['Network']['Interfaces'].each do |name, data|
|
884
|
+
if data.is_a?(Hash) && data['Gateway']
|
885
|
+
ipaddr = data['IP']
|
886
|
+
gateway = data['Gateway']
|
887
|
+
dns = data['DNS']
|
888
|
+
interface = name
|
889
|
+
if data['Ports']
|
890
|
+
interface = data['Ports'].first
|
891
|
+
end
|
892
|
+
if name.split('.').length == 2
|
893
|
+
interface, vlan = name.split('.')
|
894
|
+
if target['Network']['Interfaces'][interface].is_a?(Hash) &&
|
895
|
+
target['Network']['Interfaces']['Ports']
|
896
|
+
interface = target['Network']['Interfaces']['Ports'].first
|
897
|
+
end
|
898
|
+
end
|
899
|
+
break
|
900
|
+
end
|
901
|
+
end
|
902
|
+
end
|
903
|
+
if ipaddr
|
904
|
+
target['DefaultNetwork']['IP'] = ipaddr
|
905
|
+
addr = IPAddr.new(ipaddr)
|
906
|
+
target['DefaultNetwork']['Subnet'] = [((1 << 32) - 1) << (32 - addr.prefix)].pack('N').bytes.join('.')
|
907
|
+
target['DefaultNetwork']['Broadcast'] = addr.to_range.last.to_s
|
908
|
+
end
|
909
|
+
target['DefaultNetwork']['Gateway'] = gateway if gateway
|
910
|
+
target['DefaultNetwork']['DNS'] = dns if dns
|
911
|
+
target['DefaultNetwork']['Interface'] = interface if interface
|
912
|
+
target['DefaultNetwork']['VLAN'] = vlan if vlan
|
378
913
|
end
|
379
|
-
target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro'])
|
380
914
|
end
|
381
915
|
|
382
916
|
def self.linuxPasswordHash(password)
|