ConfigLMM 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/Examples/Implemented.mm.yaml +75 -1
  4. data/Plugins/Apps/Authentik/Authentik-Server.container +18 -0
  5. data/Plugins/Apps/Authentik/Authentik-Worker.container +17 -0
  6. data/Plugins/Apps/Authentik/Authentik.conf.erb +35 -0
  7. data/Plugins/Apps/Authentik/Authentik.lmm.rb +73 -0
  8. data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +41 -0
  9. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +148 -0
  10. data/Plugins/Apps/GitLab/GitLab.conf.erb +26 -0
  11. data/Plugins/Apps/GitLab/GitLab.container +17 -0
  12. data/Plugins/Apps/GitLab/GitLab.lmm.rb +75 -0
  13. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +48 -10
  14. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +59 -2
  15. data/Plugins/Apps/Nextcloud/config.php +18 -0
  16. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +62 -0
  17. data/Plugins/Apps/Nginx/config-lmm/errors.conf +1 -1
  18. data/Plugins/Apps/Nginx/main.conf.erb +31 -0
  19. data/Plugins/Apps/Nginx/nginx.conf +3 -68
  20. data/Plugins/Apps/Nginx/nginx.lmm.rb +71 -14
  21. data/Plugins/Apps/Odoo/Odoo.conf.erb +30 -13
  22. data/Plugins/Apps/Odoo/Odoo.container +17 -0
  23. data/Plugins/Apps/Odoo/Odoo.lmm.rb +62 -2
  24. data/Plugins/Apps/Odoo/odoo.conf +37 -0
  25. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +95 -0
  26. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +64 -0
  27. data/Plugins/Apps/Peppermint/Peppermint.container +14 -0
  28. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +58 -0
  29. data/Plugins/Apps/Postfix/Postfix.lmm.rb +139 -31
  30. data/Plugins/Apps/Postfix/smtpd.conf +3 -0
  31. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +172 -23
  32. data/Plugins/Apps/SSH/SSH.lmm.rb +51 -0
  33. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +52 -0
  34. data/Plugins/Apps/UVdesk/UVdesk.lmm.rb +85 -0
  35. data/Plugins/Apps/Valkey/Valkey.lmm.rb +2 -1
  36. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +35 -18
  37. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +16 -0
  38. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +42 -3
  39. data/Plugins/Apps/gollum/gollum.conf.erb +45 -18
  40. data/Plugins/Apps/gollum/gollum.container +12 -0
  41. data/Plugins/Apps/gollum/gollum.lmm.rb +39 -10
  42. data/Plugins/OS/Linux/Distributions.yaml +10 -0
  43. data/Plugins/OS/Linux/Linux.lmm.rb +145 -12
  44. data/Plugins/OS/Linux/Packages.yaml +42 -4
  45. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +108 -0
  46. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +15 -0
  47. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +28 -0
  48. data/Plugins/OS/Linux/systemd/user-0.slice +9 -0
  49. data/Plugins/OS/Linux/systemd/user@.service.d/delegate.conf +3 -0
  50. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +6 -2
  51. data/Plugins/Services/DNS/PowerDNS.lmm.rb +69 -6
  52. data/README.md +6 -0
  53. data/bootstrap.sh +54 -0
  54. data/lib/ConfigLMM/Framework/plugins/dns.rb +1 -2
  55. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +157 -35
  56. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +24 -6
  57. data/lib/ConfigLMM/Framework/plugins/plugin.rb +52 -12
  58. data/lib/ConfigLMM/version.rb +1 -1
  59. metadata +31 -3
  60. data/Plugins/Apps/Nginx/main.conf +0 -30
@@ -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
 
@@ -4,3 +4,13 @@ opensuse-leap:
4
4
  InstallPackage: zypper install --no-confirm
5
5
  AutoStartService: systemctl enable
6
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
@@ -10,8 +10,11 @@ module ConfigLMM
10
10
 
11
11
  ISO_LOCATION = '~/.cache/configlmm/images/'
12
12
  HOSTS_FILE = '/etc/hosts'
13
+ FSTAB_FILE = '/etc/fstab'
13
14
  SSH_CONFIG = '~/.ssh/config'
14
- SYSCTL_FILE = '/etc/sysctl.d/10-configlmm.conf'
15
+ SYSCTL_FILE = '/etc/sysctl.d/90-configlmm.conf'
16
+ FIREWALL_PACKAGE = 'firewalld'
17
+ FIREWALL_SERVICE = 'firewalld'
15
18
 
16
19
  def actionLinuxBuild(id, target, activeState, context, options)
17
20
  prepareConfig(target)
@@ -33,8 +36,7 @@ module ConfigLMM
33
36
  raise Framework::PluginProcessError.new("#{id}: Unknown protocol: #{uri.scheme}!")
34
37
  end
35
38
  else
36
- deployLocalHostsFile(target, options)
37
- deployLocalSSHConfig(target, options)
39
+ deployLocal(target, options)
38
40
  end
39
41
  if target['AlternativeLocation']
40
42
  uri = Addressable::URI.parse(target['AlternativeLocation'])
@@ -44,31 +46,162 @@ module ConfigLMM
44
46
  end
45
47
 
46
48
  def deployOverSSH(locationUri, id, target, activeState, context, options)
47
- if target['Domain'] || target['Hosts']
48
- hostsLines = []
49
- if target['Domain']
50
- self.class.sshStart(locationUri) do |ssh|
49
+ self.class.sshStart(locationUri) do |ssh|
50
+ if target['Domain'] || target['Hosts']
51
+ hostsLines = []
52
+ if target['Domain']
51
53
  envs = self.class.sshExec!(ssh, "env").split("\n")
52
54
  envVars = Hash[envs.map { |vars| vars.split('=', 2) }]
53
55
  ipAddr = envVars['SSH_CONNECTION'].split[-2]
54
56
  hostsLines << ipAddr.ljust(16) + target['Domain'] + ' ' + target['Name'] + "\n"
55
57
  end
56
- end
57
- target['Hosts'].to_a.each do |ip, entries|
58
+ target['Hosts'].to_a.each do |ip, entries|
58
59
  hostsLines << ip.ljust(16) + entries.join(' ') + "\n"
60
+ end
61
+ updateRemoteFile(ssh, HOSTS_FILE, options, false) do |fileLines|
62
+ fileLines + hostsLines
63
+ end
59
64
  end
60
- updateRemoteFile(locationUri, HOSTS_FILE, options, false) do |fileLines|
61
- fileLines + hostsLines
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
62
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)
63
146
  end
147
+ end
148
+
149
+ def deployLocal(target, options)
150
+ deployLocalHostsFile(target, options)
151
+ deployLocalSSHConfig(target, options)
64
152
  if target['Sysctl']
65
- updateRemoteFile(locationUri, SYSCTL_FILE, options, false) do |fileLines|
153
+ updateLocalFile(SYSCTL_FILE, options) do |fileLines|
66
154
  target['Sysctl'].each do |name, value|
67
155
  fileLines << "#{name} = #{value}\n"
156
+ `sysctl #{name}=#{value}`
68
157
  end
69
158
  fileLines
70
159
  end
71
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
72
205
  end
73
206
 
74
207
  def deployOverLibvirt(id, target, activeState, context, options)
@@ -1,13 +1,51 @@
1
1
  Arch Linux:
2
- sshd: openssh
3
- Postfix: postfix
2
+ Cassandra: AUR|cassandra
3
+ CertBotNginx:
4
+ - certbot-nginx
5
+ - certbot-dns-rfc2136
6
+ CyrusSASL: cyrus-sasl
4
7
  Dovecot: dovecot
8
+ firewalld: firewalld
9
+ Nextcloud: nextcloud
10
+ nginx: nginx
11
+ PHP-FPM: php-fpm
12
+ php-pecl: php-pecl
13
+ Podman: podman
5
14
  PostgreSQL: postgresql
15
+ Postfix: postfix
16
+ PowerDNS: powerdns
17
+ sshd: openssh
6
18
  Valkey: redis
19
+ WireGuard: wireguard-tools
20
+ Yarn: yarn
7
21
 
8
22
  openSUSE Leap:
9
- sshd: openssh
10
- Postfix: postfix
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
11
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
12
42
  PostgreSQL: postgresql-server
43
+ Postfix: postfix
44
+ PowerDNS:
45
+ - pdns-backend-geoip
46
+ - pdns-backend-sqlite3
47
+ - pdns-backend-postgresql
48
+ sshd: openssh
13
49
  Valkey: redis
50
+ WireGuard: wireguard-tools
51
+ Yarn: yarn
@@ -0,0 +1,108 @@
1
+
2
+ module ConfigLMM
3
+ module LMM
4
+ class WireGuard < Framework::LinuxApp
5
+
6
+ WIREGUARD_PACKAGE = 'WireGuard'
7
+ SERVICE_NAME = 'wg-quick@wg0'
8
+ CONFIG_FILE = '/etc/wireguard/wg0.conf'
9
+ PORT = '51820'
10
+ SUBNET = '172.20.0.0/20'
11
+
12
+ persistBuildDir
13
+
14
+ def actionWireGuardDeploy(id, target, activeState, context, options)
15
+ self.prepareConfig(target)
16
+ if target['Location'] && target['Location'] != '@me'
17
+ uri = Addressable::URI.parse(target['Location'])
18
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
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")
23
+
24
+ self.class.ensurePackages([WIREGUARD_PACKAGE], ssh)
25
+ self.class.ensureServiceAutoStartOverSSH(SERVICE_NAME, ssh)
26
+
27
+ dir = options['output'] + '/' + id + '/etc/wireguard/'
28
+ mkdir(dir, false)
29
+ template = ERB.new(File.read(__dir__ + '/wg0.conf.erb'))
30
+
31
+ if self.class.remoteFilePresent?(CONFIG_FILE, ssh)
32
+ # TODO Implement adding and removing peers
33
+ else
34
+ if !target['PrivateKey']
35
+ target['PrivateKey'] = ENV['WIREGUARD_PRIVATEKEY_' + id]
36
+ if !target['PrivateKey']
37
+ target['PrivateKey'] = genkeyOverSSH(ssh)
38
+ end
39
+ end
40
+ publicKey = pubkeyOverSSH(target['PrivateKey'], ssh)
41
+ self.class.sshExec!(ssh, "echo '#{publicKey}' > /etc/wireguard/pubkey")
42
+ target['Peers'].each do |name, data|
43
+ if !data['PublicKey']
44
+ data['PrivateKey'] = genkeyOverSSH(ssh)
45
+ data['PublicKey'] = pubkeyOverSSH(data['PrivateKey'], ssh)
46
+ end
47
+ if !data['PresharedKey']
48
+ data['PresharedKey'] = ENV['WIREGUARD_PRESHAREDKEY_' + id + '_' + name]
49
+ if !data['PresharedKey']
50
+ data['PresharedKey'] = genpskOverSSH(ssh)
51
+ end
52
+ end
53
+ end
54
+
55
+ target['Peers'].each do |name, data|
56
+ templateData = {}
57
+ templateData['Address'] = target['Address']
58
+ templateData['PrivateKey'] = data['PrivateKey']
59
+ templateData['Peers'] = {}
60
+ templateData['Peers'][id] = { 'PublicKey' => publicKey, 'PresharedKey' => data['PresharedKey'] }
61
+ target['Peers'].each do |otherName, otherData|
62
+ next if name == otherName
63
+ pskIdB = 'PresharedKey_' + otherName + '_' + name
64
+ if otherData.key?(pskIdB)
65
+ psk = otherData[pskIdB]
66
+ else
67
+ pskIdA = 'PresharedKey_' + name + '_' + otherName
68
+ data[pskIdA] = genpskOverSSH(ssh)
69
+ psk = data[pskIdA]
70
+ end
71
+ templateData['Peers'][otherName] = { 'PublicKey' => otherData['PublicKey'], 'PresharedKey' => psk }
72
+ end
73
+
74
+ renderTemplate(template, templateData, dir + name + '.conf', options)
75
+ end
76
+
77
+ renderTemplate(template, target, dir + 'wg0.conf', options)
78
+ ssh.scp.upload!(dir + 'wg0.conf', CONFIG_FILE)
79
+ end
80
+
81
+ end
82
+ else
83
+ # TODO
84
+ end
85
+ self.startService(SERVICE_NAME, target['Location'])
86
+ end
87
+
88
+ def genkeyOverSSH(ssh)
89
+ self.class.sshExec!(ssh, 'wg genkey')
90
+ end
91
+
92
+ def genpskOverSSH(ssh)
93
+ self.class.sshExec!(ssh, 'wg genpsk')
94
+ end
95
+
96
+ def pubkeyOverSSH(privateKey, ssh)
97
+ self.class.sshExec!(ssh, " echo '#{privateKey}' | wg pubkey")
98
+ end
99
+
100
+ def prepareConfig(target)
101
+ target['Address'] = '172.20.0.1' unless target['Address']
102
+ target['Peers'].each do |name, data|
103
+ data['AllowedIPs'] = SUBNET unless data['AllowedIPs']
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,15 @@
1
+ [Interface]
2
+ Address = <%= config['Address'] %>
3
+ ListenPort = 51820
4
+ PrivateKey = <%= config['PrivateKey'] %>
5
+
6
+ <% config['Peers'].to_h.each do |name, peer| %>
7
+ # <%= name %>
8
+ [Peer]
9
+ PublicKey = <%= peer['PublicKey'] %>
10
+ PresharedKey = <%= peer['PresharedKey'] %>
11
+ AllowedIPs = <%= peer['AllowedIPs'] %>
12
+ <% if peer['Endpoint'] %>
13
+ Endpoint = <%= peer['Endpoint'] %>:51820
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,28 @@
1
+
2
+ module ConfigLMM
3
+ module LMM
4
+ class Systemd < Framework::LinuxApp
5
+
6
+ SYSTEMD_CONFIG_PATH = '/etc/systemd/system/'
7
+ USER_SERVICE_DIR = '/etc/systemd/system/user@.service.d/'
8
+
9
+ def actionSystemdDeploy(id, target, activeState, context, options)
10
+ if target['Location'] && target['Location'] != '@me'
11
+ if target['UserCgroups']
12
+ uri = Addressable::URI.parse(target['Location'])
13
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
14
+ self.class.sshStart(uri) do |ssh|
15
+ self.class.sshExec!(ssh, "mkdir -p #{USER_SERVICE_DIR}")
16
+ ssh.scp.upload!(__dir__ + '/user-0.slice', SYSTEMD_CONFIG_PATH)
17
+ ssh.scp.upload!(__dir__ + '/user@.service.d/delegate.conf', USER_SERVICE_DIR)
18
+ end
19
+ end
20
+ else
21
+ # TODO
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+
2
+ [Unit]
3
+ Before=systemd-logind.service
4
+
5
+ [Slice]
6
+ Slice=user.slice
7
+
8
+ [Install]
9
+ WantedBy=multi-user.target
@@ -0,0 +1,3 @@
1
+
2
+ [Service]
3
+ Delegate=memory pids
@@ -26,8 +26,12 @@ module ConfigLMM
26
26
  data.each do |name, data|
27
27
  self.processDNS(domain, data).each do |type, records|
28
28
  records.each do |record|
29
- shortName = Addressable::IDNA.to_ascii(name)
30
- record[:content] = Addressable::IDNA.to_ascii(record[:content]) if record[:type] == 'CNAME'
29
+ shortName = Addressable::IDNA.to_ascii(name) + '.' if type == 'CNAME' || type == 'ALIAS'
30
+ if record[:type] == 'MX'
31
+ priority, name = record[:content].split(' ')
32
+ name = Addressable::IDNA.to_ascii(name) + '.'
33
+ record[:content] = [priority, name].join(' ')
34
+ end
31
35
  config['Records'] += [shortName, record[:ttl], ' IN ', record[:type], record[:content]].join("\t") + "\n"
32
36
  end
33
37
  end