ConfigLMM 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -0
  3. data/Examples/Implemented.mm.yaml +75 -1
  4. data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +14 -0
  5. data/Plugins/Apps/Authentik/Authentik-Server.container +19 -0
  6. data/Plugins/Apps/Authentik/Authentik-Worker.container +18 -0
  7. data/Plugins/Apps/Authentik/Authentik.conf.erb +42 -0
  8. data/Plugins/Apps/Authentik/Authentik.lmm.rb +95 -0
  9. data/Plugins/Apps/BookStack/BookStack.conf.erb +41 -0
  10. data/Plugins/Apps/BookStack/BookStack.container +15 -0
  11. data/Plugins/Apps/BookStack/BookStack.lmm.rb +80 -0
  12. data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +41 -0
  13. data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +17 -0
  14. data/Plugins/Apps/Discourse/Discourse.conf.erb +41 -0
  15. data/Plugins/Apps/Discourse/Discourse.container +17 -0
  16. data/Plugins/Apps/Discourse/Discourse.lmm.rb +95 -0
  17. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +171 -0
  18. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +19 -0
  19. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +17 -0
  20. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +17 -0
  21. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +19 -0
  22. data/Plugins/Apps/ERPNext/ERPNext.container +18 -0
  23. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +193 -0
  24. data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
  25. data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
  26. data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
  27. data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
  28. data/Plugins/Apps/GitLab/GitLab.container +18 -0
  29. data/Plugins/Apps/GitLab/GitLab.lmm.rb +100 -0
  30. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +57 -0
  31. data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
  32. data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
  33. data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
  34. data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
  35. data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
  36. data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
  37. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +115 -0
  38. data/Plugins/Apps/Matrix/Element.container +14 -0
  39. data/Plugins/Apps/Matrix/Matrix.conf.erb +49 -5
  40. data/Plugins/Apps/Matrix/Matrix.lmm.rb +86 -1
  41. data/Plugins/Apps/Matrix/Synapse.container +17 -0
  42. data/Plugins/Apps/Matrix/config.json +50 -0
  43. data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
  44. data/Plugins/Apps/Matrix/log.config +30 -0
  45. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +48 -10
  46. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +83 -1
  47. data/Plugins/Apps/Nextcloud/config.php +18 -0
  48. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +71 -0
  49. data/Plugins/Apps/Nginx/config-lmm/errors.conf +11 -5
  50. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +5 -1
  51. data/Plugins/Apps/Nginx/main.conf.erb +31 -0
  52. data/Plugins/Apps/Nginx/nginx.conf +3 -68
  53. data/Plugins/Apps/Nginx/nginx.lmm.rb +83 -22
  54. data/Plugins/Apps/Nginx/proxy.conf.erb +13 -3
  55. data/Plugins/Apps/Odoo/Odoo.conf.erb +30 -13
  56. data/Plugins/Apps/Odoo/Odoo.container +18 -0
  57. data/Plugins/Apps/Odoo/Odoo.lmm.rb +62 -2
  58. data/Plugins/Apps/Odoo/odoo.conf +37 -0
  59. data/Plugins/Apps/OpenVidu/Ingress.container +18 -0
  60. data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +34 -0
  61. data/Plugins/Apps/OpenVidu/OpenVidu.container +16 -0
  62. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +90 -0
  63. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +35 -0
  64. data/Plugins/Apps/OpenVidu/OpenViduCall.container +15 -0
  65. data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
  66. data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
  67. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +95 -0
  68. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +60 -0
  69. data/Plugins/Apps/Peppermint/Peppermint.container +15 -0
  70. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +58 -0
  71. data/Plugins/Apps/Postfix/Postfix.lmm.rb +165 -31
  72. data/Plugins/Apps/Postfix/smtpd.conf +3 -0
  73. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +242 -24
  74. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +75 -0
  75. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +145 -0
  76. data/Plugins/Apps/SSH/SSH.lmm.rb +51 -0
  77. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +63 -0
  78. data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
  79. data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
  80. data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
  81. data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
  82. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +52 -0
  83. data/Plugins/Apps/UVdesk/UVdesk.lmm.rb +85 -0
  84. data/Plugins/Apps/Valkey/Valkey.lmm.rb +34 -1
  85. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +35 -18
  86. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +16 -0
  87. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +46 -3
  88. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +42 -0
  89. data/Plugins/Apps/Wiki.js/Wiki.js.container +15 -0
  90. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +61 -0
  91. data/Plugins/Apps/gollum/gollum.conf.erb +84 -19
  92. data/Plugins/Apps/gollum/gollum.container +15 -0
  93. data/Plugins/Apps/gollum/gollum.lmm.rb +48 -11
  94. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +62 -0
  95. data/Plugins/OS/Linux/Distributions.yaml +42 -0
  96. data/Plugins/OS/Linux/Flavours.yaml +11 -0
  97. data/Plugins/OS/Linux/Linux.lmm.rb +362 -41
  98. data/Plugins/OS/Linux/Packages.yaml +88 -5
  99. data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
  100. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +137 -0
  101. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +15 -0
  102. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +28 -0
  103. data/Plugins/OS/Linux/systemd/user-0.slice +9 -0
  104. data/Plugins/OS/Linux/systemd/user@.service.d/delegate.conf +3 -0
  105. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +7 -3
  106. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +3 -2
  107. data/Plugins/Services/DNS/PowerDNS.lmm.rb +158 -8
  108. data/README.md +6 -0
  109. data/bootstrap.sh +92 -0
  110. data/lib/ConfigLMM/Framework/plugins/dns.rb +1 -2
  111. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +249 -45
  112. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +56 -7
  113. data/lib/ConfigLMM/Framework/plugins/plugin.rb +112 -16
  114. data/lib/ConfigLMM/cli.rb +3 -1
  115. data/lib/ConfigLMM/commands/cleanup.rb +1 -0
  116. data/lib/ConfigLMM/commands/configsCommand.rb +3 -1
  117. data/lib/ConfigLMM/io/configList.rb +3 -1
  118. data/lib/ConfigLMM/state.rb +10 -2
  119. data/lib/ConfigLMM/version.rb +1 -1
  120. metadata +82 -3
  121. data/Plugins/Apps/Nginx/main.conf +0 -30
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tty-which'
4
+
3
5
  module ConfigLMM
4
6
  module Framework
5
7
 
@@ -8,79 +10,244 @@ module ConfigLMM
8
10
  LINUX_FOLDER = __dir__ + '/../../../../Plugins/OS/Linux/'
9
11
  SUSE_NAME = 'openSUSE Leap'
10
12
  SUSE_ID = 'opensuse-leap'
13
+ DEBIAN_NAME = 'Debian'
14
+ PROXMOXVE_NAME = 'Proxmox VE'
15
+ PODMAN_PACKAGE = 'Podman'
16
+ SYSTEMD_CONTAINERS_PATH = '~/.config/containers/systemd/'
11
17
 
12
- def ensurePackage(name, location)
13
- self.class.ensurePackage(name, location)
18
+ def ensurePackage(name, location, binary = nil)
19
+ self.class.ensurePackage(name, location, binary)
14
20
  end
15
21
 
16
- def self.ensurePackage(name, location)
17
- if location && location != '@me'
18
- uri = Addressable::URI.parse(location)
19
- raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
20
- self.ensurePackageOverSSH(name, uri)
21
- else
22
- # TODO
23
- end
22
+ def ensurePackages(names, location)
23
+ self.class.ensurePackages(names, location)
24
24
  end
25
25
 
26
- def self.ensurePackageOverSSH(name, locationOrSSH)
26
+ def self.ensurePackage(name, location, binary = nil)
27
+ if binary && TTY::Which.which(binary)
28
+ return
29
+ end
30
+ self.ensurePackages([name], location)
31
+ end
27
32
 
28
- closure = Proc.new do |ssh|
29
- distroInfo = self.distroInfoFromSSH(ssh)
30
- pkgs = self.mapPackages([name], distroInfo['Name']).first
33
+ def self.ensurePackages(names, locationOrSSH)
34
+ self.doSSH(locationOrSSH) do |ssh|
35
+ distroInfo = self.currentDistroInfo(ssh)
36
+ reposPackages = self.mapPackages(names, distroInfo['Name'])
31
37
 
32
- pkgs = [pkgs] unless pkgs.is_a?(Array)
38
+ repos = []
39
+ pkgs = []
40
+ reposPackages.each do |pkg|
41
+ if pkg.include?('|')
42
+ repoName, pkg = pkg.split('|')
43
+ repos << repoName
44
+ pkgs << pkg
45
+ else
46
+ pkgs << pkg
47
+ end
48
+ end
49
+ repos.each do |repoName|
50
+ self.addRepo(repoName, distroInfo, ssh)
51
+ end
33
52
  command = distroInfo['InstallPackage'] + ' ' + pkgs.map { |pkg| pkg.shellescape }.join(' ')
34
- self.sshExec!(ssh, command)
53
+ if ssh
54
+ self.sshExec!(ssh, command)
55
+ else
56
+ if `echo $EUID`.strip == '0'
57
+ `#{command} >/dev/null`
58
+ else
59
+ `sudo #{command} >/dev/null`
60
+ end
61
+ end
62
+ distroInfo
35
63
  end
64
+ end
36
65
 
37
- if locationOrSSH.is_a?(String) || locationOrSSH.is_a?(Addressable::URI)
38
- self.sshStart(locationOrSSH) do |ssh|
39
- closure.call(ssh)
66
+ def self.removePackage(name, locationOrSSH, dry = false)
67
+ self.doSSH(locationOrSSH) do |ssh|
68
+ distroInfo = self.currentDistroInfo(ssh)
69
+ reposPackages = self.mapPackages([name], distroInfo['Name'])
70
+
71
+ pkgs = []
72
+ reposPackages.each do |pkg|
73
+ if pkg.include?('|')
74
+ repoName, pkg = pkg.split('|')
75
+ pkgs << pkg
76
+ else
77
+ pkgs << pkg
78
+ end
40
79
  end
41
- else
42
- closure.call(locationOrSSH)
43
- end
44
80
 
81
+ command = distroInfo['RemovePackage'] + ' ' + pkgs.map { |pkg| pkg.shellescape }.join(' ')
82
+ if ssh
83
+ self.sshExec!(ssh, command, true, dry)
84
+ else
85
+ if `echo $EUID`.strip == '0'
86
+ if dry
87
+ puts "Would execute: #{command} >/dev/null"
88
+ else
89
+ `#{command} >/dev/null`
90
+ end
91
+ else
92
+ if dry
93
+ puts "Would execute: sudo #{command} >/dev/null"
94
+ else
95
+ `sudo #{command} >/dev/null`
96
+ end
97
+ end
98
+ end
99
+ distroInfo
100
+ end
45
101
  end
46
102
 
47
103
  def ensureServiceAutoStart(name, location)
48
104
  if location && location != '@me'
49
105
  uri = Addressable::URI.parse(location)
50
106
  raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
51
- self.class.sshStart(uri) do |ssh|
52
- distroInfo = self.class.distroInfoFromSSH(ssh)
53
-
54
- command = distroInfo['AutoStartService'] + ' ' + name.shellescape
55
- self.class.sshExec!(ssh, command)
56
- end
107
+ self.class.ensureServiceAutoStartOverSSH(name, uri)
57
108
  else
58
109
  # TODO
59
110
  end
60
111
  end
61
112
 
62
- def startService(name, location)
113
+ def self.ensureServiceAutoStart(name, locationOrSSH)
114
+ self.execDistroCommand(name, 'AutoStartService', locationOrSSH)
115
+ end
116
+
117
+ # Deprecated
118
+ def self.ensureServiceAutoStartOverSSH(name, locationOrSSH)
119
+ self.ensureServiceAutoStart(name, locationOrSSH)
120
+ end
121
+
122
+ def startService(name, location, dry = false)
63
123
  if location && location != '@me'
64
124
  uri = Addressable::URI.parse(location)
65
125
  raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
66
- self.class.sshStart(uri) do |ssh|
67
- distroInfo = self.class.distroInfoFromSSH(ssh)
126
+ self.class.startService(name, location, dry = false)
127
+ else
128
+ # TODO
129
+ end
130
+ end
131
+
132
+ def self.startService(name, locationOrSSH, dry = false)
133
+ self.execDistroCommand(name, 'StartService', locationOrSSH, false, dry)
134
+ end
68
135
 
69
- command = distroInfo['StartService'] + ' ' + name.shellescape
70
- self.class.sshExec!(ssh, command)
136
+ # Deprecated
137
+ def self.startServiceOverSSH(name, locationOrSSH, dry = false)
138
+ self.startService(name, locationOrSSH, dry)
139
+ end
140
+
141
+ def self.restartService(name, locationOrSSH, dry = false)
142
+ self.execDistroCommand(name, 'RestartService', locationOrSSH, false, dry)
143
+ end
144
+
145
+ def self.reloadService(name, locationOrSSH, dry = false)
146
+ self.execDistroCommand(name, 'ReloadService', locationOrSSH, false, dry)
147
+ end
148
+
149
+ def self.stopService(name, locationOrSSH, dry = false)
150
+ self.execDistroCommand(name, 'StopService', locationOrSSH, true, dry)
151
+ end
152
+
153
+ def self.disableService(name, locationOrSSH, dry = false)
154
+ self.execDistroCommand(name, 'DisableService', locationOrSSH, true, dry)
155
+ end
156
+
157
+ def self.reloadServiceManager(locationOrSSH, dry = false)
158
+ self.execDistroCommand(nil, 'ReloadServiceManager', locationOrSSH, false, dry)
159
+ end
160
+
161
+ def self.deleteUserAndGroup(name, locationOrSSH, dry = false)
162
+ self.execDistroCommand(name, 'DeleteUser', locationOrSSH, true, dry)
163
+ self.execDistroCommand(name, 'DeleteGroup', locationOrSSH, true, dry)
164
+ end
165
+
166
+ def self.execDistroCommand(param, commandName, locationOrSSH, allowFailure = false, dry = false)
167
+ self.doSSH(locationOrSSH) do |ssh|
168
+ distroInfo = self.currentDistroInfo(ssh)
169
+
170
+ command = distroInfo[commandName]
171
+ command += ' ' + param.shellescape unless param.nil?
172
+ self.exec(command, ssh, allowFailure, dry)
173
+ end
174
+ end
175
+
176
+ def self.doSSH(locationOrSSH, &block)
177
+ if locationOrSSH.nil? || locationOrSSH == '@me'
178
+ result = block.call(nil)
179
+ elsif locationOrSSH.is_a?(String) || locationOrSSH.is_a?(Addressable::URI)
180
+ uri = Addressable::URI.parse(locationOrSSH)
181
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
182
+
183
+ self.sshStart(locationOrSSH) do |ssh|
184
+ result = block.call(ssh)
71
185
  end
72
186
  else
73
- # TODO
187
+ result = block.call(locationOrSSH)
188
+ end
189
+ result
190
+ end
191
+
192
+ # Deprecated
193
+ def self.firewallAddServiceOverSSH(serviceName, locationOrSSH)
194
+ self.firewallAddService(serviceName, locationOrSSH)
195
+ end
196
+
197
+ # Deprecated
198
+ def self.firewallAddPortOverSSH(portName, locationOrSSH)
199
+ self.firewallAddPort(portName, locationOrSSH)
200
+ end
201
+
202
+ def self.firewallAddService(serviceName, locationOrSSH = nil, dry = false)
203
+ self.doSSH(locationOrSSH) do |ssh|
204
+ command = 'firewall-cmd --permanent --add-service ' + serviceName.shellescape
205
+ self.exec(command, ssh, true, dry)
206
+ command = 'firewall-cmd --add-service ' + serviceName.shellescape
207
+ self.exec(command, ssh, true, dry)
208
+ end
209
+ end
210
+
211
+ def self.firewallRemoveService(serviceName, locationOrSSH = nil, dry = false)
212
+ self.doSSH(locationOrSSH) do |ssh|
213
+ command = 'firewall-cmd --permanent --remove-service ' + serviceName.shellescape
214
+ self.exec(command, ssh, false, dry)
215
+ command = 'firewall-cmd --remove-service ' + serviceName.shellescape
216
+ self.exec(command, ssh, false, dry)
217
+ end
218
+ end
219
+
220
+ def self.firewallAddPort(portName, locationOrSSH = nil, dry = false)
221
+ self.doSSH(locationOrSSH) do |ssh|
222
+ command = 'firewall-cmd --permanent --add-port ' + portName.shellescape
223
+ self.exec(command, ssh, true, dry)
224
+ command = 'firewall-cmd --add-port ' + portName.shellescape
225
+ self.exec(command, ssh, true, dry)
226
+ end
227
+ end
228
+
229
+ def self.firewallRemovePort(portName, locationOrSSH = nil, dry = false)
230
+ self.doSSH(locationOrSSH) do |ssh|
231
+ command = 'firewall-cmd --permanent --remove-port ' + portName.shellescape
232
+ self.exec(command, ssh, false, dry)
233
+ command = 'firewall-cmd --remove-port ' + portName.shellescape
234
+ self.exec(command, ssh, false, dry)
74
235
  end
75
236
  end
76
237
 
77
238
  def self.mapPackages(packages, distroName)
78
- distroPackages = YAML.load_file(LINUX_FOLDER + 'Packages.yaml')
239
+ allPackages = YAML.load_file(LINUX_FOLDER + 'Packages.yaml')
79
240
  names = []
241
+ raise "Distro '#{distroName}' not implemented!" unless allPackages.key?(distroName)
242
+ distroPackages = allPackages[distroName].to_h
80
243
  packages.to_a.each do |pkg|
81
- packageName = distroPackages[distroName][pkg]
244
+ packageName = distroPackages[pkg]
82
245
  if packageName
83
- names << packageName
246
+ if packageName.is_a?(Array)
247
+ names += packageName
248
+ else
249
+ names << packageName
250
+ end
84
251
  else
85
252
  names << pkg.downcase
86
253
  end
@@ -88,20 +255,57 @@ module ConfigLMM
88
255
  names
89
256
  end
90
257
 
91
- def self.createSubuidsOverSSH(user, distroInfo, ssh)
92
- self.sshExec!(ssh, "#{distroInfo['ModifyUser']} --add-subuids 100000-165535 --add-subgids 100000-165535 #{user}")
258
+ def self.createCertificateOverSSH(ssh)
259
+ dir = "/etc/letsencrypt/live/Wildcard/"
260
+ self.sshExec!(ssh, "mkdir -p #{dir}")
261
+ # Need this temporarily before real certs are created
262
+ if !self.remoteFilePresent?(dir + 'fullchain.pem', ssh)
263
+ self.sshExec!(ssh, "openssl req -x509 -noenc -days 90 -newkey rsa:2048 -keyout #{dir}privkey.pem -out #{dir}fullchain.pem -subj '/C=US/O=ConfigLMM/CN=Wildcard'")
264
+ self.sshExec!(ssh, "cp #{dir}fullchain.pem #{dir}chain.pem")
265
+ end
266
+ dir
93
267
  end
94
268
 
95
- def self.distroID
96
- `cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2`.strip.gsub('"', '')
269
+ def self.configurePodmanServiceOverSSH(user, homedir, userComment, distroInfo, ssh)
270
+ Framework::LinuxApp.ensurePackages([PODMAN_PACKAGE], ssh)
271
+ addUserCmd = "#{distroInfo['CreateServiceUser']} --home-dir '#{homedir}' --create-home --comment '#{userComment}' #{user}"
272
+ self.sshExec!(ssh, addUserCmd, true)
273
+ self.sshExec!(ssh, "chmod o-rwx #{homedir}")
274
+ self.createSubuidsOverSSH(user, distroInfo, ssh)
275
+ self.sshExec!(ssh, "loginctl enable-linger #{user}")
276
+ self.sshExec!(ssh, "su --login #{user} --shell /bin/sh --command 'mkdir -p #{SYSTEMD_CONTAINERS_PATH}'")
97
277
  end
98
278
 
99
- def self.distroIDfromSSH(ssh)
100
- ssh.exec!('cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2').strip.gsub('"', '')
279
+ def self.addRepo(name, distroInfo, ssh = nil)
280
+ if distroInfo['Name'] == 'openSUSE Leap'
281
+ if ssh
282
+ versionId = ssh.exec!('cat /etc/os-release | grep "^VERSION_ID=" | cut -d "=" -f 2').strip.gsub('"', '')
283
+ self.sshExec!(ssh, "zypper addrepo https://download.opensuse.org/repositories/#{name}/#{versionId}/#{name}.repo", true)
284
+ self.sshExec!(ssh, "zypper --gpg-auto-import-keys refresh")
285
+ else
286
+ versionId = `cat /etc/os-release | grep "^VERSION_ID=" | cut -d "=" -f 2`.strip.gsub('"', '')
287
+ `zypper addrepo https://download.opensuse.org/repositories/#{name}/#{versionId}/#{name}.repo`
288
+ `zypper --gpg-auto-import-keys refresh`
289
+ end
290
+ else
291
+ # TODO
292
+ end
293
+ end
294
+
295
+ def self.createSubuidsOverSSH(user, distroInfo, ssh)
296
+ self.sshExec!(ssh, "#{distroInfo['ModifyUser']} --add-subuids 100000-165535 --add-subgids 100000-165535 #{user}")
297
+ end
298
+
299
+ def self.distroID(ssh = nil)
300
+ if ssh
301
+ ssh.exec!('cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2').strip.gsub('"', '')
302
+ else
303
+ `cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2`.strip.gsub('"', '')
304
+ end
101
305
  end
102
306
 
103
- def self.distroInfoFromSSH(ssh)
104
- distroInfo = self.distroInfo(self.distroIDfromSSH(ssh))
307
+ def self.currentDistroInfo(ssh)
308
+ self.distroInfo(self.distroID(ssh))
105
309
  end
106
310
 
107
311
  def self.distroInfo(distroID)
@@ -13,28 +13,77 @@ module ConfigLMM
13
13
 
14
14
  class NginxApp < Framework::Plugin
15
15
 
16
+ NGINX_PACKAGE = 'nginx'
17
+ CONFIG_DIR = '/etc/nginx/'
18
+ WWW_DIR = '/srv/www/'
19
+
16
20
  def writeNginxConfig(dir, name, id, target, activeState, context, options)
17
21
  outputFolder = options['output']
18
22
 
19
23
  updateTargetConfig(target)
20
24
 
25
+ target = target.dup
26
+ target['NginxVersion'] = 0 unless target['NginxVersion']
21
27
  template = ERB.new(File.read(dir + '/' + name + '.conf.erb'))
28
+ name = target['ConfigName'] if target['ConfigName']
22
29
  renderTemplate(template, target, outputFolder + '/nginx/servers-lmm/' + name + '.conf', options)
23
- plugins[:Nginx].actionNginxBuild(id, target, activeState, context, options)
24
30
  end
25
31
 
26
32
  def deployNginxConfig(id, target, activeState, context, options)
27
- outputFolder = options['output']
33
+ outputFolder = options['output'] + '/nginx/servers-lmm'
34
+
35
+ if target['Location'] && target['Location'] != '@me'
36
+ uri = Addressable::URI.parse(target['Location'])
37
+ raise Framework::PluginProcessError.new("Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
38
+ self.class.sshStart(uri) do |ssh|
39
+ self.class.uploadFolder(outputFolder, CONFIG_DIR, ssh)
40
+ if target['TLS']
41
+ Framework::LinuxApp.firewallAddServiceOverSSH('https', ssh)
42
+ else
43
+ Framework::LinuxApp.firewallAddServiceOverSSH('http', ssh)
44
+ end
45
+ end
46
+ else
47
+ copy(outputFolder, CONFIG_DIR, options['dry'])
48
+ end
49
+ end
50
+
51
+ def cleanupNginxConfig(name, id, state, context, options, ssh = nil)
52
+ rm('/etc/nginx/servers-lmm/' + name + '.conf', options['dry'], ssh)
53
+ end
28
54
 
29
- if !target['Location'] || target['Location'] == '@me'
30
- copy(outputFolder + '/nginx/servers-lmm', '/etc/nginx/', options['dry'])
55
+ def self.prepareNginxConfig(target, ssh = nil)
56
+ if ssh
57
+ target['NginxVersion'] = self.sshExec!(ssh, 'nginx -v').strip.split('/')[1].to_f
58
+ else
59
+ target['NginxVersion'] = `nginx -v`.strip.split('/')[1].to_f
31
60
  end
61
+ end
62
+
63
+ def self.reload(ssh = nil, dry = false)
64
+ self.exec("systemctl reload nginx", ssh, false, dry)
65
+ end
66
+
67
+ def self.ensurePackage(ssh = nil)
68
+ Framework::LinuxApp.ensurePackages([NGINX_PACKAGE], ssh)
69
+ Framework::LinuxApp.ensureServiceAutoStartOverSSH(NGINX_PACKAGE, ssh)
70
+ end
32
71
 
33
- plugins[:Nginx].actionNginxDeploy(id, target, activeState, context, options)
72
+ def useNginxProxy(dir, configName, id, target, activeState, state, context, options, ssh)
73
+ self.class.ensurePackage(ssh)
74
+ self.class.prepareNginxConfig(target, ssh)
75
+ self.writeNginxConfig(dir, configName, id, target, state, context, options)
76
+ self.deployNginxConfig(id, target, activeState, context, options)
77
+ Framework::LinuxApp.startServiceOverSSH(NGINX_PACKAGE, ssh)
78
+ self.class.reload(ssh)
34
79
  end
35
80
 
36
- def cleanupNginxConfig(name, id, state, context, options)
37
- rm('/etc/nginx/servers-lmm/' + name + '.conf', options['dry'])
81
+ def deployNginxProxyConfig(server, name, id, target, activeState, state, context, options, ssh)
82
+ target = target.dup
83
+ target['Proxy'] = server
84
+ target['Name'] = name if name
85
+ target['ConfigName'] = target['Name']
86
+ useNginxProxy(__dir__ + '/../../../../Plugins/Apps/Nginx', 'proxy', id, target, activeState, state, context, options, ssh)
38
87
  end
39
88
 
40
89
  private
@@ -7,12 +7,14 @@ require 'http'
7
7
  require 'fileutils'
8
8
  require 'net/ssh'
9
9
  require 'net/scp'
10
+ require 'open3'
10
11
 
11
12
  module ConfigLMM
12
13
  module Framework
13
14
 
14
15
  class Plugin
15
16
 
17
+ REPOS_CACHE = '~/.cache/configlmm/repos'
16
18
 
17
19
  def self.inherited(plugin)
18
20
  Store.registerPlugin(plugin)
@@ -122,11 +124,19 @@ module ConfigLMM
122
124
  end
123
125
  end
124
126
 
125
- def rm(path, dry)
127
+ def rm(path, dry, ssh = nil)
126
128
  if dry
127
- prompt.say('Would remove ' + path)
129
+ if ssh
130
+ prompt.say("Would remove ssh://#{ssh.transport.host}:#{ssh.transport.port}" + path)
131
+ else
132
+ prompt.say('Would remove ' + path)
133
+ end
128
134
  else
129
- FileUtils.rm_r(path, noop: dry)
135
+ if ssh
136
+ self.class.sshExec!(ssh, "rm -rf #{path}")
137
+ else
138
+ FileUtils.rm_r(path, noop: dry)
139
+ end
130
140
  end
131
141
  end
132
142
 
@@ -146,25 +156,41 @@ module ConfigLMM
146
156
  end
147
157
  end
148
158
 
159
+ def self.loadVariable(value, target)
160
+ variableStart = value.index('${')
161
+ return value unless variableStart
162
+ variableEnd = value.index('}', variableStart + 2)
163
+ variableName = value[variableStart + 2...variableEnd]
164
+ if variableName.start_with?('ENV:')
165
+ value = value[0...variableStart].to_s + ENV[variableName[4..variableEnd]] + value[(variableEnd + 1)..-1].to_s
166
+ else
167
+ raise 'Not implemented!'
168
+ end
169
+ value
170
+ end
171
+
149
172
  CONFIGLMM_SECTION_BEGIN = "# -----BEGIN CONFIGLMM-----\n"
150
173
  CONFIGLMM_SECTION_END = "# -----END CONFIGLMM-----\n"
151
174
 
152
- def updateLocalFile(file, options, atTop = false)
175
+ def updateLocalFile(file, options, atTop = false, comment = '#')
176
+ File.write(file, '') unless File.exist?(file)
177
+ sectionBegin = CONFIGLMM_SECTION_BEGIN.gsub('#', comment)
178
+ sectionEnd = CONFIGLMM_SECTION_END.gsub('#', comment)
153
179
  fileLines = File.read(file).lines
154
- sectionBeginIndex = fileLines.index(CONFIGLMM_SECTION_BEGIN)
155
- sectionEndIndex = fileLines.index(CONFIGLMM_SECTION_END)
180
+ sectionBeginIndex = fileLines.index(sectionBegin)
181
+ sectionEndIndex = fileLines.index(sectionEnd)
156
182
  if sectionBeginIndex.nil?
157
183
  linesBefore = []
158
184
  linesBefore = fileLines unless atTop
159
185
  linesBefore << "\n"
160
- linesBefore << CONFIGLMM_SECTION_BEGIN
161
- linesAfter = [CONFIGLMM_SECTION_END]
186
+ linesBefore << sectionBegin
187
+ linesAfter = [sectionEnd]
162
188
  linesAfter << "\n"
163
189
  linesAfter += fileLines if atTop
164
190
  else
165
191
  linesBefore = fileLines[0..sectionBeginIndex]
166
192
  if sectionEndIndex.nil?
167
- linesAfter = [CONFIGLMM_SECTION_END]
193
+ linesAfter = [sectionEnd]
168
194
  linesAfter << "\n"
169
195
  else
170
196
  linesAfter = fileLines[sectionEndIndex..fileLines.length]
@@ -178,14 +204,14 @@ module ConfigLMM
178
204
  fileWrite(file, fileLines.join(), options[:dry])
179
205
  end
180
206
 
181
- def updateRemoteFile(locationOrSSH, file, options, atTop = false, &block)
207
+ def updateRemoteFile(locationOrSSH, file, options, atTop = false, comment = '#', &block)
182
208
 
183
209
  closure = Proc.new do |ssh|
184
210
  localFile = options['output'] + '/' + SecureRandom.alphanumeric(10)
185
211
  File.write(localFile, '')
186
212
  self.class.sshExec!(ssh, "touch #{file}")
187
213
  ssh.scp.download!(file, localFile)
188
- updateLocalFile(localFile, options, atTop, &block)
214
+ updateLocalFile(localFile, options, atTop, comment, &block)
189
215
  ssh.scp.upload!(localFile, file)
190
216
  end
191
217
 
@@ -201,14 +227,23 @@ module ConfigLMM
201
227
  end
202
228
  end
203
229
 
204
- def self.remoteFilePresent?(file, ssh)
205
- result = self.sshExec!(ssh, "stat #{file}", true)
230
+ def self.filePresent?(file, ssh = nil)
231
+ result = self.exec("stat #{file}", ssh, true)
206
232
  !result.start_with?('stat: cannot')
207
233
  end
208
234
 
235
+ # DEPRECATED - use filePresent()
236
+ def self.remoteFilePresent?(file, ssh)
237
+ self.filePresent?(file, ssh)
238
+ end
239
+
240
+ def self.remoteFileContains?(file, content, ssh)
241
+ !self.sshExec!(ssh, "grep '#{content}' #{file}", true).strip.empty?
242
+ end
243
+
209
244
  def self.uploadNotPresent(file, target, ssh)
210
245
  target += '/' + File.basename(file)
211
- if !self.remoteFilePresent?(target)
246
+ if !self.remoteFilePresent?(target, ssh)
212
247
  ssh.scp.upload!(file, target)
213
248
  end
214
249
  end
@@ -217,7 +252,33 @@ module ConfigLMM
217
252
  target += '/' + File.basename(folder) + '/'
218
253
  self.sshExec!(ssh, "mkdir -p #{target}")
219
254
  Dir[folder + '/*'].each do |file|
220
- ssh.scp.upload!(file, target + File.basename(file))
255
+ ssh.scp.upload!(file, target + File.basename(file), recursive: true)
256
+ end
257
+ end
258
+
259
+ def self.exec(command, ssh = nil, allowFailure = false, dry = false)
260
+ if ssh.nil?
261
+ if dry
262
+ puts "Would execute: #{command}"
263
+ return
264
+ end
265
+ stdout, stdeerr, status = Open3.capture3(command)
266
+ if !allowFailure && !status.success?
267
+ $stderr.puts(stdout)
268
+ $stderr.puts(stdeerr)
269
+ raise Framework::PluginProcessError.new("Failed '#{command}'")
270
+ end
271
+ stdout + stdeerr
272
+ else
273
+ self.sshExec!(ssh, command, allowFailure, dry)
274
+ end
275
+ end
276
+
277
+ def self.cmdSuccess?(command, ssh = nil)
278
+ if ssh.nil?
279
+ system(command, :out => File::NULL)
280
+ else
281
+ self.sshSuccess?(ssh, command)
221
282
  end
222
283
  end
223
284
 
@@ -229,6 +290,15 @@ module ConfigLMM
229
290
  [server, params]
230
291
  end
231
292
 
293
+ def self.cmdSSH(uri)
294
+ uri = Addressable::URI.parse(uri) if uri.is_a?(String)
295
+ server, sshParams = self.toSSHparams(uri)
296
+ cmd = 'ssh '
297
+ cmd += '-p ' + sshParams[:port] if sshParams[:port]
298
+ cmd += sshParams[:user] + '@' if sshParams[:port]
299
+ cmd + server
300
+ end
301
+
232
302
  def self.sshStart(uri)
233
303
  uri = Addressable::URI.parse(uri) if uri.is_a?(String)
234
304
  server, sshParams = self.toSSHparams(uri)
@@ -237,7 +307,11 @@ module ConfigLMM
237
307
  end
238
308
  end
239
309
 
240
- def self.sshExec!(ssh, command, allowFailure = false)
310
+ def self.sshExec!(ssh, command, allowFailure = false, dry = false)
311
+ if dry
312
+ puts "Would execute: ssh #{ssh.transport.host} -p #{ssh.transport.port} '#{command}'"
313
+ return
314
+ end
241
315
  status = {}
242
316
  output = ''
243
317
  channel = ssh.exec(command, status: status) do |channel, stream, data|
@@ -251,6 +325,12 @@ module ConfigLMM
251
325
  output
252
326
  end
253
327
 
328
+ def self.sshSuccess?(ssh, command)
329
+ status = {}
330
+ ssh.exec!(command, status)
331
+ status[:exit_code].zero?
332
+ end
333
+
254
334
  def renderTemplate(template, target, outputPath, options)
255
335
  variables = {
256
336
  config: target,
@@ -264,6 +344,22 @@ module ConfigLMM
264
344
  end
265
345
  end
266
346
 
347
+ def cleanupType(type, configs, state, context, options)
348
+ items = state.selectType(type)
349
+ items.each do |id, item|
350
+ if !configs.key?(id) && item['Status'] != State::STATUS_DESTROYED && (item['Status'] != State::STATUS_DELETED || options[:destroy])
351
+ if item['Location'].nil? || item['Location'] == '@me'
352
+ yield(item, id, state, context, options, nil)
353
+ else
354
+ uri = Addressable::URI.parse(item['Location'])
355
+ self.class.sshStart(uri) do |ssh|
356
+ yield(item, id, state, context, options, ssh)
357
+ end
358
+ end
359
+ end
360
+ end
361
+ end
362
+
267
363
  def self.normalizeId(id)
268
364
  id = id.split('::').last
269
365
  if id.downcase.end_with?('plugin')