ConfigLMM 0.3.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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +14 -0
  4. data/Plugins/Apps/Authentik/Authentik-Server.container +1 -0
  5. data/Plugins/Apps/Authentik/Authentik-Worker.container +1 -0
  6. data/Plugins/Apps/Authentik/Authentik.conf.erb +7 -0
  7. data/Plugins/Apps/Authentik/Authentik.lmm.rb +24 -2
  8. data/Plugins/Apps/BookStack/BookStack.conf.erb +41 -0
  9. data/Plugins/Apps/BookStack/BookStack.container +15 -0
  10. data/Plugins/Apps/BookStack/BookStack.lmm.rb +80 -0
  11. data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +17 -0
  12. data/Plugins/Apps/Discourse/Discourse.conf.erb +41 -0
  13. data/Plugins/Apps/Discourse/Discourse.container +17 -0
  14. data/Plugins/Apps/Discourse/Discourse.lmm.rb +95 -0
  15. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +25 -2
  16. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +19 -0
  17. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +17 -0
  18. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +17 -0
  19. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +19 -0
  20. data/Plugins/Apps/ERPNext/ERPNext.container +18 -0
  21. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +193 -0
  22. data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
  23. data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
  24. data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
  25. data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
  26. data/Plugins/Apps/GitLab/GitLab.container +3 -2
  27. data/Plugins/Apps/GitLab/GitLab.lmm.rb +37 -12
  28. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +57 -0
  29. data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
  30. data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
  31. data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
  32. data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
  33. data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
  34. data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
  35. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +115 -0
  36. data/Plugins/Apps/Matrix/Element.container +14 -0
  37. data/Plugins/Apps/Matrix/Matrix.conf.erb +49 -5
  38. data/Plugins/Apps/Matrix/Matrix.lmm.rb +86 -1
  39. data/Plugins/Apps/Matrix/Synapse.container +17 -0
  40. data/Plugins/Apps/Matrix/config.json +50 -0
  41. data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
  42. data/Plugins/Apps/Matrix/log.config +30 -0
  43. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +70 -45
  44. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +9 -0
  45. data/Plugins/Apps/Nginx/config-lmm/errors.conf +10 -4
  46. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +5 -1
  47. data/Plugins/Apps/Nginx/nginx.lmm.rb +15 -11
  48. data/Plugins/Apps/Nginx/proxy.conf.erb +13 -3
  49. data/Plugins/Apps/Odoo/Odoo.container +2 -1
  50. data/Plugins/Apps/Odoo/Odoo.lmm.rb +1 -1
  51. data/Plugins/Apps/OpenVidu/Ingress.container +18 -0
  52. data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +34 -0
  53. data/Plugins/Apps/OpenVidu/OpenVidu.container +16 -0
  54. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +90 -0
  55. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +35 -0
  56. data/Plugins/Apps/OpenVidu/OpenViduCall.container +15 -0
  57. data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
  58. data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
  59. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +0 -4
  60. data/Plugins/Apps/Peppermint/Peppermint.container +2 -1
  61. data/Plugins/Apps/Postfix/Postfix.lmm.rb +32 -6
  62. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +80 -11
  63. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +75 -0
  64. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +145 -0
  65. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +63 -0
  66. data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
  67. data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
  68. data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
  69. data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
  70. data/Plugins/Apps/Valkey/Valkey.lmm.rb +32 -0
  71. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +4 -0
  72. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +42 -0
  73. data/Plugins/Apps/Wiki.js/Wiki.js.container +15 -0
  74. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +61 -0
  75. data/Plugins/Apps/gollum/gollum.conf.erb +39 -1
  76. data/Plugins/Apps/gollum/gollum.container +4 -1
  77. data/Plugins/Apps/gollum/gollum.lmm.rb +11 -3
  78. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +62 -0
  79. data/Plugins/OS/Linux/Distributions.yaml +32 -0
  80. data/Plugins/OS/Linux/Flavours.yaml +11 -0
  81. data/Plugins/OS/Linux/Linux.lmm.rb +255 -67
  82. data/Plugins/OS/Linux/Packages.yaml +47 -2
  83. data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
  84. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +33 -4
  85. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +1 -1
  86. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +3 -2
  87. data/Plugins/Services/DNS/PowerDNS.lmm.rb +95 -8
  88. data/bootstrap.sh +41 -3
  89. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +146 -64
  90. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +34 -3
  91. data/lib/ConfigLMM/Framework/plugins/plugin.rb +62 -6
  92. data/lib/ConfigLMM/cli.rb +3 -1
  93. data/lib/ConfigLMM/commands/cleanup.rb +1 -0
  94. data/lib/ConfigLMM/commands/configsCommand.rb +3 -1
  95. data/lib/ConfigLMM/io/configList.rb +3 -1
  96. data/lib/ConfigLMM/state.rb +10 -2
  97. data/lib/ConfigLMM/version.rb +1 -1
  98. metadata +54 -3
  99. data/Plugins/Apps/GitLab/GitLab.conf.erb +0 -26
@@ -2,15 +2,47 @@
2
2
  opensuse-leap:
3
3
  Name: openSUSE Leap
4
4
  InstallPackage: zypper install --no-confirm
5
+ RemovePackage: zypper remove --no-confirm
5
6
  AutoStartService: systemctl enable
6
7
  StartService: systemctl start
8
+ RestartService: systemctl restart
9
+ ReloadService: systemctl reload
10
+ StopService: systemctl stop
11
+ DisableService: systemctl disable
12
+ ReloadServiceManager: systemctl daemon-reload
7
13
  CreateServiceUser: useradd --system --shell /usr/sbin/nologin --user-group
8
14
  ModifyUser: usermod
15
+ DeleteUser: userdel --remove --force
16
+ DeleteGroup: groupdel
9
17
 
10
18
  arch:
11
19
  Name: Arch Linux
12
20
  InstallPackage: pacman -S --noconfirm --needed
21
+ RemovePackage: pacman --remove --noconfirm
13
22
  AutoStartService: systemctl enable
14
23
  StartService: systemctl start
24
+ RestartService: systemctl restart
25
+ ReloadService: systemctl reload
26
+ StopService: systemctl stop
27
+ DisableService: systemctl disable
28
+ ReloadServiceManager: systemctl daemon-reload
15
29
  CreateServiceUser: useradd --system --shell /usr/sbin/nologin --user-group
16
30
  ModifyUser: usermod
31
+ DeleteUser: userdel --remove --force
32
+ DeleteGroup: groupdel
33
+
34
+ debian:
35
+ Name: Debian
36
+ InstallPackage: DEBIAN_FRONTEND=noninteractive apt-get install --assume-yes
37
+ RemovePackage: apt-get remove --assume-yes
38
+ AutoStartService: systemctl enable
39
+ StartService: systemctl start
40
+ RestartService: systemctl restart
41
+ ReloadService: systemctl reload
42
+ StopService: systemctl stop
43
+ DisableService: systemctl disable
44
+ ReloadServiceManager: systemctl daemon-reload
45
+ CreateServiceUser: useradd --system --shell /usr/sbin/nologin --user-group
46
+ ModifyUser: usermod
47
+ DeleteUser: userdel --remove --force
48
+ DeleteGroup: groupdel
@@ -0,0 +1,11 @@
1
+ openSUSE Leap:
2
+ ISO: https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso
3
+
4
+ Arch Linux:
5
+ ISO: https://mirror.rackspace.com/archlinux/iso/2024.10.01/archlinux-x86_64.iso
6
+
7
+ Debian:
8
+ ISO: https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.7.0-amd64-netinst.iso
9
+
10
+ Proxmox VE:
11
+ ISO: https://enterprise.proxmox.com/iso/proxmox-ve_8.2-2.iso
@@ -20,7 +20,7 @@ module ConfigLMM
20
20
  prepareConfig(target)
21
21
  buildHostsFile(id, target, options)
22
22
  buildSSHConfig(id, target, options)
23
- buildAutoYaST(id, target, options)
23
+ buildAutoInstall(id, target, options)
24
24
  end
25
25
 
26
26
  def actionLinuxDeploy(id, target, activeState, context, options)
@@ -53,7 +53,7 @@ module ConfigLMM
53
53
  envs = self.class.sshExec!(ssh, "env").split("\n")
54
54
  envVars = Hash[envs.map { |vars| vars.split('=', 2) }]
55
55
  ipAddr = envVars['SSH_CONNECTION'].split[-2]
56
- hostsLines << ipAddr.ljust(16) + target['Domain'] + ' ' + target['Name'] + "\n"
56
+ hostsLines << ipAddr.ljust(16) + Addressable::IDNA.to_ascii(target['Domain']) + ' ' + target['Name'] + "\n"
57
57
  end
58
58
  target['Hosts'].to_a.each do |ip, entries|
59
59
  hostsLines << ip.ljust(16) + entries.join(' ') + "\n"
@@ -63,44 +63,8 @@ module ConfigLMM
63
63
  end
64
64
  end
65
65
  distroInfo = self.class.currentDistroInfo(ssh)
66
- if target['Network']
67
- if distroInfo['Name'] == 'openSUSE Leap'
68
- networkFile = '/etc/sysconfig/network/ifcfg-eth0'
69
- if target['Network'] == 'dhcp'
70
- self.class.sshExec!(ssh, "sed -i \"s|^BOOTPROTO=.*|BOOTPROTO='dhcp'|\" #{networkFile}")
71
- else
72
- self.class.sshExec!(ssh, "sed -i \"s|^BOOTPROTO=.*|BOOTPROTO='static'|\" #{networkFile}")
73
- updateRemoteFile(ssh, networkFile, options, false) do |fileLines|
74
- fileLines << "\n"
75
- if target['Network']['IP']
76
- if target['Network']['IP'].is_a?(Array)
77
- # TODO
78
- else
79
- self.class.sshExec!(ssh, "sed -i 's|^IPADDR=|#IPADDR=|' #{networkFile}")
80
- fileLines << "IPADDR=#{target['Network']['IP']}\n"
81
- end
82
- end
83
- fileLines
84
- end
85
- if target['Network']['DNS']
86
- configFile = '/etc/sysconfig/network/config'
87
- dns = target['Network']['DNS']
88
- dns = [dns] unless dns.is_a?(Array)
89
- self.class.sshExec!(ssh, "sed -i 's|^NETCONFIG_DNS_STATIC_SERVERS=.*|NETCONFIG_DNS_STATIC_SERVERS=\"#{dns.join(' ')}\"|' #{configFile}")
90
- end
91
- if target['Network']['Gateway']
92
- routesFile = '/etc/sysconfig/network/routes'
93
- self.class.sshExec!(ssh, "sed -i 's|^default |#default |' #{routesFile}")
94
- updateRemoteFile(ssh, routesFile, options) do |fileLines|
95
- fileLines << "default #{target['Network']['Gateway']}\n"
96
- end
97
- end
98
- end
99
- else
100
- # TODO
101
- raise 'Not Unimplemented!'
102
- end
103
- end
66
+ convertFlavour(distroInfo, target, ssh, options)
67
+ configureNetwork(distroInfo, target, ssh, options)
104
68
  if target['Tmpfs']
105
69
  self.class.sshExec!(ssh, "sed -i '/ \\/tmp /d' #{FSTAB_FILE}")
106
70
  updateRemoteFile(ssh, FSTAB_FILE, options, false) do |fileLines|
@@ -146,6 +110,187 @@ module ConfigLMM
146
110
  end
147
111
  end
148
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!'
254
+ end
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
285
+ end
286
+ fileLines
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
+
149
294
  def deployLocal(target, options)
150
295
  deployLocalHostsFile(target, options)
151
296
  deployLocalSSHConfig(target, options)
@@ -206,9 +351,11 @@ module ConfigLMM
206
351
 
207
352
  def deployOverLibvirt(id, target, activeState, context, options)
208
353
  location = Libvirt.getLocation(target['Location'])
209
- iso = installationISO(target['Distro'], location)
210
- iso = buildISOAutoYaST(id, iso, target, options) if target['Distro'] == SUSE_NAME
211
- 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
212
359
  end
213
360
 
214
361
  def buildHostsFile(id, target, options)
@@ -252,11 +399,20 @@ module ConfigLMM
252
399
  end
253
400
  end
254
401
 
255
- def buildAutoYaST(id, target, options)
256
- if target['Distro'] == SUSE_NAME
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
257
409
  outputFolder = options['output'] + '/' + id + '/'
258
410
  template = ERB.new(File.read(__dir__ + '/openSUSE/autoinst.xml.erb'))
259
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)
260
416
  end
261
417
  end
262
418
 
@@ -286,20 +442,14 @@ module ConfigLMM
286
442
  end
287
443
  end
288
444
 
289
- def installationISO(distro, location)
445
+ def installationISO(distro, flavour, location)
290
446
  url = nil
291
- case distro
292
- when SUSE_NAME
293
- if location.empty?
294
- # TODO automatically fetch latest version from website
295
- url = 'https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso'
296
- else
297
- raise Framework::PluginProcessError.new("#{id}: Unimplemented!")
298
- end
299
- else
300
- 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}!")
301
451
  end
302
-
452
+ url = flavourInfo['ISO']
303
453
  filename = File.basename(Addressable::URI.parse(url).path)
304
454
  iso = File.expand_path(ISO_LOCATION + filename)
305
455
  if !File.exist?(iso)
@@ -316,21 +466,59 @@ module ConfigLMM
316
466
  iso
317
467
  end
318
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
+
319
480
  def buildISOAutoYaST(id, iso, target, options)
320
481
  outputFolder = options['output'] + '/iso/'
321
482
  mkdir(outputFolder, false)
322
- `xorriso -osirrox on -indev #{iso} -extract / #{outputFolder} 2>&1 >/dev/null`
483
+ self.class.exec("xorriso -osirrox on -indev #{iso} -extract / #{outputFolder}")
323
484
  FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
324
485
  copy(options['output'] + '/' + id + '/autoinst.xml', outputFolder, false)
325
486
 
326
487
  cfg = outputFolder + "boot/x86_64/loader/isolinux.cfg"
327
- `sed -i 's|default harddisk|default linux|' #{cfg}`
328
- `sed -i 's|append initrd=initrd splash=silent showopts|append initrd=initrd splash=silent autoyast=device://sr0/autoinst.xml|' #{cfg}`
329
- `sed -i 's|prompt 1|prompt 0|' #{cfg}`
330
- `sed -i 's|timeout 600|timeout 1|' #{cfg}`
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}")
331
492
 
332
493
  patchedIso = File.dirname(iso) + '/patched.iso'
333
- `xorriso -as mkisofs -no-emul-boot -boot-load-size 4 -boot-info-table -iso-level 4 -b boot/x86_64/loader/isolinux.bin -c boot/x86_64/loader/boot.cat -eltorito-alt-boot -e boot/x86_64/efi -no-emul-boot -o #{patchedIso} #{outputFolder} 2>&1 >/dev/null`
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
513
+
514
+ def buildISOAutoProxmox(id, iso, target, options)
515
+ outputFolder = options['output'] + '/iso/'
516
+ patchedIso = File.dirname(iso) + '/patched.iso'
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")
334
522
  patchedIso
335
523
  end
336
524
 
@@ -345,13 +533,13 @@ module ConfigLMM
345
533
  target['Users']['root']['PasswordHash'] = ENV['LINUX_ROOT_PASSWORD_HASH']
346
534
  elsif ENV['LINUX_ROOT_PASSWORD']
347
535
  target['Users']['root'] ||= {}
536
+ target['Users']['root']['Password'] = ENV['LINUX_ROOT_PASSWORD']
348
537
  target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(ENV['LINUX_ROOT_PASSWORD'])
349
538
  elsif target['Users'].key?('root')
350
539
  if !target['Users']['root']['Password'] &&
351
540
  !target['Users']['root']['PasswordHash']
352
- rootPassword = SecureRandom.urlsafe_base64(12)
353
- prompt.say("Root password: #{rootPassword}", :color => :magenta)
354
- 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'])
355
543
  elsif target['Users']['root']['Password'] == 'no'
356
544
  target['Users']['root'].delete('Password')
357
545
  end
@@ -376,7 +564,7 @@ module ConfigLMM
376
564
  target['Services'] << 'sshd'
377
565
  target['Services'].uniq!
378
566
  end
379
- target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro'])
567
+ target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro']) if target['Distro']
380
568
  end
381
569
 
382
570
  def self.linuxPasswordHash(password)
@@ -6,6 +6,7 @@ Arch Linux:
6
6
  CyrusSASL: cyrus-sasl
7
7
  Dovecot: dovecot
8
8
  firewalld: firewalld
9
+ MariaDB: mariadb
9
10
  Nextcloud: nextcloud
10
11
  nginx: nginx
11
12
  PHP-FPM: php-fpm
@@ -14,13 +15,15 @@ Arch Linux:
14
15
  PostgreSQL: postgresql
15
16
  Postfix: postfix
16
17
  PowerDNS: powerdns
18
+ Roundcube: roundcubemail
19
+ socat: socat
17
20
  sshd: openssh
18
21
  Valkey: redis
19
22
  WireGuard: wireguard-tools
20
23
  Yarn: yarn
21
24
 
22
25
  openSUSE Leap:
23
- Cassandra: home:davispuh:branches:server:database|cassandra
26
+ Cassandra: server:database|cassandra
24
27
  CertBotNginx:
25
28
  - python3-certbot-nginx
26
29
  - certbot-systemd-timer
@@ -28,6 +31,7 @@ openSUSE Leap:
28
31
  CyrusSASL: cyrus-sasl-plain
29
32
  Dovecot: dovecot
30
33
  firewalld: firewalld
34
+ MariaDB: mariadb
31
35
  Nextcloud: server:php:applications|nextcloud
32
36
  nginx: nginx
33
37
  PHP-FPM:
@@ -39,13 +43,54 @@ openSUSE Leap:
39
43
  - php8-mysql
40
44
  php-pecl: php8-pecl
41
45
  Podman: podman
42
- PostgreSQL: postgresql-server
46
+ PostgreSQL:
47
+ - postgresql-server
48
+ - postgresql-contrib
43
49
  Postfix: postfix
44
50
  PowerDNS:
51
+ - pdns
45
52
  - pdns-backend-geoip
46
53
  - pdns-backend-sqlite3
47
54
  - pdns-backend-postgresql
55
+ Roundcube: roundcubemail
56
+ socat: socat
48
57
  sshd: openssh
49
58
  Valkey: redis
50
59
  WireGuard: wireguard-tools
51
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
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"]
@@ -17,9 +17,12 @@ module ConfigLMM
17
17
  uri = Addressable::URI.parse(target['Location'])
18
18
  raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
19
19
  self.class.sshStart(uri) do |ssh|
20
- sharedKey = self.class.sshExec!(ssh, "firewall-cmd -q --permanent --add-port='#{PORT}'/udp")
21
- sharedKey = self.class.sshExec!(ssh, "firewall-cmd -q --permanent --zone=trusted --add-source=#{SUBNET}")
22
- sharedKey = self.class.sshExec!(ssh, "firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE")
20
+ self.class.sshExec!(ssh, "firewall-cmd -q --permanent --add-port='#{PORT}/udp'")
21
+ self.class.sshExec!(ssh, "firewall-cmd -q --add-port='#{PORT}/udp'")
22
+ self.class.sshExec!(ssh, "firewall-cmd -q --permanent --zone=trusted --add-source=#{SUBNET}")
23
+ self.class.sshExec!(ssh, "firewall-cmd -q --zone=trusted --add-source=#{SUBNET}")
24
+ self.class.sshExec!(ssh, "firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE")
25
+ self.class.sshExec!(ssh, "firewall-cmd -q --direct --add-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE")
23
26
 
24
27
  self.class.ensurePackages([WIREGUARD_PACKAGE], ssh)
25
28
  self.class.ensureServiceAutoStartOverSSH(SERVICE_NAME, ssh)
@@ -83,6 +86,31 @@ module ConfigLMM
83
86
  # TODO
84
87
  end
85
88
  self.startService(SERVICE_NAME, target['Location'])
89
+
90
+ activeState['Status'] = State::STATUS_DEPLOYED
91
+ end
92
+
93
+ def cleanup(configs, state, context, options)
94
+ cleanupType(:WireGuard, configs, state, context, options) do |item, id, state, context, options, ssh|
95
+ Framework::LinuxApp.stopService(SERVICE_NAME, ssh, options[:dry])
96
+ Framework::LinuxApp.disableService(SERVICE_NAME, ssh, options[:dry])
97
+ Framework::LinuxApp.removePackage(WIREGUARD_PACKAGE, ssh, options[:dry])
98
+
99
+ self.class.exec("firewall-cmd -q --permanent --remove-port='#{PORT}/udp'", ssh, false, options[:dry])
100
+ self.class.exec("firewall-cmd -q --remove-port='#{PORT}/udp'", ssh, false, options[:dry])
101
+ self.class.exec("firewall-cmd -q --permanent --zone=trusted --remove-source=#{SUBNET}", ssh, false, options[:dry])
102
+ self.class.exec("firewall-cmd -q --zone=trusted --remove-source=#{SUBNET}", ssh, false, options[:dry])
103
+ self.class.exec("firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE", ssh, false, options[:dry])
104
+ self.class.exec("firewall-cmd -q --direct --remove-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE", ssh, false, options[:dry])
105
+
106
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
107
+
108
+ if options[:destroy]
109
+ rm('/etc/wireguard', options[:dry], ssh)
110
+
111
+ state.item(id)['Status'] = State::STATUS_DESTROYED unless options[:dry]
112
+ end
113
+ end
86
114
  end
87
115
 
88
116
  def genkeyOverSSH(ssh)
@@ -100,7 +128,8 @@ module ConfigLMM
100
128
  def prepareConfig(target)
101
129
  target['Address'] = '172.20.0.1' unless target['Address']
102
130
  target['Peers'].each do |name, data|
103
- data['AllowedIPs'] = SUBNET unless data['AllowedIPs']
131
+ target['Peers'][name] ||= {}
132
+ target['Peers'][name]['AllowedIPs'] = SUBNET unless target['Peers'][name]['AllowedIPs']
104
133
  end
105
134
  end
106
135
  end