ConfigLMM 0.4.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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -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 +7 -1
  13. data/Plugins/Apps/Authentik/Authentik-Server.container +6 -1
  14. data/Plugins/Apps/Authentik/Authentik-Worker.container +6 -1
  15. data/Plugins/Apps/Authentik/Authentik.conf.erb +12 -7
  16. data/Plugins/Apps/Authentik/Authentik.lmm.rb +226 -61
  17. data/Plugins/Apps/BookStack/BookStack.conf.erb +0 -3
  18. data/Plugins/Apps/BookStack/BookStack.container +5 -0
  19. data/Plugins/Apps/BookStack/BookStack.lmm.rb +14 -3
  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 +5 -0
  28. data/Plugins/Apps/Discourse/Discourse.conf.erb +1 -4
  29. data/Plugins/Apps/Discourse/Discourse.container +4 -0
  30. data/Plugins/Apps/Discourse/Discourse.lmm.rb +116 -55
  31. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +74 -62
  32. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +6 -1
  33. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +5 -0
  34. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +5 -0
  35. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +6 -1
  36. data/Plugins/Apps/ERPNext/ERPNext.container +6 -1
  37. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +138 -127
  38. data/Plugins/Apps/GitLab/GitLab.container +6 -0
  39. data/Plugins/Apps/GitLab/GitLab.lmm.rb +43 -49
  40. data/Plugins/Apps/Homepage/Homepage.conf.erb +86 -0
  41. data/Plugins/Apps/Homepage/Homepage.container +19 -0
  42. data/Plugins/Apps/Homepage/Homepage.lmm.rb +54 -0
  43. data/Plugins/Apps/IPFS/IPFS.conf.erb +0 -3
  44. data/Plugins/Apps/IPFS/IPFS.lmm.rb +0 -1
  45. data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +0 -3
  46. data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +0 -1
  47. data/Plugins/Apps/Jackett/Jackett.conf.erb +0 -3
  48. data/Plugins/Apps/Jackett/Jackett.lmm.rb +0 -1
  49. data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +0 -3
  50. data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +0 -1
  51. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +49 -28
  52. data/Plugins/Apps/LibreTranslate/LibreTranslate.container +21 -0
  53. data/Plugins/Apps/LibreTranslate/LibreTranslate.lmm.rb +34 -0
  54. data/Plugins/Apps/Lobsters/Containerfile +81 -0
  55. data/Plugins/Apps/Lobsters/Lobsters-Tasks.container +26 -0
  56. data/Plugins/Apps/Lobsters/Lobsters.conf.erb +99 -0
  57. data/Plugins/Apps/Lobsters/Lobsters.container +27 -0
  58. data/Plugins/Apps/Lobsters/Lobsters.lmm.rb +196 -0
  59. data/Plugins/Apps/Lobsters/crontab +3 -0
  60. data/Plugins/Apps/Lobsters/database.yml +26 -0
  61. data/Plugins/Apps/Lobsters/entrypoint.sh +30 -0
  62. data/Plugins/Apps/Lobsters/generateCredentials.rb +19 -0
  63. data/Plugins/Apps/Lobsters/lobsters-cron.sh +25 -0
  64. data/Plugins/Apps/Lobsters/lobsters-daily.sh +23 -0
  65. data/Plugins/Apps/Lobsters/puma.rb +49 -0
  66. data/Plugins/Apps/MariaDB/Connection.rb +55 -0
  67. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +60 -53
  68. data/Plugins/Apps/Mastodon/Mastodon-Sidekiq.container +22 -0
  69. data/Plugins/Apps/Mastodon/Mastodon-Streaming.container +20 -0
  70. data/Plugins/Apps/Mastodon/Mastodon.conf.erb +34 -45
  71. data/Plugins/Apps/Mastodon/Mastodon.container +28 -0
  72. data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +240 -5
  73. data/Plugins/Apps/Mastodon/configlmm.rake +30 -0
  74. data/Plugins/Apps/Mastodon/entrypoint.sh +16 -0
  75. data/Plugins/Apps/Matrix/Element.container +5 -0
  76. data/Plugins/Apps/Matrix/Matrix.conf.erb +2 -8
  77. data/Plugins/Apps/Matrix/Matrix.lmm.rb +100 -71
  78. data/Plugins/Apps/Matrix/Synapse.container +5 -0
  79. data/Plugins/Apps/Netdata/Netdata.conf.erb +0 -3
  80. data/Plugins/Apps/Netdata/Netdata.lmm.rb +0 -1
  81. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +3 -4
  82. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +150 -68
  83. data/Plugins/Apps/Nextcloud/autoconfig.php +13 -0
  84. data/Plugins/Apps/Nextcloud/config.php +10 -1
  85. data/Plugins/Apps/Nextcloud/nextcloudcron.service +8 -0
  86. data/Plugins/Apps/Nextcloud/nextcloudcron.timer +10 -0
  87. data/Plugins/Apps/Nginx/Connection.rb +93 -0
  88. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +50 -9
  89. data/Plugins/Apps/Nginx/conf.d/languages.conf +21 -0
  90. data/Plugins/Apps/Nginx/config-lmm/errors.conf +25 -20
  91. data/Plugins/Apps/Nginx/config-lmm/gateway-errors.conf +20 -0
  92. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +1 -1
  93. data/Plugins/Apps/Nginx/main.conf.erb +7 -3
  94. data/Plugins/Apps/Nginx/nginx.conf +2 -2
  95. data/Plugins/Apps/Nginx/nginx.lmm.rb +99 -81
  96. data/Plugins/Apps/Nginx/proxy.conf.erb +11 -3
  97. data/Plugins/Apps/Odoo/Odoo.conf.erb +0 -3
  98. data/Plugins/Apps/Odoo/Odoo.container +5 -0
  99. data/Plugins/Apps/Odoo/Odoo.lmm.rb +4 -5
  100. data/Plugins/Apps/Ollama/Ollama.container +26 -0
  101. data/Plugins/Apps/Ollama/Ollama.lmm.rb +73 -0
  102. data/Plugins/Apps/OpenTelemetry/Config/config.yaml +704 -0
  103. data/Plugins/Apps/OpenTelemetry/OpenTelemetry.lmm.rb +154 -0
  104. data/Plugins/Apps/OpenVidu/Ingress.container +5 -0
  105. data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +0 -3
  106. data/Plugins/Apps/OpenVidu/OpenVidu.container +5 -0
  107. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +7 -3
  108. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +0 -3
  109. data/Plugins/Apps/OpenVidu/OpenViduCall.container +5 -0
  110. data/Plugins/Apps/PHP-FPM/Connection.rb +91 -0
  111. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +31 -4
  112. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +2 -5
  113. data/Plugins/Apps/Peppermint/Peppermint.container +5 -0
  114. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +29 -33
  115. data/Plugins/Apps/Perplexica/Perplexica.container +25 -0
  116. data/Plugins/Apps/Perplexica/Perplexica.lmm.rb +92 -0
  117. data/Plugins/Apps/Perplexica/config.toml +26 -0
  118. data/Plugins/Apps/Podman/Connection.rb +24 -0
  119. data/Plugins/Apps/Podman/Podman.lmm.rb +80 -0
  120. data/Plugins/Apps/Podman/storage.conf +6 -0
  121. data/Plugins/Apps/Postfix/Postfix.lmm.rb +242 -164
  122. data/Plugins/Apps/PostgreSQL/Connection.rb +97 -0
  123. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +184 -148
  124. data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +0 -3
  125. data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +0 -2
  126. data/Plugins/Apps/Pterodactyl/Wings.conf.erb +0 -3
  127. data/Plugins/Apps/RVM/RVM.lmm.rb +57 -0
  128. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +0 -3
  129. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +15 -19
  130. data/Plugins/Apps/SSH/SSH.lmm.rb +9 -15
  131. data/Plugins/Apps/SearXNG/SearXNG.container +22 -0
  132. data/Plugins/Apps/SearXNG/SearXNG.lmm.rb +79 -0
  133. data/Plugins/Apps/SearXNG/limiter.toml +40 -0
  134. data/Plugins/Apps/SearXNG/settings.yml +2 -0
  135. data/Plugins/Apps/SigNoz/Config/alerts.yml +11 -0
  136. data/Plugins/Apps/SigNoz/Config/otel-collector-config.yaml +110 -0
  137. data/Plugins/Apps/SigNoz/Config/otel-collector-opamp-config.yaml +1 -0
  138. data/Plugins/Apps/SigNoz/Config/prometheus.yml +18 -0
  139. data/Plugins/Apps/SigNoz/SigNoz-Collector.container +23 -0
  140. data/Plugins/Apps/SigNoz/SigNoz-Migrator.container +17 -0
  141. data/Plugins/Apps/SigNoz/SigNoz.conf.erb +61 -0
  142. data/Plugins/Apps/SigNoz/SigNoz.container +26 -0
  143. data/Plugins/Apps/SigNoz/SigNoz.lmm.rb +319 -0
  144. data/Plugins/Apps/Solr/log4j2.xml +89 -0
  145. data/Plugins/Apps/Solr/solr.lmm.rb +82 -0
  146. data/Plugins/Apps/Sunshine/Sunshine.conf.erb +0 -3
  147. data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +0 -1
  148. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +33 -37
  149. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +0 -3
  150. data/Plugins/Apps/Umami/Umami.container +19 -0
  151. data/Plugins/Apps/Umami/Umami.lmm.rb +108 -0
  152. data/Plugins/Apps/Valkey/Valkey.lmm.rb +54 -42
  153. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +9 -6
  154. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +7 -1
  155. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +64 -29
  156. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +1 -4
  157. data/Plugins/Apps/Wiki.js/Wiki.js.container +5 -0
  158. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +31 -37
  159. data/Plugins/Apps/YaCy/YaCy.conf.erb +93 -0
  160. data/Plugins/Apps/YaCy/YaCy.container +21 -0
  161. data/Plugins/Apps/YaCy/YaCy.lmm.rb +160 -0
  162. data/Plugins/Apps/ZooKeeper/ZooKeeper.container +24 -0
  163. data/Plugins/Apps/ZooKeeper/ZooKeeper.lmm.rb +68 -0
  164. data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +0 -3
  165. data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +0 -1
  166. data/Plugins/Apps/gollum/gollum.conf.erb +2 -4
  167. data/Plugins/Apps/gollum/gollum.container +6 -0
  168. data/Plugins/Apps/gollum/gollum.lmm.rb +51 -50
  169. data/Plugins/Apps/llama.cpp/llama.cpp.container +28 -0
  170. data/Plugins/Apps/llama.cpp/llama.cpp.lmm.rb +90 -0
  171. data/Plugins/Apps/vLLM/vLLM.container +32 -0
  172. data/Plugins/Apps/vLLM/vLLM.lmm.rb +89 -0
  173. data/Plugins/OS/General/Utils.lmm.rb +26 -0
  174. data/Plugins/OS/Linux/Connection.rb +472 -0
  175. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +25 -6
  176. data/Plugins/OS/Linux/Flavours.yaml +13 -0
  177. data/Plugins/OS/Linux/Grub/grub.cfg +10 -0
  178. data/Plugins/OS/Linux/HTTP.rb +32 -0
  179. data/Plugins/OS/Linux/Linux.lmm.rb +533 -187
  180. data/Plugins/OS/Linux/Packages.yaml +20 -1
  181. data/Plugins/OS/Linux/Services.yaml +8 -0
  182. data/Plugins/OS/Linux/Shell.rb +70 -0
  183. data/Plugins/OS/Linux/Syslinux/default +8 -0
  184. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +83 -59
  185. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +3 -0
  186. data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +29 -3
  187. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +13 -11
  188. data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +6 -5
  189. data/Plugins/Platforms/GitHub.lmm.rb +73 -28
  190. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +9 -6
  191. data/Plugins/Platforms/Proxmox/Proxmox.lmm.rb +402 -0
  192. data/Plugins/Platforms/Proxmox/XTerm.rb +321 -0
  193. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +38 -13
  194. data/Plugins/Platforms/porkbun.lmm.rb +12 -2
  195. data/Plugins/Platforms/porkbun_spec.rb +2 -2
  196. data/Plugins/Services/DNS/AmberBit.lmm.rb +1 -1
  197. data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +1 -1
  198. data/Plugins/Services/DNS/NICLV.lmm.rb +1 -1
  199. data/Plugins/Services/DNS/PowerDNS.lmm.rb +70 -68
  200. data/Plugins/Services/DNS/tonic.lmm.rb +22 -12
  201. data/lib/ConfigLMM/Framework/plugins/dns.rb +4 -3
  202. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +145 -184
  203. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +34 -17
  204. data/lib/ConfigLMM/Framework/plugins/plugin.rb +53 -181
  205. data/lib/ConfigLMM/Framework/plugins/store.rb +4 -4
  206. data/lib/ConfigLMM/Framework/variables.rb +75 -0
  207. data/lib/ConfigLMM/Framework.rb +1 -0
  208. data/lib/ConfigLMM/cli.rb +12 -6
  209. data/lib/ConfigLMM/commands/configsCommand.rb +37 -6
  210. data/lib/ConfigLMM/commands/diff.rb +33 -9
  211. data/lib/ConfigLMM/context.rb +22 -3
  212. data/lib/ConfigLMM/io/configList.rb +82 -6
  213. data/lib/ConfigLMM/io/connection.rb +143 -0
  214. data/lib/ConfigLMM/io/dhcp.rb +330 -0
  215. data/lib/ConfigLMM/io/http.rb +78 -0
  216. data/lib/ConfigLMM/io/local.rb +207 -0
  217. data/lib/ConfigLMM/io/pxe.rb +92 -0
  218. data/lib/ConfigLMM/io/ssh.rb +156 -0
  219. data/lib/ConfigLMM/io/tftp.rb +105 -0
  220. data/lib/ConfigLMM/io.rb +2 -0
  221. data/lib/ConfigLMM/secrets/envStore.rb +39 -0
  222. data/lib/ConfigLMM/secrets/fileStore.rb +43 -0
  223. data/lib/ConfigLMM/state.rb +2 -1
  224. data/lib/ConfigLMM/version.rb +2 -1
  225. data/lib/ConfigLMM.rb +1 -0
  226. data/{Examples → scripts}/configlmmAuth.sh +7 -5
  227. metadata +205 -8
@@ -24,7 +24,7 @@ module ConfigLMM
24
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