ConfigLMM 0.2.0 → 0.4.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 +67 -0
- data/Examples/Implemented.mm.yaml +75 -1
- data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +14 -0
- data/Plugins/Apps/Authentik/Authentik-Server.container +19 -0
- data/Plugins/Apps/Authentik/Authentik-Worker.container +18 -0
- data/Plugins/Apps/Authentik/Authentik.conf.erb +42 -0
- data/Plugins/Apps/Authentik/Authentik.lmm.rb +95 -0
- data/Plugins/Apps/BookStack/BookStack.conf.erb +41 -0
- data/Plugins/Apps/BookStack/BookStack.container +15 -0
- data/Plugins/Apps/BookStack/BookStack.lmm.rb +80 -0
- data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +41 -0
- data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +17 -0
- data/Plugins/Apps/Discourse/Discourse.conf.erb +41 -0
- data/Plugins/Apps/Discourse/Discourse.container +17 -0
- data/Plugins/Apps/Discourse/Discourse.lmm.rb +95 -0
- data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +171 -0
- data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +19 -0
- data/Plugins/Apps/ERPNext/ERPNext-Queue.container +17 -0
- data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +17 -0
- data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +19 -0
- data/Plugins/Apps/ERPNext/ERPNext.container +18 -0
- data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +193 -0
- data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
- data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
- data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
- data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
- data/Plugins/Apps/GitLab/GitLab.container +18 -0
- data/Plugins/Apps/GitLab/GitLab.lmm.rb +100 -0
- data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +57 -0
- data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
- data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
- data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
- data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +115 -0
- data/Plugins/Apps/Matrix/Element.container +14 -0
- data/Plugins/Apps/Matrix/Matrix.conf.erb +49 -5
- data/Plugins/Apps/Matrix/Matrix.lmm.rb +86 -1
- data/Plugins/Apps/Matrix/Synapse.container +17 -0
- data/Plugins/Apps/Matrix/config.json +50 -0
- data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
- data/Plugins/Apps/Matrix/log.config +30 -0
- data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +48 -10
- data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +83 -1
- data/Plugins/Apps/Nextcloud/config.php +18 -0
- data/Plugins/Apps/Nginx/conf.d/configlmm.conf +71 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +11 -5
- data/Plugins/Apps/Nginx/config-lmm/proxy.conf +5 -1
- data/Plugins/Apps/Nginx/main.conf.erb +31 -0
- data/Plugins/Apps/Nginx/nginx.conf +3 -68
- data/Plugins/Apps/Nginx/nginx.lmm.rb +83 -22
- data/Plugins/Apps/Nginx/proxy.conf.erb +13 -3
- data/Plugins/Apps/Odoo/Odoo.conf.erb +30 -13
- data/Plugins/Apps/Odoo/Odoo.container +18 -0
- data/Plugins/Apps/Odoo/Odoo.lmm.rb +62 -2
- data/Plugins/Apps/Odoo/odoo.conf +37 -0
- data/Plugins/Apps/OpenVidu/Ingress.container +18 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +34 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.container +16 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +90 -0
- data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +35 -0
- data/Plugins/Apps/OpenVidu/OpenViduCall.container +15 -0
- data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
- data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
- data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +95 -0
- data/Plugins/Apps/Peppermint/Peppermint.conf.erb +60 -0
- data/Plugins/Apps/Peppermint/Peppermint.container +15 -0
- data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +58 -0
- data/Plugins/Apps/Postfix/Postfix.lmm.rb +165 -31
- data/Plugins/Apps/Postfix/smtpd.conf +3 -0
- data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +242 -24
- data/Plugins/Apps/Roundcube/Roundcube.conf.erb +75 -0
- data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +145 -0
- data/Plugins/Apps/SSH/SSH.lmm.rb +51 -0
- data/Plugins/Apps/Tunnel/tunnel.lmm.rb +63 -0
- data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
- data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
- data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
- data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
- data/Plugins/Apps/UVdesk/UVdesk.conf.erb +52 -0
- data/Plugins/Apps/UVdesk/UVdesk.lmm.rb +85 -0
- data/Plugins/Apps/Valkey/Valkey.lmm.rb +34 -1
- data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +35 -18
- data/Plugins/Apps/Vaultwarden/Vaultwarden.container +16 -0
- data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +46 -3
- data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +42 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.container +15 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +61 -0
- data/Plugins/Apps/gollum/gollum.conf.erb +84 -19
- data/Plugins/Apps/gollum/gollum.container +15 -0
- data/Plugins/Apps/gollum/gollum.lmm.rb +48 -11
- data/Plugins/OS/Linux/Debian/preseed.cfg.erb +62 -0
- data/Plugins/OS/Linux/Distributions.yaml +42 -0
- data/Plugins/OS/Linux/Flavours.yaml +11 -0
- data/Plugins/OS/Linux/Linux.lmm.rb +362 -41
- data/Plugins/OS/Linux/Packages.yaml +88 -5
- data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
- data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +137 -0
- data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +15 -0
- data/Plugins/OS/Linux/systemd/systemd.lmm.rb +28 -0
- data/Plugins/OS/Linux/systemd/user-0.slice +9 -0
- data/Plugins/OS/Linux/systemd/user@.service.d/delegate.conf +3 -0
- data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +7 -3
- data/Plugins/Platforms/libvirt/libvirt.lmm.rb +3 -2
- data/Plugins/Services/DNS/PowerDNS.lmm.rb +158 -8
- data/README.md +6 -0
- data/bootstrap.sh +92 -0
- data/lib/ConfigLMM/Framework/plugins/dns.rb +1 -2
- data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +249 -45
- data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +56 -7
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +112 -16
- data/lib/ConfigLMM/cli.rb +3 -1
- data/lib/ConfigLMM/commands/cleanup.rb +1 -0
- data/lib/ConfigLMM/commands/configsCommand.rb +3 -1
- data/lib/ConfigLMM/io/configList.rb +3 -1
- data/lib/ConfigLMM/state.rb +10 -2
- data/lib/ConfigLMM/version.rb +1 -1
- metadata +82 -3
- data/Plugins/Apps/Nginx/main.conf +0 -30
@@ -10,14 +10,17 @@ module ConfigLMM
|
|
10
10
|
|
11
11
|
ISO_LOCATION = '~/.cache/configlmm/images/'
|
12
12
|
HOSTS_FILE = '/etc/hosts'
|
13
|
+
FSTAB_FILE = '/etc/fstab'
|
13
14
|
SSH_CONFIG = '~/.ssh/config'
|
14
|
-
SYSCTL_FILE = '/etc/sysctl.d/
|
15
|
+
SYSCTL_FILE = '/etc/sysctl.d/90-configlmm.conf'
|
16
|
+
FIREWALL_PACKAGE = 'firewalld'
|
17
|
+
FIREWALL_SERVICE = 'firewalld'
|
15
18
|
|
16
19
|
def actionLinuxBuild(id, target, activeState, context, options)
|
17
20
|
prepareConfig(target)
|
18
21
|
buildHostsFile(id, target, options)
|
19
22
|
buildSSHConfig(id, target, options)
|
20
|
-
|
23
|
+
buildAutoInstall(id, target, options)
|
21
24
|
end
|
22
25
|
|
23
26
|
def actionLinuxDeploy(id, target, activeState, context, options)
|
@@ -33,8 +36,7 @@ module ConfigLMM
|
|
33
36
|
raise Framework::PluginProcessError.new("#{id}: Unknown protocol: #{uri.scheme}!")
|
34
37
|
end
|
35
38
|
else
|
36
|
-
|
37
|
-
deployLocalSSHConfig(target, options)
|
39
|
+
deployLocal(target, options)
|
38
40
|
end
|
39
41
|
if target['AlternativeLocation']
|
40
42
|
uri = Addressable::URI.parse(target['AlternativeLocation'])
|
@@ -44,38 +46,316 @@ module ConfigLMM
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def deployOverSSH(locationUri, id, target, activeState, context, options)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
self.class.sshStart(locationUri) do |ssh|
|
50
|
+
if target['Domain'] || target['Hosts']
|
51
|
+
hostsLines = []
|
52
|
+
if target['Domain']
|
51
53
|
envs = self.class.sshExec!(ssh, "env").split("\n")
|
52
54
|
envVars = Hash[envs.map { |vars| vars.split('=', 2) }]
|
53
55
|
ipAddr = envVars['SSH_CONNECTION'].split[-2]
|
54
|
-
hostsLines << ipAddr.ljust(16) + target['Domain'] + ' ' + target['Name'] + "\n"
|
56
|
+
hostsLines << ipAddr.ljust(16) + Addressable::IDNA.to_ascii(target['Domain']) + ' ' + target['Name'] + "\n"
|
55
57
|
end
|
56
|
-
|
57
|
-
target['Hosts'].to_a.each do |ip, entries|
|
58
|
+
target['Hosts'].to_a.each do |ip, entries|
|
58
59
|
hostsLines << ip.ljust(16) + entries.join(' ') + "\n"
|
60
|
+
end
|
61
|
+
updateRemoteFile(ssh, HOSTS_FILE, options, false) do |fileLines|
|
62
|
+
fileLines + hostsLines
|
63
|
+
end
|
64
|
+
end
|
65
|
+
distroInfo = self.class.currentDistroInfo(ssh)
|
66
|
+
convertFlavour(distroInfo, target, ssh, options)
|
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
|
73
|
+
end
|
74
|
+
if target['Sysctl']
|
75
|
+
updateRemoteFile(ssh, SYSCTL_FILE, options, false) do |fileLines|
|
76
|
+
target['Sysctl'].each do |name, value|
|
77
|
+
fileLines << "#{name} = #{value}\n"
|
78
|
+
self.class.sshExec!(ssh, "sysctl #{name}=#{value}")
|
79
|
+
end
|
80
|
+
fileLines
|
81
|
+
end
|
82
|
+
end
|
83
|
+
if target['Users']
|
84
|
+
target['Users'].each do |name, info|
|
85
|
+
userId = ssh.exec!("id -u #{name} 2>/dev/null").strip
|
86
|
+
if userId.empty?
|
87
|
+
shell = ''
|
88
|
+
if info['Shell']
|
89
|
+
shell = "--shell '/usr/bin/#{info['Shell']}'"
|
90
|
+
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
|
+
end
|
95
|
+
homeDir = self.class.sshExec!(ssh, "getent passwd #{name} | cut -d ':' -f 6").strip
|
96
|
+
keyFile = homeDir + "/.ssh/id_ed25519"
|
97
|
+
if info['SSHKey'] && !self.class.remoteFilePresent?(keyFile, ssh)
|
98
|
+
self.class.sshExec!(ssh, "mkdir -p #{homeDir}/.ssh")
|
99
|
+
self.class.sshExec!(ssh, "ssh-keygen -t ed25519 -f #{keyFile} -P ''")
|
100
|
+
self.class.sshExec!(ssh, "chown -R #{name}:#{name} #{homeDir}/.ssh")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
self.executeCommands(target['Execute'], ssh)
|
105
|
+
end
|
106
|
+
if target['Firewall'] && target['Firewall'] != 'no'
|
107
|
+
self.ensurePackage(FIREWALL_PACKAGE, locationUri)
|
108
|
+
self.ensureServiceAutoStart(FIREWALL_SERVICE, locationUri)
|
109
|
+
self.startService(FIREWALL_SERVICE, locationUri)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def convertFlavour(distroInfo, target, ssh, options)
|
114
|
+
if target['Flavour']
|
115
|
+
if target['Flavour'] == PROXMOXVE_NAME
|
116
|
+
if distroInfo['Name'] != DEBIAN_NAME
|
117
|
+
raise 'Can\'t convert flavour!'
|
118
|
+
end
|
119
|
+
if self.class.filePresent?('/etc/apt/sources.list.d/pve-install-repo.list', ssh)
|
120
|
+
needInstall = self.class.exec('dpkg --status proxmox-ve 2>/dev/null | grep Status | grep installed | wc -l', ssh).strip.to_i.zero?
|
121
|
+
if needInstall
|
122
|
+
self.class.exec('DEBIAN_FRONTEND=noninteractive apt install --assume-yes proxmox-ve postfix open-iscsi chrony', ssh)
|
123
|
+
self.class.exec("apt remove --assume-yes os-prober linux-image-amd64 'linux-image-*'", ssh)
|
124
|
+
self.class.exec('update-grub', ssh)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
self.class.exec('echo "deb [arch=amd64] http://download.proxmox.com/debian/pve bookworm pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list', ssh)
|
128
|
+
File.write(options['output'] + 'proxmox-release-bookworm.gpg', HTTP.follow.get('https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg').body)
|
129
|
+
ssh.scp.upload!(options['output'] + 'proxmox-release-bookworm.gpg', '/etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg')
|
130
|
+
self.class.exec('apt update && apt full-upgrade --assume-yes', ssh)
|
131
|
+
self.class.exec('apt install --assume-yes proxmox-default-kernel', ssh)
|
132
|
+
self.class.exec('systemctl reboot', ssh)
|
133
|
+
end
|
134
|
+
target['Network'] = {} unless target['Network'].is_a?(Hash)
|
135
|
+
target['Network']['Interfaces'] = {} unless target['Network']['Interfaces'].is_a?(Hash)
|
136
|
+
if !target['Network']['Interfaces'].key?('vmbr0')
|
137
|
+
if target['Network']['IP']
|
138
|
+
target['Network']['Interfaces']['vmbr0'] = {}
|
139
|
+
target['Network']['Interfaces']['vmbr0']['Type'] = 'Bridge'
|
140
|
+
target['Network']['Interfaces']['vmbr0']['IP'] = target['Network']['IP']
|
141
|
+
target['Network']['Interfaces']['vmbr0']['Gateway'] = target['Network']['Gateway']
|
142
|
+
target['Network']['Interfaces']['vmbr0']['DNS'] = target['Network']['DNS']
|
143
|
+
else
|
144
|
+
target['Network']['Interfaces']['vmbr0'] = 'dhcp'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
else
|
148
|
+
raise 'Unimplemented flavour!'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def configureNetwork(distroInfo, target, ssh, options)
|
154
|
+
if target['Network']
|
155
|
+
if distroInfo['Name'] == 'openSUSE Leap'
|
156
|
+
updateNetworkInterface(target['Network'], 'eth0', ssh, options)
|
157
|
+
if target['Network']['Interfaces']
|
158
|
+
target['Network']['Interfaces'].each do |interface, config|
|
159
|
+
updateNetworkInterface(config, interface, ssh, options)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
if target['Network']['DNS']
|
163
|
+
configFile = '/etc/sysconfig/network/config'
|
164
|
+
dns = target['Network']['DNS']
|
165
|
+
dns = [dns] unless dns.is_a?(Array)
|
166
|
+
self.class.sshExec!(ssh, "sed -i 's|^NETCONFIG_DNS_STATIC_SERVERS=.*|NETCONFIG_DNS_STATIC_SERVERS=\"#{dns.join(' ')}\"|' #{configFile}")
|
167
|
+
end
|
168
|
+
if target['Network']['Gateway']
|
169
|
+
routesFile = '/etc/sysconfig/network/routes'
|
170
|
+
self.class.sshExec!(ssh, "sed -i 's|^default |#default |' #{routesFile}")
|
171
|
+
updateRemoteFile(ssh, routesFile, options) do |fileLines|
|
172
|
+
fileLines << "default #{target['Network']['Gateway']}\n"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
elsif distroInfo['Name'] == 'Debian'
|
176
|
+
links = self.networkLinks(ssh)
|
177
|
+
raise 'Didn\'t find network links!' if links.empty?
|
178
|
+
linkType = nil
|
179
|
+
dnsSearch = self.class.exec('cat /etc/resolv.conf | grep search', ssh).strip.split(' ').last
|
180
|
+
if target['Network'].is_a?(String)
|
181
|
+
linkType = target['Network']
|
182
|
+
target['Network'] = {}
|
183
|
+
end
|
184
|
+
if !target['Network'].key?('Interfaces') ||
|
185
|
+
target['Network']['Interfaces'].to_h.empty? ||
|
186
|
+
!target['Network']['Interfaces'].key?(links.first)
|
187
|
+
target['Network']['Interfaces'] ||= {}
|
188
|
+
if !linkType.nil?
|
189
|
+
target['Network']['Interfaces'][links.first] = linkType
|
190
|
+
else
|
191
|
+
target['Network']['Interfaces'][links.first] = {}
|
192
|
+
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']
|
195
|
+
end
|
196
|
+
end
|
197
|
+
if target['Network']['Interfaces'].key?('vmbr0')
|
198
|
+
if target['Network']['Interfaces']['vmbr0']['Ports'].nil?
|
199
|
+
target['Network']['Interfaces']['vmbr0']['Ports'] = [links.first]
|
200
|
+
target['Network']['Interfaces'][links.first] = 'manual'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
interfacesFile = '/etc/network/interfaces'
|
204
|
+
localFile = options['output'] + '/' + SecureRandom.alphanumeric(10)
|
205
|
+
ssh.scp.download!(interfacesFile, localFile)
|
206
|
+
fileLines = File.read(localFile).lines
|
207
|
+
if fileLines.index(CONFIGLMM_SECTION_BEGIN).nil?
|
208
|
+
lines = []
|
209
|
+
iface = false
|
210
|
+
fileLines.each do |line|
|
211
|
+
if line.start_with?('iface')
|
212
|
+
if line.strip.split(' ')[1].start_with?('enp')
|
213
|
+
iface = true
|
214
|
+
else
|
215
|
+
lines << line
|
216
|
+
end
|
217
|
+
elsif iface && (line.start_with?(' ') || line.start_with?("\t"))
|
218
|
+
# Drop line
|
219
|
+
else
|
220
|
+
iface = false
|
221
|
+
lines << line
|
222
|
+
end
|
223
|
+
end
|
224
|
+
fileWrite(localFile, lines.join(), options[:dry])
|
225
|
+
ssh.scp.upload!(localFile, interfacesFile)
|
226
|
+
end
|
227
|
+
self.updateRemoteFile(ssh, interfacesFile, options) do |fileLines|
|
228
|
+
target['Network']['Interfaces'].each do |name, data|
|
229
|
+
fileLines << "auto #{name}\n"
|
230
|
+
if data.is_a?(String)
|
231
|
+
fileLines << "iface #{name} inet #{data}\n"
|
232
|
+
else
|
233
|
+
fileLines << "iface #{name} inet static\n"
|
234
|
+
fileLines << " address #{data['IP']}\n"
|
235
|
+
fileLines << " gateway #{data['Gateway']}\n"
|
236
|
+
if data['Ports']
|
237
|
+
fileLines << " bridge-ports #{data['Ports'].join(' ')}\n"
|
238
|
+
fileLines << " bridge-stp off\n"
|
239
|
+
fileLines << " bridge-fd 0\n"
|
240
|
+
end
|
241
|
+
fileLines << " # dns-* options are implemented by the resolvconf package, if installed\n"
|
242
|
+
fileLines << " dns-nameservers #{data['DNS']}\n"
|
243
|
+
if dnsSearch
|
244
|
+
fileLines << " dns-search #{dnsSearch}\n"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
fileLines << "\n"
|
248
|
+
end
|
249
|
+
fileLines
|
250
|
+
end
|
251
|
+
else
|
252
|
+
# TODO
|
253
|
+
raise 'Not Unimplemented!'
|
59
254
|
end
|
60
|
-
|
61
|
-
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def updateNetworkInterface(config, interface, ssh, options)
|
259
|
+
baseFile = '/etc/sysconfig/network/ifcfg-'
|
260
|
+
networkFile = baseFile + interface
|
261
|
+
self.class.sshExec!(ssh, "touch #{networkFile}")
|
262
|
+
self.class.sshExec!(ssh, "sed -i \"/^BOOTPROTO=.*/d\" #{networkFile}")
|
263
|
+
self.class.sshExec!(ssh, "sed -i \"/^STARTMODE=.*/d\" #{networkFile}")
|
264
|
+
self.class.sshExec!(ssh, "sed -i \"/^ZONE=.*/d\" #{networkFile}")
|
265
|
+
updateRemoteFile(ssh, networkFile, options, false) do |fileLines|
|
266
|
+
fileLines << "STARTMODE=auto\n"
|
267
|
+
fileLines << "ZONE=public\n"
|
268
|
+
if config == 'dhcp'
|
269
|
+
fileLines << "BOOTPROTO=dhcp\n"
|
270
|
+
else
|
271
|
+
fileLines << "BOOTPROTO=static\n"
|
272
|
+
fileLines << "\n"
|
273
|
+
if config['IP']
|
274
|
+
self.class.sshExec!(ssh, "sed -i 's|^IPADDR=|#IPADDR=|' #{networkFile}")
|
275
|
+
if config['IP'].is_a?(Array)
|
276
|
+
config['IP'].each_with_index do |ip, i|
|
277
|
+
c = "_#{i}"
|
278
|
+
c = '' if i.zero?
|
279
|
+
fileLines << "IPADDR#{c}=#{ip}\n"
|
280
|
+
end
|
281
|
+
else
|
282
|
+
fileLines << "IPADDR=#{config['IP']}\n"
|
283
|
+
end
|
284
|
+
end
|
62
285
|
end
|
286
|
+
fileLines
|
63
287
|
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def networkLinks(ssh)
|
291
|
+
self.class.exec("ls /sys/class/net/", ssh).strip.split("\n").select { |name| name.start_with?('enp') }
|
292
|
+
end
|
293
|
+
|
294
|
+
def deployLocal(target, options)
|
295
|
+
deployLocalHostsFile(target, options)
|
296
|
+
deployLocalSSHConfig(target, options)
|
64
297
|
if target['Sysctl']
|
65
|
-
|
298
|
+
updateLocalFile(SYSCTL_FILE, options) do |fileLines|
|
66
299
|
target['Sysctl'].each do |name, value|
|
67
300
|
fileLines << "#{name} = #{value}\n"
|
301
|
+
`sysctl #{name}=#{value}`
|
68
302
|
end
|
69
303
|
fileLines
|
70
304
|
end
|
71
305
|
end
|
306
|
+
if target['Users']
|
307
|
+
target['Users'].each do |name, info|
|
308
|
+
userId = self.class.exec("id -u #{name} 2>/dev/null", nil, true).strip
|
309
|
+
if userId.empty?
|
310
|
+
shell = ''
|
311
|
+
if info['Shell']
|
312
|
+
shell = "--shell '/usr/bin/#{info['Shell']}'"
|
313
|
+
end
|
314
|
+
distroInfo = self.class.currentDistroInfo(nil)
|
315
|
+
badname = '--badname'
|
316
|
+
badname = '--badnames' if distroInfo['Name'] == 'openSUSE Leap'
|
317
|
+
self.class.exec("useradd #{badname} --create-home --user-group #{shell} #{name}")
|
318
|
+
end
|
319
|
+
homeDir = self.class.exec("getent passwd #{name} | cut -d ':' -f 6").strip
|
320
|
+
keyFile = homeDir + "/.ssh/id_ed25519"
|
321
|
+
if info['SSHKey'] && !self.class.filePresent?(keyFile)
|
322
|
+
self.class.exec("mkdir -p #{homeDir}/.ssh")
|
323
|
+
self.class.exec("ssh-keygen -t ed25519 -f #{keyFile} -P ''")
|
324
|
+
self.class.exec("chown -R #{name}:#{name} #{homeDir}/.ssh")
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
if target['Firewall'] && target['Firewall'] != 'no'
|
329
|
+
self.ensurePackage(FIREWALL_PACKAGE, locationUri)
|
330
|
+
self.ensureServiceAutoStart(FIREWALL_SERVICE, locationUri)
|
331
|
+
self.startService(FIREWALL_SERVICE, locationUri)
|
332
|
+
end
|
333
|
+
self.executeCommands(target['Execute'])
|
334
|
+
end
|
335
|
+
|
336
|
+
def executeCommands(commands, ssh = nil)
|
337
|
+
return unless commands
|
338
|
+
|
339
|
+
commands.each do |type, data|
|
340
|
+
case type
|
341
|
+
when 'sh'
|
342
|
+
data = [data] unless data.is_a?(Array)
|
343
|
+
data.each do |cmd|
|
344
|
+
self.class.exec(cmd, ssh)
|
345
|
+
end
|
346
|
+
else
|
347
|
+
raise 'Unimplemented!'
|
348
|
+
end
|
349
|
+
end
|
72
350
|
end
|
73
351
|
|
74
352
|
def deployOverLibvirt(id, target, activeState, context, options)
|
75
353
|
location = Libvirt.getLocation(target['Location'])
|
76
|
-
iso = installationISO(target['Distro'], location)
|
77
|
-
iso =
|
78
|
-
plugins[:Libvirt].createVM(target['Name'], target, target['Location'], iso, activeState)
|
354
|
+
iso = installationISO(target['Distro'], target['Flavour'], location)
|
355
|
+
iso = buildAutoInstallISO(id, iso, target, options)
|
356
|
+
if plugins[:Libvirt].createVM(target['Name'], target, target['Location'], iso, activeState)
|
357
|
+
prompt.say("Root password: #{target['Users']['root']['Password']}", :color => :magenta) if target['Users']['root'].key?('Password')
|
358
|
+
end
|
79
359
|
end
|
80
360
|
|
81
361
|
def buildHostsFile(id, target, options)
|
@@ -119,11 +399,20 @@ module ConfigLMM
|
|
119
399
|
end
|
120
400
|
end
|
121
401
|
|
122
|
-
def
|
123
|
-
if target['
|
402
|
+
def buildAutoInstall(id, target, options)
|
403
|
+
if target['Flavour'] == PROXMOXVE_NAME
|
404
|
+
outputFolder = options['output'] + '/' + id + '/'
|
405
|
+
template = ERB.new(File.read(__dir__ + '/Proxmox/answer.toml.erb'))
|
406
|
+
renderTemplate(template, target, outputFolder + 'answer.toml', options)
|
407
|
+
File.write("#{outputFolder}/auto-installer-mode.toml", 'mode = "iso"')
|
408
|
+
elsif target['Distro'] == SUSE_NAME
|
124
409
|
outputFolder = options['output'] + '/' + id + '/'
|
125
410
|
template = ERB.new(File.read(__dir__ + '/openSUSE/autoinst.xml.erb'))
|
126
411
|
renderTemplate(template, target, outputFolder + 'autoinst.xml', options)
|
412
|
+
elsif target['Distro'] == DEBIAN_NAME
|
413
|
+
outputFolder = options['output'] + '/' + id + '/'
|
414
|
+
template = ERB.new(File.read(__dir__ + '/Debian/preseed.cfg.erb'))
|
415
|
+
renderTemplate(template, target, outputFolder + 'preseed.cfg', options)
|
127
416
|
end
|
128
417
|
end
|
129
418
|
|
@@ -153,20 +442,14 @@ module ConfigLMM
|
|
153
442
|
end
|
154
443
|
end
|
155
444
|
|
156
|
-
def installationISO(distro, location)
|
445
|
+
def installationISO(distro, flavour, location)
|
157
446
|
url = nil
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
url = 'https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso'
|
163
|
-
else
|
164
|
-
raise Framework::PluginProcessError.new("#{id}: Unimplemented!")
|
165
|
-
end
|
166
|
-
else
|
167
|
-
raise Framework::PluginProcessError.new("#{id}: Unknown Linux Distro: #{distro}!")
|
447
|
+
flavour = distro unless flavour
|
448
|
+
flavourInfo = YAML.load_file(__dir__ + '/Flavours.yaml')[flavour]
|
449
|
+
if flavourInfo.nil?
|
450
|
+
raise Framework::PluginProcessError.new("#{id}: Unknown Linux Distro: #{flavour}!")
|
168
451
|
end
|
169
|
-
|
452
|
+
url = flavourInfo['ISO']
|
170
453
|
filename = File.basename(Addressable::URI.parse(url).path)
|
171
454
|
iso = File.expand_path(ISO_LOCATION + filename)
|
172
455
|
if !File.exist?(iso)
|
@@ -183,21 +466,59 @@ module ConfigLMM
|
|
183
466
|
iso
|
184
467
|
end
|
185
468
|
|
469
|
+
def buildAutoInstallISO(id, iso, target, options)
|
470
|
+
if target['Flavour'] == PROXMOXVE_NAME
|
471
|
+
iso = buildISOAutoProxmox(id, iso, target, options)
|
472
|
+
elsif target['Distro'] == SUSE_NAME
|
473
|
+
iso = buildISOAutoYaST(id, iso, target, options)
|
474
|
+
elsif target['Distro'] == DEBIAN_NAME
|
475
|
+
iso = buildISOPreseed(id, iso, target, options)
|
476
|
+
end
|
477
|
+
iso
|
478
|
+
end
|
479
|
+
|
186
480
|
def buildISOAutoYaST(id, iso, target, options)
|
187
481
|
outputFolder = options['output'] + '/iso/'
|
188
482
|
mkdir(outputFolder, false)
|
189
|
-
|
483
|
+
self.class.exec("xorriso -osirrox on -indev #{iso} -extract / #{outputFolder}")
|
190
484
|
FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
|
191
485
|
copy(options['output'] + '/' + id + '/autoinst.xml', outputFolder, false)
|
192
486
|
|
193
487
|
cfg = outputFolder + "boot/x86_64/loader/isolinux.cfg"
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
488
|
+
self.class.exec("sed -i 's|default harddisk|default linux|' #{cfg}")
|
489
|
+
self.class.exec("sed -i 's|append initrd=initrd splash=silent showopts|append initrd=initrd splash=silent autoyast=device://sr0/autoinst.xml|' #{cfg}")
|
490
|
+
self.class.exec("sed -i 's|prompt 1|prompt 0|' #{cfg}")
|
491
|
+
self.class.exec("sed -i 's|timeout 600|timeout 1|' #{cfg}")
|
492
|
+
|
493
|
+
patchedIso = File.dirname(iso) + '/patched.iso'
|
494
|
+
self.class.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
|
+
patchedIso
|
496
|
+
end
|
497
|
+
|
498
|
+
def buildISOPreseed(id, iso, target, options)
|
499
|
+
outputFolder = options['output'] + '/iso/'
|
500
|
+
mkdir(outputFolder, false)
|
501
|
+
self.class.exec("xorriso -osirrox on -indev #{iso} -extract / #{outputFolder}")
|
502
|
+
FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
|
503
|
+
copy(options['output'] + '/' + id + '/preseed.cfg', outputFolder, false)
|
504
|
+
|
505
|
+
self.class.exec("sed -i 's|vga=788 --- quiet|auto=true file=/cdrom/preseed.cfg vga=788 --- quiet|' #{outputFolder + "boot/grub/grub.cfg"}")
|
506
|
+
self.class.exec("sed -i 's|--- quiet|file=/cdrom/preseed.cfg --- quiet|' #{outputFolder + "isolinux/adgtk.cfg"}")
|
507
|
+
self.class.exec("sed -i 's|default .*|default autogui|' #{outputFolder + "isolinux/isolinux.cfg"}")
|
508
|
+
|
509
|
+
patchedIso = File.dirname(iso) + '/patched.iso'
|
510
|
+
self.class.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 -o #{patchedIso} #{outputFolder}")
|
511
|
+
patchedIso
|
512
|
+
end
|
198
513
|
|
514
|
+
def buildISOAutoProxmox(id, iso, target, options)
|
515
|
+
outputFolder = options['output'] + '/iso/'
|
199
516
|
patchedIso = File.dirname(iso) + '/patched.iso'
|
200
|
-
|
517
|
+
|
518
|
+
copy(iso, patchedIso, false)
|
519
|
+
|
520
|
+
self.class.exec("xorriso -boot_image any keep -dev #{patchedIso} -map #{options['output'] + '/' + id + '/auto-installer-mode.toml'} /auto-installer-mode.toml")
|
521
|
+
self.class.exec("xorriso -boot_image any keep -dev #{patchedIso} -map #{options['output'] + '/' + id + '/answer.toml'} /answer.toml")
|
201
522
|
patchedIso
|
202
523
|
end
|
203
524
|
|
@@ -212,13 +533,13 @@ module ConfigLMM
|
|
212
533
|
target['Users']['root']['PasswordHash'] = ENV['LINUX_ROOT_PASSWORD_HASH']
|
213
534
|
elsif ENV['LINUX_ROOT_PASSWORD']
|
214
535
|
target['Users']['root'] ||= {}
|
536
|
+
target['Users']['root']['Password'] = ENV['LINUX_ROOT_PASSWORD']
|
215
537
|
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(ENV['LINUX_ROOT_PASSWORD'])
|
216
538
|
elsif target['Users'].key?('root')
|
217
539
|
if !target['Users']['root']['Password'] &&
|
218
540
|
!target['Users']['root']['PasswordHash']
|
219
|
-
|
220
|
-
|
221
|
-
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(rootPassword)
|
541
|
+
target['Users']['root']['Password'] = SecureRandom.urlsafe_base64(12)
|
542
|
+
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(target['Users']['root']['Password'])
|
222
543
|
elsif target['Users']['root']['Password'] == 'no'
|
223
544
|
target['Users']['root'].delete('Password')
|
224
545
|
end
|
@@ -243,7 +564,7 @@ module ConfigLMM
|
|
243
564
|
target['Services'] << 'sshd'
|
244
565
|
target['Services'].uniq!
|
245
566
|
end
|
246
|
-
target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro'])
|
567
|
+
target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro']) if target['Distro']
|
247
568
|
end
|
248
569
|
|
249
570
|
def self.linuxPasswordHash(password)
|
@@ -1,13 +1,96 @@
|
|
1
1
|
Arch Linux:
|
2
|
-
|
3
|
-
|
2
|
+
Cassandra: AUR|cassandra
|
3
|
+
CertBotNginx:
|
4
|
+
- certbot-nginx
|
5
|
+
- certbot-dns-rfc2136
|
6
|
+
CyrusSASL: cyrus-sasl
|
4
7
|
Dovecot: dovecot
|
8
|
+
firewalld: firewalld
|
9
|
+
MariaDB: mariadb
|
10
|
+
Nextcloud: nextcloud
|
11
|
+
nginx: nginx
|
12
|
+
PHP-FPM: php-fpm
|
13
|
+
php-pecl: php-pecl
|
14
|
+
Podman: podman
|
5
15
|
PostgreSQL: postgresql
|
16
|
+
Postfix: postfix
|
17
|
+
PowerDNS: powerdns
|
18
|
+
Roundcube: roundcubemail
|
19
|
+
socat: socat
|
20
|
+
sshd: openssh
|
6
21
|
Valkey: redis
|
22
|
+
WireGuard: wireguard-tools
|
23
|
+
Yarn: yarn
|
7
24
|
|
8
25
|
openSUSE Leap:
|
9
|
-
|
10
|
-
|
26
|
+
Cassandra: server:database|cassandra
|
27
|
+
CertBotNginx:
|
28
|
+
- python3-certbot-nginx
|
29
|
+
- certbot-systemd-timer
|
30
|
+
- python3-certbot-dns-rfc2136
|
31
|
+
CyrusSASL: cyrus-sasl-plain
|
11
32
|
Dovecot: dovecot
|
12
|
-
|
33
|
+
firewalld: firewalld
|
34
|
+
MariaDB: mariadb
|
35
|
+
Nextcloud: server:php:applications|nextcloud
|
36
|
+
nginx: nginx
|
37
|
+
PHP-FPM:
|
38
|
+
- php8-devel
|
39
|
+
- php8-mbstring
|
40
|
+
- php8-fpm
|
41
|
+
- php8-redis
|
42
|
+
- php8-pgsql
|
43
|
+
- php8-mysql
|
44
|
+
php-pecl: php8-pecl
|
45
|
+
Podman: podman
|
46
|
+
PostgreSQL:
|
47
|
+
- postgresql-server
|
48
|
+
- postgresql-contrib
|
49
|
+
Postfix: postfix
|
50
|
+
PowerDNS:
|
51
|
+
- pdns
|
52
|
+
- pdns-backend-geoip
|
53
|
+
- pdns-backend-sqlite3
|
54
|
+
- pdns-backend-postgresql
|
55
|
+
Roundcube: roundcubemail
|
56
|
+
socat: socat
|
57
|
+
sshd: openssh
|
58
|
+
Valkey: redis
|
59
|
+
WireGuard: wireguard-tools
|
60
|
+
Yarn: yarn
|
61
|
+
|
62
|
+
Debian:
|
63
|
+
Cassandra: https://debian.cassandra.apache.org 41x main|cassandra
|
64
|
+
CertBotNginx:
|
65
|
+
- certbot
|
66
|
+
- python3-certbot-dns-rfc2136
|
67
|
+
CyrusSASL: libsasl2-modules
|
68
|
+
Dovecot:
|
69
|
+
- dovecot-imapd
|
70
|
+
- dovecot-lmtpd
|
71
|
+
- dovecot-submissiond
|
72
|
+
firewalld: firewalld
|
73
|
+
MariaDB: mariadb-server
|
74
|
+
Nextcloud:
|
75
|
+
nginx: nginx
|
76
|
+
PHP-FPM:
|
77
|
+
- php-mbstring
|
78
|
+
- php-fpm
|
79
|
+
- php-redis
|
80
|
+
- php-pgsql
|
81
|
+
- php-mysql
|
82
|
+
php-pecl:
|
83
|
+
Podman: podman
|
84
|
+
PostgreSQL: postgresql
|
85
|
+
Postfix: postfix-lmdb
|
86
|
+
PowerDNS:
|
87
|
+
- pdns-server
|
88
|
+
- pdns-backend-geoip
|
89
|
+
- pdns-backend-sqlite3
|
90
|
+
- pdns-backend-pgsql
|
91
|
+
Roundcube: roundcube-pgsql
|
92
|
+
socat: socat
|
93
|
+
sshd: openssh-server
|
13
94
|
Valkey: redis
|
95
|
+
WireGuard: wireguard
|
96
|
+
Yarn: yarnpkg
|
@@ -0,0 +1,30 @@
|
|
1
|
+
[global]
|
2
|
+
keyboard = "en-us"
|
3
|
+
country = "us"
|
4
|
+
fqdn = "<%= Addressable::IDNA.to_ascii(config['Domain']) %>"
|
5
|
+
mailto = "<%= config['EMail'] %>"
|
6
|
+
timezone = "UTC"
|
7
|
+
root_password = "<%= config['Users'].to_h['root'].to_h['Password'] %>"
|
8
|
+
<% if !config['Users'].to_h['root'].to_h['AuthorizedKeys'].to_a.empty? %>
|
9
|
+
root_ssh_keys = [
|
10
|
+
<% config['Users']['root']['AuthorizedKeys'].each do |entry| %>
|
11
|
+
"<%= entry %>"
|
12
|
+
<% end %>
|
13
|
+
]
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
[network]
|
17
|
+
<% if config['Network'].is_a?(Hash) %>
|
18
|
+
source = "from-answer"
|
19
|
+
cidr = "<%= config['Network']['IP'] %>"
|
20
|
+
dns = "<%= config['Network']['DNS'] %>"
|
21
|
+
gateway = "<%= config['Network']['Gateway'] %>"
|
22
|
+
filter.IFINDEX = "2"
|
23
|
+
<% else %>
|
24
|
+
source = "from-dhcp"
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
[disk-setup]
|
28
|
+
filesystem = "btrfs"
|
29
|
+
btrfs.raid = "raid1"
|
30
|
+
disk_list = ["vda"]
|