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,472 @@
1
+
2
+ require_relative 'Shell'
3
+ require_relative 'HTTP'
4
+
5
+ require 'shellwords'
6
+
7
+ module ConfigLMM
8
+ module LMM
9
+ class LinuxConnection
10
+
11
+ attr_reader :connection
12
+ attr_reader :local
13
+ attr_reader :distroID
14
+ attr_reader :distributions
15
+ attr_reader :allServices
16
+
17
+ def initialize(connection)
18
+ @connection = connection
19
+ @local = connection.local
20
+ @distroID = self.class.distroID(@connection)
21
+ @distributions = YAML.load_file(__dir__ + '/Distributions.yaml')
22
+ @allServices = YAML.load_file(__dir__ + '/Services.yaml')
23
+ end
24
+
25
+ def prompt
26
+ @connection.prompt
27
+ end
28
+
29
+ def logger
30
+ @connection.logger
31
+ end
32
+
33
+ def distroInfo
34
+ raise Framework::PluginProcessError.new("Unknown Linux Distro: #{distroID}!") unless distributions.key?(distroID)
35
+ distributions[distroID]
36
+ end
37
+
38
+ def distroName
39
+ distroInfo['Name']
40
+ end
41
+
42
+ def distroVersion
43
+ @VersionId ||= connection.exec('cat /etc/os-release | grep "^VERSION_ID=" | cut -d "=" -f 2').strip.gsub('"', '')
44
+ @VersionId
45
+ end
46
+
47
+ def exec(*args)
48
+ connection.exec(*args)
49
+ end
50
+
51
+ def rm(*args)
52
+ connection.rm(*args)
53
+ end
54
+
55
+ def filePresent?(*args)
56
+ connection.filePresent?(*args)
57
+ end
58
+
59
+ def fileLink?(*args)
60
+ connection.fileLink?(*args)
61
+ end
62
+
63
+ def upload(*args)
64
+ connection.upload(*args)
65
+ end
66
+
67
+ def download(*args)
68
+ connection.download(*args)
69
+ end
70
+
71
+ def updateFile(*args, &block)
72
+ connection.updateFile(*args, &block)
73
+ end
74
+
75
+ def uploadFolder(folder, target, options = {})
76
+ createDirs(options, target + '/' + File.basename(folder))
77
+ connection.uploadFolder(folder, target, options)
78
+ end
79
+
80
+ def escapePath(path)
81
+ escaped = path.shellescape
82
+ escaped = escaped[1..] if escaped.start_with?('\~')
83
+ escaped
84
+ end
85
+
86
+ def ensureFile(file, options = {})
87
+ connection.exec("touch #{escapePath(file)}", false, options)
88
+ end
89
+
90
+ def fileContains?(file, content, options = {})
91
+ !connection.exec("grep #{content.shellescape} #{escapePath(file)}", true, options).strip.empty?
92
+ end
93
+
94
+ def fileRead(file, options = {})
95
+ connection.exec("cat #{escapePath(file)}", false, options)
96
+ end
97
+
98
+ def fileWrite(target, data, options = {})
99
+ hide = ''
100
+ hide = ' ' if options[:hide]
101
+ connection.exec("#{hide}echo #{data.shellescape} > #{escapePath(target)}", false, options)
102
+ end
103
+
104
+ def fileAppend(target, data, options = {})
105
+ hide = ''
106
+ hide = ' ' if options[:hide]
107
+ connection.exec("#{hide}echo #{data.shellescape} >> #{escapePath(target)}", false, options)
108
+ end
109
+
110
+ def fileMerge(target, file, options = {})
111
+ connection.exec("cat #{escapePath(file)} >> #{escapePath(target)}", false, options)
112
+ end
113
+
114
+ def fileReplace(target, placeholder, result, options = {})
115
+ hide = ''
116
+ hide = ' ' if options[:hide]
117
+ result = result.to_s.gsub('\\', '\\\\\\') if options[:escape] != false
118
+ pattern = "s|#{placeholder}|#{result.to_s.gsub('&', '\\\\&').gsub('|', '\\\\|')}|"
119
+ connection.exec("#{hide}sed -Ei #{pattern.shellescape} #{escapePath(target)}", false, options)
120
+ end
121
+
122
+ def setUserGroup(path, user, group = nil, options = {})
123
+ if group
124
+ connection.exec("chown -R #{user}:#{group} #{escapePath(path)}", false, options)
125
+ else
126
+ connection.exec("chown -R #{user} #{escapePath(path)}", false, options)
127
+ end
128
+ end
129
+
130
+ def setPrivate(path, options = {})
131
+ connection.exec("chmod 600 #{escapePath(path)}", false, options)
132
+ end
133
+
134
+ def makeAccessible(path, options = {})
135
+ connection.exec("chmod og+rX #{escapePath(path)}", false, options)
136
+ end
137
+
138
+ def makeExecutable(path, options = {})
139
+ connection.exec("chmod +x #{escapePath(path)}", false, options)
140
+ end
141
+
142
+ def createDirs(options, *paths)
143
+ paths = paths.map { |path| escapePath(path) }
144
+ connection.exec("mkdir -p #{paths.join(' ')}", false, options)
145
+ end
146
+
147
+ def http(url, options, headers = {}, method = 'GET', data = nil, cookieFile = nil)
148
+ cmd = "curl --no-progress-meter #{url.shellescape} -X #{method}"
149
+ if cookieFile
150
+ cmd += " --cookie #{cookieFile.shellescape} --cookie-jar #{cookieFile.shellescape}"
151
+ end
152
+ headers.each do |name, value|
153
+ cmd += " -H '#{name}: #{value}'"
154
+ end
155
+ if !data.nil?
156
+ cmd += " --data-raw #{data.shellescape}"
157
+ end
158
+ connection.exec(cmd, false, options)
159
+ end
160
+
161
+ def withHTTP(options)
162
+ http = HttpConnection.new(self, options)
163
+ result = yield(http)
164
+ result
165
+ ensure
166
+ http.cleanup
167
+ end
168
+
169
+ def withUserShell(user)
170
+ yield(LinuxShell.new(self, user))
171
+ end
172
+
173
+ def execDistroCommand(param, commandName, allowFailure = false, options = {})
174
+ command = distroInfo[commandName]
175
+ raise Framework::PluginProcessError.new('Invalid command!') unless command
176
+ command += ' ' + param.shellescape unless param.nil?
177
+ connection.exec(command, allowFailure, options)
178
+ end
179
+
180
+ def ensurePackage(name, options = {})
181
+ ensurePackages([name], options)
182
+ end
183
+
184
+ def ensurePackages(names, options = {})
185
+ reposPackages = Framework::LinuxApp.mapPackages(names, distroName)
186
+
187
+ repos = []
188
+ pkgs = []
189
+ githubPackages = []
190
+
191
+ reposPackages.each do |pkg|
192
+ if pkg.include?('|')
193
+ repoName, pkg = pkg.split('|')
194
+ if repoName == 'GitHub'
195
+ githubPackages << pkg
196
+ else
197
+ repos << repoName
198
+ pkgs << pkg
199
+ end
200
+ else
201
+ pkgs << pkg
202
+ end
203
+ end
204
+ repos.each do |repoName|
205
+ addRepo(repoName, options)
206
+ end
207
+
208
+ if !pkgs.empty?
209
+ command = distroInfo['InstallPackage'] + ' ' + pkgs.map { |pkg| pkg.shellescape }.join(' ')
210
+ connection.adminExec(command, false, options)
211
+ end
212
+
213
+ handleGitHubPackages(githubPackages, options) unless githubPackages.empty?
214
+ end
215
+
216
+ def handleGitHubPackages(githubPackages, options)
217
+ githubPackages.each do |pkg|
218
+ repo, name = pkg.split(':')
219
+ namePattern = name.gsub('.', '\\.').gsub('*', '.*')
220
+ releases = GitHub::getReleases(repo, logger, {}, options)
221
+ releases.each do |release|
222
+ break if installGitHubRelease(release, namePattern, options)
223
+ end
224
+ end
225
+ end
226
+
227
+ def installGitHubRelease(release, namePattern, options)
228
+ release['assets'].each do |asset|
229
+ if asset['name'].match?(namePattern)
230
+ if asset['name'].end_with?('.rpm')
231
+ installRPM(asset['name'], asset['browser_download_url'], options)
232
+ elsif asset['name'].end_with?('.deb')
233
+ installDeb(asset['name'], asset['browser_download_url'], options)
234
+ elsif !asset['name'].include?('.')
235
+ binaryName = asset['name'].gsub(/\-(linux|amd64|x64|bin)/, '')
236
+ connection.exec("curl --silent --location --output /tmp/#{binaryName} #{asset['browser_download_url']}", false, options)
237
+ connection.exec("chmod +rx /tmp/#{binaryName}", false, options)
238
+ connection.adminExec("mv /tmp/#{binaryName} /usr/local/bin/", false, options)
239
+ else
240
+ $stderr.puts(asset)
241
+ raise 'Not Implemented!'
242
+ end
243
+ return true
244
+ end
245
+ end
246
+ false
247
+ end
248
+
249
+ def installRPM(name, url, options)
250
+ command = "rpm -U #{url.shellescape}"
251
+ connection.adminExec(command, true, options)
252
+ end
253
+
254
+ def installDeb(name, url, options)
255
+ pkgName = name.split('_').first
256
+ command = "dpkg-query --status #{pkgName.shellescape} | grep Version: | cut -d ' ' -f 2"
257
+ version = connection.exec(command, false, { **options, 'dry' => false }).strip
258
+ if version.empty? || !name.include?(version)
259
+ command = "curl --silent --location --output /tmp/pkg.deb #{url.shellescape}"
260
+ connection.exec(command, false, options)
261
+
262
+ command = "dpkg --install /tmp/pkg.deb"
263
+ connection.adminExec(command, false, options)
264
+
265
+ connection.rm('/tmp/pkg.deb', options['dry'])
266
+ end
267
+ end
268
+
269
+ def removePackage(name, options = {})
270
+ reposPackages = Framework::LinuxApp.mapPackages([name], distroName)
271
+
272
+ pkgs = []
273
+ reposPackages.each do |pkg|
274
+ if pkg.include?('|')
275
+ repoName, pkg = pkg.split('|')
276
+ pkgs << pkg
277
+ else
278
+ pkgs << pkg
279
+ end
280
+ end
281
+
282
+ command = distroInfo['RemovePackage'] + ' ' + pkgs.map { |pkg| pkg.shellescape }.join(' ')
283
+ connection.adminExec(command, true, options)
284
+ end
285
+
286
+ def addRepo(name, options)
287
+ if distroName == Linux::SUSE_NAME
288
+ connection.exec("zypper addrepo https://download.opensuse.org/repositories/#{name}/#{distroVersion}/#{name}.repo", true, options)
289
+ connection.exec("zypper --gpg-auto-import-keys refresh", false, options)
290
+ else
291
+ raise 'Not Implemented!'
292
+ end
293
+ end
294
+
295
+ def hasBinaries?(names, options)
296
+ names = [names] unless names.is_a?(Array)
297
+ names.each do |name|
298
+ connection.exec("which #{name}", true, options) if options['dry']
299
+ result = connection.exec("which #{name}", true, { **options, 'dry' => false }).strip
300
+ return false if result.empty? || result.include?("no #{name}")
301
+ end
302
+ true
303
+ end
304
+
305
+ def ensureServiceAutoStart(name, options = {})
306
+ name = convertServiceName(name)
307
+ execDistroCommand(name, 'AutoStartService', false, options)
308
+ end
309
+
310
+ def startService(name, options = {})
311
+ name = convertServiceName(name)
312
+ execDistroCommand(name, 'StartService', false, options)
313
+ end
314
+
315
+ def restartService(name, options = {})
316
+ name = convertServiceName(name)
317
+ execDistroCommand(name, 'RestartService', false, options)
318
+ end
319
+
320
+ def reloadService(name, options = {})
321
+ name = convertServiceName(name)
322
+ execDistroCommand(name, 'ReloadService', false, options)
323
+ end
324
+
325
+ def stopService(name, options = {})
326
+ name = convertServiceName(name)
327
+ execDistroCommand(name, 'StopService', true, options)
328
+ end
329
+
330
+ def disableService(name, options = {})
331
+ name = convertServiceName(name)
332
+ execDistroCommand(name, 'DisableService', true, options)
333
+ end
334
+
335
+ def reloadServiceManager(options = {})
336
+ execDistroCommand(nil, 'ReloadServiceManager', false, options)
337
+ end
338
+
339
+ def deleteUserAndGroup(name, options = {})
340
+ execDistroCommand(name, 'DeleteUser', true, options)
341
+ execDistroCommand(name, 'DeleteGroup', true, options)
342
+ end
343
+
344
+ def userAddGroups(name, groups, options = {})
345
+ return if groups.empty?
346
+ groups = [groups] unless groups.is_a?(Array)
347
+ groups = groups.map { |group| group.shellescape }
348
+
349
+ command = distroInfo['ModifyUser']
350
+ connection.exec("#{command} --append --groups #{groups.join(' ')} #{name.shellescape}", false, options)
351
+ end
352
+
353
+ def firewallAddPort(portName, options = {})
354
+ command = 'firewall-cmd --quiet --permanent --add-port ' + portName.shellescape
355
+ connection.exec(command, true, options)
356
+ command = 'firewall-cmd --quiet --add-port ' + portName.shellescape
357
+ connection.exec(command, true, options)
358
+ end
359
+
360
+ def firewallRemovePort(portName, options = {})
361
+ command = 'firewall-cmd --quiet --permanent --remove-port ' + portName.shellescape
362
+ connection.exec(command, false, options)
363
+ command = 'firewall-cmd --quiet --remove-port ' + portName.shellescape
364
+ connection.exec(command, false, options)
365
+ end
366
+
367
+ def firewallAddService(serviceName, options = {})
368
+ command = 'firewall-cmd --permanent --add-service ' + serviceName.shellescape
369
+ connection.exec(command, true, options)
370
+ command = 'firewall-cmd --add-service ' + serviceName.shellescape
371
+ connection.exec(command, true, options)
372
+ end
373
+
374
+ def firewallRemoveService(serviceName, options = {})
375
+ command = 'firewall-cmd --permanent --remove-service ' + serviceName.shellescape
376
+ connection.exec(command, false, options)
377
+ command = 'firewall-cmd --remove-service ' + serviceName.shellescape
378
+ connection.exec(command, false, options)
379
+ end
380
+
381
+ def createServiceUser(user, homedir, userComment = '', options = {})
382
+ addUserCmd = "#{distroInfo['CreateServiceUser']} --home-dir #{homedir.shellescape} --create-home --comment #{userComment.shellescape} #{user.shellescape}"
383
+ result = connection.exec(addUserCmd, true, options)
384
+ if result.strip.start_with?('useradd:')
385
+ # Allow these
386
+ if !result.match?(/user '.*' already exists/) &&
387
+ !result.match?(/home directory .* already exists/)
388
+ raise Framework::PluginProcessError.new(result)
389
+ end
390
+ end
391
+ connection.exec("chmod o-rwx #{homedir.shellescape}", false, options)
392
+ end
393
+
394
+ def createSubuids(user, options, uids = '100000-165535', guids = '100000-165535')
395
+ connection.exec("#{distroInfo['ModifyUser']} --add-subuids #{uids} --add-subgids #{guids} #{user.shellescape}", options)
396
+ end
397
+
398
+ def enableLinger(user, options)
399
+ connection.exec("loginctl enable-linger #{user.shellescape}", options)
400
+ end
401
+
402
+ def reloadUserServices(user, options = {})
403
+ connection.exec("systemctl --user --machine=#{user}@ daemon-reload", false, options)
404
+ end
405
+
406
+ def stopUserService(user, service, options = {})
407
+ connection.exec("systemctl --user --machine=#{user}@ stop #{service}", true, options)
408
+ end
409
+
410
+ def restartUserService(user, service, options = {})
411
+ connection.exec("systemctl --user --machine=#{user}@ restart #{service}", false, options)
412
+ end
413
+
414
+ def convertServiceName(name)
415
+ if name.is_a?(Symbol)
416
+ raise "Distro '#{distroName}' not implemented!" unless allServices.key?(distroName)
417
+ serviceName = allServices[distroName][name.to_s]
418
+ name = serviceName || name.to_s
419
+ end
420
+ name
421
+ end
422
+
423
+ def getGPUDevices(options = {})
424
+ devices = []
425
+ ['/dev/kfd', '/dev/dri'].each do |device|
426
+ devices << device if self.filePresent?(device, { **options, 'dry': false })
427
+ end
428
+
429
+ # TODO
430
+ #if isNVIDIA
431
+ # devices << 'nvidia.com/gpu=all'
432
+ #end
433
+
434
+ devices
435
+ end
436
+
437
+ def getGPUGroups(devices, options = {})
438
+ groups = []
439
+ devices.each do |device|
440
+ group = self.exec("stat --format='%G' #{device}", false, options).strip
441
+ groups << group unless group == 'root'
442
+ end
443
+ groups.uniq
444
+ end
445
+
446
+ def createWildecardCertificate(options = {})
447
+ dir = "/etc/letsencrypt/live/Wildcard/"
448
+ createDirs(options, dir)
449
+ # Need this temporarily before real certs are created
450
+ if !connection.filePresent?(dir + 'fullchain.pem', { **options, 'dry' => false })
451
+ connection.exec("openssl req -x509 -noenc -days 90 -newkey rsa:2048 -keyout #{dir}privkey.pem -out #{dir}fullchain.pem -subj '/C=US/O=ConfigLMM/CN=Wildcard'", false, options)
452
+ connection.exec("cp #{dir}fullchain.pem #{dir}chain.pem", false, options)
453
+ end
454
+ dir
455
+ end
456
+
457
+ def self.distroID(connection = nil)
458
+ cmd = 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2'
459
+ if connection
460
+ if connection.is_a?(IO::Connection) || connection.is_a?(IO::Local)
461
+ connection.exec(cmd).strip.gsub('"', '')
462
+ else
463
+ connection.exec!(cmd).strip.gsub('"', '')
464
+ end
465
+ else
466
+ `#{cmd}`.strip.gsub('"', '')
467
+ end
468
+ end
469
+
470
+ end
471
+ end
472
+ end
@@ -3,18 +3,32 @@
3
3
  d-i debian-installer/locale string en_US
4
4
  d-i keyboard-configuration/xkb-keymap select us
5
5
 
6
- <% if config['Network'].is_a?(Hash) %>
6
+ <% if config['DefaultNetwork']['Interface'] %>
7
+ d-i netcfg/choose_interface <%= config['DefaultNetwork']['Interface'] %>
8
+ <% end %>
9
+
10
+ <% if config['DefaultNetwork']['IP'] && config['DefaultNetwork']['IP'] != 'dhcp'%>
7
11
  d-i netcfg/disable_autoconfig boolean true
8
12
  d-i netcfg/dhcp_failed note
9
13
  d-i netcfg/dhcp_options select Configure network manually
10
-
11
- d-i netcfg/get_ipaddress string <%= config['Network']['IP'].split('/').first %>
12
- d-i netcfg/get_netmask string <%= [((1 << 32) - 1) << (32 - config['Network']['IP'].split('/').last.to_i)].pack('N').bytes.join('.') %>
13
- d-i netcfg/get_gateway string <%= config['Network']['Gateway'] %>
14
- d-i netcfg/get_nameservers string <%= config['Network']['DNS'] %>
14
+ d-i netcfg/get_ipaddress string <%= config['DefaultNetwork']['IP'].split('/').first %>
15
+ d-i netcfg/get_netmask string <%= config['DefaultNetwork']['Subnet'] %>
15
16
  d-i netcfg/confirm_static boolean true
16
17
  <% end %>
17
18
 
19
+ <% if config['DefaultNetwork']['Gateway'] %>
20
+ d-i netcfg/get_gateway string <%= config['DefaultNetwork']['Gateway'] %>
21
+ <% end %>
22
+
23
+ <% if config['DefaultNetwork']['DNS'] %>
24
+ d-i netcfg/get_nameservers string <%= config['DefaultNetwork']['DNS'] %>
25
+ <% end %>
26
+
27
+ <% if config['DefaultNetwork']['VLAN'] %>
28
+ d-i netcfg/use_vlan boolean true
29
+ d-i netcfg/vlan_id string <%= config['DefaultNetwork']['VLAN'] %>
30
+ <% end %>
31
+
18
32
  d-i netcfg/get_hostname string <%= Addressable::IDNA.to_ascii(config['Domain']) %>
19
33
 
20
34
  d-i passwd/make-user boolean false
@@ -29,8 +43,13 @@ d-i time/zone string UTC
29
43
 
30
44
  d-i partman-auto/method string regular
31
45
  d-i partman-auto/choose_recipe select atomic
46
+ <% if !config['Disks'].empty? %>
47
+ d-i partman-auto/disk string <%= config['Disks'].join(' ') %>
48
+ <% end %>
49
+ <% if config['AutoPartition'] %>
32
50
  d-i partman/choose_partition select finish
33
51
  d-i partman/confirm_nooverwrite boolean true
52
+ <% end %>
34
53
 
35
54
  d-i base-installer/install-recommends boolean false
36
55
  d-i apt-setup/cdrom/set-first boolean false
@@ -1,11 +1,24 @@
1
1
  openSUSE Leap:
2
2
  ISO: https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso
3
+ FILES: https://download.opensuse.org/distribution/leap/15.6/repo/oss/
4
+ LXC: opensuse-15.6-default
5
+ Type: opensuse
3
6
 
4
7
  Arch Linux:
5
8
  ISO: https://mirror.rackspace.com/archlinux/iso/2024.10.01/archlinux-x86_64.iso
9
+ LXC: archlinux-base
10
+ Type: archlinux
6
11
 
7
12
  Debian:
8
13
  ISO: https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.7.0-amd64-netinst.iso
14
+ PXE: https://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/netboot/netboot.tar.gz
15
+ LXC: debian-12-standard
16
+ Type: debian
9
17
 
10
18
  Proxmox VE:
11
19
  ISO: https://enterprise.proxmox.com/iso/proxmox-ve_8.2-2.iso
20
+ # Just install Debian first
21
+ PXE: https://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/netboot/netboot.tar.gz
22
+
23
+ Syslinux:
24
+ Archive: https://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.gz
@@ -0,0 +1,10 @@
1
+
2
+ timeout=1
3
+ default=1
4
+
5
+ menuentry 'Installation' {
6
+ echo 'Loading kernel ...'
7
+ linux /linux splash=silent $OPTIONS
8
+ echo 'Loading initial ramdisk ...'
9
+ initrd /initrd
10
+ }
@@ -0,0 +1,32 @@
1
+
2
+ require 'securerandom'
3
+
4
+ module ConfigLMM
5
+ module LMM
6
+ class HttpConnection
7
+ attr_reader :connection
8
+
9
+ def initialize(connection, options)
10
+ @connection = connection
11
+ @options = options
12
+ @cookieFile = '/tmp/cookies_' + SecureRandom.urlsafe_base64(20) + '.txt'
13
+ end
14
+
15
+ def cleanup
16
+ @connection.rm(@cookieFile, @options['dry'])
17
+ end
18
+
19
+ def get(url, options, headers = {})
20
+ @connection.http(url, options, headers, 'GET', nil, @cookieFile)
21
+ end
22
+
23
+ def post(url, data, options, headers = {})
24
+ @connection.http(url, options, headers, 'POST', data, @cookieFile)
25
+ end
26
+
27
+ def cookie(name, options)
28
+ @connection.exec("cat #{@cookieFile} | grep #{name} | cut -f 7", false, options)
29
+ end
30
+ end
31
+ end
32
+ end