ConfigLMM 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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