ConfigLMM 0.4.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 +34 -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 +7 -1
- data/Plugins/Apps/Authentik/Authentik-Server.container +6 -1
- data/Plugins/Apps/Authentik/Authentik-Worker.container +6 -1
- data/Plugins/Apps/Authentik/Authentik.conf.erb +12 -7
- data/Plugins/Apps/Authentik/Authentik.lmm.rb +226 -61
- data/Plugins/Apps/BookStack/BookStack.conf.erb +0 -3
- data/Plugins/Apps/BookStack/BookStack.container +5 -0
- data/Plugins/Apps/BookStack/BookStack.lmm.rb +14 -3
- 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 +5 -0
- data/Plugins/Apps/Discourse/Discourse.conf.erb +1 -4
- data/Plugins/Apps/Discourse/Discourse.container +4 -0
- data/Plugins/Apps/Discourse/Discourse.lmm.rb +116 -55
- data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +74 -62
- data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +6 -1
- data/Plugins/Apps/ERPNext/ERPNext-Queue.container +5 -0
- data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +5 -0
- data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +6 -1
- data/Plugins/Apps/ERPNext/ERPNext.container +6 -1
- data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +138 -127
- data/Plugins/Apps/GitLab/GitLab.container +6 -0
- data/Plugins/Apps/GitLab/GitLab.lmm.rb +43 -49
- 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 +49 -28
- 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 +60 -53
- 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 +5 -0
- data/Plugins/Apps/Matrix/Matrix.conf.erb +2 -8
- data/Plugins/Apps/Matrix/Matrix.lmm.rb +100 -71
- data/Plugins/Apps/Matrix/Synapse.container +5 -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 +150 -68
- 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 +50 -9
- data/Plugins/Apps/Nginx/conf.d/languages.conf +21 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +25 -20
- data/Plugins/Apps/Nginx/config-lmm/gateway-errors.conf +20 -0
- data/Plugins/Apps/Nginx/config-lmm/proxy.conf +1 -1
- data/Plugins/Apps/Nginx/main.conf.erb +7 -3
- data/Plugins/Apps/Nginx/nginx.conf +2 -2
- data/Plugins/Apps/Nginx/nginx.lmm.rb +99 -81
- data/Plugins/Apps/Nginx/proxy.conf.erb +11 -3
- data/Plugins/Apps/Odoo/Odoo.conf.erb +0 -3
- data/Plugins/Apps/Odoo/Odoo.container +5 -0
- 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 +5 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +0 -3
- data/Plugins/Apps/OpenVidu/OpenVidu.container +5 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +7 -3
- data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +0 -3
- data/Plugins/Apps/OpenVidu/OpenViduCall.container +5 -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 -5
- data/Plugins/Apps/Peppermint/Peppermint.container +5 -0
- 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 +242 -164
- data/Plugins/Apps/PostgreSQL/Connection.rb +97 -0
- data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +184 -148
- 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 +0 -3
- data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +15 -19
- 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 +33 -37
- 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 +54 -42
- data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +9 -6
- data/Plugins/Apps/Vaultwarden/Vaultwarden.container +7 -1
- data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +64 -29
- data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +1 -4
- data/Plugins/Apps/Wiki.js/Wiki.js.container +5 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +31 -37
- 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 +2 -4
- data/Plugins/Apps/gollum/gollum.container +6 -0
- data/Plugins/Apps/gollum/gollum.lmm.rb +51 -50
- 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 +25 -6
- data/Plugins/OS/Linux/Flavours.yaml +13 -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 +533 -187
- data/Plugins/OS/Linux/Packages.yaml +20 -1
- 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 +83 -59
- 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 +9 -6
- data/Plugins/Platforms/Proxmox/Proxmox.lmm.rb +402 -0
- data/Plugins/Platforms/Proxmox/XTerm.rb +321 -0
- data/Plugins/Platforms/libvirt/libvirt.lmm.rb +38 -13
- 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 +70 -68
- data/Plugins/Services/DNS/tonic.lmm.rb +22 -12
- data/lib/ConfigLMM/Framework/plugins/dns.rb +4 -3
- data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +145 -184
- data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +34 -17
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +53 -181
- 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 +12 -6
- data/lib/ConfigLMM/commands/configsCommand.rb +37 -6
- data/lib/ConfigLMM/commands/diff.rb +33 -9
- data/lib/ConfigLMM/context.rb +22 -3
- data/lib/ConfigLMM/io/configList.rb +82 -6
- 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 +2 -1
- data/lib/ConfigLMM/version.rb +2 -1
- data/lib/ConfigLMM.rb +1 -0
- data/{Examples → scripts}/configlmmAuth.sh +7 -5
- metadata +205 -8
|
@@ -1,135 +1,199 @@
|
|
|
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
|
|
57
|
-
end
|
|
58
|
-
target['Hosts'].to_a.each do |ip, entries|
|
|
59
|
-
hostsLines << ip.ljust(16) + entries.join(' ') + "\n"
|
|
60
|
-
end
|
|
61
|
-
updateRemoteFile(ssh, HOSTS_FILE, options, false) do |fileLines|
|
|
62
|
-
fileLines + hostsLines
|
|
78
|
+
hostsLines << getHostsLine(ipAddr, [Addressable::IDNA.to_ascii(target['Domain']), target['Name']]) + "\n"
|
|
63
79
|
end
|
|
64
80
|
end
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
configureNetwork(distroInfo, target, ssh, options)
|
|
68
|
-
if target['Tmpfs']
|
|
69
|
-
self.class.sshExec!(ssh, "sed -i '/ \\/tmp /d' #{FSTAB_FILE}")
|
|
70
|
-
updateRemoteFile(ssh, FSTAB_FILE, options, false) do |fileLines|
|
|
71
|
-
fileLines << "tmpfs /tmp tmpfs nodev,nosuid,size=#{target['Tmpfs']} 0 0\n"
|
|
72
|
-
end
|
|
81
|
+
target['Hosts'].to_a.each do |ip, entries|
|
|
82
|
+
hostsLines << getHostsLine(ip, entries) + "\n"
|
|
73
83
|
end
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)
|
|
81
102
|
end
|
|
103
|
+
fileLines
|
|
82
104
|
end
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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)
|
|
90
117
|
end
|
|
91
|
-
badname = '--badname'
|
|
92
|
-
badname = '--badnames' if distroInfo['Name'] == 'openSUSE Leap'
|
|
93
|
-
self.class.sshExec!(ssh, "useradd #{badname} --create-home --user-group #{shell} #{name}")
|
|
94
118
|
end
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
self.class.sshExec!(ssh, "chown -R #{name}:#{name} #{homeDir}/.ssh")
|
|
119
|
+
params = '--create-home --user-group'
|
|
120
|
+
badname = '--badname'
|
|
121
|
+
badname = '--badnames' if distroInfo['Name'] == 'openSUSE Leap'
|
|
122
|
+
if info['System']
|
|
123
|
+
params += ' --system'
|
|
101
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
|
|
146
|
+
end
|
|
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)
|
|
154
|
+
end
|
|
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)
|
|
102
159
|
end
|
|
103
160
|
end
|
|
104
|
-
self.executeCommands(target['Execute'], ssh)
|
|
105
161
|
end
|
|
106
162
|
if target['Firewall'] && target['Firewall'] != 'no'
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
163
|
+
connection.ensurePackage(FIREWALL_PACKAGE, options)
|
|
164
|
+
connection.ensureServiceAutoStart(FIREWALL_SERVICE, options)
|
|
165
|
+
connection.startService(FIREWALL_SERVICE, options)
|
|
110
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)
|
|
111
175
|
end
|
|
112
176
|
|
|
113
|
-
def convertFlavour(distroInfo, target,
|
|
177
|
+
def convertFlavour(distroInfo, target, connection, options)
|
|
114
178
|
if target['Flavour']
|
|
115
179
|
if target['Flavour'] == PROXMOXVE_NAME
|
|
116
180
|
if distroInfo['Name'] != DEBIAN_NAME
|
|
117
181
|
raise 'Can\'t convert flavour!'
|
|
118
182
|
end
|
|
119
|
-
if
|
|
120
|
-
needInstall =
|
|
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?
|
|
121
185
|
if needInstall
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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)
|
|
125
189
|
end
|
|
126
190
|
else
|
|
127
|
-
|
|
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)
|
|
128
192
|
File.write(options['output'] + 'proxmox-release-bookworm.gpg', HTTP.follow.get('https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg').body)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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)
|
|
133
197
|
end
|
|
134
198
|
target['Network'] = {} unless target['Network'].is_a?(Hash)
|
|
135
199
|
target['Network']['Interfaces'] = {} unless target['Network']['Interfaces'].is_a?(Hash)
|
|
@@ -150,51 +214,57 @@ module ConfigLMM
|
|
|
150
214
|
end
|
|
151
215
|
end
|
|
152
216
|
|
|
153
|
-
def configureNetwork(distroInfo, target,
|
|
217
|
+
def configureNetwork(distroInfo, target, connection, options)
|
|
154
218
|
if target['Network']
|
|
155
219
|
if distroInfo['Name'] == 'openSUSE Leap'
|
|
156
|
-
updateNetworkInterface(target['Network'], 'eth0',
|
|
220
|
+
updateNetworkInterface(target['Network'], 'eth0', connection, options)
|
|
157
221
|
if target['Network']['Interfaces']
|
|
158
222
|
target['Network']['Interfaces'].each do |interface, config|
|
|
159
|
-
updateNetworkInterface(config, interface,
|
|
223
|
+
updateNetworkInterface(config, interface, connection, options)
|
|
160
224
|
end
|
|
161
225
|
end
|
|
162
226
|
if target['Network']['DNS']
|
|
163
227
|
configFile = '/etc/sysconfig/network/config'
|
|
164
228
|
dns = target['Network']['DNS']
|
|
165
229
|
dns = [dns] unless dns.is_a?(Array)
|
|
166
|
-
|
|
230
|
+
connection.exec("sed -i 's|^NETCONFIG_DNS_STATIC_SERVERS=.*|NETCONFIG_DNS_STATIC_SERVERS=\"#{dns.join(' ')}\"|' #{configFile}", false, options)
|
|
167
231
|
end
|
|
168
232
|
if target['Network']['Gateway']
|
|
169
233
|
routesFile = '/etc/sysconfig/network/routes'
|
|
170
|
-
|
|
171
|
-
|
|
234
|
+
connection.exec("sed -i 's|^default |#default |' #{routesFile}", false, options)
|
|
235
|
+
connection.updateFile(routesFile, options) do |fileLines|
|
|
172
236
|
fileLines << "default #{target['Network']['Gateway']}\n"
|
|
173
237
|
end
|
|
174
238
|
end
|
|
175
239
|
elsif distroInfo['Name'] == 'Debian'
|
|
176
|
-
links = self.networkLinks(
|
|
240
|
+
links = self.networkLinks(connection)
|
|
177
241
|
raise 'Didn\'t find network links!' if links.empty?
|
|
178
242
|
linkType = nil
|
|
179
|
-
dnsSearch =
|
|
243
|
+
dnsSearch = connection.exec('cat /etc/resolv.conf | grep search', false, { **options, 'dry' => false }).strip.split(' ').last
|
|
180
244
|
if target['Network'].is_a?(String)
|
|
181
245
|
linkType = target['Network']
|
|
182
246
|
target['Network'] = {}
|
|
183
247
|
end
|
|
248
|
+
links.each do |link|
|
|
249
|
+
target['Network']['Interfaces'][link] = 'manual' unless target['Network']['Interfaces'].key?(link)
|
|
250
|
+
end
|
|
184
251
|
if !target['Network'].key?('Interfaces') ||
|
|
185
252
|
target['Network']['Interfaces'].to_h.empty? ||
|
|
186
253
|
!target['Network']['Interfaces'].key?(links.first)
|
|
187
254
|
target['Network']['Interfaces'] ||= {}
|
|
188
255
|
if !linkType.nil?
|
|
189
256
|
target['Network']['Interfaces'][links.first] = linkType
|
|
190
|
-
|
|
257
|
+
elsif target['Network']['IP']
|
|
191
258
|
target['Network']['Interfaces'][links.first] = {}
|
|
192
259
|
target['Network']['Interfaces'][links.first]['IP'] = target['Network']['IP']
|
|
193
|
-
target['Network']['Interfaces'][links.first]['Gateway'] = target['Network']['Gateway']
|
|
194
|
-
target['Network']['Interfaces'][links.first]['DNS'] = target['Network']['DNS']
|
|
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']
|
|
195
262
|
end
|
|
196
263
|
end
|
|
197
264
|
if target['Network']['Interfaces'].key?('vmbr0')
|
|
265
|
+
if !target['Network']['Interfaces']['vmbr0'].is_a?(Hash)
|
|
266
|
+
target['Network']['Interfaces']['vmbr0'] = { }
|
|
267
|
+
end
|
|
198
268
|
if target['Network']['Interfaces']['vmbr0']['Ports'].nil?
|
|
199
269
|
target['Network']['Interfaces']['vmbr0']['Ports'] = [links.first]
|
|
200
270
|
target['Network']['Interfaces'][links.first] = 'manual'
|
|
@@ -202,9 +272,9 @@ module ConfigLMM
|
|
|
202
272
|
end
|
|
203
273
|
interfacesFile = '/etc/network/interfaces'
|
|
204
274
|
localFile = options['output'] + '/' + SecureRandom.alphanumeric(10)
|
|
205
|
-
|
|
275
|
+
connection.download(interfacesFile, localFile)
|
|
206
276
|
fileLines = File.read(localFile).lines
|
|
207
|
-
if fileLines.index(CONFIGLMM_SECTION_BEGIN).nil?
|
|
277
|
+
if fileLines.index(IO::Local::CONFIGLMM_SECTION_BEGIN).nil?
|
|
208
278
|
lines = []
|
|
209
279
|
iface = false
|
|
210
280
|
fileLines.each do |line|
|
|
@@ -222,26 +292,51 @@ module ConfigLMM
|
|
|
222
292
|
end
|
|
223
293
|
end
|
|
224
294
|
fileWrite(localFile, lines.join(), options[:dry])
|
|
225
|
-
|
|
295
|
+
connection.upload(localFile, interfacesFile)
|
|
226
296
|
end
|
|
227
|
-
|
|
297
|
+
connection.updateFile(interfacesFile, options) do |fileLines|
|
|
228
298
|
target['Network']['Interfaces'].each do |name, data|
|
|
229
299
|
fileLines << "auto #{name}\n"
|
|
300
|
+
data = 'manual' if data.nil?
|
|
230
301
|
if data.is_a?(String)
|
|
231
302
|
fileLines << "iface #{name} inet #{data}\n"
|
|
232
303
|
else
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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"
|
|
314
|
+
else
|
|
315
|
+
fileLines << " bridge-ports none\n"
|
|
316
|
+
end
|
|
238
317
|
fileLines << " bridge-stp off\n"
|
|
239
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"
|
|
324
|
+
end
|
|
325
|
+
end
|
|
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
|
|
240
330
|
end
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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"
|
|
245
340
|
end
|
|
246
341
|
end
|
|
247
342
|
fileLines << "\n"
|
|
@@ -255,14 +350,17 @@ module ConfigLMM
|
|
|
255
350
|
end
|
|
256
351
|
end
|
|
257
352
|
|
|
258
|
-
def updateNetworkInterface(config, interface,
|
|
353
|
+
def updateNetworkInterface(config, interface, connection, options)
|
|
259
354
|
baseFile = '/etc/sysconfig/network/ifcfg-'
|
|
260
355
|
networkFile = baseFile + interface
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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|
|
|
266
364
|
fileLines << "STARTMODE=auto\n"
|
|
267
365
|
fileLines << "ZONE=public\n"
|
|
268
366
|
if config == 'dhcp'
|
|
@@ -271,7 +369,6 @@ module ConfigLMM
|
|
|
271
369
|
fileLines << "BOOTPROTO=static\n"
|
|
272
370
|
fileLines << "\n"
|
|
273
371
|
if config['IP']
|
|
274
|
-
self.class.sshExec!(ssh, "sed -i 's|^IPADDR=|#IPADDR=|' #{networkFile}")
|
|
275
372
|
if config['IP'].is_a?(Array)
|
|
276
373
|
config['IP'].each_with_index do |ip, i|
|
|
277
374
|
c = "_#{i}"
|
|
@@ -287,13 +384,13 @@ module ConfigLMM
|
|
|
287
384
|
end
|
|
288
385
|
end
|
|
289
386
|
|
|
290
|
-
def networkLinks(
|
|
291
|
-
|
|
387
|
+
def networkLinks(connection)
|
|
388
|
+
connection.exec("ls /sys/class/net/").strip.split("\n").select { |name| name.start_with?('enp') }
|
|
292
389
|
end
|
|
293
390
|
|
|
294
|
-
def deployLocal(target, options)
|
|
391
|
+
def deployLocal(connection, target, options)
|
|
295
392
|
deployLocalHostsFile(target, options)
|
|
296
|
-
deployLocalSSHConfig(target, options)
|
|
393
|
+
deployLocalSSHConfig(connection, target, options)
|
|
297
394
|
if target['Sysctl']
|
|
298
395
|
updateLocalFile(SYSCTL_FILE, options) do |fileLines|
|
|
299
396
|
target['Sysctl'].each do |name, value|
|
|
@@ -305,35 +402,34 @@ module ConfigLMM
|
|
|
305
402
|
end
|
|
306
403
|
if target['Users']
|
|
307
404
|
target['Users'].each do |name, info|
|
|
308
|
-
userId =
|
|
405
|
+
userId = connection.exec("id -u #{name} 2>/dev/null", true, { **options, 'dry' => false }).strip
|
|
309
406
|
if userId.empty?
|
|
310
407
|
shell = ''
|
|
311
408
|
if info['Shell']
|
|
312
409
|
shell = "--shell '/usr/bin/#{info['Shell']}'"
|
|
313
410
|
end
|
|
314
|
-
distroInfo = self.class.currentDistroInfo(nil)
|
|
315
411
|
badname = '--badname'
|
|
316
|
-
badname = '--badnames' if
|
|
317
|
-
|
|
412
|
+
badname = '--badnames' if connection.distroName == 'openSUSE Leap'
|
|
413
|
+
connection.exec("useradd #{badname} --create-home --user-group #{shell} #{name}", false, options)
|
|
318
414
|
end
|
|
319
|
-
homeDir =
|
|
415
|
+
homeDir = connection.exec("getent passwd #{name} | cut -d ':' -f 6", false, options).strip
|
|
320
416
|
keyFile = homeDir + "/.ssh/id_ed25519"
|
|
321
|
-
if info['
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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)
|
|
325
421
|
end
|
|
326
422
|
end
|
|
327
423
|
end
|
|
328
424
|
if target['Firewall'] && target['Firewall'] != 'no'
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
425
|
+
connection.ensurePackage(FIREWALL_PACKAGE, options)
|
|
426
|
+
connection.ensureServiceAutoStart(FIREWALL_SERVICE, options)
|
|
427
|
+
connection.startService(FIREWALL_SERVICE, options)
|
|
332
428
|
end
|
|
333
429
|
self.executeCommands(target['Execute'])
|
|
334
430
|
end
|
|
335
431
|
|
|
336
|
-
def executeCommands(commands,
|
|
432
|
+
def executeCommands(commands, connection)
|
|
337
433
|
return unless commands
|
|
338
434
|
|
|
339
435
|
commands.each do |type, data|
|
|
@@ -341,7 +437,7 @@ module ConfigLMM
|
|
|
341
437
|
when 'sh'
|
|
342
438
|
data = [data] unless data.is_a?(Array)
|
|
343
439
|
data.each do |cmd|
|
|
344
|
-
|
|
440
|
+
connection.exec(cmd)
|
|
345
441
|
end
|
|
346
442
|
else
|
|
347
443
|
raise 'Unimplemented!'
|
|
@@ -354,8 +450,68 @@ module ConfigLMM
|
|
|
354
450
|
iso = installationISO(target['Distro'], target['Flavour'], location)
|
|
355
451
|
iso = buildAutoInstallISO(id, iso, target, options)
|
|
356
452
|
if plugins[:Libvirt].createVM(target['Name'], target, target['Location'], iso, activeState)
|
|
357
|
-
|
|
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)
|
|
358
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)
|
|
359
515
|
end
|
|
360
516
|
|
|
361
517
|
def buildHostsFile(id, target, options)
|
|
@@ -366,11 +522,11 @@ module ConfigLMM
|
|
|
366
522
|
hosts += "#<ip-address> <hostname.domain.org> <hostname>\n"
|
|
367
523
|
hosts += "127.0.0.1 localhost\n"
|
|
368
524
|
hosts += "::1 localhost\n\n"
|
|
369
|
-
hosts += CONFIGLMM_SECTION_BEGIN
|
|
525
|
+
hosts += IO::Local::CONFIGLMM_SECTION_BEGIN
|
|
370
526
|
target['Hosts'].each do |ip, entries|
|
|
371
|
-
hosts += ip
|
|
527
|
+
hosts += getHostsLine(ip, entries) + "\n"
|
|
372
528
|
end
|
|
373
|
-
hosts += CONFIGLMM_SECTION_END
|
|
529
|
+
hosts += IO::Local::CONFIGLMM_SECTION_END
|
|
374
530
|
|
|
375
531
|
path = options['output'] + '/' + id
|
|
376
532
|
mkdir(path + '/etc', options[:dry])
|
|
@@ -381,16 +537,16 @@ module ConfigLMM
|
|
|
381
537
|
def buildSSHConfig(id, target, options)
|
|
382
538
|
if !target['SSH']['Config'].empty?
|
|
383
539
|
sshConfig = "\n"
|
|
384
|
-
sshConfig += CONFIGLMM_SECTION_BEGIN
|
|
540
|
+
sshConfig += IO::Local::CONFIGLMM_SECTION_BEGIN
|
|
385
541
|
target['SSH']['Config'].each do |name, info|
|
|
386
542
|
sshConfig += "Host #{name} #{info['HostName']}\n"
|
|
387
|
-
sshConfig += " HostName " + info['HostName'] + "\n" if info['HostName']
|
|
388
|
-
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']
|
|
389
545
|
sshConfig += " User " + info['User'] + "\n" if info['User']
|
|
390
546
|
sshConfig += " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
|
|
391
547
|
sshConfig += "\n"
|
|
392
548
|
end
|
|
393
|
-
sshConfig += CONFIGLMM_SECTION_END
|
|
549
|
+
sshConfig += IO::Local::CONFIGLMM_SECTION_END
|
|
394
550
|
sshConfig += "\n"
|
|
395
551
|
|
|
396
552
|
configPath = options['output'] + '/' + id
|
|
@@ -400,70 +556,182 @@ module ConfigLMM
|
|
|
400
556
|
end
|
|
401
557
|
|
|
402
558
|
def buildAutoInstall(id, target, options)
|
|
403
|
-
|
|
559
|
+
config = prepareAutoInstallConfig(target)
|
|
560
|
+
if config['Flavour'] == PROXMOXVE_NAME
|
|
404
561
|
outputFolder = options['output'] + '/' + id + '/'
|
|
405
562
|
template = ERB.new(File.read(__dir__ + '/Proxmox/answer.toml.erb'))
|
|
406
|
-
renderTemplate(template,
|
|
563
|
+
renderTemplate(template, config, outputFolder + 'answer.toml', options)
|
|
407
564
|
File.write("#{outputFolder}/auto-installer-mode.toml", 'mode = "iso"')
|
|
408
|
-
elsif
|
|
565
|
+
elsif config['Distro'] == SUSE_NAME
|
|
409
566
|
outputFolder = options['output'] + '/' + id + '/'
|
|
410
567
|
template = ERB.new(File.read(__dir__ + '/openSUSE/autoinst.xml.erb'))
|
|
411
|
-
renderTemplate(template,
|
|
412
|
-
elsif
|
|
568
|
+
renderTemplate(template, config, outputFolder + 'autoinst.xml', options)
|
|
569
|
+
elsif config['Distro'] == DEBIAN_NAME
|
|
570
|
+
variables = prepareDebianStorage(config, options)
|
|
413
571
|
outputFolder = options['output'] + '/' + id + '/'
|
|
414
572
|
template = ERB.new(File.read(__dir__ + '/Debian/preseed.cfg.erb'))
|
|
415
|
-
renderTemplate(template,
|
|
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
|
|
416
604
|
end
|
|
605
|
+
if !variables['AutoPartition']
|
|
606
|
+
logger.warn('Specified disk/partition configuration is not implemented! You will have to configure it manually!')
|
|
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(' ')
|
|
417
617
|
end
|
|
418
618
|
|
|
419
619
|
def deployLocalHostsFile(target, options)
|
|
420
620
|
if target['Hosts']
|
|
421
621
|
updateLocalFile(HOSTS_FILE, options) do |hostsLines|
|
|
422
622
|
target['Hosts'].each do |ip, entries|
|
|
423
|
-
hostsLines << ip
|
|
623
|
+
hostsLines << getHostsLine(ip, entries) + "\n"
|
|
424
624
|
end
|
|
425
625
|
hostsLines
|
|
426
626
|
end
|
|
427
627
|
end
|
|
428
628
|
end
|
|
429
629
|
|
|
430
|
-
def deployLocalSSHConfig(target, options)
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
configLines << " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
|
|
439
|
-
end
|
|
440
|
-
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)
|
|
441
638
|
end
|
|
442
639
|
end
|
|
443
640
|
end
|
|
444
641
|
|
|
445
|
-
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)
|
|
446
654
|
url = nil
|
|
447
655
|
flavour = distro unless flavour
|
|
448
656
|
flavourInfo = YAML.load_file(__dir__ + '/Flavours.yaml')[flavour]
|
|
449
657
|
if flavourInfo.nil?
|
|
450
658
|
raise Framework::PluginProcessError.new("#{id}: Unknown Linux Distro: #{flavour}!")
|
|
451
659
|
end
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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)
|
|
464
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)
|
|
731
|
+
else
|
|
732
|
+
raise 'Not implemented!'
|
|
465
733
|
end
|
|
466
|
-
|
|
734
|
+
outputFolder
|
|
467
735
|
end
|
|
468
736
|
|
|
469
737
|
def buildAutoInstallISO(id, iso, target, options)
|
|
@@ -480,34 +748,53 @@ module ConfigLMM
|
|
|
480
748
|
def buildISOAutoYaST(id, iso, target, options)
|
|
481
749
|
outputFolder = options['output'] + '/iso/'
|
|
482
750
|
mkdir(outputFolder, false)
|
|
483
|
-
|
|
751
|
+
local.exec("xorriso -osirrox on -indev #{iso} -extract / #{outputFolder}")
|
|
484
752
|
FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
|
|
485
753
|
copy(options['output'] + '/' + id + '/autoinst.xml', outputFolder, false)
|
|
486
754
|
|
|
487
755
|
cfg = outputFolder + "boot/x86_64/loader/isolinux.cfg"
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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}")
|
|
492
777
|
|
|
493
778
|
patchedIso = File.dirname(iso) + '/patched.iso'
|
|
494
|
-
|
|
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}")
|
|
495
780
|
patchedIso
|
|
496
781
|
end
|
|
497
782
|
|
|
498
783
|
def buildISOPreseed(id, iso, target, options)
|
|
499
784
|
outputFolder = options['output'] + '/iso/'
|
|
500
785
|
mkdir(outputFolder, false)
|
|
501
|
-
|
|
786
|
+
local.exec("xorriso -osirrox on -indev #{iso} -extract / #{outputFolder}", false)
|
|
502
787
|
FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
|
|
503
|
-
copy(options['output'] + '/' + id + '/preseed.cfg', outputFolder, false)
|
|
788
|
+
local.copy(options['output'] + '/' + id + '/preseed.cfg', outputFolder, false)
|
|
504
789
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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"}")
|
|
508
795
|
|
|
509
796
|
patchedIso = File.dirname(iso) + '/patched.iso'
|
|
510
|
-
|
|
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}")
|
|
511
798
|
patchedIso
|
|
512
799
|
end
|
|
513
800
|
|
|
@@ -522,25 +809,31 @@ module ConfigLMM
|
|
|
522
809
|
patchedIso
|
|
523
810
|
end
|
|
524
811
|
|
|
525
|
-
def
|
|
812
|
+
def self.withConnection(connection)
|
|
813
|
+
yield(LinuxConnection.new(connection))
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def prepareConfig(target, context)
|
|
526
817
|
target['SSH'] ||= {}
|
|
527
818
|
target['SSH']['Config'] ||= {}
|
|
528
819
|
target['Users'] ||= {}
|
|
529
820
|
target['HostName'] = target['Name'] unless target['HostName']
|
|
530
821
|
|
|
531
|
-
if
|
|
822
|
+
if target['SecretId'] && context.secrets.load(target['SecretId'], 'ROOT_PASSWORD_HASH')
|
|
532
823
|
target['Users']['root'] ||= {}
|
|
533
|
-
target['Users']['root']['PasswordHash'] =
|
|
534
|
-
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')
|
|
535
826
|
target['Users']['root'] ||= {}
|
|
536
|
-
target['Users']['root']['Password'] =
|
|
537
|
-
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(
|
|
827
|
+
target['Users']['root']['Password'] = context.secrets.load(target['SecretId'], 'ROOT_PASSWORD')
|
|
828
|
+
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(target['Users']['root']['Password'])
|
|
538
829
|
elsif target['Users'].key?('root')
|
|
539
|
-
if !target['Users']['root']
|
|
540
|
-
!target['Users']['root']
|
|
541
|
-
|
|
542
|
-
target['
|
|
543
|
-
|
|
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
|
|
544
837
|
target['Users']['root'].delete('Password')
|
|
545
838
|
end
|
|
546
839
|
end
|
|
@@ -548,7 +841,7 @@ module ConfigLMM
|
|
|
548
841
|
target['Users'].each do |user, info|
|
|
549
842
|
newKeys = []
|
|
550
843
|
info['AuthorizedKeys'].to_a.each do |key|
|
|
551
|
-
if key.start_with?('/') || key.start_with?('~')
|
|
844
|
+
if key.start_with?('/') || key.start_with?('~') || key.start_with?('.') || key.end_with?('.pub')
|
|
552
845
|
newKeys << File.read(File.expand_path(key)).strip
|
|
553
846
|
else
|
|
554
847
|
newKeys << key
|
|
@@ -557,14 +850,67 @@ module ConfigLMM
|
|
|
557
850
|
info['AuthorizedKeys'] = newKeys
|
|
558
851
|
end
|
|
559
852
|
|
|
560
|
-
packages = YAML.load_file(__dir__ + '/Packages.yaml')
|
|
561
853
|
newApps = []
|
|
562
854
|
target['Services'] ||= []
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
|
566
913
|
end
|
|
567
|
-
target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro']) if target['Distro']
|
|
568
914
|
end
|
|
569
915
|
|
|
570
916
|
def self.linuxPasswordHash(password)
|