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)
|