ConfigLMM 0.3.0 → 0.5.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 (250) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -0
  3. data/CNAME +1 -0
  4. data/Examples/.lmm.state.yaml +159 -0
  5. data/Examples/ConfigLMM.mm.yaml +32 -0
  6. data/Examples/Implemented.mm.yaml +252 -4
  7. data/Examples/SmallBusiness.mm.yaml +492 -0
  8. data/Plugins/Apps/Answer/answer.lmm.rb +165 -0
  9. data/Plugins/Apps/Answer/answer@.service +40 -0
  10. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.conf.erb +0 -3
  11. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.lmm.rb +0 -1
  12. data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +20 -0
  13. data/Plugins/Apps/Authentik/Authentik-Server.container +7 -1
  14. data/Plugins/Apps/Authentik/Authentik-Worker.container +7 -1
  15. data/Plugins/Apps/Authentik/Authentik.conf.erb +18 -6
  16. data/Plugins/Apps/Authentik/Authentik.lmm.rb +232 -45
  17. data/Plugins/Apps/BookStack/BookStack.conf.erb +38 -0
  18. data/Plugins/Apps/BookStack/BookStack.container +20 -0
  19. data/Plugins/Apps/BookStack/BookStack.lmm.rb +91 -0
  20. data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +9 -19
  21. data/Plugins/Apps/ClickHouse/ClickHouse.container +28 -0
  22. data/Plugins/Apps/ClickHouse/ClickHouse.lmm.rb +113 -0
  23. data/Plugins/Apps/ClickHouse/Config/listen.yaml +2 -0
  24. data/Plugins/Apps/ClickHouse/Config/logger.yaml +8 -0
  25. data/Plugins/Apps/ClickHouse/Config/zookeepers.yaml +5 -0
  26. data/Plugins/Apps/ClickHouse/Connection.rb +96 -0
  27. data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +22 -0
  28. data/Plugins/Apps/Discourse/Discourse.conf.erb +38 -0
  29. data/Plugins/Apps/Discourse/Discourse.container +21 -0
  30. data/Plugins/Apps/Discourse/Discourse.lmm.rb +156 -0
  31. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +87 -52
  32. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +24 -0
  33. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +22 -0
  34. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +22 -0
  35. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +24 -0
  36. data/Plugins/Apps/ERPNext/ERPNext.container +23 -0
  37. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +204 -0
  38. data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
  39. data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
  40. data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
  41. data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
  42. data/Plugins/Apps/GitLab/GitLab.container +9 -2
  43. data/Plugins/Apps/GitLab/GitLab.lmm.rb +52 -33
  44. data/Plugins/Apps/Homepage/Homepage.conf.erb +86 -0
  45. data/Plugins/Apps/Homepage/Homepage.container +19 -0
  46. data/Plugins/Apps/Homepage/Homepage.lmm.rb +54 -0
  47. data/Plugins/Apps/IPFS/IPFS.conf.erb +0 -3
  48. data/Plugins/Apps/IPFS/IPFS.lmm.rb +0 -1
  49. data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +0 -3
  50. data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +0 -1
  51. data/Plugins/Apps/Jackett/Jackett.conf.erb +0 -3
  52. data/Plugins/Apps/Jackett/Jackett.lmm.rb +0 -1
  53. data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +0 -3
  54. data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +0 -1
  55. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +78 -0
  56. data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
  57. data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
  58. data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
  59. data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
  60. data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
  61. data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
  62. data/Plugins/Apps/LibreTranslate/LibreTranslate.container +21 -0
  63. data/Plugins/Apps/LibreTranslate/LibreTranslate.lmm.rb +34 -0
  64. data/Plugins/Apps/Lobsters/Containerfile +81 -0
  65. data/Plugins/Apps/Lobsters/Lobsters-Tasks.container +26 -0
  66. data/Plugins/Apps/Lobsters/Lobsters.conf.erb +99 -0
  67. data/Plugins/Apps/Lobsters/Lobsters.container +27 -0
  68. data/Plugins/Apps/Lobsters/Lobsters.lmm.rb +196 -0
  69. data/Plugins/Apps/Lobsters/crontab +3 -0
  70. data/Plugins/Apps/Lobsters/database.yml +26 -0
  71. data/Plugins/Apps/Lobsters/entrypoint.sh +30 -0
  72. data/Plugins/Apps/Lobsters/generateCredentials.rb +19 -0
  73. data/Plugins/Apps/Lobsters/lobsters-cron.sh +25 -0
  74. data/Plugins/Apps/Lobsters/lobsters-daily.sh +23 -0
  75. data/Plugins/Apps/Lobsters/puma.rb +49 -0
  76. data/Plugins/Apps/MariaDB/Connection.rb +55 -0
  77. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +122 -0
  78. data/Plugins/Apps/Mastodon/Mastodon-Sidekiq.container +22 -0
  79. data/Plugins/Apps/Mastodon/Mastodon-Streaming.container +20 -0
  80. data/Plugins/Apps/Mastodon/Mastodon.conf.erb +34 -45
  81. data/Plugins/Apps/Mastodon/Mastodon.container +28 -0
  82. data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +240 -5
  83. data/Plugins/Apps/Mastodon/configlmm.rake +30 -0
  84. data/Plugins/Apps/Mastodon/entrypoint.sh +16 -0
  85. data/Plugins/Apps/Matrix/Element.container +19 -0
  86. data/Plugins/Apps/Matrix/Matrix.conf.erb +47 -9
  87. data/Plugins/Apps/Matrix/Matrix.lmm.rb +119 -5
  88. data/Plugins/Apps/Matrix/Synapse.container +22 -0
  89. data/Plugins/Apps/Matrix/config.json +50 -0
  90. data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
  91. data/Plugins/Apps/Matrix/log.config +30 -0
  92. data/Plugins/Apps/Netdata/Netdata.conf.erb +0 -3
  93. data/Plugins/Apps/Netdata/Netdata.lmm.rb +0 -1
  94. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +3 -4
  95. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +155 -48
  96. data/Plugins/Apps/Nextcloud/autoconfig.php +13 -0
  97. data/Plugins/Apps/Nextcloud/config.php +10 -1
  98. data/Plugins/Apps/Nextcloud/nextcloudcron.service +8 -0
  99. data/Plugins/Apps/Nextcloud/nextcloudcron.timer +10 -0
  100. data/Plugins/Apps/Nginx/Connection.rb +93 -0
  101. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +54 -4
  102. data/Plugins/Apps/Nginx/conf.d/languages.conf +21 -0
  103. data/Plugins/Apps/Nginx/config-lmm/errors.conf +33 -22
  104. data/Plugins/Apps/Nginx/config-lmm/gateway-errors.conf +20 -0
  105. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +6 -2
  106. data/Plugins/Apps/Nginx/main.conf.erb +7 -3
  107. data/Plugins/Apps/Nginx/nginx.conf +2 -2
  108. data/Plugins/Apps/Nginx/nginx.lmm.rb +103 -81
  109. data/Plugins/Apps/Nginx/proxy.conf.erb +24 -6
  110. data/Plugins/Apps/Odoo/Odoo.conf.erb +0 -3
  111. data/Plugins/Apps/Odoo/Odoo.container +7 -1
  112. data/Plugins/Apps/Odoo/Odoo.lmm.rb +4 -5
  113. data/Plugins/Apps/Ollama/Ollama.container +26 -0
  114. data/Plugins/Apps/Ollama/Ollama.lmm.rb +73 -0
  115. data/Plugins/Apps/OpenTelemetry/Config/config.yaml +704 -0
  116. data/Plugins/Apps/OpenTelemetry/OpenTelemetry.lmm.rb +154 -0
  117. data/Plugins/Apps/OpenVidu/Ingress.container +23 -0
  118. data/Plugins/Apps/{GitLab/GitLab.conf.erb → OpenVidu/OpenVidu.conf.erb} +8 -3
  119. data/Plugins/Apps/OpenVidu/OpenVidu.container +21 -0
  120. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +94 -0
  121. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +32 -0
  122. data/Plugins/Apps/OpenVidu/OpenViduCall.container +20 -0
  123. data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
  124. data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
  125. data/Plugins/Apps/PHP-FPM/Connection.rb +91 -0
  126. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +31 -4
  127. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +2 -9
  128. data/Plugins/Apps/Peppermint/Peppermint.container +7 -1
  129. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +29 -33
  130. data/Plugins/Apps/Perplexica/Perplexica.container +25 -0
  131. data/Plugins/Apps/Perplexica/Perplexica.lmm.rb +92 -0
  132. data/Plugins/Apps/Perplexica/config.toml +26 -0
  133. data/Plugins/Apps/Podman/Connection.rb +24 -0
  134. data/Plugins/Apps/Podman/Podman.lmm.rb +80 -0
  135. data/Plugins/Apps/Podman/storage.conf +6 -0
  136. data/Plugins/Apps/Postfix/Postfix.lmm.rb +249 -145
  137. data/Plugins/Apps/PostgreSQL/Connection.rb +97 -0
  138. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +204 -99
  139. data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +0 -3
  140. data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +0 -2
  141. data/Plugins/Apps/Pterodactyl/Wings.conf.erb +0 -3
  142. data/Plugins/Apps/RVM/RVM.lmm.rb +57 -0
  143. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +72 -0
  144. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +141 -0
  145. data/Plugins/Apps/SSH/SSH.lmm.rb +9 -15
  146. data/Plugins/Apps/SearXNG/SearXNG.container +22 -0
  147. data/Plugins/Apps/SearXNG/SearXNG.lmm.rb +79 -0
  148. data/Plugins/Apps/SearXNG/limiter.toml +40 -0
  149. data/Plugins/Apps/SearXNG/settings.yml +2 -0
  150. data/Plugins/Apps/SigNoz/Config/alerts.yml +11 -0
  151. data/Plugins/Apps/SigNoz/Config/otel-collector-config.yaml +110 -0
  152. data/Plugins/Apps/SigNoz/Config/otel-collector-opamp-config.yaml +1 -0
  153. data/Plugins/Apps/SigNoz/Config/prometheus.yml +18 -0
  154. data/Plugins/Apps/SigNoz/SigNoz-Collector.container +23 -0
  155. data/Plugins/Apps/SigNoz/SigNoz-Migrator.container +17 -0
  156. data/Plugins/Apps/SigNoz/SigNoz.conf.erb +61 -0
  157. data/Plugins/Apps/SigNoz/SigNoz.container +26 -0
  158. data/Plugins/Apps/SigNoz/SigNoz.lmm.rb +319 -0
  159. data/Plugins/Apps/Solr/log4j2.xml +89 -0
  160. data/Plugins/Apps/Solr/solr.lmm.rb +82 -0
  161. data/Plugins/Apps/Sunshine/Sunshine.conf.erb +0 -3
  162. data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +0 -1
  163. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +59 -0
  164. data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
  165. data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
  166. data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
  167. data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
  168. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +0 -3
  169. data/Plugins/Apps/Umami/Umami.container +19 -0
  170. data/Plugins/Apps/Umami/Umami.lmm.rb +108 -0
  171. data/Plugins/Apps/Valkey/Valkey.lmm.rb +64 -20
  172. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +9 -6
  173. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +7 -1
  174. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +67 -28
  175. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +39 -0
  176. data/Plugins/Apps/Wiki.js/Wiki.js.container +20 -0
  177. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +55 -0
  178. data/Plugins/Apps/YaCy/YaCy.conf.erb +93 -0
  179. data/Plugins/Apps/YaCy/YaCy.container +21 -0
  180. data/Plugins/Apps/YaCy/YaCy.lmm.rb +160 -0
  181. data/Plugins/Apps/ZooKeeper/ZooKeeper.container +24 -0
  182. data/Plugins/Apps/ZooKeeper/ZooKeeper.lmm.rb +68 -0
  183. data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +0 -3
  184. data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +0 -1
  185. data/Plugins/Apps/gollum/gollum.conf.erb +40 -4
  186. data/Plugins/Apps/gollum/gollum.container +10 -1
  187. data/Plugins/Apps/gollum/gollum.lmm.rb +56 -47
  188. data/Plugins/Apps/llama.cpp/llama.cpp.container +28 -0
  189. data/Plugins/Apps/llama.cpp/llama.cpp.lmm.rb +90 -0
  190. data/Plugins/Apps/vLLM/vLLM.container +32 -0
  191. data/Plugins/Apps/vLLM/vLLM.lmm.rb +89 -0
  192. data/Plugins/OS/General/Utils.lmm.rb +26 -0
  193. data/Plugins/OS/Linux/Connection.rb +472 -0
  194. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +81 -0
  195. data/Plugins/OS/Linux/Distributions.yaml +32 -0
  196. data/Plugins/OS/Linux/Flavours.yaml +24 -0
  197. data/Plugins/OS/Linux/Grub/grub.cfg +10 -0
  198. data/Plugins/OS/Linux/HTTP.rb +32 -0
  199. data/Plugins/OS/Linux/Linux.lmm.rb +708 -174
  200. data/Plugins/OS/Linux/Packages.yaml +67 -3
  201. data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
  202. data/Plugins/OS/Linux/Services.yaml +8 -0
  203. data/Plugins/OS/Linux/Shell.rb +70 -0
  204. data/Plugins/OS/Linux/Syslinux/default +8 -0
  205. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +93 -40
  206. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +3 -0
  207. data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +29 -3
  208. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +13 -11
  209. data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +6 -5
  210. data/Plugins/Platforms/GitHub.lmm.rb +73 -28
  211. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +10 -7
  212. data/Plugins/Platforms/Proxmox/Proxmox.lmm.rb +402 -0
  213. data/Plugins/Platforms/Proxmox/XTerm.rb +321 -0
  214. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +41 -15
  215. data/Plugins/Platforms/porkbun.lmm.rb +12 -2
  216. data/Plugins/Platforms/porkbun_spec.rb +2 -2
  217. data/Plugins/Services/DNS/AmberBit.lmm.rb +1 -1
  218. data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +1 -1
  219. data/Plugins/Services/DNS/NICLV.lmm.rb +1 -1
  220. data/Plugins/Services/DNS/PowerDNS.lmm.rb +130 -41
  221. data/Plugins/Services/DNS/tonic.lmm.rb +22 -12
  222. data/bootstrap.sh +41 -3
  223. data/lib/ConfigLMM/Framework/plugins/dns.rb +4 -3
  224. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +187 -144
  225. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +54 -6
  226. data/lib/ConfigLMM/Framework/plugins/plugin.rb +68 -140
  227. data/lib/ConfigLMM/Framework/plugins/store.rb +4 -4
  228. data/lib/ConfigLMM/Framework/variables.rb +75 -0
  229. data/lib/ConfigLMM/Framework.rb +1 -0
  230. data/lib/ConfigLMM/cli.rb +13 -5
  231. data/lib/ConfigLMM/commands/cleanup.rb +1 -0
  232. data/lib/ConfigLMM/commands/configsCommand.rb +38 -5
  233. data/lib/ConfigLMM/commands/diff.rb +33 -9
  234. data/lib/ConfigLMM/context.rb +22 -3
  235. data/lib/ConfigLMM/io/configList.rb +85 -7
  236. data/lib/ConfigLMM/io/connection.rb +143 -0
  237. data/lib/ConfigLMM/io/dhcp.rb +330 -0
  238. data/lib/ConfigLMM/io/http.rb +78 -0
  239. data/lib/ConfigLMM/io/local.rb +207 -0
  240. data/lib/ConfigLMM/io/pxe.rb +92 -0
  241. data/lib/ConfigLMM/io/ssh.rb +156 -0
  242. data/lib/ConfigLMM/io/tftp.rb +105 -0
  243. data/lib/ConfigLMM/io.rb +2 -0
  244. data/lib/ConfigLMM/secrets/envStore.rb +39 -0
  245. data/lib/ConfigLMM/secrets/fileStore.rb +43 -0
  246. data/lib/ConfigLMM/state.rb +12 -3
  247. data/lib/ConfigLMM/version.rb +2 -1
  248. data/lib/ConfigLMM.rb +1 -0
  249. data/{Examples → scripts}/configlmmAuth.sh +7 -5
  250. metadata +257 -9
@@ -5,53 +5,98 @@ module ConfigLMM
5
5
  module LMM
6
6
  class GitHub < Framework::Plugin
7
7
 
8
- def actionGitHubOrganizationRefresh(id, target, activeState, context, options)
9
-
10
- client = Octokit::Client.new(:access_token => ENV['GITHUB_TOKEN'])
11
- orgs = client.organizations.select { |org| org[:login] == target['Name'] }
12
- if orgs.empty?
13
- prompt.say("Didn\'t find organization with name #{target['Name']}")
14
- prompt.say('You need to create it manually - https://github.com/organizations/plan')
15
- raise Framework::PluginPrerequisite.new('Organization must exist!')
8
+ def actionGitHubRefresh(id, target, activeState, context, options)
9
+ if !target['Organizations'].to_h.empty?
10
+ activeState['Organizations'] = {}
11
+ target['Organizations'].each do |name, organization|
12
+ organizationRefresh(name, target, activeState, context, options)
13
+ end
16
14
  end
15
+ end
17
16
 
18
- raise "This shouldn't happen!" if orgs.length != 1
19
-
20
- activeState.clear
17
+ def actionGitHubDiff(id, target, activeState, context, options)
18
+ state = prepareState(target, activeState)
19
+ shouldMatch(id, state, 'Organizations', target, 'Organizations')
20
+ end
21
21
 
22
- orgs.first.each do |name, value|
23
- activeState[name.to_s] = value
22
+ def actionGitHubDeploy(id, target, activeState, context, options)
23
+ actionGitHubDiff(id, target, activeState, context, options)
24
+ diff.each do |name, states|
25
+ # TODO FIXME
24
26
  end
25
27
  end
26
28
 
27
- def actionGitHubOrganizationDiff(id, target, activeState, context, options)
28
- shouldMatch(id, 'Name', 'login', target, activeState)
29
- shouldMatch(id, 'Description', 'description', target, activeState)
29
+ def prepareState(target, activeState)
30
+ state = activeState.dup
31
+ state['Organizations'] ||= {}
32
+ state['Organizations'].each do |name, data|
33
+ #state['Organizations'][name]['Name'] = state['Organizations'][name].delete('Login')
34
+ state['Organizations'][name]['Description'] = state['Organizations'][name].delete('description')
35
+ end
36
+ state
30
37
  end
31
38
 
32
- def actionGitHubOrganizationDeploy(id, target, activeState, context, options)
33
- actionGitHubOrganizationDiff(id, target, activeState, context, options)
34
- diff.each do |name, states|
35
- if name == 'Name'
36
- # TODO
37
- elsif name == 'Description'
38
- # TODO
39
+ def organizationRefresh(name, target, activeState, context, options)
40
+ authToken = context.secrets.load(target['SecretId'], 'TOKEN')
41
+ authToken = context.secrets.load('GITHUB', 'TOKEN') if authToken.nil?
42
+ client = Octokit::Client.new(:access_token => authToken)
43
+
44
+ allOrgs = client.organizations
45
+ if allOrgs.empty?
46
+ # Fine-grained access token never returns any orgs
47
+ org = client.organization(name)
48
+ else
49
+ orgs = allOrgs.select { |org| org[:login] == name }
50
+ if orgs.empty?
51
+ prompt.say("Didn\'t find organization with name #{name}")
52
+ prompt.say('You need to create it manually - https://github.com/organizations/plan')
53
+ raise Framework::PluginPrerequisite.new('Organization must exist!')
39
54
  end
55
+ org = orgs.first
56
+ end
57
+
58
+ activeState['Organizations'][org.login] ||= {}
59
+
60
+ org.each do |name, value|
61
+ data = value
62
+ data = value.to_h if value && value.respond_to?(:to_h)
63
+ data = value.to_s if value.is_a?(Time)
64
+ activeState['Organizations'][org.login][name.to_s] = data
40
65
  end
41
- # TODO FIXME
42
- raise 'Not implemented!'
43
66
  end
44
67
 
45
68
  def authenticate(actionMethod, target, activeState, context, options)
46
- authToken = ENV['GITHUB_TOKEN']
69
+ authToken = context.secrets.load(target['SecretId'], 'TOKEN')
70
+ authToken = context.secrets.load('GITHUB', 'TOKEN') if authToken.nil?
47
71
  if authToken.to_s.empty?
48
72
  prompt.say('Open https://github.com/settings/tokens and create a token!')
49
- prompt.say('Then set it\'s value to GITHUB_TOKEN as Environment Variable')
50
- raise Framework::PluginPrerequisite.new('Need GITHUB_TOKEN!')
73
+ prompt.say("Then set it\'s value in #{target['SecretId']}_TOKEN")
74
+ raise Framework::PluginPrerequisite.new('Need GitHub token!')
51
75
  end
52
76
  true
53
77
  end
54
78
 
79
+ def self.getReleases(repoId, logger, context, options)
80
+ response = HTTP.get("https://api.github.com/repos/#{repoId}/releases")
81
+ if response.status.success?
82
+ releases = response.parse
83
+ releases.reject! { |release| release['draft'] || release['prerelease'] }
84
+ return releases
85
+ end
86
+ logger.error("Failed to load GitHub release for #{repoId}")
87
+ raise response
88
+ end
89
+
90
+ def self.getReleaseAsset(name, releases)
91
+ pattern = name.gsub('.', '\\.').gsub('*', '.*')
92
+ releases.each do |release|
93
+ release['assets'].each do |asset|
94
+ return asset if asset['name'].match?(pattern)
95
+ end
96
+ end
97
+ raise "Couldn't find GitHub asset #{name}!"
98
+ end
99
+
55
100
  end
56
101
  end
57
102
  end
@@ -21,10 +21,10 @@ module ConfigLMM
21
21
 
22
22
  template = ERB.new(File.read(__dir__ + '/zone.txt.erb'))
23
23
 
24
- target['DNS'].each do |domain, data|
24
+ target['DNS'].to_h.each do |domain, data|
25
25
  config = { 'Domain' => domain, 'Records' => '' }
26
26
  data.each do |name, data|
27
- self.processDNS(domain, data).each do |type, records|
27
+ self.processDNS(domain, data, context).each do |type, records|
28
28
  records.each do |record|
29
29
  shortName = Addressable::IDNA.to_ascii(name) + '.' if type == 'CNAME' || type == 'ALIAS'
30
30
  if record[:type] == 'MX'
@@ -42,7 +42,9 @@ module ConfigLMM
42
42
 
43
43
  def actionGoDaddyDNSRefresh(id, target, activeState, context, options)
44
44
  if USE_API
45
- http = HTTP.auth("sso-key #{ENV['GODADDY_SECRET']}")
45
+ authSecret = context.secrets.load(target['SecretId'], 'SECRET')
46
+ authSecret = context.secrets.load('GODADDY', 'SECRET') if authSecret.nil?
47
+ http = HTTP.auth("sso-key #{authSecret}")
46
48
  apiDomain = (options['dry'] || target['Test']) ? TEST_API_DOMAIN : API_DOMAIN
47
49
 
48
50
  target['DNS'].each do |domain, records|
@@ -65,7 +67,7 @@ module ConfigLMM
65
67
  if USE_API
66
68
  # TODO
67
69
  else
68
- showManualDNSSteps(target, "Click on DNS tab and either import generated Zone file or add these records:") do |domain|
70
+ showManualDNSSteps(target, "Click on DNS tab and either import generated Zone file or add these records:", context) do |domain|
69
71
  prompt.say("Open https://dcc.godaddy.com/control/portfolio/#{domain}/settings", :color => :magenta)
70
72
  end
71
73
  end
@@ -73,11 +75,12 @@ module ConfigLMM
73
75
 
74
76
  def authenticate(actionMethod, target, activeState, context, options)
75
77
  if USE_API
76
- authSecret = ENV['GODADDY_SECRET']
78
+ authSecret = context.secrets.load(target['SecretId'], 'SECRET')
79
+ authSecret = context.secrets.load('GODADDY', 'SECRET') if authSecret.nil?
77
80
  if authSecret.to_s.empty?
78
81
  prompt.say('Open https://developer.godaddy.com/keys and create API Key!')
79
- prompt.say('Then set "KEY:SECRET" to GODADDY_SECRET as Environment Variable')
80
- raise Framework::PluginPrerequisite.new('Need GODADDY_SECRET')
82
+ prompt.say("Then set \"KEY:SECRET\" in #{target['SecretId']}_SECRET")
83
+ raise Framework::PluginPrerequisite.new('Need GoDaddy secret!')
81
84
  end
82
85
  end
83
86
  true
@@ -0,0 +1,402 @@
1
+
2
+ require_relative 'XTerm'
3
+ require 'fog/proxmox'
4
+ require 'cgi'
5
+ require 'addressable/uri'
6
+
7
+ module ConfigLMM
8
+ module LMM
9
+ class Proxmox < Framework::Plugin
10
+
11
+ def self.buildURI(uri)
12
+ uri = uri.dup
13
+ uri.scheme = 'https'
14
+ uri.host = Addressable::IDNA.to_ascii(uri.host)
15
+ uri.port = 8006 if uri.port.nil?
16
+ uri.path = '/api2/json' if uri.path.to_s.empty? || uri.path == '/'
17
+ uri.query = nil
18
+ uri
19
+ end
20
+
21
+ def self.getAuthParams(uri, context)
22
+ uri = Addressable::URI.parse(uri) if uri.is_a?(String)
23
+ raise 'Invalid Proxmox URL!' unless uri.scheme == 'proxmox'
24
+ connectionOptions = { }
25
+ parsedQuery = CGI.parse(uri.query)
26
+ if parsedQuery['insecure']
27
+ connectionOptions[:ssl_verify_peer] = false
28
+ end
29
+ secretId = parsedQuery['proxmoxSecretId'].to_a.first || 'PROXMOX'
30
+ uri = self.buildURI(uri)
31
+
32
+ proxmoxUsername = context.secrets.load(secretId, 'PROXMOX_USER')
33
+ proxmoxPassword = context.secrets.load(secretId, 'PROXMOX_PASSWORD')
34
+ if !proxmoxPassword
35
+ proxmoxUsername = 'root@pam'
36
+ proxmoxPassword = context.secrets.load(secretId, 'ROOT_PASSWORD')
37
+ end
38
+
39
+ raise 'Missing Proxmox password!' unless proxmoxPassword
40
+
41
+ authParams = {
42
+ proxmox_url: uri.to_s,
43
+ connection_options: connectionOptions,
44
+ proxmox_auth_method: 'access_ticket',
45
+ proxmox_username: proxmoxUsername,
46
+ proxmox_password: proxmoxPassword
47
+ }
48
+ authParams
49
+ end
50
+
51
+ def self.getNode(authParams)
52
+ # For some reason Proxmox doesn't handle SSL shutdown correctly so we use this workaround
53
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
54
+
55
+ compute = Fog::Compute.new(provider: :proxmox, **authParams)
56
+ node = compute.nodes.find { |node| node.node == 'pve' }
57
+ raise 'Couldn\'t find pve node!' unless node
58
+ [node, compute]
59
+ ensure
60
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
61
+ end
62
+
63
+ def actionProxmoxDeploy(id, target, activeState, context, options)
64
+ authParams = self.class.getAuthParams(target['Location'], context)
65
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
66
+
67
+ if !target['Storage'].to_h.empty?
68
+ storage = Fog::Storage.new(provider: :proxmox, **authParams)
69
+ all = storage.list
70
+ target['Storage'].each do |name, data|
71
+ if all.none? { |entry| entry['storage'] == name }
72
+ storage.create({ 'storage' => name, **data })
73
+ end
74
+ end
75
+ end
76
+ ensure
77
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
78
+ end
79
+
80
+ def createVM(serverName, serverInfo, targetUri, iso, activeState, context)
81
+ authParams = self.class.getAuthParams(targetUri, context)
82
+ node, compute = self.class.getNode(authParams)
83
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
84
+ server = node.servers.find { |server| server.name == serverName }
85
+ if server
86
+ if server.status != 'running'
87
+ server.action('start')
88
+ server.wait_for { server.ready? }
89
+ end
90
+ return false
91
+ end
92
+
93
+ isoStorages = node.storages.list_by_content_type('iso')
94
+ isoStorageName = isoStorages.first.storage
95
+
96
+ storage = Fog::Storage.new(provider: :proxmox, **authParams)
97
+ file = File.open(iso, 'rb')
98
+ filename = File.basename(iso)
99
+ storage.upload({
100
+ node: node.node,
101
+ storage: isoStorageName
102
+ },
103
+ {
104
+ content: 'iso',
105
+ file: file,
106
+ filename: filename
107
+ }
108
+ )
109
+
110
+ settings = {
111
+ vmid: node.servers.next_id,
112
+ name: serverName,
113
+ bios: 'ovmf',
114
+ boot: 'order=virtio0;scsi0;net0',
115
+ cpu: 'cputype=host',
116
+ machine: 'q35',
117
+ onboot: 1,
118
+ ostype: 'l26',
119
+ scsi0: "#{isoStorageName}:iso/#{filename},media=cdrom",
120
+ scsihw: 'virtio-scsi-pci',
121
+ serial0: 'socket',
122
+ vga: 'qxl'
123
+ }
124
+
125
+ if serverInfo['CPU']
126
+ settings[:cores] = serverInfo['CPU']
127
+ end
128
+ if serverInfo['RAM']
129
+ settings[:memory] = Filesize.from(serverInfo['RAM']).to_f('MiB').to_i
130
+ end
131
+
132
+ if serverInfo['NIC']
133
+ nics = serverInfo['NIC']
134
+ nics = [nics] unless nics.is_a?(Array)
135
+ nics.each_with_index do |nic, i|
136
+ nic.transform_keys!(&:downcase)
137
+ nic['model'] = 'virtio' unless nic['model']
138
+ if nic['mac']
139
+ nic['macaddr'] = nic['mac']
140
+ nic.delete('mac')
141
+ end
142
+ if nic['vlan']
143
+ nic['tag'] = nic['vlan']
144
+ nic.delete('vlan')
145
+ end
146
+ settings["net#{i}"] = nic.map { |name_value| name_value.join('=') }.join(',')
147
+ end
148
+ elsif serverInfo['NetworkBridge']
149
+ settings['net0'] = "virtio,bridge=#{serverInfo['NetworkBridge']}"
150
+ end
151
+
152
+ server = node.servers.create(settings)
153
+
154
+ imageStorages = node.storages.list_by_content_type('images')
155
+ imageStorageName = imageStorages.first.storage
156
+ efidisk = { id: 'efidisk0', storage: imageStorageName, size: '528' }
157
+ server.attach(efidisk, { efitype: '4m', 'pre-enrolled-keys': 1 })
158
+
159
+ if serverInfo['Storage']
160
+ disk = { id: 'virtio0', storage: imageStorageName, size: Filesize.from(serverInfo['Storage']).to_f('GiB').to_i }
161
+ server.attach(disk, { replicate: 0 })
162
+ end
163
+
164
+ if server.status != 'running'
165
+ server.action('start')
166
+ server.wait_for { server.ready? }
167
+ end
168
+ true
169
+ ensure
170
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
171
+ end
172
+
173
+ def createContainer(serverInfo, targetUri, flavourInfo, activeState, context)
174
+ authParams = self.class.getAuthParams(targetUri, context)
175
+ node, compute = self.class.getNode(authParams)
176
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
177
+
178
+ serverInfo['Domain'] = serverInfo['Name'] unless serverInfo['Domain']
179
+ container = node.containers.find { |container| container.name == Addressable::IDNA.to_ascii(serverInfo['Domain']) }
180
+ if container
181
+ if container.status != 'running'
182
+ container.action('start')
183
+ container.wait_for { container.ready? }
184
+ end
185
+ return false
186
+ end
187
+
188
+ raise Framework::PluginProcessError.new("Don't have LXC template!") unless flavourInfo['LXC']
189
+
190
+ storage = Fog::Storage.new(provider: :proxmox, **authParams)
191
+ appliances = storage.list_appliances({ node: node.node })
192
+ appliance = appliances.find { |appliance| appliance['package'] == flavourInfo['LXC'] }
193
+ raise "Couldn't find LXC template #{flavourInfo['LXC']}" unless appliance
194
+ templateStorages = node.storages.list_by_content_type('vztmpl')
195
+ templateStorageName = templateStorages.first.storage
196
+ storage.download_appliance({ node: node.node }, { storage: templateStorageName, template: appliance['template'] })
197
+
198
+ settings = {
199
+ vmid: node.servers.next_id,
200
+ ostemplate: "#{templateStorageName}:vztmpl/#{appliance['template']}",
201
+ onboot: 1,
202
+ unprivileged: 1
203
+ }
204
+
205
+ if serverInfo['CPU']
206
+ settings[:cores] = serverInfo['CPU']
207
+ end
208
+
209
+ if serverInfo['RAM']
210
+ settings[:memory] = Filesize.from(serverInfo['RAM'].to_s).to_f('MiB').to_i
211
+ end
212
+
213
+ if serverInfo['Swap']
214
+ settings[:swap] = Filesize.from(serverInfo['Swap'].to_s).to_f('MiB').to_i
215
+ end
216
+
217
+ if serverInfo['Storage']
218
+ storagePool = serverInfo['StoragePool']
219
+ if !storagePool
220
+ storages = node.storages.list_by_content_type('rootdir')
221
+ storagePool = storages.first.storage
222
+ end
223
+ settings[:rootfs] = storagePool + ':' + Filesize.from(serverInfo['Storage'].to_s).to_f('GiB').to_i.to_s
224
+ end
225
+
226
+ if serverInfo['Domain']
227
+ settings[:hostname] = Addressable::IDNA.to_ascii(serverInfo['Domain'])
228
+ end
229
+
230
+ if serverInfo['Network'].is_a?(Hash) && serverInfo['Network']['DNS']
231
+ settings[:nameserver] = serverInfo['Network']['DNS']
232
+ end
233
+
234
+ if serverInfo['NIC']
235
+ nics = serverInfo['NIC']
236
+ nics = [nics] unless nics.is_a?(Array)
237
+ nics.each_with_index do |nic, i|
238
+ nic.transform_keys!(&:downcase)
239
+ nic['name'] = "eth#{i}" unless nic['name']
240
+ if nic['mac']
241
+ nic['hwaddr'] = nic['mac']
242
+ nic.delete('mac')
243
+ end
244
+ if nic['vlan']
245
+ nic['tag'] = nic['vlan']
246
+ nic.delete('vlan')
247
+ end
248
+ nic[:ip] = 'dhcp'
249
+ if serverInfo['Network'].is_a?(Hash)
250
+ if nic['name'] == 'eth0'
251
+ if serverInfo['Network'].key?('IP')
252
+ nic[:ip] = serverInfo['Network']['IP']
253
+ end
254
+ if serverInfo['Network'].key?('Gateway')
255
+ nic[:gw] = serverInfo['Network']['Gateway']
256
+ end
257
+ else
258
+ interface = serverInfo['Network']['Interfaces'][nic['name']]
259
+ nic[:ip] = interface['IP'] if interface
260
+ end
261
+ end
262
+ settings["net#{i}"] = nic.map { |name_value| name_value.join('=') }.join(',')
263
+ end
264
+ elsif serverInfo['NetworkBridge']
265
+ nic = {
266
+ name: 'eth0',
267
+ bridge: serverInfo['NetworkBridge'],
268
+ ip: 'dhcp'
269
+ }
270
+
271
+ if serverInfo['Network'].is_a?(Hash)
272
+ if serverInfo['Network'].key?('IP')
273
+ nic[:ip] = serverInfo['Network']['IP']
274
+ end
275
+ if serverInfo['Network'].key?('Gateway')
276
+ nic[:gw] = serverInfo['Network']['Gateway']
277
+ end
278
+ end
279
+ settings['net0'] = nic.map { |name_value| name_value.join('=') }.join(',')
280
+ end
281
+
282
+ if flavourInfo['Type']
283
+ settings[:ostype] = flavourInfo['Type']
284
+ end
285
+
286
+ if serverInfo['Users']['root'].key?('Password')
287
+ settings[:password] = serverInfo['Users']['root']['Password']
288
+ end
289
+
290
+ if !serverInfo['Users']['root']['AuthorizedKeys'].to_a.empty?
291
+ settings['ssh-public-keys'] = serverInfo['Users']['root']['AuthorizedKeys'].join("\n")
292
+ end
293
+
294
+ if serverInfo['Features']
295
+ settings[:features] = serverInfo['Features'].map { |feature| "#{feature}=1" }.join(',')
296
+ end
297
+
298
+ container = node.containers.create(settings)
299
+
300
+ if serverInfo['LXC'].is_a?(Array)
301
+ self.addLXCOptions(serverInfo, targetUri, compute, node.node, container.vmid, context)
302
+ end
303
+
304
+ # TODO - Need to be readable/executable by everyone. Otherwise some things will break inside container like `su`
305
+ # if storageIsSubvolume
306
+ # proxmoxServer.exec("chmod +rx #{storagePath}/images/$ID/subvol-$ID-disk-0.subvol")
307
+ #end
308
+
309
+ if container.status != 'running'
310
+ container.action('start')
311
+ container.wait_for { container.ready? }
312
+ end
313
+ true
314
+ ensure
315
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
316
+ end
317
+
318
+ def addLXCOptions(serverInfo, uri, compute, node, vmid, context)
319
+ options = serverInfo['LXC'].map { |option| 'lxc.' + option.map { |name, value| "#{name}: #{value}" }.first }.join("\n")
320
+
321
+ uri = Addressable::URI.parse(uri) if uri.is_a?(String)
322
+ self.class.xtermTunnel(uri, serverInfo, compute, node, nil, nil, context, prompt, logger) do |xterm|
323
+ connection = IO::Connection.new(:Proxmox, xterm, prompt, logger)
324
+ connection.exec("echo \"#{options}\" >> /etc/pve/lxc/#{vmid}.conf")
325
+ end
326
+ end
327
+
328
+ def self.withXTerm(targetUri, target, context, prompt, logger, &block)
329
+ targetUri.scheme = 'proxmox'
330
+ authParams = getAuthParams(targetUri, context)
331
+ node, compute = getNode(authParams)
332
+ name = nil
333
+ name = CGI.parse(targetUri.query)['name'] if targetUri.query
334
+ name = name.first if name
335
+
336
+ lxc = target['LXC']
337
+ if targetUri.query
338
+ parsedQuery = CGI.parse(targetUri.query)
339
+ name = parsedQuery['name']
340
+ name = name.first if name
341
+ lxc = true if parsedQuery['lxc'] && lxc.nil?
342
+ end
343
+
344
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
345
+
346
+ if lxc
347
+ unless name
348
+ name = target['Name']
349
+ name = Addressable::IDNA.to_ascii(target['Domain']) if target['Domain']
350
+ end
351
+ server = node.containers.find { |container| container.name == name }
352
+ type = 'lxc'
353
+ else
354
+ name = target['Name'] unless name
355
+ server = node.servers.find { |server| server.name == name }
356
+ type = 'qemu'
357
+ end
358
+ raise "Couldn't find server with name #{name}" unless server
359
+
360
+ self.xtermTunnel(targetUri, target, compute, node.node, type, server.vmid, context, prompt, logger, &block)
361
+ ensure
362
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
363
+ end
364
+
365
+ def self.xtermTunnel(targetUri, target, compute, node, type, vmid, context, prompt, logger, &block)
366
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
367
+ term = compute.create_term({ node: node, type: type, vmid: vmid }, {})
368
+
369
+ parsedQuery = CGI.parse(targetUri.query)
370
+ insecure = !!parsedQuery['insecure']
371
+
372
+ if target['Type'] == :Linux
373
+ username = 'root'
374
+ password = target['Users']['root']['Password']
375
+ else
376
+ secretId = parsedQuery['secretId'].to_a.first || target['SecretId']
377
+ username = context.secrets.load(secretId, 'ROOT_USER') || 'root'
378
+ password = context.secrets.load(secretId, 'ROOT_PASSWORD')
379
+ end
380
+
381
+ uri = self.buildURI(targetUri)
382
+ uri.scheme = 'wss'
383
+ if type.nil? && vmid.nil?
384
+ uri.path += "/nodes/#{node}/vncwebsocket"
385
+ else
386
+ uri.path += "/nodes/#{node}/#{type}/#{vmid}/vncwebsocket"
387
+ end
388
+ uri.query = URI.encode_www_form({ port: term['port'], vncticket: term['ticket'] })
389
+
390
+ ProxmoxXTerm.tunnel(uri.to_s, insecure, compute.token, term, username, password, prompt, logger, &block)
391
+ ensure
392
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
393
+ end
394
+
395
+ def self.getLocation(location)
396
+ uri = Addressable::URI.parse(location)
397
+ uri.hostname
398
+ end
399
+
400
+ end
401
+ end
402
+ end