ConfigLMM 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +4 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Examples/Android.mm.yaml +8 -0
  6. data/Examples/Apps/Blog.mm.yaml +7 -0
  7. data/Examples/Apps/Jellyfin.mm.yaml +3 -0
  8. data/Examples/Implemented.mm.yaml +155 -0
  9. data/Examples/Keys.ini +7 -0
  10. data/Examples/Linux.mm.yaml +16 -0
  11. data/Examples/Windows.mm.yaml +11 -0
  12. data/Examples/configlmmAuth.sh +26 -0
  13. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.conf.erb +38 -0
  14. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.lmm.rb +19 -0
  15. data/Plugins/Apps/IPFS/IPFS.conf.erb +44 -0
  16. data/Plugins/Apps/IPFS/IPFS.lmm.rb +23 -0
  17. data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +34 -0
  18. data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +19 -0
  19. data/Plugins/Apps/Jackett/Jackett.conf.erb +38 -0
  20. data/Plugins/Apps/Jackett/Jackett.lmm.rb +19 -0
  21. data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +59 -0
  22. data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +23 -0
  23. data/Plugins/Apps/Mastodon/Mastodon.conf.erb +81 -0
  24. data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +23 -0
  25. data/Plugins/Apps/Matrix/Matrix.conf.erb +36 -0
  26. data/Plugins/Apps/Matrix/Matrix.lmm.rb +23 -0
  27. data/Plugins/Apps/Netdata/Netdata.conf.erb +37 -0
  28. data/Plugins/Apps/Netdata/Netdata.lmm.rb +23 -0
  29. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +165 -0
  30. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +23 -0
  31. data/Plugins/Apps/Nginx/config-lmm/errors.conf +31 -0
  32. data/Plugins/Apps/Nginx/config-lmm/private.conf +6 -0
  33. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +15 -0
  34. data/Plugins/Apps/Nginx/config-lmm/public.conf +3 -0
  35. data/Plugins/Apps/Nginx/config-lmm/ssl.conf +18 -0
  36. data/Plugins/Apps/Nginx/main.conf +30 -0
  37. data/Plugins/Apps/Nginx/nginx.conf +90 -0
  38. data/Plugins/Apps/Nginx/nginx.lmm.rb +62 -0
  39. data/Plugins/Apps/Nginx/proxy.conf.erb +31 -0
  40. data/Plugins/Apps/Odoo/Odoo.conf.erb +44 -0
  41. data/Plugins/Apps/Odoo/Odoo.lmm.rb +23 -0
  42. data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +50 -0
  43. data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +30 -0
  44. data/Plugins/Apps/Pterodactyl/Wings.conf.erb +38 -0
  45. data/Plugins/Apps/Sunshine/Sunshine.conf.erb +31 -0
  46. data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +21 -0
  47. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +48 -0
  48. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +25 -0
  49. data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +35 -0
  50. data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +19 -0
  51. data/Plugins/Apps/gollum/config.ru +11 -0
  52. data/Plugins/Apps/gollum/gollum.conf.erb +41 -0
  53. data/Plugins/Apps/gollum/gollum.lmm.rb +52 -0
  54. data/Plugins/OS/Linux.lmm.rb +64 -0
  55. data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +144 -0
  56. data/Plugins/Platforms/GitHub.lmm.rb +57 -0
  57. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +83 -0
  58. data/Plugins/Platforms/GoDaddy/zone.txt.erb +13 -0
  59. data/Plugins/Platforms/porkbun.lmm.rb +129 -0
  60. data/Plugins/Platforms/porkbun_spec.rb +110 -0
  61. data/Plugins/Services/DNS/AmberBit.lmm.rb +14 -0
  62. data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +14 -0
  63. data/Plugins/Services/DNS/NICLV.lmm.rb +18 -0
  64. data/Plugins/Services/DNS/PowerDNS.lmm.rb +261 -0
  65. data/Plugins/Services/DNS/tonic.lmm.rb +126 -0
  66. data/README.md +337 -0
  67. data/Rakefile +15 -0
  68. data/UNLICENSE +24 -0
  69. data/bin/configlmm +7 -0
  70. data/bin/console +11 -0
  71. data/bin/setup +8 -0
  72. data/lib/ConfigLMM/Framework/plugins/dns.rb +63 -0
  73. data/lib/ConfigLMM/Framework/plugins/errors.rb +23 -0
  74. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +55 -0
  75. data/lib/ConfigLMM/Framework/plugins/plugin.rb +167 -0
  76. data/lib/ConfigLMM/Framework/plugins/ssh.rb +37 -0
  77. data/lib/ConfigLMM/Framework/plugins/store.rb +57 -0
  78. data/lib/ConfigLMM/Framework/plugins.rb +5 -0
  79. data/lib/ConfigLMM/Framework/registrator.rb +32 -0
  80. data/lib/ConfigLMM/Framework.rb +9 -0
  81. data/lib/ConfigLMM/LMM/plugins.rb +5 -0
  82. data/lib/ConfigLMM/LMM.rb +8 -0
  83. data/lib/ConfigLMM/cli.rb +161 -0
  84. data/lib/ConfigLMM/command.rb +53 -0
  85. data/lib/ConfigLMM/commands/build.rb +41 -0
  86. data/lib/ConfigLMM/commands/cleanup.rb +30 -0
  87. data/lib/ConfigLMM/commands/configsCommand.rb +167 -0
  88. data/lib/ConfigLMM/commands/deploy.rb +39 -0
  89. data/lib/ConfigLMM/commands/diff.rb +45 -0
  90. data/lib/ConfigLMM/commands/list.rb +15 -0
  91. data/lib/ConfigLMM/commands/refresh.rb +46 -0
  92. data/lib/ConfigLMM/commands/types.rb +35 -0
  93. data/lib/ConfigLMM/commands/validate.rb +49 -0
  94. data/lib/ConfigLMM/context.rb +52 -0
  95. data/lib/ConfigLMM/io/configList.rb +98 -0
  96. data/lib/ConfigLMM/io/path.rb +48 -0
  97. data/lib/ConfigLMM/io/source.rb +47 -0
  98. data/lib/ConfigLMM/io.rb +2 -0
  99. data/lib/ConfigLMM/state.rb +78 -0
  100. data/lib/ConfigLMM/utils/filters.rb +126 -0
  101. data/lib/ConfigLMM/version.rb +5 -0
  102. data/lib/ConfigLMM.rb +6 -0
  103. data/sig/ConfigLMM.rbs +4 -0
  104. metadata +485 -0
@@ -0,0 +1,261 @@
1
+
2
+ # require 'fog'
3
+ require 'uri'
4
+ require 'addressable/uri'
5
+ require 'addressable/idna'
6
+ require 'fog/powerdns'
7
+
8
+ module ConfigLMM
9
+ module LMM
10
+ class PowerDNS < Framework::DNS
11
+
12
+ CONFIG_DIR = '/etc/pdns/pdns.d'
13
+ DEFAULT_HOST = 'localhost'
14
+ DEFAULT_PORT = 8081
15
+ SSH_TIMEOUT = 10
16
+
17
+ # TODO
18
+ # def actionPowerDNSValidate(id, target, activeState, context, options)
19
+ # We should check that target['DNS'] looks like valid config
20
+ #end
21
+
22
+ def actionPowerDNSBuild(id, target, activeState, context, options)
23
+ if target['Settings']
24
+ targetDir = options['output'] + CONFIG_DIR + '/'
25
+ mkdir(targetDir, options['dry'])
26
+ content = ''
27
+ target['Settings'].each do |name, value|
28
+ content += "#{name}=#{value}\n"
29
+ end
30
+ fileWrite(targetDir + 'configlmm.conf', content, options['dry'])
31
+ end
32
+ end
33
+
34
+ def actionPowerDNSRefresh(id, target, activeState, context, options)
35
+
36
+ connect(id, target, activeState, context, options) do |host, port, key|
37
+ refreshDNS(host, port, key, target['DNS'], activeState) if target['DNS']
38
+ end
39
+ end
40
+
41
+ #def actionPowerDNSDiff(id, target, activeState, context, options)
42
+ #end
43
+
44
+ def actionPowerDNSDeploy(id, target, activeState, context, options)
45
+ #actionPowerDNSDiff(id, target, activeState, context, options)
46
+
47
+ deploySettings(target)
48
+ if target['DNS']
49
+ connect(id, target, activeState, context, options) do |host, port, key|
50
+ updateDNS(host, port, key, target['DNS'])
51
+ end
52
+ end
53
+ end
54
+
55
+ def authenticate(actionMethod, target, activeState, context, options)
56
+ true
57
+ end
58
+
59
+ private
60
+
61
+ def refreshDNS(host, port, key, targetDNS, activeState)
62
+ dns = Fog::DNS::PowerDNS.new({
63
+ host: host,
64
+ port: port,
65
+ pdns_api_key: key
66
+ })
67
+
68
+ server = 'localhost'
69
+
70
+ targetDNS.each do |domain, info|
71
+ domain = Addressable::IDNA.to_ascii(domain)
72
+ canonicalDomain = domain + '.'
73
+
74
+ result = dns.list_zones(server)
75
+ domainsInfo = result.map { |info| [ info['name'], { :_meta_ => info } ] }.flatten
76
+ activeState['DNS'] = Hash[*domainsInfo]
77
+ activeState['DNS'].each do |domain, info|
78
+ zone = dns.get_zone(server, domain)
79
+ info[:_records_] = zone.to_h
80
+ end
81
+ end
82
+ end
83
+
84
+ def updateDNS(host, port, key, targetDNS)
85
+
86
+ dns = Fog::DNS::PowerDNS.new({
87
+ host: host,
88
+ port: port,
89
+ pdns_api_key: key
90
+ })
91
+
92
+ server = 'localhost'
93
+ targetDNS.each do |domain, info|
94
+ domain = Addressable::IDNA.to_ascii(domain)
95
+ canonicalDomain = domain + '.'
96
+
97
+ if !dns.list_zones(server).map { |zone| zone['name'].downcase }.include?(canonicalDomain.downcase)
98
+ dns.create_zone(server, canonicalDomain, [], { kind: 'Native' })
99
+ end
100
+
101
+ zone = dns.get_zone(server, canonicalDomain)
102
+
103
+ rrsets = []
104
+ remove = []
105
+ info.each do |name, data|
106
+ fullName = name + '.' + domain + '.'
107
+ fullName = domain + '.' if name == '@'
108
+ self.processDNS(domain, data).each do |type, records|
109
+ #remove += removeConflicting(zone, fullName, type)
110
+ rrset = {
111
+ name: fullName,
112
+ type: type,
113
+ ttl: records.first[:ttl],
114
+ changetype: 'REPLACE',
115
+ records: []
116
+ }
117
+ records.each do |record|
118
+ rrset[:records] << { content: record[:content], disabled: false }
119
+ end
120
+ rrsets << rrset
121
+ end
122
+ end
123
+
124
+ if !remove.empty?
125
+ dns.update_rrsets('localhost', zone['name'], { 'rrsets' => remove })
126
+ end
127
+ dns.update_rrsets('localhost', zone['name'], { 'rrsets' => rrsets })
128
+ end
129
+
130
+ end
131
+
132
+ def deploySettings(target)
133
+ if target['Settings']
134
+ # TODO
135
+ end
136
+ end
137
+
138
+
139
+ def connect(id, target, activeState, context, options)
140
+ host = DEFAULT_HOST
141
+ port = DEFAULT_PORT
142
+ key = ENV['POWERDNS_API_KEY']
143
+ key = target['Key'] if target['Key']
144
+ raise Framework::PluginProcessError.new('PowerDNS missing API key!') unless key
145
+
146
+ sshServer = nil
147
+ sshUser = nil
148
+ sshPort = nil
149
+ sshPassword = ENV['POWERDNS_SSH_PASSWORD']
150
+
151
+ if target['Location']
152
+ uri = Addressable::URI.parse(target['Location'])
153
+ if uri.scheme == 'ssh'
154
+ sshServer = uri.hostname
155
+ sshUser = uri.user
156
+ sshPort = uri.port if uri.port
157
+ params = {}
158
+ params = CGI.parse(uri.query) if uri.query
159
+ host = params['host'].first if params['host']
160
+ port = params['port'].first if params['port']
161
+ elsif uri.scheme == 'pdns'
162
+ host = uri.hostname
163
+ port = uri.port if uri.port
164
+ else
165
+ raise Framework::PluginProcessError.new('Unexpected protocol! Should be either ssh or pdns!')
166
+ end
167
+ end
168
+
169
+ if sshServer
170
+ sshParams = {}
171
+ sshParams[:port] = sshPort if sshPort
172
+ sshParams[:user] = sshUser if sshUser
173
+ sshParams[:password] = sshPassword if sshPassword
174
+
175
+ startPortForward(sshServer, sshParams, host, port) do |acquiredPort|
176
+ port = acquiredPort
177
+ host = 'localhost'
178
+ self.class.externalIp = externalIpFromSSH
179
+ end
180
+ waitPortForward(SSH_TIMEOUT)
181
+ end
182
+
183
+ yield(host, port, key)
184
+
185
+ if sshServer
186
+ finishPortForward
187
+ end
188
+ end
189
+
190
+ def removeConflicting(zone, name, type)
191
+ remove = []
192
+ if type == 'CNAME'
193
+ zone['rrsets'].each do |rrset|
194
+ if (rrset['name'].downcase == name.downcase && rrset['type'] == 'A')
195
+ remove << {
196
+ name: name,
197
+ type: 'A',
198
+ changetype: 'DELETE',
199
+ records: []
200
+ }
201
+ end
202
+ end
203
+ end
204
+ remove
205
+ end
206
+
207
+ def startPortForward(server, sshParams, targetHost, targetPort)
208
+ @SSHFowardProcessing = false
209
+ @SSHError = nil
210
+ @SSHThread = Thread.new do
211
+ @SSH = nil
212
+ Net::SSH.start(server, nil, sshParams) do |ssh|
213
+ @SSH = ssh
214
+ port = getFreePort
215
+ yield(port)
216
+
217
+ ssh.forward.local(port, targetHost, targetPort)
218
+ @SSHFowardProcessing = true
219
+ ssh.loop { ssh.busy? || @SSHFowardProcessing }
220
+ end
221
+ @SSH = nil
222
+ rescue IOError, SocketError, SystemCallError, Net::SSH::Exception, ScriptError => e
223
+ @SSHError = e.message
224
+ end
225
+ @SSHThread.report_on_exception = true
226
+ @SSHThread.abort_on_exception = true
227
+ end
228
+
229
+ def waitPortForward(timeout)
230
+ while !@SSHFowardProcessing && timeout.positive?
231
+ raise Framework::PluginProcessError, 'PowerDNS: ' + @SSHError.to_s unless @SSHError.nil?
232
+
233
+ sleep(0.2)
234
+ timeout -= 0.2
235
+ end
236
+
237
+ return if timeout.positive?
238
+
239
+ @SSHThread.terminate
240
+ raise Framework::PluginProcessError, 'PowerDNS: Timeout while waiting for SSH connection!'
241
+ end
242
+
243
+ def finishPortForward
244
+ @SSHFowardProcessing = false
245
+ @SSHThread.join(3)
246
+ end
247
+
248
+ def externalIpFromSSH
249
+ envs = @SSH.exec!('env').split("\n")
250
+ envVars = Hash[envs.map { |vars| vars.split('=', 2) }]
251
+ envVars['SSH_CLIENT'].split.first
252
+ end
253
+
254
+ def getFreePort
255
+ TCPServer.open(0) do |socket|
256
+ return socket.addr[1]
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,126 @@
1
+
2
+ require 'addressable/idna'
3
+ require 'http'
4
+ require 'nokogiri'
5
+
6
+ module ConfigLMM
7
+ module LMM
8
+ class Tonic < Framework::DNS
9
+
10
+ EDIT_URL = 'https://www.tonic.to/editdns.htm'
11
+ # UPDATE_URL = 'https://www.tonic.to/upddnssucc.htm'
12
+
13
+ def actionTonicDNSValidate(id, target, activeState, context, options)
14
+ errors = []
15
+ if target['Domain'].to_s.empty?
16
+ errors << id + ': Domain must be provided!'
17
+ end
18
+ if target['Nameservers'].to_h.empty?
19
+ errors << id + ': Nameservers must be provided!'
20
+ end
21
+ errors
22
+ end
23
+
24
+ def actionTonicDNSRefresh(id, target, activeState, context, options)
25
+ domain = target['Domain'].split('.').first
26
+ if domain.empty?
27
+ raise Framework::PluginProcessError.new('Invalid Domain for ' + id)
28
+ end
29
+ activeState['Domain'] = target['Domain']
30
+ response = HTTP.post(EDIT_URL, :form => {
31
+ command: 'editdns',
32
+ error: 'badpass.htm',
33
+ sld: domain,
34
+ password: ENV['TONIC_PASSWORD'],
35
+ 'B1.x' => "40",
36
+ 'B1.y' => "20"
37
+ })
38
+
39
+ fields = Nokogiri::HTML(response.to_s).xpath('//input[@value]/@value')
40
+ if !fields.empty?
41
+ if fields.first.value == 'updatedns'
42
+ values = fields.map { |attrs| attrs.value }
43
+ .reject { |v| v.empty? ||
44
+ v == 'updatedns' ||
45
+ v == 'Submit'
46
+ }
47
+ nameservers = Hash[*values].invert
48
+ activeState['Nameservers'] = nameservers
49
+ else
50
+ raise Framework::PluginProcessError.new('Unexpected value in response for ' + id)
51
+ end
52
+ else
53
+ raise Framework::PluginProcessError.new("Couldn't refresh " + id + "! Invalid TONIC_PASSWORD ?")
54
+ end
55
+ end
56
+
57
+ def actionTonicDNSDiff(id, target, activeState, context, options)
58
+ shouldMatch(id, 'Domain', 'Domain', target, activeState)
59
+ nameservers = activeState['Nameservers']&.transform_keys { |ns| Addressable::IDNA.to_unicode(ns) }
60
+ if target['Nameservers'] != nameservers
61
+ @Diff.update({'Nameservers' => [target['Nameservers'], nameservers]})
62
+ end
63
+ end
64
+
65
+ def actionTonicDNSDeploy(id, target, activeState, context, options)
66
+ domain = target['Domain'].split('.').first
67
+ if domain.empty?
68
+ raise Framework::PluginProcessError.new('Invalid Domain for ' + id)
69
+ end
70
+
71
+ actionTonicDNSDiff(id, target, activeState, context, options)
72
+
73
+ hosts = target['Nameservers'].keys.map { |ns| Addressable::IDNA.to_ascii(ns) }
74
+ addrs = target['Nameservers'].values
75
+
76
+ response = HTTP.post(EDIT_URL, :form => {
77
+ command: 'editdns',
78
+ error: 'badpass.htm',
79
+ sld: domain,
80
+ password: ENV['TONIC_PASSWORD'],
81
+ 'B1.x' => "40",
82
+ 'B1.y' => "20"
83
+ })
84
+
85
+ updateURL = Nokogiri::HTML(response.to_s).at('//form[@action]/@action')
86
+ if updateURL.nil?
87
+ raise Framework::PluginProcessError.new("Couldn't deploy " + id + "! Invalid TONIC_PASSWORD ?")
88
+ end
89
+
90
+ if options['dry']
91
+ prompt.say("Would HTTP POST #{updateURL.value}")
92
+ else
93
+ response = HTTP.cookies(response.cookies)
94
+ .post(updateURL.value, :form => {
95
+ command: 'updatedns',
96
+ priadd: addrs[0].to_s,
97
+ prihost: hosts[0].to_s,
98
+ secadd1: addrs[1].to_s,
99
+ sechost1: hosts[1].to_s,
100
+ secadd2: addrs[2].to_s,
101
+ sechost2: hosts[2].to_s,
102
+ secadd3: addrs[3].to_s,
103
+ sechost3: hosts[3].to_s,
104
+ 'B1.x': 45,
105
+ 'B1.y': 30
106
+ })
107
+
108
+ activeState['Domain'] = target['Domain']
109
+ activeState['Nameservers'] = target['Nameservers']
110
+
111
+ prompt.say(Nokogiri::HTML(response.to_s).at('//title/text()'))
112
+ end
113
+ end
114
+
115
+ def authenticate(actionMethod, target, activeState, context, options)
116
+ authSecret = ENV['TONIC_PASSWORD']
117
+ if authSecret.to_s.empty?
118
+ prompt.say('Set your Tonic DNS password to TONIC_PASSWORD as Environment Variable')
119
+ raise Framework::PluginPrerequisite.new('Need TONIC_PASSWORD')
120
+ end
121
+ true
122
+ end
123
+
124
+ end
125
+ end
126
+ end