ConfigLMM 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +63 -1
- data/Examples/Implemented.mm.yaml +120 -0
- data/Examples/Keys.ini +2 -0
- data/Examples/Linux.mm.yaml +14 -3
- data/Images/configINconfig.png +0 -0
- data/Images/singleConfig.png +0 -0
- data/Plugins/Apps/Authentik/Authentik-Server.container +18 -0
- data/Plugins/Apps/Authentik/Authentik-Worker.container +17 -0
- data/Plugins/Apps/Authentik/Authentik.conf.erb +35 -0
- data/Plugins/Apps/Authentik/Authentik.lmm.rb +73 -0
- data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +41 -0
- data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +165 -0
- data/Plugins/Apps/GitLab/GitLab.conf.erb +26 -0
- data/Plugins/Apps/GitLab/GitLab.container +17 -0
- data/Plugins/Apps/GitLab/GitLab.lmm.rb +75 -0
- data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +48 -10
- data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +59 -2
- data/Plugins/Apps/Nextcloud/config.php +18 -0
- data/Plugins/Apps/Nginx/conf.d/configlmm.conf +62 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +2 -2
- data/Plugins/Apps/Nginx/config-lmm/security.conf +4 -0
- data/Plugins/Apps/Nginx/main.conf.erb +31 -0
- data/Plugins/Apps/Nginx/nginx.conf +3 -68
- data/Plugins/Apps/Nginx/nginx.lmm.rb +71 -14
- data/Plugins/Apps/Odoo/Odoo.conf.erb +30 -13
- data/Plugins/Apps/Odoo/Odoo.container +17 -0
- data/Plugins/Apps/Odoo/Odoo.lmm.rb +62 -2
- data/Plugins/Apps/Odoo/odoo.conf +37 -0
- data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +95 -0
- data/Plugins/Apps/Peppermint/Peppermint.conf.erb +64 -0
- data/Plugins/Apps/Peppermint/Peppermint.container +14 -0
- data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +58 -0
- data/Plugins/Apps/Postfix/Postfix.lmm.rb +184 -0
- data/Plugins/Apps/Postfix/smtpd.conf +3 -0
- data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +225 -0
- data/Plugins/Apps/SSH/SSH.lmm.rb +51 -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 +56 -0
- data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +35 -18
- data/Plugins/Apps/Vaultwarden/Vaultwarden.container +16 -0
- data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +42 -3
- data/Plugins/Apps/gollum/gollum.conf.erb +45 -18
- data/Plugins/Apps/gollum/gollum.container +12 -0
- data/Plugins/Apps/gollum/gollum.lmm.rb +39 -10
- data/Plugins/OS/Linux/Distributions.yaml +16 -0
- data/Plugins/OS/Linux/Linux.lmm.rb +389 -0
- data/Plugins/OS/Linux/Packages.yaml +51 -0
- data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +108 -0
- data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +15 -0
- data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +87 -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 +6 -1
- data/Plugins/Platforms/libvirt/libvirt.lmm.rb +103 -0
- data/Plugins/Services/DNS/PowerDNS.lmm.rb +69 -6
- data/README.md +10 -0
- data/bootstrap.sh +54 -0
- data/lib/ConfigLMM/Framework/plugins/dns.rb +1 -2
- data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +237 -0
- data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +24 -6
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +150 -0
- data/lib/ConfigLMM/Framework/plugins.rb +1 -0
- data/lib/ConfigLMM/commands/configsCommand.rb +3 -0
- data/lib/ConfigLMM/version.rb +1 -1
- metadata +87 -5
- data/Plugins/Apps/Nginx/main.conf +0 -30
- data/Plugins/OS/Linux.lmm.rb +0 -64
@@ -1,22 +1,37 @@
|
|
1
1
|
|
2
2
|
server {
|
3
3
|
|
4
|
-
<% if
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
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
|
-
|
34
|
-
|
35
|
-
passenger_min_instances 1;
|
36
|
-
rails_env production;
|
48
|
+
<% if config['Passenger'] %>
|
49
|
+
passenger_app_root /srv/gollum;
|
37
50
|
|
38
|
-
|
39
|
-
|
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
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|