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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +67 -0
- data/Examples/Implemented.mm.yaml +75 -1
- data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +14 -0
- data/Plugins/Apps/Authentik/Authentik-Server.container +19 -0
- data/Plugins/Apps/Authentik/Authentik-Worker.container +18 -0
- data/Plugins/Apps/Authentik/Authentik.conf.erb +42 -0
- data/Plugins/Apps/Authentik/Authentik.lmm.rb +95 -0
- data/Plugins/Apps/BookStack/BookStack.conf.erb +41 -0
- data/Plugins/Apps/BookStack/BookStack.container +15 -0
- data/Plugins/Apps/BookStack/BookStack.lmm.rb +80 -0
- data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +41 -0
- data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +17 -0
- data/Plugins/Apps/Discourse/Discourse.conf.erb +41 -0
- data/Plugins/Apps/Discourse/Discourse.container +17 -0
- data/Plugins/Apps/Discourse/Discourse.lmm.rb +95 -0
- data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +171 -0
- data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +19 -0
- data/Plugins/Apps/ERPNext/ERPNext-Queue.container +17 -0
- data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +17 -0
- data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +19 -0
- data/Plugins/Apps/ERPNext/ERPNext.container +18 -0
- data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +193 -0
- data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
- data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
- data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
- data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
- data/Plugins/Apps/GitLab/GitLab.container +18 -0
- data/Plugins/Apps/GitLab/GitLab.lmm.rb +100 -0
- data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +57 -0
- data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
- data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
- data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
- data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
- data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +115 -0
- data/Plugins/Apps/Matrix/Element.container +14 -0
- data/Plugins/Apps/Matrix/Matrix.conf.erb +49 -5
- data/Plugins/Apps/Matrix/Matrix.lmm.rb +86 -1
- data/Plugins/Apps/Matrix/Synapse.container +17 -0
- data/Plugins/Apps/Matrix/config.json +50 -0
- data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
- data/Plugins/Apps/Matrix/log.config +30 -0
- data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +48 -10
- data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +83 -1
- data/Plugins/Apps/Nextcloud/config.php +18 -0
- data/Plugins/Apps/Nginx/conf.d/configlmm.conf +71 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +11 -5
- data/Plugins/Apps/Nginx/config-lmm/proxy.conf +5 -1
- data/Plugins/Apps/Nginx/main.conf.erb +31 -0
- data/Plugins/Apps/Nginx/nginx.conf +3 -68
- data/Plugins/Apps/Nginx/nginx.lmm.rb +83 -22
- data/Plugins/Apps/Nginx/proxy.conf.erb +13 -3
- data/Plugins/Apps/Odoo/Odoo.conf.erb +30 -13
- data/Plugins/Apps/Odoo/Odoo.container +18 -0
- data/Plugins/Apps/Odoo/Odoo.lmm.rb +62 -2
- data/Plugins/Apps/Odoo/odoo.conf +37 -0
- data/Plugins/Apps/OpenVidu/Ingress.container +18 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +34 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.container +16 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +90 -0
- data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +35 -0
- data/Plugins/Apps/OpenVidu/OpenViduCall.container +15 -0
- data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
- data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
- data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +95 -0
- data/Plugins/Apps/Peppermint/Peppermint.conf.erb +60 -0
- data/Plugins/Apps/Peppermint/Peppermint.container +15 -0
- data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +58 -0
- data/Plugins/Apps/Postfix/Postfix.lmm.rb +165 -31
- data/Plugins/Apps/Postfix/smtpd.conf +3 -0
- data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +242 -24
- data/Plugins/Apps/Roundcube/Roundcube.conf.erb +75 -0
- data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +145 -0
- data/Plugins/Apps/SSH/SSH.lmm.rb +51 -0
- data/Plugins/Apps/Tunnel/tunnel.lmm.rb +63 -0
- data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
- data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
- data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
- data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
- data/Plugins/Apps/UVdesk/UVdesk.conf.erb +52 -0
- data/Plugins/Apps/UVdesk/UVdesk.lmm.rb +85 -0
- data/Plugins/Apps/Valkey/Valkey.lmm.rb +34 -1
- data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +35 -18
- data/Plugins/Apps/Vaultwarden/Vaultwarden.container +16 -0
- data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +46 -3
- data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +42 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.container +15 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +61 -0
- data/Plugins/Apps/gollum/gollum.conf.erb +84 -19
- data/Plugins/Apps/gollum/gollum.container +15 -0
- data/Plugins/Apps/gollum/gollum.lmm.rb +48 -11
- data/Plugins/OS/Linux/Debian/preseed.cfg.erb +62 -0
- data/Plugins/OS/Linux/Distributions.yaml +42 -0
- data/Plugins/OS/Linux/Flavours.yaml +11 -0
- data/Plugins/OS/Linux/Linux.lmm.rb +362 -41
- data/Plugins/OS/Linux/Packages.yaml +88 -5
- data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
- data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +137 -0
- data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +15 -0
- data/Plugins/OS/Linux/systemd/systemd.lmm.rb +28 -0
- data/Plugins/OS/Linux/systemd/user-0.slice +9 -0
- data/Plugins/OS/Linux/systemd/user@.service.d/delegate.conf +3 -0
- data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +7 -3
- data/Plugins/Platforms/libvirt/libvirt.lmm.rb +3 -2
- data/Plugins/Services/DNS/PowerDNS.lmm.rb +158 -8
- data/README.md +6 -0
- data/bootstrap.sh +92 -0
- data/lib/ConfigLMM/Framework/plugins/dns.rb +1 -2
- data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +249 -45
- data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +56 -7
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +112 -16
- data/lib/ConfigLMM/cli.rb +3 -1
- data/lib/ConfigLMM/commands/cleanup.rb +1 -0
- data/lib/ConfigLMM/commands/configsCommand.rb +3 -1
- data/lib/ConfigLMM/io/configList.rb +3 -1
- data/lib/ConfigLMM/state.rb +10 -2
- data/lib/ConfigLMM/version.rb +1 -1
- metadata +82 -3
- 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
|
@@ -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
|
-
|
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'] =
|
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
|
-
|
49
|
-
|
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
|
-
|
107
|
-
fullName =
|
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
|
133
|
-
|
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
|
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 }
|