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
@@ -0,0 +1,321 @@
1
+ require 'faye/websocket'
2
+ require 'eventmachine'
3
+ require 'strings-ansi'
4
+ require 'digest'
5
+
6
+ module ConfigLMM
7
+ module LMM
8
+ class ProxmoxXTerm
9
+
10
+ $WS = nil
11
+
12
+ class Tunnel
13
+
14
+ attr_reader :prompt
15
+ attr_reader :logger
16
+
17
+ def initialize(state, prompt, logger)
18
+ @State = state
19
+ @prompt = prompt
20
+ @logger = logger
21
+ waitReady()
22
+ end
23
+
24
+ def waitReady
25
+ @State[:mutex].synchronize {
26
+ @State[:condition].wait(@State[:mutex])
27
+ }
28
+ end
29
+
30
+ def exec(command, allowFailure = false, options = {})
31
+ if options['dry']
32
+ message = "Would execute: proxmox+xterm: '#{command}'"
33
+ prompt.say(message)
34
+ return ''
35
+ end
36
+ if options[:hide]
37
+ logger.debug("[xterm]# **HIDDEN**")
38
+ else
39
+ logger.debug("[xterm]# #{command}")
40
+ end
41
+ @State[:mutex].synchronize {
42
+ @State[:stage] = :command
43
+ ProxmoxXTerm.sendMessage($WS, command + "\n")
44
+ @State[:condition].wait(@State[:mutex])
45
+ }
46
+ @State[:data]
47
+ end
48
+
49
+ def adminExec(command, allowFailure = false, dry = false)
50
+ self.exec(command, allowFailure, dry)
51
+ end
52
+
53
+ def download(source, target, dry = false)
54
+ if dry
55
+ message = "Would download proxmox+xterm:#{source}"
56
+ prompt.say(message)
57
+ return
58
+ end
59
+ checksum = self.exec("md5sum #{source}").split(' ').first.strip
60
+ begin
61
+ @State[:mutex].synchronize {
62
+ @State[:stage] = :raw
63
+ ProxmoxXTerm.sendMessage($WS, "stty raw -onlcr -echo -echonl\n")
64
+ @State[:condition].wait(@State[:mutex])
65
+ }
66
+ @State[:mutex].synchronize {
67
+ @State[:stage] = :raw
68
+ ProxmoxXTerm.sendMessage($WS, "cat #{source}\n")
69
+ @State[:condition].wait(@State[:mutex])
70
+ }
71
+ compare = Digest::MD5.hexdigest(@State[:data])
72
+ if checksum != compare
73
+ raise "Failed to download #{source} file"
74
+ end
75
+ File.write(target, @State[:data])
76
+ ensure
77
+ self.exec("stty -raw onlcr echo echonl")
78
+ end
79
+ end
80
+
81
+ def upload(source, target, dry = false)
82
+ if dry
83
+ message = "Would upload #{source} to proxmox+xterm:#{target}"
84
+ prompt.say(message)
85
+ return
86
+ end
87
+ data = File.read(source)
88
+ checksum = Digest::MD5.hexdigest(data)
89
+ begin
90
+ @State[:mutex].synchronize {
91
+ @State[:stage] = :ignore
92
+ ProxmoxXTerm.sendMessage($WS, "stty raw isig -onlcr && cat > #{target}\n")
93
+ @State[:condition].wait(@State[:mutex])
94
+ }
95
+ @State[:mutex].synchronize {
96
+ ProxmoxXTerm.sendMessage($WS, data)
97
+ @State[:condition].wait(@State[:mutex])
98
+ }
99
+ @State[:mutex].synchronize {
100
+ @State[:stage] = :shell
101
+ ProxmoxXTerm.sendMessage($WS, "\u0003")
102
+ @State[:condition].wait(@State[:mutex])
103
+ }
104
+ compare = self.exec("md5sum #{target}").split(' ').first.strip
105
+ if checksum != compare
106
+ raise "Failed to upload #{source} file"
107
+ end
108
+ ensure
109
+ self.exec("stty -raw onlcr")
110
+ end
111
+ end
112
+
113
+ def updateFile(file, options, atTop = false, comment = '#', &block)
114
+ localFile = options['output'] + '/' + SecureRandom.alphanumeric(10)
115
+ File.write(localFile, '')
116
+ self.exec("touch #{file}", false, options)
117
+ self.download(file, localFile, options['dry'])
118
+ IO::Local.new(self.prompt, self.logger).updateFile(localFile, options, atTop, comment, &block)
119
+ self.upload(localFile, file, options['dry'])
120
+ end
121
+
122
+ end
123
+
124
+ def self.tunnel(url, insecure, token, term, username, password, prompt, logger, &block)
125
+ headers = {}
126
+ headers['Cookie'] = 'PVEAuthCookie=' + token
127
+ state = {
128
+ mutex: Mutex.new,
129
+ condition: ConditionVariable.new
130
+ }
131
+ $WS = nil
132
+ thread = Thread.new do
133
+ EM.run do
134
+ $WS = Faye::WebSocket::Client.new(url, [],
135
+ :headers => headers,
136
+ :tls => {
137
+ :verify_peer => !insecure
138
+ }
139
+ )
140
+ $WS.on :open do |event|
141
+ $WS.send("#{term['user']}:#{term['ticket']}\n")
142
+ state[:stage] = :start
143
+ state[:message] = ''
144
+ state[:data] = ''
145
+ state[:invalidLogin] = 0
146
+ state[:timer] = nil
147
+ state[:delay] = 0.1
148
+ end
149
+
150
+ $WS.on :message do |event|
151
+ self.processMessage($WS, event, state, username, password)
152
+ end
153
+
154
+ $WS.on(:error) do |event|
155
+ prompt.say(event.message, color: :red)
156
+ end
157
+
158
+ $WS.on :close do |event|
159
+ $WS = nil
160
+ EM.stop_event_loop
161
+ end
162
+ end
163
+ end
164
+ thread.abort_on_exception = true
165
+ yield(Tunnel.new(state, prompt, logger))
166
+ state[:mutex].synchronize {
167
+ state[:stage] = :exit
168
+ self.sendMessage($WS, "exit\n")
169
+ state[:condition].wait(state[:mutex])
170
+ }
171
+ state[:mutex].synchronize {
172
+ state[:stage] = :exit
173
+ self.sendMessage($WS, "exit\n")
174
+ state[:condition].wait(state[:mutex])
175
+ }
176
+ EM.stop_event_loop
177
+ thread.join
178
+ end
179
+
180
+ def self.processMessage(ws, event, state, username, password)
181
+ state[:message] += event.data.pack('C*')
182
+ if !state[:timer].nil?
183
+ EM.cancel_timer(state[:timer])
184
+ end
185
+ if state[:stage] == :login && state[:message].include?('unable to find a serial interface')
186
+ raise 'Console not available!'
187
+ end
188
+ if state[:stage] == :exit && state[:message].strip.end_with?('logout')
189
+ state[:mutex].synchronize {
190
+ state[:condition].signal()
191
+ }
192
+ else
193
+ state[:timer] = EM.add_timer(state[:delay]) { self.handleData(ws, state, username, password) }
194
+ end
195
+ end
196
+
197
+ def self.handleData(ws, state, username, password)
198
+ state[:delay] = 0.1
199
+ if state[:stage] == :raw
200
+ rawData = data = state[:data] = state[:message]
201
+ else
202
+ rawData = state[:message]
203
+ data = state[:data] = self.cleanupMessage(state[:message])
204
+ end
205
+ state[:message] = ''
206
+ case state[:stage]
207
+ when :start
208
+ state[:stage] = :login
209
+ raise 'Unexpected response!' unless data.start_with?('OK')
210
+ state[:delay] = 3
211
+ self.sendMessage(ws, "\n")
212
+ when :login
213
+ self.doLogin(ws, data, state, username)
214
+ when :password
215
+ self.doPassword(ws, data, state, password)
216
+ when :checkPassword
217
+ if data.lines.last.include?('login:') && data.include?('Login incorrect')
218
+ state[:invalidLogin] += 1
219
+ state[:stage] = :login
220
+ self.doLogin(ws, data, state, username)
221
+ return
222
+ end
223
+ raise 'Unexpected Console state!' unless data.lines.last.strip.end_with?('#')
224
+ state[:stage] = :shell
225
+ # Couldn't get Fish shell to work properly so force using `sh`
226
+ self.sendMessage(ws, "sh\n")
227
+ when :shell
228
+ raise 'Unexpected Console state!' unless data.lines.last.strip.end_with?('#')
229
+ state[:mutex].synchronize {
230
+ state[:condition].signal()
231
+ }
232
+ when :command
233
+ lastNewline = state[:data].rindex("\n")
234
+ if state[:data][lastNewline..-1].strip.end_with?('#')
235
+ firstNewline = state[:data].index("\n")
236
+ state[:data] = firstNewline.nil? ? '' : state[:data][(firstNewline + 1)..(lastNewline - 1)]
237
+ state[:stage] = :shell
238
+ state[:mutex].synchronize {
239
+ state[:condition].signal()
240
+ }
241
+ else
242
+ state[:message] = state[:data]
243
+ end
244
+ when :raw
245
+ lastNewline = state[:data].rindex("\n")
246
+ if !lastNewline.nil? && self.cleanupMessage(state[:data][lastNewline..-1]).strip.end_with?('#')
247
+ state[:data] = state[:data][0..lastNewline]
248
+ .gsub("\r\n", "\n")
249
+ .gsub("\e[30m\e[m\u000F\e[?2004l", '') # Fish shell...
250
+ .gsub(/\e\[2m\^J\e\[m\u000F\s+\r\^J \r\e\[/, '')
251
+ state[:stage] = :shell
252
+ state[:mutex].synchronize {
253
+ state[:condition].signal()
254
+ }
255
+ else
256
+ state[:message] = state[:data]
257
+ end
258
+ when :ignore
259
+ # Ignore
260
+ state[:mutex].synchronize {
261
+ state[:condition].signal()
262
+ }
263
+ when :exit
264
+ state[:mutex].synchronize {
265
+ state[:condition].signal()
266
+ }
267
+ else
268
+ raise 'Unknown stage!'
269
+ end
270
+ end
271
+
272
+ def self.doLogin(ws, data, state, username)
273
+ if data.include?('login:')
274
+ if state[:invalidLogin] >= 2
275
+ raise 'Too many failed login attempts!'
276
+ end
277
+ state[:stage] = :password
278
+ self.sendMessage(ws, "#{username}\n")
279
+ elsif data.strip.end_with?('#')
280
+ state[:stage] = :shell
281
+ state[:mutex].synchronize {
282
+ state[:condition].signal()
283
+ }
284
+ elsif data.include?('Password:')
285
+ state[:delay] = 3
286
+ self.sendMessage(ws, "\n")
287
+ elsif data == "\n"
288
+ state[:delay] = 10
289
+ self.sendMessage(ws, "\n")
290
+ else
291
+ raise 'Unexpected Console state!'
292
+ end
293
+ end
294
+
295
+ def self.doPassword(ws, data, state, password)
296
+ if data.include?('Password:')
297
+ state[:stage] = :checkPassword
298
+ raise 'Missing ROOT_PASSWORD!' unless password
299
+ state[:delay] = 3
300
+ self.sendMessage(ws, password + "\n")
301
+ else
302
+ raise 'Unexpected Console state!'
303
+ end
304
+ end
305
+
306
+ def self.cleanupMessage(message)
307
+ Strings::ANSI.sanitize(message)
308
+ .gsub("\r\r\n", "\n")
309
+ .gsub("\u000F", '')
310
+ .gsub("\e(B", '') # should be removed by sanitize but looks like bug there https://github.com/piotrmurach/strings-ansi/issues/4
311
+ end
312
+
313
+ def self.sendMessage(ws, message)
314
+ return unless ws
315
+ length = message.bytesize
316
+ data = '0:' + length.to_s + ':' + message
317
+ ws.send(data)
318
+ end
319
+ end
320
+ end
321
+ end
@@ -52,25 +52,27 @@ module ConfigLMM
52
52
  if serverInfo['RAM']
53
53
  settings[:memory_size] = Filesize.from(serverInfo['RAM']).to_f('KiB').to_i
54
54
  end
55
- volumeName = serverName + '.img'
56
- volume = compute.volumes.all.find { |volume| volume.name == volumeName }
57
- if volume
58
- settings[:volumes] = [volume]
59
- elsif serverInfo['Storage']
60
- storage = Filesize.from(serverInfo['Storage']).to_f('GiB').to_i
61
- volume = compute.volumes.create(
62
- name: volumeName,
63
- pool_name: compute.pools.first.name,
64
- capacity: storage
65
- )
66
- settings[:volumes] = [volume]
55
+ if serverInfo.key?('Storage')
56
+ if serverInfo['Storage'].is_a?(Array)
57
+ volumeInfos = serverInfo['Storage']
58
+ else
59
+ volumeInfos = [{ 'Size' => serverInfo['Storage'] }]
60
+ end
61
+ settings[:volumes] = getVolumes(compute, serverName, volumeInfos)
67
62
  end
68
- if serverInfo['NetworkBridge']
63
+ if serverInfo['NIC']
64
+ nics = serverInfo['NIC']
65
+ nics = [nics] unless nics.is_a?(Array)
66
+ settings[:nics] = nics.map { |nic| nic.transform_keys(&:downcase) }
67
+ elsif serverInfo['NetworkBridge']
69
68
  nic = {
70
69
  bridge: serverInfo['NetworkBridge'],
71
70
  }
72
71
  settings[:nics] = [nic]
73
72
  end
73
+ if serverInfo['Firmware'] && serverInfo['Firmware'].upcase == 'UEFI'
74
+ settings[:firmware] = 'efi'
75
+ end
74
76
  server = compute.servers.new(**settings)
75
77
  if iso
76
78
  server.iso_dir = File.dirname(iso)
@@ -83,6 +85,29 @@ module ConfigLMM
83
85
  true
84
86
  end
85
87
 
88
+ def getVolumes(compute, baseName, volumeInfos)
89
+ volumes = []
90
+ volumeInfos.each_with_index do |volumeInfo, i|
91
+ if volumeInfo['Name']
92
+ volumeName = volumeInfo['Name'] + '.img'
93
+ else
94
+ number = i.zero? ? '' : (i + 1).to_s
95
+ volumeName = baseName + number + '.img'
96
+ end
97
+ volume = compute.volumes.all.find { |volume| volume.name == volumeName }
98
+ if volume
99
+ volumes << volume
100
+ else
101
+ volumes << compute.volumes.create(
102
+ name: volumeName,
103
+ pool_name: compute.pools.first.name,
104
+ capacity: Filesize.from(volumeInfo['Size']).to_f('GiB').to_i
105
+ )
106
+ end
107
+ end
108
+ volumes
109
+ end
110
+
86
111
  def dirPoolXML(name, path)
87
112
  xml = '<pool type="dir">'
88
113
  xml += " <name>#{name.encode(:xml => :text)}</name>"
@@ -50,7 +50,7 @@ module ConfigLMM
50
50
  target['DNS'].each do |domain, info|
51
51
  presentRecords = ::Porkbun::DNS.retrieve(domain)
52
52
  if presentRecords.instance_of?(::Porkbun::Error)
53
- raise Framework::PluginProcessError.new("#{id}: #{domain} - #{result.message}")
53
+ raise Framework::PluginProcessError.new("#{id}: #{domain} - #{presentRecords.message}")
54
54
  end
55
55
  activeState['DNS'] ||= {}
56
56
  activeState['DNS'][domain] ||= {}
@@ -59,7 +59,7 @@ module ConfigLMM
59
59
  info.each do |name, data|
60
60
  name = '' if name == '@'
61
61
 
62
- self.processDNS(domain, data).each do |type, records|
62
+ self.processDNS(domain, data, context).each do |type, records|
63
63
  records.each do |record|
64
64
  found = false
65
65
  remove = []
@@ -112,6 +112,16 @@ module ConfigLMM
112
112
  end
113
113
 
114
114
  def authenticate(actionMethod, target, activeState, context, options)
115
+ if ENV['PORKBUN_API_KEY'].nil?
116
+ apiKey = context.secrets.load(target['SecretId'], 'API_KEY')
117
+ apiKey = context.secrets.load('PORKBUN', 'API_KEY') if apiKey.nil?
118
+ ENV['PORKBUN_API_KEY'] = apiKey
119
+ end
120
+ if ENV['PORKBUN_SECRET_API_KEY'].nil?
121
+ secret = context.secrets.load(target['SecretId'], 'API_SECRET')
122
+ secret = context.secrets.load('PORKBUN', 'API_SECRET') if secret.nil?
123
+ ENV['PORKBUN_SECRET_API_KEY'] = secret
124
+ end
115
125
  if ENV['PORKBUN_API_KEY'].to_s.empty? || ENV['PORKBUN_SECRET_API_KEY'].to_s.empty?
116
126
  prompt.error('Set your porkbun API key to PORKBUN_API_KEY and PORKBUN_SECRET_API_KEY as Environment Variable')
117
127
  raise Framework::PluginPrerequisite.new('Need PORKBUN_API_KEY and PORKBUN_SECRET_API_KEY')
@@ -40,7 +40,7 @@ RSpec.describe 'Porkbun' do
40
40
  }])
41
41
 
42
42
  ENV['PORKBUN_API_KEY'] = 'whatever'
43
- ENV['PORKBUN_SECRET_API_KEY'] = 'whatever'
43
+ ENV['PORKBUN_API_SECRET'] = 'whatever'
44
44
  ConfigLMM::Commands::Refresh.new('whatever.yaml', { :level => :info }).processConfig({'Porkbun' => target}, {})
45
45
 
46
46
  expect(state.item('Porkbun')).to include({
@@ -93,7 +93,7 @@ RSpec.describe 'Porkbun' do
93
93
  }).and_return(record)
94
94
 
95
95
  ENV['PORKBUN_API_KEY'] = 'whatever'
96
- ENV['PORKBUN_SECRET_API_KEY'] = 'whatever'
96
+ ENV['PORKBUN_API_SECRET'] = 'whatever'
97
97
  ConfigLMM::Commands::Deploy.new('whatever.yaml', { :level => :info }).processConfig({'Porkbun' => target}, {})
98
98
 
99
99
  expect(state.item('Porkbun')['DNS']['example.org']).to include({ '@' => {
@@ -4,7 +4,7 @@ module ConfigLMM
4
4
  class AmberBit < Framework::DNS
5
5
 
6
6
  def actionAmberBitDNSDeploy(id, target, activeState, context, options)
7
- showManualDNSSteps(target, "Click on Technical information and add these records:") do |domain|
7
+ showManualDNSSteps(target, "Click on Technical information and add these records:", context) do |domain|
8
8
  prompt.say("Open https://my.amberbit.eu/domain/list/ and under #{domain}", :color => :magenta)
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module ConfigLMM
4
4
  class ArubaItDNS < Framework::DNS
5
5
 
6
6
  def actionArubaItDNSDeploy(id, target, activeState, context, options)
7
- showManualDNSSteps(target, "then click on 'DNS and Name Server Management' and add these records:") do |domain|
7
+ showManualDNSSteps(target, "then click on 'DNS and Name Server Management' and add these records:", context) do |domain|
8
8
  prompt.say("Open https://admin.aruba.it/PannelloAdmin/LoginDomain.aspx and select #{domain}", :color => :magenta)
9
9
  end
10
10
  end
@@ -8,7 +8,7 @@ module ConfigLMM
8
8
  class NICLV < Framework::DNS
9
9
 
10
10
  def actionNICLVDNSDeploy(id, target, activeState, context, options)
11
- showManualDNSSteps(target, "and add these records:") do |domain|
11
+ showManualDNSSteps(target, "and add these records:", context) do |domain|
12
12
  prompt.say("Open https://www.nic.lv/client/topview/edit_domain?dname=#{domain}", :color => :magenta)
13
13
  end
14
14
  end