ConfigLMM 0.2.0 → 0.4.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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -0
  3. data/Examples/Implemented.mm.yaml +75 -1
  4. data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +14 -0
  5. data/Plugins/Apps/Authentik/Authentik-Server.container +19 -0
  6. data/Plugins/Apps/Authentik/Authentik-Worker.container +18 -0
  7. data/Plugins/Apps/Authentik/Authentik.conf.erb +42 -0
  8. data/Plugins/Apps/Authentik/Authentik.lmm.rb +95 -0
  9. data/Plugins/Apps/BookStack/BookStack.conf.erb +41 -0
  10. data/Plugins/Apps/BookStack/BookStack.container +15 -0
  11. data/Plugins/Apps/BookStack/BookStack.lmm.rb +80 -0
  12. data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +41 -0
  13. data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +17 -0
  14. data/Plugins/Apps/Discourse/Discourse.conf.erb +41 -0
  15. data/Plugins/Apps/Discourse/Discourse.container +17 -0
  16. data/Plugins/Apps/Discourse/Discourse.lmm.rb +95 -0
  17. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +171 -0
  18. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +19 -0
  19. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +17 -0
  20. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +17 -0
  21. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +19 -0
  22. data/Plugins/Apps/ERPNext/ERPNext.container +18 -0
  23. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +193 -0
  24. data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
  25. data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
  26. data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
  27. data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
  28. data/Plugins/Apps/GitLab/GitLab.container +18 -0
  29. data/Plugins/Apps/GitLab/GitLab.lmm.rb +100 -0
  30. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +57 -0
  31. data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
  32. data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
  33. data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
  34. data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
  35. data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
  36. data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
  37. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +115 -0
  38. data/Plugins/Apps/Matrix/Element.container +14 -0
  39. data/Plugins/Apps/Matrix/Matrix.conf.erb +49 -5
  40. data/Plugins/Apps/Matrix/Matrix.lmm.rb +86 -1
  41. data/Plugins/Apps/Matrix/Synapse.container +17 -0
  42. data/Plugins/Apps/Matrix/config.json +50 -0
  43. data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
  44. data/Plugins/Apps/Matrix/log.config +30 -0
  45. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +48 -10
  46. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +83 -1
  47. data/Plugins/Apps/Nextcloud/config.php +18 -0
  48. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +71 -0
  49. data/Plugins/Apps/Nginx/config-lmm/errors.conf +11 -5
  50. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +5 -1
  51. data/Plugins/Apps/Nginx/main.conf.erb +31 -0
  52. data/Plugins/Apps/Nginx/nginx.conf +3 -68
  53. data/Plugins/Apps/Nginx/nginx.lmm.rb +83 -22
  54. data/Plugins/Apps/Nginx/proxy.conf.erb +13 -3
  55. data/Plugins/Apps/Odoo/Odoo.conf.erb +30 -13
  56. data/Plugins/Apps/Odoo/Odoo.container +18 -0
  57. data/Plugins/Apps/Odoo/Odoo.lmm.rb +62 -2
  58. data/Plugins/Apps/Odoo/odoo.conf +37 -0
  59. data/Plugins/Apps/OpenVidu/Ingress.container +18 -0
  60. data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +34 -0
  61. data/Plugins/Apps/OpenVidu/OpenVidu.container +16 -0
  62. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +90 -0
  63. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +35 -0
  64. data/Plugins/Apps/OpenVidu/OpenViduCall.container +15 -0
  65. data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
  66. data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
  67. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +95 -0
  68. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +60 -0
  69. data/Plugins/Apps/Peppermint/Peppermint.container +15 -0
  70. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +58 -0
  71. data/Plugins/Apps/Postfix/Postfix.lmm.rb +165 -31
  72. data/Plugins/Apps/Postfix/smtpd.conf +3 -0
  73. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +242 -24
  74. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +75 -0
  75. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +145 -0
  76. data/Plugins/Apps/SSH/SSH.lmm.rb +51 -0
  77. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +63 -0
  78. data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
  79. data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
  80. data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
  81. data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
  82. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +52 -0
  83. data/Plugins/Apps/UVdesk/UVdesk.lmm.rb +85 -0
  84. data/Plugins/Apps/Valkey/Valkey.lmm.rb +34 -1
  85. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +35 -18
  86. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +16 -0
  87. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +46 -3
  88. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +42 -0
  89. data/Plugins/Apps/Wiki.js/Wiki.js.container +15 -0
  90. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +61 -0
  91. data/Plugins/Apps/gollum/gollum.conf.erb +84 -19
  92. data/Plugins/Apps/gollum/gollum.container +15 -0
  93. data/Plugins/Apps/gollum/gollum.lmm.rb +48 -11
  94. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +62 -0
  95. data/Plugins/OS/Linux/Distributions.yaml +42 -0
  96. data/Plugins/OS/Linux/Flavours.yaml +11 -0
  97. data/Plugins/OS/Linux/Linux.lmm.rb +362 -41
  98. data/Plugins/OS/Linux/Packages.yaml +88 -5
  99. data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
  100. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +137 -0
  101. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +15 -0
  102. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +28 -0
  103. data/Plugins/OS/Linux/systemd/user-0.slice +9 -0
  104. data/Plugins/OS/Linux/systemd/user@.service.d/delegate.conf +3 -0
  105. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +7 -3
  106. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +3 -2
  107. data/Plugins/Services/DNS/PowerDNS.lmm.rb +158 -8
  108. data/README.md +6 -0
  109. data/bootstrap.sh +92 -0
  110. data/lib/ConfigLMM/Framework/plugins/dns.rb +1 -2
  111. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +249 -45
  112. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +56 -7
  113. data/lib/ConfigLMM/Framework/plugins/plugin.rb +112 -16
  114. data/lib/ConfigLMM/cli.rb +3 -1
  115. data/lib/ConfigLMM/commands/cleanup.rb +1 -0
  116. data/lib/ConfigLMM/commands/configsCommand.rb +3 -1
  117. data/lib/ConfigLMM/io/configList.rb +3 -1
  118. data/lib/ConfigLMM/state.rb +10 -2
  119. data/lib/ConfigLMM/version.rb +1 -1
  120. metadata +82 -3
  121. data/Plugins/Apps/Nginx/main.conf +0 -30
@@ -0,0 +1,137 @@
1
+
2
+ module ConfigLMM
3
+ module LMM
4
+ class WireGuard < Framework::LinuxApp
5
+
6
+ WIREGUARD_PACKAGE = 'WireGuard'
7
+ SERVICE_NAME = 'wg-quick@wg0'
8
+ CONFIG_FILE = '/etc/wireguard/wg0.conf'
9
+ PORT = '51820'
10
+ SUBNET = '172.20.0.0/20'
11
+
12
+ persistBuildDir
13
+
14
+ def actionWireGuardDeploy(id, target, activeState, context, options)
15
+ self.prepareConfig(target)
16
+ if target['Location'] && target['Location'] != '@me'
17
+ uri = Addressable::URI.parse(target['Location'])
18
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
19
+ self.class.sshStart(uri) do |ssh|
20
+ self.class.sshExec!(ssh, "firewall-cmd -q --permanent --add-port='#{PORT}/udp'")
21
+ self.class.sshExec!(ssh, "firewall-cmd -q --add-port='#{PORT}/udp'")
22
+ self.class.sshExec!(ssh, "firewall-cmd -q --permanent --zone=trusted --add-source=#{SUBNET}")
23
+ self.class.sshExec!(ssh, "firewall-cmd -q --zone=trusted --add-source=#{SUBNET}")
24
+ self.class.sshExec!(ssh, "firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE")
25
+ self.class.sshExec!(ssh, "firewall-cmd -q --direct --add-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE")
26
+
27
+ self.class.ensurePackages([WIREGUARD_PACKAGE], ssh)
28
+ self.class.ensureServiceAutoStartOverSSH(SERVICE_NAME, ssh)
29
+
30
+ dir = options['output'] + '/' + id + '/etc/wireguard/'
31
+ mkdir(dir, false)
32
+ template = ERB.new(File.read(__dir__ + '/wg0.conf.erb'))
33
+
34
+ if self.class.remoteFilePresent?(CONFIG_FILE, ssh)
35
+ # TODO Implement adding and removing peers
36
+ else
37
+ if !target['PrivateKey']
38
+ target['PrivateKey'] = ENV['WIREGUARD_PRIVATEKEY_' + id]
39
+ if !target['PrivateKey']
40
+ target['PrivateKey'] = genkeyOverSSH(ssh)
41
+ end
42
+ end
43
+ publicKey = pubkeyOverSSH(target['PrivateKey'], ssh)
44
+ self.class.sshExec!(ssh, "echo '#{publicKey}' > /etc/wireguard/pubkey")
45
+ target['Peers'].each do |name, data|
46
+ if !data['PublicKey']
47
+ data['PrivateKey'] = genkeyOverSSH(ssh)
48
+ data['PublicKey'] = pubkeyOverSSH(data['PrivateKey'], ssh)
49
+ end
50
+ if !data['PresharedKey']
51
+ data['PresharedKey'] = ENV['WIREGUARD_PRESHAREDKEY_' + id + '_' + name]
52
+ if !data['PresharedKey']
53
+ data['PresharedKey'] = genpskOverSSH(ssh)
54
+ end
55
+ end
56
+ end
57
+
58
+ target['Peers'].each do |name, data|
59
+ templateData = {}
60
+ templateData['Address'] = target['Address']
61
+ templateData['PrivateKey'] = data['PrivateKey']
62
+ templateData['Peers'] = {}
63
+ templateData['Peers'][id] = { 'PublicKey' => publicKey, 'PresharedKey' => data['PresharedKey'] }
64
+ target['Peers'].each do |otherName, otherData|
65
+ next if name == otherName
66
+ pskIdB = 'PresharedKey_' + otherName + '_' + name
67
+ if otherData.key?(pskIdB)
68
+ psk = otherData[pskIdB]
69
+ else
70
+ pskIdA = 'PresharedKey_' + name + '_' + otherName
71
+ data[pskIdA] = genpskOverSSH(ssh)
72
+ psk = data[pskIdA]
73
+ end
74
+ templateData['Peers'][otherName] = { 'PublicKey' => otherData['PublicKey'], 'PresharedKey' => psk }
75
+ end
76
+
77
+ renderTemplate(template, templateData, dir + name + '.conf', options)
78
+ end
79
+
80
+ renderTemplate(template, target, dir + 'wg0.conf', options)
81
+ ssh.scp.upload!(dir + 'wg0.conf', CONFIG_FILE)
82
+ end
83
+
84
+ end
85
+ else
86
+ # TODO
87
+ end
88
+ self.startService(SERVICE_NAME, target['Location'])
89
+
90
+ activeState['Status'] = State::STATUS_DEPLOYED
91
+ end
92
+
93
+ def cleanup(configs, state, context, options)
94
+ cleanupType(:WireGuard, configs, state, context, options) do |item, id, state, context, options, ssh|
95
+ Framework::LinuxApp.stopService(SERVICE_NAME, ssh, options[:dry])
96
+ Framework::LinuxApp.disableService(SERVICE_NAME, ssh, options[:dry])
97
+ Framework::LinuxApp.removePackage(WIREGUARD_PACKAGE, ssh, options[:dry])
98
+
99
+ self.class.exec("firewall-cmd -q --permanent --remove-port='#{PORT}/udp'", ssh, false, options[:dry])
100
+ self.class.exec("firewall-cmd -q --remove-port='#{PORT}/udp'", ssh, false, options[:dry])
101
+ self.class.exec("firewall-cmd -q --permanent --zone=trusted --remove-source=#{SUBNET}", ssh, false, options[:dry])
102
+ self.class.exec("firewall-cmd -q --zone=trusted --remove-source=#{SUBNET}", ssh, false, options[:dry])
103
+ self.class.exec("firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE", ssh, false, options[:dry])
104
+ self.class.exec("firewall-cmd -q --direct --remove-rule ipv4 nat POSTROUTING 0 -s #{SUBNET} ! -d #{SUBNET} -j MASQUERADE", ssh, false, options[:dry])
105
+
106
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
107
+
108
+ if options[:destroy]
109
+ rm('/etc/wireguard', options[:dry], ssh)
110
+
111
+ state.item(id)['Status'] = State::STATUS_DESTROYED unless options[:dry]
112
+ end
113
+ end
114
+ end
115
+
116
+ def genkeyOverSSH(ssh)
117
+ self.class.sshExec!(ssh, 'wg genkey')
118
+ end
119
+
120
+ def genpskOverSSH(ssh)
121
+ self.class.sshExec!(ssh, 'wg genpsk')
122
+ end
123
+
124
+ def pubkeyOverSSH(privateKey, ssh)
125
+ self.class.sshExec!(ssh, " echo '#{privateKey}' | wg pubkey")
126
+ end
127
+
128
+ def prepareConfig(target)
129
+ target['Address'] = '172.20.0.1' unless target['Address']
130
+ target['Peers'].each do |name, data|
131
+ target['Peers'][name] ||= {}
132
+ target['Peers'][name]['AllowedIPs'] = SUBNET unless target['Peers'][name]['AllowedIPs']
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,15 @@
1
+ [Interface]
2
+ Address = <%= config['Address'] %>
3
+ ListenPort = 51820
4
+ PrivateKey = <%= config['PrivateKey'] %>
5
+
6
+ <% config['Peers'].to_h.each do |name, peer| %>
7
+ # <%= name %>
8
+ [Peer]
9
+ PublicKey = <%= peer['PublicKey'] %>
10
+ PresharedKey = <%= peer['PresharedKey'] %>
11
+ AllowedIPs = <%= peer['AllowedIPs'] %>
12
+ <% if peer['Endpoint'] %>
13
+ Endpoint = <%= peer['Endpoint'] %>:51820
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,28 @@
1
+
2
+ module ConfigLMM
3
+ module LMM
4
+ class Systemd < Framework::LinuxApp
5
+
6
+ SYSTEMD_CONFIG_PATH = '/etc/systemd/system/'
7
+ USER_SERVICE_DIR = '/etc/systemd/system/user@.service.d/'
8
+
9
+ def actionSystemdDeploy(id, target, activeState, context, options)
10
+ if target['Location'] && target['Location'] != '@me'
11
+ if target['UserCgroups']
12
+ uri = Addressable::URI.parse(target['Location'])
13
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
14
+ self.class.sshStart(uri) do |ssh|
15
+ self.class.sshExec!(ssh, "mkdir -p #{USER_SERVICE_DIR}")
16
+ ssh.scp.upload!(__dir__ + '/user-0.slice', SYSTEMD_CONFIG_PATH)
17
+ ssh.scp.upload!(__dir__ + '/user@.service.d/delegate.conf', USER_SERVICE_DIR)
18
+ end
19
+ end
20
+ else
21
+ # TODO
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+
2
+ [Unit]
3
+ Before=systemd-logind.service
4
+
5
+ [Slice]
6
+ Slice=user.slice
7
+
8
+ [Install]
9
+ WantedBy=multi-user.target
@@ -0,0 +1,3 @@
1
+
2
+ [Service]
3
+ Delegate=memory pids
@@ -21,13 +21,17 @@ module ConfigLMM
21
21
 
22
22
  template = ERB.new(File.read(__dir__ + '/zone.txt.erb'))
23
23
 
24
- target['DNS'].each do |domain, data|
24
+ target['DNS'].to_h.each do |domain, data|
25
25
  config = { 'Domain' => domain, 'Records' => '' }
26
26
  data.each do |name, data|
27
27
  self.processDNS(domain, data).each do |type, records|
28
28
  records.each do |record|
29
- shortName = Addressable::IDNA.to_ascii(name)
30
- record[:content] = Addressable::IDNA.to_ascii(record[:content]) if record[:type] == 'CNAME'
29
+ shortName = Addressable::IDNA.to_ascii(name) + '.' if type == 'CNAME' || type == 'ALIAS'
30
+ if record[:type] == 'MX'
31
+ priority, name = record[:content].split(' ')
32
+ name = Addressable::IDNA.to_ascii(name) + '.'
33
+ record[:content] = [priority, name].join(' ')
34
+ end
31
35
  config['Records'] += [shortName, record[:ttl], ' IN ', record[:type], record[:content]].join("\t") + "\n"
32
36
  end
33
37
  end
@@ -37,7 +37,7 @@ module ConfigLMM
37
37
  server = compute.servers.all.find { |server| server.name == serverName }
38
38
  if server
39
39
  server.start
40
- return
40
+ return false
41
41
  end
42
42
  settings = {
43
43
  name: serverName,
@@ -77,9 +77,10 @@ module ConfigLMM
77
77
  server.iso_file = File.basename(iso)
78
78
  end
79
79
  server.save
80
- activeState['Status'] = 'CREATED'
80
+ activeState['Status'] = State::STATUS_CREATED
81
81
  state.save
82
82
  server.start
83
+ true
83
84
  end
84
85
 
85
86
  def dirPoolXML(name, path)
@@ -4,6 +4,7 @@ require 'uri'
4
4
  require 'addressable/uri'
5
5
  require 'addressable/idna'
6
6
  require 'fog/powerdns'
7
+ require 'http'
7
8
 
8
9
  module ConfigLMM
9
10
  module LMM
@@ -13,6 +14,9 @@ module ConfigLMM
13
14
  DEFAULT_HOST = 'localhost'
14
15
  DEFAULT_PORT = 8081
15
16
  SSH_TIMEOUT = 10
17
+ PACKAGE_NAME = 'PowerDNS'
18
+ SERVICE_NAME = 'pdns'
19
+ USER = 'pdns'
16
20
 
17
21
  # TODO
18
22
  # def actionPowerDNSValidate(id, target, activeState, context, options)
@@ -21,6 +25,7 @@ module ConfigLMM
21
25
 
22
26
  def actionPowerDNSBuild(id, target, activeState, context, options)
23
27
  if target['Settings']
28
+ prepareSettings(target)
24
29
  targetDir = options['output'] + CONFIG_DIR + '/'
25
30
  mkdir(targetDir, options['dry'])
26
31
  content = ''
@@ -44,11 +49,38 @@ module ConfigLMM
44
49
  def actionPowerDNSDeploy(id, target, activeState, context, options)
45
50
  #actionPowerDNSDiff(id, target, activeState, context, options)
46
51
 
47
- deploySettings(target)
48
- if target['DNS']
49
- connect(id, target, activeState, context, options) do |host, port, key|
52
+ deploySettings(target, activeState, options)
53
+ connect(id, target, activeState, context, options) do |host, port, key|
54
+ if target['TSIG']
55
+ updateTSIG(host, port, key, target['TSIG'])
56
+ end
57
+ if target['DNS']
50
58
  updateDNS(host, port, key, target['DNS'])
51
59
  end
60
+ if target['Metadata']
61
+ updateMetadata(host, port, key, target['Metadata'])
62
+ end
63
+ end
64
+ end
65
+
66
+ def cleanup(configs, state, context, options)
67
+ cleanupType(:PowerDNS, configs, state, context, options) do |item, id, state, context, options, ssh|
68
+ if item['Deploy']
69
+ Framework::LinuxApp.stopService(SERVICE_NAME, ssh, options[:dry])
70
+ Framework::LinuxApp.firewallRemoveService('dns', ssh, options[:dry])
71
+ Framework::LinuxApp.removePackage(PACKAGE_NAME, ssh, options[:dry])
72
+
73
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
74
+
75
+ if options[:destroy]
76
+ item['Database'] ||= {}
77
+ PostgreSQL.dropUserAndDB(item['Database'], USER, ssh, options[:dry])
78
+ rm('/etc/pdns', options[:dry], ssh)
79
+ state.item(id)['Status'] = State::STATUS_DESTROYED unless options[:dry]
80
+ end
81
+ else
82
+ # TODO
83
+ end
52
84
  end
53
85
  end
54
86
 
@@ -95,7 +127,10 @@ module ConfigLMM
95
127
  canonicalDomain = domain + '.'
96
128
 
97
129
  if !dns.list_zones(server).map { |zone| zone['name'].downcase }.include?(canonicalDomain.downcase)
98
- dns.create_zone(server, canonicalDomain, [], { kind: 'Native' })
130
+ dns.create_zone(server, canonicalDomain, [], { kind: 'Native' }.update(info['!'].to_h))
131
+ elsif !info['!'].to_h.empty?
132
+ puts ({ kind: 'Native' }.update(info['!'].to_h).inspect)
133
+ dns.update_zone(server, canonicalDomain, { kind: 'Native' }.update(info['!'].to_h))
99
134
  end
100
135
 
101
136
  zone = dns.get_zone(server, canonicalDomain)
@@ -103,8 +138,9 @@ module ConfigLMM
103
138
  rrsets = []
104
139
  remove = []
105
140
  info.each do |name, data|
106
- fullName = name + '.' + domain + '.'
107
- fullName = domain + '.' if name == '@'
141
+ next if name == '!'
142
+ fullName = Addressable::IDNA.to_ascii(name) + '.' + Addressable::IDNA.to_ascii(domain) + '.'
143
+ fullName = Addressable::IDNA.to_ascii(domain) + '.' if name == '@'
108
144
  self.processDNS(domain, data).each do |type, records|
109
145
  #remove += removeConflicting(zone, fullName, type)
110
146
  rrset = {
@@ -115,6 +151,21 @@ module ConfigLMM
115
151
  records: []
116
152
  }
117
153
  records.each do |record|
154
+ record[:content] = Addressable::IDNA.to_ascii(record[:content]) + '.' if type == 'CNAME' || type == 'ALIAS'
155
+ if type == 'MX'
156
+ priority, name = record[:content].split(' ')
157
+ name = Addressable::IDNA.to_ascii(name) + '.'
158
+ record[:content] = [priority, name].join(' ')
159
+ elsif type == 'SOA'
160
+ ns, email, serial, refresh, again, expire, ttl = record[:content].split(' ')
161
+ record[:content] = [Addressable::IDNA.to_ascii(ns) + '.',
162
+ Addressable::IDNA.to_ascii(email) + '.',
163
+ serial.to_s,
164
+ refresh.to_s,
165
+ again.to_s,
166
+ expire.to_s,
167
+ ttl.to_s].join(' ')
168
+ end
118
169
  rrset[:records] << { content: record[:content], disabled: false }
119
170
  end
120
171
  rrsets << rrset
@@ -129,12 +180,111 @@ module ConfigLMM
129
180
 
130
181
  end
131
182
 
132
- def deploySettings(target)
133
- if target['Settings']
183
+ def updateTSIG(host, port, key, targetTSIG)
184
+ server = 'localhost'
185
+ url = "http://#{host}:#{port}/api/v1/servers/#{server}/tsigkeys"
186
+ headers = { 'X-Api-Key' => key }
187
+ targetTSIG.each do |name, info|
188
+ data = { name: name, algorithm: info['Algorithm'] }
189
+ response = HTTP.headers(headers).post(url, json: data)
190
+ if response.status == 201
191
+ result = response.parse(:json)
192
+ prompt.say("TSIG #{result['name']} key: #{result['key']}", :color => :magenta)
193
+ elsif response.status != 409
194
+ prompt.say(response.body.to_s, :color => :red)
195
+ raise 'Failed to create TSIG key!'
196
+ end
197
+ end
198
+ end
199
+
200
+ def updateMetadata(host, port, key, targetMetadata)
201
+ server = 'localhost'
202
+ headers = { 'X-Api-Key' => key }
203
+ targetMetadata.each do |zone, info|
204
+ info.each do |kind, metadata|
205
+ url = "http://#{host}:#{port}/api/v1/servers/#{server}/zones/#{Addressable::IDNA.to_ascii(zone)}/metadata/#{kind}"
206
+ metadata = [metadata] unless metadata.is_a?(Array)
207
+ data = { kind: kind, metadata: metadata }
208
+ response = HTTP.headers(headers).put(url, json: data)
209
+ if response.status != 200
210
+ prompt.say(response.body.to_s, :color => :red)
211
+ raise "Failed to update Metadata for #{zone}!"
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ def prepareSettings(target)
218
+ if !target['Settings'].key?('api')
219
+ target['Settings']['api'] = 'yes'
220
+ end
221
+ if !target['Settings'].key?('expand-alias')
222
+ target['Settings']['expand-alias'] = 'yes'
223
+ end
224
+ if !target['Settings'].key?('launch')
225
+ target['Settings']['launch'] = 'gpgsql'
226
+ target['Settings']['gpgsql-host'] = '/run/postgresql'
227
+ target['Settings']['gpgsql-user'] = USER
228
+ target['Settings']['gpgsql-dbname'] = USER
229
+ end
230
+ end
231
+
232
+ def deploySettings(target, activeState, options)
233
+ if target['Location']
234
+ uri = Addressable::URI.parse(target['Location'])
235
+ params = {}
236
+ params = CGI.parse(uri.query) if uri.query
237
+ if uri.scheme == 'ssh' && !params.key?('host')
238
+ self.class.sshStart(uri) do |ssh|
239
+ target['Deploy'] = !!target['Settings'] unless target.key?('Deploy')
240
+ activeState['Deploy'] = target['Deploy']
241
+ if target['Deploy']
242
+ Framework::LinuxApp.ensurePackages([PACKAGE_NAME], ssh)
243
+ Framework::LinuxApp.ensureServiceAutoStartOverSSH(SERVICE_NAME, ssh)
244
+ end
245
+ if target['Settings']
246
+ prepareSettings(target)
247
+ self.class.sshExec!(ssh, "mkdir -p #{CONFIG_DIR}")
248
+ self.class.sshExec!(ssh, "sed -i 's|# include-dir=|include-dir=#{CONFIG_DIR}|' /etc/pdns/pdns.conf")
249
+ ssh.scp.upload!(options['output'] + CONFIG_DIR + '/configlmm.conf', CONFIG_DIR + '/configlmm.conf')
250
+ apiKeyFile = CONFIG_DIR + '/apiKey.conf'
251
+ if !self.class.remoteFilePresent?(apiKeyFile, ssh)
252
+ apiKey = ENV['POWERDNS_API_KEY']
253
+ apiKey = SecureRandom.urlsafe_base64(60) unless apiKey
254
+ self.class.sshExec!(ssh, " echo 'api-key=#{apiKey}' > #{apiKeyFile}")
255
+ self.class.sshExec!(ssh, " chown #{USER}:#{USER} #{apiKeyFile}")
256
+ self.class.sshExec!(ssh, " chmod 400 #{apiKeyFile}")
257
+ prompt.say("PowerDNS API Key: #{apiKey}", )
258
+ end
259
+ self.configurePostgreSQL(target['Settings'], ssh)
260
+ end
261
+ if target['Deploy']
262
+ Framework::LinuxApp.firewallAddServiceOverSSH('dns', ssh)
263
+ Framework::LinuxApp.startServiceOverSSH(SERVICE_NAME, ssh)
264
+ activeState['Status'] = State::STATUS_DEPLOYED
265
+ end
266
+ end
267
+ end
268
+ else
134
269
  # TODO
135
270
  end
136
271
  end
137
272
 
273
+ def configurePostgreSQL(settings, ssh)
274
+ password = SecureRandom.alphanumeric(20)
275
+ if settings['gpgsql-host'] == 'localhost' || settings['gpgsql-host'].start_with?('/')
276
+ PostgreSQL.createUserAndDBOverSSH(USER, password, ssh)
277
+ PostgreSQL.importSQL(USER, USER, '/usr/share/doc/packages/pdns/schema.pgsql.sql', ssh)
278
+ PostgreSQL.updateOwner(USER, USER, ssh)
279
+ else
280
+ self.class.sshStart("ssh://#{settings['gpgsql-host']}/") do |ssh|
281
+ PostgreSQL.createUserAndDBOverSSH(USER, password, ssh)
282
+ PostgreSQL.importSQL(USER, USER, '/usr/share/doc/packages/pdns/schema.pgsql.sql', ssh)
283
+ PostgreSQL.updateOwner(USER, USER, ssh)
284
+ end
285
+ end
286
+ password
287
+ end
138
288
 
139
289
  def connect(id, target, activeState, context, options)
140
290
  host = DEFAULT_HOST
data/README.md CHANGED
@@ -221,6 +221,12 @@ First you need to have Ruby and RubyGems. Then you can install it with:
221
221
 
222
222
  $ gem install ConfigLMM
223
223
 
224
+ If that doesn't work (eg. your Ruby is too old) then install with (it will install [RVM](https://rvm.io/))
225
+
226
+ ```
227
+ $ curl -sS https://raw.githubusercontent.com/ConfigLMM/ConfigLMM/master/bootstrap.sh | sh
228
+ ```
229
+
224
230
  ## Usage
225
231
 
226
232
  Create yaml file with desired config, eg.
data/bootstrap.sh ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/sh
2
+
3
+ distro=$(cat /etc/os-release | grep ^ID= | cut -d '=' -f 2 | cut -d '"' -f 2)
4
+
5
+ function admin {
6
+ if [ "$EUID" -eq "0" ]; then
7
+ "$@"
8
+ else
9
+ sudo "$@"
10
+ fi
11
+ }
12
+
13
+ if [ "$EUID" -ne "0" ]; then
14
+ if ! command -v sudo &> /dev/null; then
15
+ case $distro in
16
+
17
+ opensuse-leap)
18
+ echo "You don't have sudo! Enter root password to install it"
19
+ su root -c "zypper install --no-confirm sudo"
20
+ ;;
21
+
22
+ arch)
23
+ echo "You don't have sudo! Enter root password to install it"
24
+ admin pacman -S --noconfirm --needed sudo
25
+ ;;
26
+
27
+ *)
28
+ echo "Sudo not found but is needed!" >&2
29
+ echo "Don't know how to install it for your $distro distribution!" >&2
30
+ echo "Submit a PR :)" >&2
31
+ exit 3
32
+ ;;
33
+ esac
34
+ fi
35
+ fi
36
+
37
+ case $distro in
38
+
39
+ opensuse-leap)
40
+ admin zypper install --no-confirm ruby libvirt-devel
41
+ ;;
42
+
43
+ arch)
44
+ admin pacman -S --noconfirm --needed ruby rubygems
45
+ ;;
46
+
47
+ *)
48
+ if ! command -v ruby &> /dev/null
49
+ then
50
+ echo "Ruby not found!" >&2
51
+ echo "Don't know how to install it for your $distro distribution!" >&2
52
+ echo "Submit a PR :)" >&2
53
+ exit 1
54
+ fi
55
+ ;;
56
+ esac
57
+
58
+ if ! command -v gem &> /dev/null; then
59
+ echo "RubyGems not found!" >&2
60
+ exit 2
61
+ fi
62
+
63
+ rubyTooOld=$(ruby -e 'puts RUBY_VERSION.to_f < 3.3 ? 1 : 0')
64
+
65
+ if [ "$rubyTooOld" -eq "1" ]; then
66
+ echo "Ruby is too old! Will install RVM" >&2
67
+ gpg2 --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB >/dev/null
68
+ curl -sSL https://get.rvm.io | bash -s stable --ruby=3.3.4
69
+
70
+ if [ "$EUID" -eq "0" ]; then
71
+ source /etc/profile.d/rvm.sh
72
+ else
73
+ source ~/.rvm/scripts/rvm
74
+ fi
75
+
76
+ if [ "$SHELL" = "/usr/bin/fish" ]; then
77
+ curl -sSL --create-dirs -o ~/.config/fish/functions/rvm.fish https://raw.github.com/lunks/fish-nuggets/master/functions/rvm.fish
78
+ sed -i "/rvm default/d" ~/.config/fish/config.fish
79
+ echo "rvm default" >> ~/.config/fish/config.fish
80
+ fi
81
+ fi
82
+
83
+ if [ "$EUID" -eq "0" ]; then
84
+ # This shouldn't be needed but without it doesn't work
85
+ export PATH=/usr/local/rvm/gems/ruby-3.3.4/bin:/usr/local/rvm/rubies/ruby-3.3.4/bin:$PATH
86
+ export GEM_HOME=/usr/local/rvm/gems/ruby-3.3.4
87
+ export GEM_PATH=/usr/local/rvm/gems/ruby-3.3.4
88
+ fi
89
+
90
+ bash -lc 'gem install ConfigLMM'
91
+
92
+ echo "You need to close and reopen your shell" >&2
@@ -23,8 +23,7 @@ module ConfigLMM
23
23
 
24
24
  items.each do |item|
25
25
  type, content = item.strip.split('=')
26
- content += '.' if type == 'CNAME' || type == 'ALIAS'
27
- content = domain + '.' if content == '@' || content == '@.'
26
+ content = domain if content == '@'
28
27
  content = self.class.externalIp if content == '@me'
29
28
  records[type] ||= []
30
29
  records[type] << { type: type, content: content, ttl: DEFAULT_TTL }