ConfigLMM 0.1.0 → 0.3.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -1
  3. data/Examples/Implemented.mm.yaml +120 -0
  4. data/Examples/Keys.ini +2 -0
  5. data/Examples/Linux.mm.yaml +14 -3
  6. data/Images/configINconfig.png +0 -0
  7. data/Images/singleConfig.png +0 -0
  8. data/Plugins/Apps/Authentik/Authentik-Server.container +18 -0
  9. data/Plugins/Apps/Authentik/Authentik-Worker.container +17 -0
  10. data/Plugins/Apps/Authentik/Authentik.conf.erb +35 -0
  11. data/Plugins/Apps/Authentik/Authentik.lmm.rb +73 -0
  12. data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +41 -0
  13. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +165 -0
  14. data/Plugins/Apps/GitLab/GitLab.conf.erb +26 -0
  15. data/Plugins/Apps/GitLab/GitLab.container +17 -0
  16. data/Plugins/Apps/GitLab/GitLab.lmm.rb +75 -0
  17. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +48 -10
  18. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +59 -2
  19. data/Plugins/Apps/Nextcloud/config.php +18 -0
  20. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +62 -0
  21. data/Plugins/Apps/Nginx/config-lmm/errors.conf +2 -2
  22. data/Plugins/Apps/Nginx/config-lmm/security.conf +4 -0
  23. data/Plugins/Apps/Nginx/main.conf.erb +31 -0
  24. data/Plugins/Apps/Nginx/nginx.conf +3 -68
  25. data/Plugins/Apps/Nginx/nginx.lmm.rb +71 -14
  26. data/Plugins/Apps/Odoo/Odoo.conf.erb +30 -13
  27. data/Plugins/Apps/Odoo/Odoo.container +17 -0
  28. data/Plugins/Apps/Odoo/Odoo.lmm.rb +62 -2
  29. data/Plugins/Apps/Odoo/odoo.conf +37 -0
  30. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +95 -0
  31. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +64 -0
  32. data/Plugins/Apps/Peppermint/Peppermint.container +14 -0
  33. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +58 -0
  34. data/Plugins/Apps/Postfix/Postfix.lmm.rb +184 -0
  35. data/Plugins/Apps/Postfix/smtpd.conf +3 -0
  36. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +225 -0
  37. data/Plugins/Apps/SSH/SSH.lmm.rb +51 -0
  38. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +52 -0
  39. data/Plugins/Apps/UVdesk/UVdesk.lmm.rb +85 -0
  40. data/Plugins/Apps/Valkey/Valkey.lmm.rb +56 -0
  41. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +35 -18
  42. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +16 -0
  43. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +42 -3
  44. data/Plugins/Apps/gollum/gollum.conf.erb +45 -18
  45. data/Plugins/Apps/gollum/gollum.container +12 -0
  46. data/Plugins/Apps/gollum/gollum.lmm.rb +39 -10
  47. data/Plugins/OS/Linux/Distributions.yaml +16 -0
  48. data/Plugins/OS/Linux/Linux.lmm.rb +389 -0
  49. data/Plugins/OS/Linux/Packages.yaml +51 -0
  50. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +108 -0
  51. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +15 -0
  52. data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +87 -0
  53. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +28 -0
  54. data/Plugins/OS/Linux/systemd/user-0.slice +9 -0
  55. data/Plugins/OS/Linux/systemd/user@.service.d/delegate.conf +3 -0
  56. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +6 -1
  57. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +103 -0
  58. data/Plugins/Services/DNS/PowerDNS.lmm.rb +69 -6
  59. data/README.md +10 -0
  60. data/bootstrap.sh +54 -0
  61. data/lib/ConfigLMM/Framework/plugins/dns.rb +1 -2
  62. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +237 -0
  63. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +24 -6
  64. data/lib/ConfigLMM/Framework/plugins/plugin.rb +150 -0
  65. data/lib/ConfigLMM/Framework/plugins.rb +1 -0
  66. data/lib/ConfigLMM/commands/configsCommand.rb +3 -0
  67. data/lib/ConfigLMM/version.rb +1 -1
  68. metadata +87 -5
  69. data/Plugins/Apps/Nginx/main.conf +0 -30
  70. data/Plugins/OS/Linux.lmm.rb +0 -64
@@ -1,22 +1,37 @@
1
1
 
2
2
  server {
3
3
 
4
- <% if !config['TLS'] %>
5
- listen <%= config['Port'] %>;
6
- listen [::]:<%= config['Port'] %>;
7
- <% else %>
8
- listen <%= config['Port'] %> ssl;
9
- listen [::]:<%= config['Port'] %> ssl;
4
+ <% if config['NginxVersion'] >= 1.25 %>
5
+ <% if !config['TLS'] %>
6
+ listen <%= config['Port'] %>;
7
+ listen [::]:<%= config['Port'] %>;
8
+ <% else %>
9
+ listen <%= config['Port'] %> ssl;
10
+ listen [::]:<%= config['Port'] %> ssl;
11
+
12
+ include config-lmm/ssl.conf;
13
+ <% end %>
10
14
  http2 on;
11
- include config-lmm/ssl.conf;
15
+ http3 on;
16
+ quic_retry on;
17
+ add_header Alt-Svc 'h3=":443"; ma=86400';
18
+ <% else %>
19
+ <% if !config['TLS'] %>
20
+ listen <%= config['Port'] %>;
21
+ listen [::]:<%= config['Port'] %>;
22
+ <% else %>
23
+ listen <%= config['Port'] %> ssl http2;
24
+ listen [::]:<%= config['Port'] %> ssl http2;
25
+
26
+ include config-lmm/ssl.conf;
27
+ <% end %>
12
28
  <% end %>
13
29
 
14
30
  server_name <%= config['Domain'] %>;
15
31
 
16
- root <%= config['Root'] %>;
17
- passenger_app_root /srv/gollum;
18
-
19
- try_files $uri @Passenger;
32
+ <% if config['Root'] %>
33
+ root <%= config['Root'] %>;
34
+ <% end %>
20
35
 
21
36
  access_log /var/log/nginx/gollum.access.log;
22
37
  error_log /var/log/nginx/gollum.error.log;
@@ -30,12 +45,24 @@ server {
30
45
  ssl_trusted_certificate "/etc/letsencrypt/live/<%= config['CertName'] %>/chain.pem";
31
46
  <% end %>
32
47
 
33
- location @Passenger {
34
- passenger_enabled on;
35
- passenger_min_instances 1;
36
- rails_env production;
48
+ <% if config['Passenger'] %>
49
+ passenger_app_root /srv/gollum;
37
50
 
38
- #passenger_set_cgi_param HTTP_X_FORWARDED_PROTO https;
39
- #limit_req zone=one burst=5;
40
- }
51
+ try_files $uri @Passenger;
52
+
53
+ location @Passenger {
54
+ passenger_enabled on;
55
+ passenger_min_instances 1;
56
+ rails_env production;
57
+
58
+ #passenger_set_cgi_param HTTP_X_FORWARDED_PROTO https;
59
+ #limit_req zone=one burst=5;
60
+ }
61
+ <% else %>
62
+ location / {
63
+ proxy_pass <%= config['Server'] %>;
64
+
65
+ include config-lmm/proxy.conf;
66
+ }
67
+ <% end %>
41
68
  }
@@ -0,0 +1,12 @@
1
+ [Unit]
2
+ Description=gollum container
3
+ After=local-fs.target
4
+
5
+ [Container]
6
+ Image=gollumwiki/gollum:master
7
+ PublishPort=0.0.0.0:14567:4567
8
+ UserNS=keep-id:uid=1000,gid=1000
9
+ Volume=/srv/gollum/repo:/wiki
10
+
11
+ [Install]
12
+ WantedBy=multi-user.target default.target
@@ -4,12 +4,11 @@ module ConfigLMM
4
4
  class Gollum < Framework::NginxApp
5
5
 
6
6
  NAME = 'gollum'
7
+ USER = 'gollum'
7
8
  GOLLUM_PATH = '/srv/gollum'
9
+ HOME_DIR = '/var/lib/authentik'
8
10
 
9
11
  def actionGollumBuild(id, target, activeState, context, options)
10
- if !target['Root'] && (!target['Location'] || target['Location'] == '@me')
11
- target['Root'] = File.dirname(`gem which gollum`.strip) + '/gollum/public'
12
- end
13
12
  writeNginxConfig(__dir__, NAME, id, target, activeState, context, options)
14
13
  targetDir = options['output'] + GOLLUM_PATH
15
14
  mkdir(targetDir, options['dry'])
@@ -22,16 +21,46 @@ module ConfigLMM
22
21
  end
23
22
 
24
23
  def actionGollumDeploy(id, target, activeState, context, options)
25
- if !target['Location'] || target['Location'] == '@me'
24
+ if target['Location'] && target['Location'] != '@me'
25
+ uri = Addressable::URI.parse(target['Location'])
26
+ self.class.sshStart(uri) do |ssh|
27
+ if !target.key?('Proxy') || !!target['Proxy']
28
+ self.class.prepareNginxConfig(target, ssh)
29
+ if !target['Root']
30
+ gollumPath = ssh.exec!('gem which gollum').strip
31
+ target['Root'] = File.dirname(gollumPath) + '/gollum/public'
32
+ end
33
+ writeNginxConfig(__dir__, NAME, id, target, state, context, options)
34
+ deployNginxConfig(id, target, activeState, context, options)
35
+ end
36
+ if !target.key?('Proxy') || target['Proxy'] != 'only'
37
+ distroInfo = Framework::LinuxApp.currentDistroInfo(ssh)
38
+ Framework::LinuxApp.configurePodmanServiceOverSSH(USER, GOLLUM_PATH, 'gollum', distroInfo, ssh)
39
+ self.class.uploadFolder(options['output'] + GOLLUM_PATH, '/srv', ssh)
40
+ path = Framework::LinuxApp::SYSTEMD_CONTAINERS_PATH.gsub('~', GOLLUM_PATH)
41
+ ssh.scp.upload!(__dir__ + '/gollum.container', path)
42
+ self.class.sshExec!(ssh, "chown -R #{USER}:#{USER} #{GOLLUM_PATH}")
43
+ self.class.sshExec!(ssh, "systemctl --user --machine=#{USER}@ daemon-reload")
44
+ self.class.sshExec!(ssh, "systemctl --user --machine=#{USER}@ start gollum")
45
+ end
46
+ end
47
+ else
26
48
  targetDir = GOLLUM_PATH
27
49
  mkdir(targetDir, options['dry'])
28
- deployNginxConfig(id, target, activeState, context, options)
29
- copy(options['output'] + GOLLUM_PATH + '/config.ru', GOLLUM_PATH, options['dry'])
30
- copyNotPresent(options['output'] + GOLLUM_PATH + '/repo', GOLLUM_PATH, options['dry'])
31
- chown('http', 'http', GOLLUM_PATH, options['dry'])
50
+ if !target.key?('Proxy') || !!target['Proxy']
51
+ self.class.prepareNginxConfig(target)
52
+ if !target['Root']
53
+ target['Root'] = File.dirname(`gem which gollum`.strip) + '/gollum/public'
54
+ end
55
+ writeNginxConfig(__dir__, NAME, id, target, state, context, options)
56
+ deployNginxConfig(id, target, activeState, context, options)
57
+ end
58
+ if !target.key?('Proxy') || target['Proxy'] != 'only'
59
+ copy(options['output'] + GOLLUM_PATH + '/config.ru', GOLLUM_PATH, options['dry'])
60
+ copyNotPresent(options['output'] + GOLLUM_PATH + '/repo', GOLLUM_PATH, options['dry'])
61
+ chown('http', 'http', GOLLUM_PATH, options['dry'])
62
+ end
32
63
  activeState['Location'] = '@me'
33
- else
34
- # TODO
35
64
  end
36
65
  end
37
66
 
@@ -0,0 +1,16 @@
1
+
2
+ opensuse-leap:
3
+ Name: openSUSE Leap
4
+ InstallPackage: zypper install --no-confirm
5
+ AutoStartService: systemctl enable
6
+ StartService: systemctl start
7
+ CreateServiceUser: useradd --system --shell /usr/sbin/nologin --user-group
8
+ ModifyUser: usermod
9
+
10
+ arch:
11
+ Name: Arch Linux
12
+ InstallPackage: pacman -S --noconfirm --needed
13
+ AutoStartService: systemctl enable
14
+ StartService: systemctl start
15
+ CreateServiceUser: useradd --system --shell /usr/sbin/nologin --user-group
16
+ ModifyUser: usermod
@@ -0,0 +1,389 @@
1
+
2
+ require 'addressable/uri'
3
+ require 'http'
4
+ require 'securerandom'
5
+ require 'shellwords'
6
+
7
+ module ConfigLMM
8
+ module LMM
9
+ class Linux < Framework::LinuxApp
10
+
11
+ ISO_LOCATION = '~/.cache/configlmm/images/'
12
+ HOSTS_FILE = '/etc/hosts'
13
+ FSTAB_FILE = '/etc/fstab'
14
+ SSH_CONFIG = '~/.ssh/config'
15
+ SYSCTL_FILE = '/etc/sysctl.d/90-configlmm.conf'
16
+ FIREWALL_PACKAGE = 'firewalld'
17
+ FIREWALL_SERVICE = 'firewalld'
18
+
19
+ def actionLinuxBuild(id, target, activeState, context, options)
20
+ prepareConfig(target)
21
+ buildHostsFile(id, target, options)
22
+ buildSSHConfig(id, target, options)
23
+ buildAutoYaST(id, target, options)
24
+ end
25
+
26
+ def actionLinuxDeploy(id, target, activeState, context, options)
27
+ prepareConfig(target)
28
+ if target['Location'] && target['Location'] != '@me'
29
+ uri = Addressable::URI.parse(target['Location'])
30
+ case uri.scheme
31
+ when 'qemu'
32
+ deployOverLibvirt(id, target, activeState, context, options)
33
+ when 'ssh'
34
+ deployOverSSH(uri, id, target, activeState, context, options)
35
+ else
36
+ raise Framework::PluginProcessError.new("#{id}: Unknown protocol: #{uri.scheme}!")
37
+ end
38
+ else
39
+ deployLocal(target, options)
40
+ end
41
+ if target['AlternativeLocation']
42
+ uri = Addressable::URI.parse(target['AlternativeLocation'])
43
+ raise Framework::PluginProcessError.new("#{id}: Unsupported protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
44
+ deployOverSSH(uri, id, target, activeState, context, options)
45
+ end
46
+ end
47
+
48
+ def deployOverSSH(locationUri, id, target, activeState, context, options)
49
+ self.class.sshStart(locationUri) do |ssh|
50
+ if target['Domain'] || target['Hosts']
51
+ hostsLines = []
52
+ if target['Domain']
53
+ envs = self.class.sshExec!(ssh, "env").split("\n")
54
+ envVars = Hash[envs.map { |vars| vars.split('=', 2) }]
55
+ ipAddr = envVars['SSH_CONNECTION'].split[-2]
56
+ hostsLines << ipAddr.ljust(16) + target['Domain'] + ' ' + target['Name'] + "\n"
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
63
+ end
64
+ end
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
104
+ if target['Tmpfs']
105
+ self.class.sshExec!(ssh, "sed -i '/ \\/tmp /d' #{FSTAB_FILE}")
106
+ updateRemoteFile(ssh, FSTAB_FILE, options, false) do |fileLines|
107
+ fileLines << "tmpfs /tmp tmpfs nodev,nosuid,size=#{target['Tmpfs']} 0 0\n"
108
+ end
109
+ end
110
+ if target['Sysctl']
111
+ updateRemoteFile(ssh, SYSCTL_FILE, options, false) do |fileLines|
112
+ target['Sysctl'].each do |name, value|
113
+ fileLines << "#{name} = #{value}\n"
114
+ self.class.sshExec!(ssh, "sysctl #{name}=#{value}")
115
+ end
116
+ fileLines
117
+ end
118
+ end
119
+ if target['Users']
120
+ target['Users'].each do |name, info|
121
+ userId = ssh.exec!("id -u #{name} 2>/dev/null").strip
122
+ if userId.empty?
123
+ shell = ''
124
+ if info['Shell']
125
+ shell = "--shell '/usr/bin/#{info['Shell']}'"
126
+ end
127
+ badname = '--badname'
128
+ badname = '--badnames' if distroInfo['Name'] == 'openSUSE Leap'
129
+ self.class.sshExec!(ssh, "useradd #{badname} --create-home --user-group #{shell} #{name}")
130
+ end
131
+ homeDir = self.class.sshExec!(ssh, "getent passwd #{name} | cut -d ':' -f 6").strip
132
+ keyFile = homeDir + "/.ssh/id_ed25519"
133
+ if info['SSHKey'] && !self.class.remoteFilePresent?(keyFile, ssh)
134
+ self.class.sshExec!(ssh, "mkdir -p #{homeDir}/.ssh")
135
+ self.class.sshExec!(ssh, "ssh-keygen -t ed25519 -f #{keyFile} -P ''")
136
+ self.class.sshExec!(ssh, "chown -R #{name}:#{name} #{homeDir}/.ssh")
137
+ end
138
+ end
139
+ end
140
+ self.executeCommands(target['Execute'], ssh)
141
+ end
142
+ if target['Firewall'] && target['Firewall'] != 'no'
143
+ self.ensurePackage(FIREWALL_PACKAGE, locationUri)
144
+ self.ensureServiceAutoStart(FIREWALL_SERVICE, locationUri)
145
+ self.startService(FIREWALL_SERVICE, locationUri)
146
+ end
147
+ end
148
+
149
+ def deployLocal(target, options)
150
+ deployLocalHostsFile(target, options)
151
+ deployLocalSSHConfig(target, options)
152
+ if target['Sysctl']
153
+ updateLocalFile(SYSCTL_FILE, options) do |fileLines|
154
+ target['Sysctl'].each do |name, value|
155
+ fileLines << "#{name} = #{value}\n"
156
+ `sysctl #{name}=#{value}`
157
+ end
158
+ fileLines
159
+ end
160
+ end
161
+ if target['Users']
162
+ target['Users'].each do |name, info|
163
+ userId = self.class.exec("id -u #{name} 2>/dev/null", nil, true).strip
164
+ if userId.empty?
165
+ shell = ''
166
+ if info['Shell']
167
+ shell = "--shell '/usr/bin/#{info['Shell']}'"
168
+ end
169
+ distroInfo = self.class.currentDistroInfo(nil)
170
+ badname = '--badname'
171
+ badname = '--badnames' if distroInfo['Name'] == 'openSUSE Leap'
172
+ self.class.exec("useradd #{badname} --create-home --user-group #{shell} #{name}")
173
+ end
174
+ homeDir = self.class.exec("getent passwd #{name} | cut -d ':' -f 6").strip
175
+ keyFile = homeDir + "/.ssh/id_ed25519"
176
+ if info['SSHKey'] && !self.class.filePresent?(keyFile)
177
+ self.class.exec("mkdir -p #{homeDir}/.ssh")
178
+ self.class.exec("ssh-keygen -t ed25519 -f #{keyFile} -P ''")
179
+ self.class.exec("chown -R #{name}:#{name} #{homeDir}/.ssh")
180
+ end
181
+ end
182
+ end
183
+ if target['Firewall'] && target['Firewall'] != 'no'
184
+ self.ensurePackage(FIREWALL_PACKAGE, locationUri)
185
+ self.ensureServiceAutoStart(FIREWALL_SERVICE, locationUri)
186
+ self.startService(FIREWALL_SERVICE, locationUri)
187
+ end
188
+ self.executeCommands(target['Execute'])
189
+ end
190
+
191
+ def executeCommands(commands, ssh = nil)
192
+ return unless commands
193
+
194
+ commands.each do |type, data|
195
+ case type
196
+ when 'sh'
197
+ data = [data] unless data.is_a?(Array)
198
+ data.each do |cmd|
199
+ self.class.exec(cmd, ssh)
200
+ end
201
+ else
202
+ raise 'Unimplemented!'
203
+ end
204
+ end
205
+ end
206
+
207
+ def deployOverLibvirt(id, target, activeState, context, options)
208
+ 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)
212
+ end
213
+
214
+ def buildHostsFile(id, target, options)
215
+ if target['Hosts']
216
+ hosts = "#\n"
217
+ hosts += "# /etc/hosts: static lookup table for host names\n"
218
+ hosts += "#\n\n"
219
+ hosts += "#<ip-address> <hostname.domain.org> <hostname>\n"
220
+ hosts += "127.0.0.1 localhost\n"
221
+ hosts += "::1 localhost\n\n"
222
+ hosts += CONFIGLMM_SECTION_BEGIN
223
+ target['Hosts'].each do |ip, entries|
224
+ hosts += ip.ljust(16) + entries.join(' ') + "\n"
225
+ end
226
+ hosts += CONFIGLMM_SECTION_END
227
+
228
+ path = options['output'] + '/' + id
229
+ mkdir(path + '/etc', options[:dry])
230
+ fileWrite(path + HOSTS_FILE, hosts, options[:dry])
231
+ end
232
+ end
233
+
234
+ def buildSSHConfig(id, target, options)
235
+ if !target['SSH']['Config'].empty?
236
+ sshConfig = "\n"
237
+ sshConfig += CONFIGLMM_SECTION_BEGIN
238
+ target['SSH']['Config'].each do |name, info|
239
+ sshConfig += "Host #{name} #{info['HostName']}\n"
240
+ sshConfig += " HostName " + info['HostName'] + "\n" if info['HostName']
241
+ sshConfig += " Port " + info['Port'] + "\n" if info['Port']
242
+ sshConfig += " User " + info['User'] + "\n" if info['User']
243
+ sshConfig += " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
244
+ sshConfig += "\n"
245
+ end
246
+ sshConfig += CONFIGLMM_SECTION_END
247
+ sshConfig += "\n"
248
+
249
+ configPath = options['output'] + '/' + id
250
+ mkdir(configPath + '/root/.ssh', options[:dry])
251
+ fileWrite(configPath + SSH_CONFIG.gsub('~', '/root'), sshConfig, options[:dry])
252
+ end
253
+ end
254
+
255
+ def buildAutoYaST(id, target, options)
256
+ if target['Distro'] == SUSE_NAME
257
+ outputFolder = options['output'] + '/' + id + '/'
258
+ template = ERB.new(File.read(__dir__ + '/openSUSE/autoinst.xml.erb'))
259
+ renderTemplate(template, target, outputFolder + 'autoinst.xml', options)
260
+ end
261
+ end
262
+
263
+ def deployLocalHostsFile(target, options)
264
+ if target['Hosts']
265
+ updateLocalFile(HOSTS_FILE, options) do |hostsLines|
266
+ target['Hosts'].each do |ip, entries|
267
+ hostsLines << ip.ljust(16) + entries.join(' ') + "\n"
268
+ end
269
+ hostsLines
270
+ end
271
+ end
272
+ end
273
+
274
+ def deployLocalSSHConfig(target, options)
275
+ if !target['SSH']['Config'].empty?
276
+ updateLocalFile(File.expand_path(SSH_CONFIG), options) do |configLines|
277
+ target['SSH']['Config'].each do |name, info|
278
+ configLines << "Host #{name} #{info['HostName']}\n"
279
+ configLines << " HostName " + info['HostName'] + "\n" if info['HostName']
280
+ configLines << " Port " + info['Port'] + "\n" if info['Port']
281
+ configLines << " User " + info['User'] + "\n" if info['User']
282
+ configLines << " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
283
+ end
284
+ configLines
285
+ end
286
+ end
287
+ end
288
+
289
+ def installationISO(distro, location)
290
+ 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}!")
301
+ end
302
+
303
+ filename = File.basename(Addressable::URI.parse(url).path)
304
+ iso = File.expand_path(ISO_LOCATION + filename)
305
+ if !File.exist?(iso)
306
+ mkdir(File.expand_path(ISO_LOCATION), false)
307
+ prompt.say('Downloading... ' + url)
308
+ response = HTTP.follow.get(url)
309
+ raise "Failed to download file: #{response.status}" unless response.status.success?
310
+ File.open(iso, 'wb') do |file|
311
+ response.body.each do |chunk|
312
+ file.write(chunk)
313
+ end
314
+ end
315
+ end
316
+ iso
317
+ end
318
+
319
+ def buildISOAutoYaST(id, iso, target, options)
320
+ outputFolder = options['output'] + '/iso/'
321
+ mkdir(outputFolder, false)
322
+ `xorriso -osirrox on -indev #{iso} -extract / #{outputFolder} 2>&1 >/dev/null`
323
+ FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
324
+ copy(options['output'] + '/' + id + '/autoinst.xml', outputFolder, false)
325
+
326
+ 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}`
331
+
332
+ 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`
334
+ patchedIso
335
+ end
336
+
337
+ def prepareConfig(target)
338
+ target['SSH'] ||= {}
339
+ target['SSH']['Config'] ||= {}
340
+ target['Users'] ||= {}
341
+ target['HostName'] = target['Name'] unless target['HostName']
342
+
343
+ if ENV['LINUX_ROOT_PASSWORD_HASH']
344
+ target['Users']['root'] ||= {}
345
+ target['Users']['root']['PasswordHash'] = ENV['LINUX_ROOT_PASSWORD_HASH']
346
+ elsif ENV['LINUX_ROOT_PASSWORD']
347
+ target['Users']['root'] ||= {}
348
+ target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(ENV['LINUX_ROOT_PASSWORD'])
349
+ elsif target['Users'].key?('root')
350
+ if !target['Users']['root']['Password'] &&
351
+ !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)
355
+ elsif target['Users']['root']['Password'] == 'no'
356
+ target['Users']['root'].delete('Password')
357
+ end
358
+ end
359
+
360
+ target['Users'].each do |user, info|
361
+ newKeys = []
362
+ info['AuthorizedKeys'].to_a.each do |key|
363
+ if key.start_with?('/') || key.start_with?('~')
364
+ newKeys << File.read(File.expand_path(key)).strip
365
+ else
366
+ newKeys << key
367
+ end
368
+ end
369
+ info['AuthorizedKeys'] = newKeys
370
+ end
371
+
372
+ packages = YAML.load_file(__dir__ + '/Packages.yaml')
373
+ newApps = []
374
+ target['Services'] ||= []
375
+ if target['Apps'].to_a.include?('sshd')
376
+ target['Services'] << 'sshd'
377
+ target['Services'].uniq!
378
+ end
379
+ target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro'])
380
+ end
381
+
382
+ def self.linuxPasswordHash(password)
383
+ salt = SecureRandom.alphanumeric(16)
384
+ password.crypt('$6$' + salt)
385
+ end
386
+
387
+ end
388
+ end
389
+ end
@@ -0,0 +1,51 @@
1
+ Arch Linux:
2
+ Cassandra: AUR|cassandra
3
+ CertBotNginx:
4
+ - certbot-nginx
5
+ - certbot-dns-rfc2136
6
+ CyrusSASL: cyrus-sasl
7
+ Dovecot: dovecot
8
+ firewalld: firewalld
9
+ Nextcloud: nextcloud
10
+ nginx: nginx
11
+ PHP-FPM: php-fpm
12
+ php-pecl: php-pecl
13
+ Podman: podman
14
+ PostgreSQL: postgresql
15
+ Postfix: postfix
16
+ PowerDNS: powerdns
17
+ sshd: openssh
18
+ Valkey: redis
19
+ WireGuard: wireguard-tools
20
+ Yarn: yarn
21
+
22
+ openSUSE Leap:
23
+ Cassandra: home:davispuh:branches:server:database|cassandra
24
+ CertBotNginx:
25
+ - python3-certbot-nginx
26
+ - certbot-systemd-timer
27
+ - python3-certbot-dns-rfc2136
28
+ CyrusSASL: cyrus-sasl-plain
29
+ Dovecot: dovecot
30
+ firewalld: firewalld
31
+ Nextcloud: server:php:applications|nextcloud
32
+ nginx: nginx
33
+ PHP-FPM:
34
+ - php8-devel
35
+ - php8-mbstring
36
+ - php8-fpm
37
+ - php8-redis
38
+ - php8-pgsql
39
+ - php8-mysql
40
+ php-pecl: php8-pecl
41
+ Podman: podman
42
+ PostgreSQL: postgresql-server
43
+ Postfix: postfix
44
+ PowerDNS:
45
+ - pdns-backend-geoip
46
+ - pdns-backend-sqlite3
47
+ - pdns-backend-postgresql
48
+ sshd: openssh
49
+ Valkey: redis
50
+ WireGuard: wireguard-tools
51
+ Yarn: yarn