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,58 @@
1
+
2
+ module ConfigLMM
3
+ module LMM
4
+ class Peppermint < Framework::NginxApp
5
+
6
+ USER = 'peppermint'
7
+ HOME_DIR = '/var/lib/peppermint'
8
+ HOST_IP = '10.0.2.2'
9
+
10
+ def actionPeppermintDeploy(id, target, activeState, context, options)
11
+ raise Framework::PluginProcessError.new('Domain field must be set!') unless target['Domain']
12
+
13
+ target['Database'] ||= {}
14
+ if target['Location'] && target['Location'] != '@me'
15
+ uri = Addressable::URI.parse(target['Location'])
16
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
17
+
18
+ self.class.sshStart(uri) do |ssh|
19
+
20
+ dbPassword = self.configurePostgreSQL(target['Database'], ssh)
21
+ distroInfo = Framework::LinuxApp.currentDistroInfo(ssh)
22
+ Framework::LinuxApp.configurePodmanServiceOverSSH(USER, HOME_DIR, 'Peppermint Ticket Management', distroInfo, ssh)
23
+
24
+ path = Framework::LinuxApp::SYSTEMD_CONTAINERS_PATH.gsub('~', HOME_DIR)
25
+ self.class.sshExec!(ssh, " echo 'DB_HOST=#{HOST_IP}' > #{path}/Peppermint.env")
26
+ self.class.sshExec!(ssh, " echo 'DB_USERNAME=#{USER}' >> #{path}/Peppermint.env")
27
+ self.class.sshExec!(ssh, " echo 'DB_PASSWORD=#{dbPassword}' >> #{path}/Peppermint.env")
28
+ self.class.sshExec!(ssh, " echo 'SECRET=#{SecureRandom.urlsafe_base64(60)}' >> #{path}/Peppermint.env")
29
+ self.class.sshExec!(ssh, " echo 'API_URL=https://#{target['Domain']}/api' >> #{path}/Peppermint.env")
30
+ self.class.sshExec!(ssh, "chown #{USER}:#{USER} #{path}/Peppermint.env")
31
+ self.class.sshExec!(ssh, "chmod 600 #{path}/Peppermint.env")
32
+
33
+ ssh.scp.upload!(__dir__ + '/Peppermint.container', path)
34
+ self.class.sshExec!(ssh, "systemctl --user --machine=#{USER}@ daemon-reload")
35
+ self.class.sshExec!(ssh, "systemctl --user --machine=#{USER}@ start Peppermint")
36
+
37
+ Framework::LinuxApp.ensurePackages([NGINX_PACKAGE], ssh)
38
+ Framework::LinuxApp.ensureServiceAutoStartOverSSH(NGINX_PACKAGE, ssh)
39
+ self.class.prepareNginxConfig(target, ssh)
40
+ self.writeNginxConfig(__dir__, 'Peppermint', id, target, state, context, options)
41
+ self.deployNginxConfig(id, target, activeState, context, options)
42
+ Framework::LinuxApp.startServiceOverSSH(NGINX_PACKAGE, ssh)
43
+ end
44
+ else
45
+ # TODO
46
+ end
47
+ end
48
+
49
+ def configurePostgreSQL(settings, ssh)
50
+ password = SecureRandom.alphanumeric(20)
51
+ PostgreSQL.createRemoteUserAndDBOverSSH(settings, USER, password, ssh)
52
+ password
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+
@@ -4,72 +4,206 @@ module ConfigLMM
4
4
  class Postfix < Framework::Plugin
5
5
  PACKAGE_NAME = 'Postfix'
6
6
  SERVICE_NAME = 'postfix'
7
- MASTER_FILE = '/etc/postfix/master.cf'
8
- MAIN_FILE = '/etc/postfix/main.cf'
9
- TRANSPORT_FILE = '/etc/postfix/transport'
7
+ MASTER_FILE = 'master.cf'
8
+ MAIN_FILE = 'main.cf'
9
+ TRANSPORT_FILE = 'transport'
10
10
 
11
11
  def actionPostfixDeploy(id, target, activeState, context, options)
12
- plugins[:Linux].ensurePackage(PACKAGE_NAME, target['Location'])
12
+ plugins[:Linux].ensurePackages([PACKAGE_NAME, 'CyrusSASL'], target['Location'])
13
13
  plugins[:Linux].ensureServiceAutoStart(SERVICE_NAME, target['Location'])
14
14
 
15
+ activeState['Instance'] = target['Instance']
16
+ activeState['AlternativePort'] = target['AlternativePort']
15
17
  deploySettings(target, target['Location'], options)
16
18
 
17
19
  plugins[:Linux].startService(SERVICE_NAME, target['Location'])
20
+
21
+ activeState['Status'] = State::STATUS_DEPLOYED
18
22
  end
19
23
 
20
24
  def deploySettings(target, location, options)
25
+ postfixDirName = 'postfix'
26
+ postfixDirName = 'postfix-' + target['Instance'] if target['Instance']
27
+ postfixDir = '/etc/' + postfixDirName + '/'
21
28
  if location && location != '@me'
22
- if target['AlternativePort']
23
- updateRemoteFile(location, MASTER_FILE, options, true) do |fileLines|
24
- fileLines << "#{target['AlternativePort']} inet n - n - - smtpd\n"
25
- end
26
- end
27
29
  self.class.sshStart(location) do |ssh|
28
- domain = self.class.sshExec!(ssh, "hostname --fqdn").strip
29
- command = "sed -i 's|^myhostname = .*|myhostname = #{domain}|' #{MAIN_FILE}"
30
- command = "sed -i 's|^#myhostname = virtual.domain.tld|myhostname = #{domain}|' #{MAIN_FILE}"
30
+ if target['Instance']
31
+ self.class.sshExec!(ssh, "postmulti -e init")
32
+ self.class.sshExec!(ssh, "postmulti -I #{postfixDirName} -e create", true)
33
+ self.class.sshExec!(ssh, "sed -i 's|^master_service_disable|#master_service_disable|' #{postfixDir + MAIN_FILE}")
34
+ end
35
+ self.class.sshExec!(ssh, "sed -i 's|^tlsmgr|#tlsmgr|' #{postfixDir + MASTER_FILE}")
36
+ if target.key?('SMTP')
37
+ if !target['SMTP'] || target['SMTP'] == 'unix'
38
+ self.class.sshExec!(ssh, "sed -i 's|^smtp|#smtp|' #{postfixDir + MASTER_FILE}")
39
+ end
40
+ end
41
+ updateRemoteFile(ssh, postfixDir + MASTER_FILE, options, true) do |fileLines|
42
+ if target['AlternativePort']
43
+ fileLines << "#{target['AlternativePort']} inet n - n - - smtpd\n"
44
+ fileLines << "tlsmgr unix - - n 1000? 1 tlsmgr\n"
45
+ else
46
+ if !target.key?('Submission') || (target.key?('Submission') && target['Submission'])
47
+ fileLines << "submissions inet n - n - - smtpd\n"
48
+ fileLines << " -o syslog_name=postfix/submissions\n"
49
+ fileLines << " -o smtpd_tls_wrappermode=yes\n"
50
+ fileLines << " -o smtpd_tls_security_level=encrypt\n"
51
+ fileLines << " -o smtpd_sasl_auth_enable=yes\n"
52
+ fileLines << " -o cleanup_service_name=header_cleanup\n"
53
+ fileLines << "header_cleanup unix n - - - 0 cleanup\n"
54
+ fileLines << " -o header_checks=regexp:/etc/postfix/header_cleanup\n"
55
+
56
+ self.class.sshExec!(ssh, "echo '/^Received:/ IGNORE' > /etc/postfix/header_cleanup")
57
+ self.class.sshExec!(ssh, "echo '/^User-Agent:/ IGNORE' >> /etc/postfix/header_cleanup")
58
+ end
59
+ fileLines << "tlsmgr unix - - n 1000? 1 tlsmgr\n"
60
+ if target['SMTP'] == 'unix'
61
+ fileLines << "smtp unix - - n - - smtp\n"
62
+ end
63
+ end
64
+ fileLines
65
+ end
66
+
67
+ domain = target['Domain']
68
+ domain = self.class.sshExec!(ssh, "hostname --fqdn").strip unless domain
69
+ command = "sed -i 's|^myhostname = .*|myhostname = #{domain}|' #{postfixDir + MAIN_FILE}"
31
70
  self.class.sshExec!(ssh, command)
32
- end
33
- if target['Settings']
71
+ command = "sed -i 's|^#myhostname = virtual.domain.tld|myhostname = #{domain}|' #{postfixDir + MAIN_FILE}"
72
+ self.class.sshExec!(ssh, command)
73
+
74
+ # Fix config bug
75
+ command = "sed -i 's|^alias_maps = :/etc/aliases|alias_maps = lmdb:/etc/aliases|' #{postfixDir + MAIN_FILE}"
76
+ self.class.sshExec!(ssh, command)
77
+ command = "sed -i 's|^canonical_maps = :/etc/postfix/canonical|canonical_maps = lmdb:/etc/postfix/canonical|' #{postfixDir + MAIN_FILE}"
78
+ self.class.sshExec!(ssh, command)
79
+ command = "sed -i 's|^relocated_maps = :/etc/postfix/relocated|relocated_maps = lmdb:/etc/postfix/relocated|' #{postfixDir + MAIN_FILE}"
80
+ self.class.sshExec!(ssh, command)
81
+ command = "sed -i 's|^sender_canonical_maps = :/etc/postfix/sender_canonical|sender_canonical_maps = lmdb:/etc/postfix/sender_canonical|' #{postfixDir + MAIN_FILE}"
82
+ self.class.sshExec!(ssh, command)
83
+ command = "sed -i 's|^transport_maps = :/etc/postfix/transport|transport_maps = lmdb:/etc/postfix/transport|' #{postfixDir + MAIN_FILE}"
84
+ self.class.sshExec!(ssh, command)
85
+ command = "sed -i 's|^smtpd_sender_restrictions = :/etc/postfix/access|smtpd_sender_restrictions = lmdb:/etc/postfix/access|' #{postfixDir + MAIN_FILE}"
86
+ self.class.sshExec!(ssh, command)
87
+ command = "sed -i 's|^virtual_alias_maps = :/etc/postfix/virtual|virtual_alias_maps = lmdb:/etc/postfix/virtual|' #{postfixDir + MAIN_FILE}"
88
+ self.class.sshExec!(ssh, command)
89
+ command = "sed -i 's|^relay_domains = $mydestination :/etc/postfix/relay|relay_domains = $mydestination lmdb:/etc/postfix/relay|' #{postfixDir + MAIN_FILE}"
90
+ self.class.sshExec!(ssh, command)
91
+ command = "sed -i 's|^relay_recipient_maps = :/etc/postfix/relay_recipients|relay_recipient_maps = lmdb:/etc/postfix/relay_recipients|' #{postfixDir + MAIN_FILE}"
92
+ self.class.sshExec!(ssh, command)
93
+ command = "sed -i 's|^virtual_mailbox_maps =.*|virtual_mailbox_maps = lmdb:/etc/postfix/mailboxes|' #{postfixDir + MAIN_FILE}"
94
+ self.class.sshExec!(ssh, command)
95
+
96
+
97
+ if target['AlternativePort']
98
+ Framework::LinuxApp.firewallAddPort("#{target['AlternativePort']}/tcp", ssh)
99
+ else
100
+ Framework::LinuxApp.firewallAddService('smtp', ssh)
101
+ end
102
+ Framework::LinuxApp.firewallAddService('smtps', ssh)
103
+
104
+ ssh.scp.upload!(__dir__ + '/smtpd.conf', '/etc/sasl2/smtpd.conf')
105
+ self.class.sshExec!(ssh, "touch /etc/sasldb2")
106
+ self.class.sshExec!(ssh, "chown postfix:postfix /etc/sasldb2")
107
+ self.class.sshExec!(ssh, "touch #{postfixDir}access")
108
+ self.class.sshExec!(ssh, "postmap #{postfixDir}access")
109
+ self.class.sshExec!(ssh, "touch #{postfixDir}sender_login")
110
+ self.class.sshExec!(ssh, "postmap #{postfixDir}sender_login")
111
+
112
+ certDir = Framework::LinuxApp.createCertificateOverSSH(ssh)
113
+ target['Settings'] ||= []
114
+ target['Settings']['smtpd_sender_login_maps'] = "lmdb:#{postfixDir}sender_login" unless target['Settings']['smtpd_sender_login_maps']
115
+ target['Settings']['smtpd_sender_restrictions'] = "reject_sender_login_mismatch, lmdb:#{postfixDir}access" unless target['Settings']['smtpd_sender_restrictions']
116
+ target['Settings']['smtp_tls_security_level'] = 'may' unless target['Settings']['smtp_tls_security_level']
117
+ target['Settings']['smtpd_tls_mandatory_protocols'] = '>=TLSv1.2' unless target['Settings']['smtpd_tls_mandatory_protocols']
118
+ target['Settings']['smtpd_tls_auth_only'] = 'yes' unless target['Settings']['smtpd_tls_auth_only']
119
+ target['Settings']['smtpd_tls_security_level'] = 'may' unless target['Settings']['smtpd_tls_security_level']
120
+ target['Settings']['smtpd_tls_cert_file'] = certDir + 'fullchain.pem' unless target['Settings']['smtpd_tls_cert_file']
121
+ target['Settings']['smtpd_tls_key_file'] = certDir + 'privkey.pem' unless target['Settings']['smtpd_tls_key_file']
122
+ target['Settings']['tls_preempt_cipherlist'] = 'yes' unless target['Settings']['tls_preempt_cipherlist']
123
+ target['Settings']['tls_ssl_options'] = 'NO_RENEGOTIATION' unless target['Settings']['tls_ssl_options']
124
+
125
+
34
126
  target['Settings'].each do |name, value|
35
- self.class.sshStart(location) do |ssh|
36
- command = "sed -i 's|^#{name} =.*|#{name} = #{value}|' #{MAIN_FILE}"
37
- self.class.sshExec!(ssh, command)
127
+ command = "sed -i 's|^#{name} =.*|##{name} = #{value}|' #{postfixDir + MAIN_FILE}"
128
+ self.class.sshExec!(ssh, command)
129
+ end
130
+ updateRemoteFile(ssh, postfixDir + MAIN_FILE, options) do |fileLines|
131
+ target['Settings'].each do |name, value|
132
+ value = 'yes' if value == true
133
+ value = 'no' if value == false
134
+ fileLines << "#{name} = #{value}\n"
38
135
  end
136
+ fileLines
39
137
  end
40
- end
41
- if target['ForwardAll']
42
- updateRemoteFile(location, TRANSPORT_FILE, options, true) do |fileLines|
43
- hostname, port = target['ForwardAll'].split(':')
44
- hostname = '[' + hostname + ']'
45
- line = '* smtp:' + hostname
46
- line += ':' + port if port
47
- fileLines << line + "\n"
138
+
139
+ if target['ForwardDovecot']
140
+ command = "sed -i 's|^#virtual_transport =.*|virtual_transport = lmtp:unix:/run/dovecot/lmtp|' #{postfixDir + MAIN_FILE}"
141
+ self.class.sshExec!(ssh, command)
48
142
  end
49
- self.class.sshStart(location) do |ssh|
50
- self.class.sshExec!(ssh, "postmap #{TRANSPORT_FILE}")
143
+
144
+ if target['ForwardAll']
145
+ command = "sed -i 's|^transport_maps =.*|transport_maps = lmdb:#{postfixDir}transport|' #{postfixDir + MAIN_FILE}"
146
+ self.class.sshExec!(ssh, command)
147
+ updateRemoteFile(ssh, postfixDir + TRANSPORT_FILE, options, true) do |fileLines|
148
+ hostname, port = target['ForwardAll'].split(':')
149
+ hostname = '[' + hostname + ']'
150
+ line = '* smtp:' + hostname
151
+ line += ':' + port if port
152
+ fileLines << line + "\n"
153
+ end
154
+ self.class.sshExec!(ssh, "postmap #{postfixDir + TRANSPORT_FILE}")
155
+ end
156
+
157
+ if target['Instance']
158
+ self.class.sshExec!(ssh, "postmulti -i #{postfixDirName} -e enable")
159
+ self.class.sshExec!(ssh, "postmulti -i #{postfixDirName} -p start", true)
51
160
  end
52
161
  end
53
162
  else
54
163
  if target['AlternativePort']
55
- updateLocalFile(MASTER_FILE, options, true) do |fileLines|
164
+ updateLocalFile(postfixDir + MASTER_FILE, options, true) do |fileLines|
56
165
  fileLines << "#{target['AlternativePort']} inet n - n - - smtpd\n"
57
166
  end
58
167
  end
59
168
  if target['Settings']
60
169
  target['Settings'].each do |name, value|
61
- `sed -i 's|^#{name} =.*|#{name} = #{value}|' #{MAIN_FILE}`
170
+ `sed -i 's|^#{name} =.*|#{name} = #{value}|' #{postfixDir + MAIN_FILE}`
62
171
  end
63
172
  end
64
173
  if target['ForwardAll']
65
- updateLocalFile(TRANSPORT_FILE, options, true) do |fileLines|
174
+ updateLocalFile(postfixDir + TRANSPORT_FILE, options, true) do |fileLines|
66
175
  fileLines << '* smtp:[' + target['ForwardAll'] + "]\n"
67
176
  end
68
- `postmap #{TRANSPORT_FILE}`
177
+ `postmap #{postfixDir + TRANSPORT_FILE}`
69
178
  end
70
179
  end
71
180
  end
72
181
 
182
+ def cleanup(configs, state, context, options)
183
+ cleanupType(:Postfix, configs, state, context, options) do |item, id, state, context, options, ssh|
184
+ instances = self.class.exec('postmulti -l | wc -l', ssh, true).strip.to_i
185
+ if instances <= 1
186
+ Framework::LinuxApp.stopService(SERVICE_NAME, ssh, options[:dry])
187
+ Framework::LinuxApp.firewallRemoveService('smtps', ssh, options[:dry])
188
+ if item['AlternativePort']
189
+ Framework::LinuxApp.firewallRemovePort("#{item['AlternativePort']}/tcp", ssh, options[:dry])
190
+ else
191
+ Framework::LinuxApp.firewallRemoveService('smtp', ssh, options[:dry])
192
+ end
193
+ Framework::LinuxApp.removePackage(PACKAGE_NAME, ssh, options[:dry])
194
+
195
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
196
+
197
+ if options[:destroy]
198
+ rm('/etc/postfix', options[:dry], ssh)
199
+
200
+ state.item(id)['Status'] = State::STATUS_DESTROYED unless options[:dry]
201
+ end
202
+ else
203
+ prompt.say('Postfix multiple instance cleanup not implemented!', :color => :red)
204
+ end
205
+ end
206
+ end
73
207
  end
74
208
 
75
209
  end
@@ -0,0 +1,3 @@
1
+ pwcheck_method: auxprop
2
+ auxprop_plugin: sasldb
3
+ mech_list: plain
@@ -12,52 +12,270 @@ module ConfigLMM
12
12
  CONFIG_FILE = 'data/postgresql.conf'
13
13
 
14
14
  def actionPostgreSQLDeploy(id, target, activeState, context, options)
15
- self.ensurePackage(PACKAGE_NAME, target['Location'])
16
- self.ensureServiceAutoStart(SERVICE_NAME, target['Location'])
15
+ target['Deploy'] = !!(target['ListenAll'] || target['Listen'] || target['Settings']) unless target.key?('Deploy')
16
+ activeState['Deploy'] = target['Deploy']
17
+ activeState['Users'] = target['Users']
18
+ activeState['Databases'] = target['Databases']
19
+ activeState['Publications'] = target['Publications']
20
+ activeState['Subscriptions'] = target['Subscriptions']
21
+
22
+ if target['Deploy']
23
+ self.ensurePackage(PACKAGE_NAME, target['Location'])
24
+ self.ensureServiceAutoStart(SERVICE_NAME, target['Location'])
25
+ self.startService(SERVICE_NAME, target['Location'])
26
+ end
17
27
 
18
28
  if target['Location'] && target['Location'] != '@me'
19
29
  uri = Addressable::URI.parse(target['Location'])
20
30
  raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
21
- if target['ListenAll']
22
- cmd = "sed -i 's|^host all all 127.0.0.1/32 ident|host all all 0.0.0.0/0 scram-sha-256|'"
23
- dir = updateConfigOverSSH(uri, cmd)
24
- updateRemoteFile(uri, dir + CONFIG_FILE, options, false) do |configLines|
25
- configLines << "listen_addresses = '*'\n"
31
+
32
+ self.class.sshStart(uri) do |ssh|
33
+ if target['Deploy']
34
+ self.updateSettingsOverSSH(target, ssh, options)
35
+ self.class.sshExec!(ssh, "su --login #{USER_NAME} --command 'pg_ctl reload'")
36
+ end
37
+ self.class.createUsersOverSSH(target, ssh)
38
+ self.class.createDatabasesOverSSH(target, ssh)
39
+ self.class.createPublicationsOverSSH(target, ssh)
40
+ self.class.createSubscriptionsOverSSH(target, ssh)
41
+ end
42
+ else
43
+ if target['Deploy']
44
+ `pg_ctl reload`
45
+ end
46
+ end
47
+
48
+ activeState['Status'] = State::STATUS_DEPLOYED
49
+ end
50
+
51
+ def cleanup(configs, state, context, options)
52
+ cleanupType(:PostgreSQL, configs, state, context, options) do |item, id, state, context, options, ssh|
53
+ if item['Deploy']
54
+ Framework::LinuxApp.stopService(SERVICE_NAME, ssh, options[:dry])
55
+ Framework::LinuxApp.disableService(SERVICE_NAME, ssh, options[:dry])
56
+ Framework::LinuxApp.removePackage(PACKAGE_NAME, ssh, options[:dry])
57
+
58
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
59
+
60
+ if options[:destroy]
61
+ Framework::LinuxApp.deleteUserAndGroup(USER_NAME, ssh, options[:dry])
62
+
63
+ state.item(id)['Status'] = State::STATUS_DESTROYED unless options[:dry]
26
64
  end
27
65
  else
28
- cmd = "sed -i 's|^host all all 127.0.0.1/32 ident|host all all 127.0.0.1/32 scram-sha-256|'"
29
- updateConfigOverSSH(uri, cmd)
66
+ # TODO
67
+ end
68
+ end
69
+ end
70
+
71
+ def updateListenLocal(target)
72
+ dir = pgsqlDir(self.class.distroID)
73
+ if target['ListenAll']
74
+ `sed -i 's|^host all all 127.0.0.1/32 ident|host all all 0.0.0.0/0 scram-sha-256|' #{dir + HBA_FILE}`
75
+ updateLocalFile(dir + CONFIG_FILE, options) do |configLines|
76
+ configLines << "listen_addresses = '*'\n"
30
77
  end
31
78
  else
32
- dir = pgsqlDir(self.class.distroID)
33
- if target['ListenAll']
34
- `sed -i 's|^host all all 127.0.0.1/32 ident|host all all 0.0.0.0/0 scram-sha-256|' #{dir + HBA_FILE}`
35
- updateLocalFile(dir + CONFIG_FILE, options) do |configLines|
36
- configLines << "listen_addresses = '*'"
79
+ `sed -i 's|^host all all 127.0.0.1/32 ident|host all all 127.0.0.1/32 scram-sha-256|' #{dir + HBA_FILE}`
80
+ end
81
+ end
82
+
83
+ def updateSettingsOverSSH(target, ssh, options)
84
+ dir = nil
85
+ settingLines = []
86
+ hbaLines = []
87
+ if target['ListenAll']
88
+ cmd = "sed -i 's|^host all all 127.0.0.1/32 ident|host all all 0.0.0.0/0 scram-sha-256|'"
89
+ dir = updateConfigOverSSH(ssh, cmd)
90
+ settingLines << "listen_addresses = '*'\n"
91
+ Framework::LinuxApp.firewallAddPortOverSSH('5432/tcp', ssh)
92
+ elsif target['Listen'] && !target['Listen'].empty?
93
+ cmd = "sed -i 's|^host all all 127.0.0.1/32 ident|host all all 127.0.0.1/32 scram-sha-256|'"
94
+ dir = updateConfigOverSSH(ssh, cmd)
95
+
96
+ ips = target['Listen'].map { |addr| addr.split('/').first }.join(',')
97
+ settingLines << "listen_addresses = '#{ips}'\n"
98
+
99
+ target['Listen'].each do |addr|
100
+ if addr != 'localhost' && !addr.start_with?('127.0.0.1') && !addr.start_with?('::1')
101
+ hbaLines << "host all all #{addr} scram-sha-256\n"
37
102
  end
103
+ end
104
+ else
105
+ cmd = "sed -i 's|^host all all 127.0.0.1/32 ident|host all all 127.0.0.1/32 scram-sha-256|'"
106
+ dir = updateConfigOverSSH(ssh, cmd)
107
+ end
108
+ #if !target['Publications'].to_h.empty?
109
+ # target['Settings'] ||= {}
110
+ # target['Settings']['wal_level'] = 'logical'
111
+ #end
112
+ target['Settings'].to_h.each do |name, value|
113
+ settingLines << "#{name} = #{value}\n"
114
+ end
115
+ if !hbaLines.empty?
116
+ updateRemoteFile(ssh, dir + HBA_FILE, options, false) do |configLines|
117
+ configLines += hbaLines
118
+ end
119
+ end
120
+ if !settingLines.empty?
121
+ updateRemoteFile(ssh, dir + CONFIG_FILE, options, false) do |configLines|
122
+ configLines += settingLines
123
+ end
124
+ end
125
+ end
126
+
127
+ def self.createUsersOverSSH(target, ssh)
128
+ target['Users'].to_a.each do |user, info|
129
+ self.sshExec!(ssh, "su --login #{USER_NAME} --command 'createuser #{user}'", true)
130
+ if !info['Password'].to_s.empty?
131
+ password = self.loadVariable(info['Password'], target).to_s
132
+ if !password.empty?
133
+ sql = "ALTER USER #{user} WITH PASSWORD '#{password}'"
134
+ self.executeSQL(sql, nil, ssh)
135
+ end
136
+ end
137
+ if info['Replication'] && info['Replication'] != 'no'
138
+ self.executeSQL("ALTER USER #{user} REPLICATION", nil, ssh)
139
+ self.executeSQL("GRANT pg_read_all_data TO #{user}", nil, ssh)
140
+ end
141
+ end
142
+ end
143
+
144
+ def self.createDatabasesOverSSH(target, ssh)
145
+ target['Databases'].to_a.each do |db, info|
146
+ self.sshExec!(ssh, "su --login #{USER_NAME} --command 'createdb #{db}'", true)
147
+ end
148
+ end
149
+
150
+ def self.createPublicationsOverSSH(target, ssh)
151
+ return if target['Publications'].to_h.empty?
152
+
153
+ target['Publications'].each do |name, data|
154
+ data['Database'] = name unless data['Database']
155
+ if data['Tables'].is_a?(Array)
156
+ # TODO
157
+ elsif data['Tables'] == 'All'
158
+ sql = "CREATE PUBLICATION #{name} FOR ALL TABLES"
159
+ self.executeSQL(sql, data['Database'], ssh, true)
38
160
  else
39
- `sed -i 's|^host all all 127.0.0.1/32 ident|host all all 127.0.0.1/32 scram-sha-256|' #{dir + HBA_FILE}`
161
+ raise "Invalid Tables field: #{data['Tables']}"
40
162
  end
41
163
  end
164
+ end
165
+
166
+ def self.createSubscriptionsOverSSH(target, ssh)
167
+ return if target['Subscriptions'].to_h.empty?
168
+
169
+ target['Subscriptions'].each do |name, data|
170
+ data['Database'] = name unless data['Database']
171
+ data['Publication'] = name unless data['Publication']
172
+ connection = self.loadVariable(data['Connection'], target).to_s
42
173
 
43
- self.startService(SERVICE_NAME, target['Location'])
174
+ authParams = '--host=' + connection.match('host=([^ ]+)')[1]
175
+ authParams += ' --username=' + connection.match('user=([^ ]+)')[1]
176
+ password = connection.match('password=([^ ]+)')[1]
177
+
178
+ self.importRemoteSchemaOverSSH(name, data['Database'], password, authParams, ssh)
179
+
180
+ sql = "CREATE SUBSCRIPTION #{name} CONNECTION '#{connection}' PUBLICATION #{data['Publication']}"
181
+ self.executeSQL(sql, data['Database'], ssh, true)
182
+ end
44
183
  end
45
184
 
46
- def updateConfigOverSSH(uri, cmd)
47
- dir = ''
48
- self.class.sshStart(uri) do |ssh|
49
- distroID = self.class.distroIDfromSSH(ssh)
50
- dir = pgsqlDir(distroID)
51
- self.class.sshExec!(ssh, cmd + ' ' + dir + HBA_FILE)
185
+ def self.importRemoteSchemaOverSSH(sourceDB, targetDB, password, authParams, ssh)
186
+ self.sshExec!(ssh, "su --login #{USER_NAME} --command 'createdb #{targetDB}'", true)
187
+ cmd = " su --login #{USER_NAME} --command 'PGPASSWORD=#{password} pg_dump --schema-only --no-owner --dbname=#{sourceDB} #{authParams} | psql --dbname=#{targetDB}'"
188
+ self.sshExec!(ssh, cmd)
189
+ end
190
+
191
+ def self.updateOwner(db, owner, ssh)
192
+ sql = "SELECT tablename FROM pg_tables WHERE NOT schemaname IN ('pg_catalog', 'information_schema')"
193
+ tables = self.executeSQL(sql, db, ssh, false, ['--csv', '--tuples-only']).strip.lines
194
+ tables.each do |table|
195
+ self.executeSQL("ALTER TABLE public.#{table} OWNER TO #{owner};", db, ssh)
52
196
  end
197
+
198
+ sql = "SELECT sequence_name FROM information_schema.sequences WHERE NOT sequence_schema IN ('pg_catalog', 'information_schema')"
199
+ sequences = self.executeSQL(sql, db, ssh, false, ['--csv', '--tuples-only']).strip.lines
200
+ sequences.each do |sequence|
201
+ self.executeSQL("ALTER SEQUENCE public.#{sequence} OWNER TO #{owner};", db, ssh)
202
+ end
203
+
204
+ sql = "SELECT table_name FROM information_schema.views WHERE NOT table_schema IN ('pg_catalog', 'information_schema')"
205
+ views = self.executeSQL(sql, db, ssh, false, ['--csv', '--tuples-only']).strip.lines
206
+ views.each do |view|
207
+ self.executeSQL("ALTER VIEW public.#{view} OWNER TO #{owner};", db, ssh)
208
+ end
209
+ end
210
+
211
+ def updateConfigOverSSH(ssh, cmd)
212
+ dir = ''
213
+ distroID = self.class.distroID(ssh)
214
+ dir = pgsqlDir(distroID)
215
+ self.class.sshExec!(ssh, cmd + ' ' + dir + HBA_FILE)
53
216
  dir
54
217
  end
55
218
 
219
+ def self.createRemoteUserAndDBOverSSH(settings, user, password, ssh)
220
+ self.executeRemotely(settings, ssh) do |ssh|
221
+ self.createUserAndDBOverSSH(user, password, ssh)
222
+ end
223
+ end
224
+
225
+ def self.dropUserAndDB(settings, user, ssh, dry)
226
+ self.executeRemotely(settings, ssh) do |ssh|
227
+ self.exec("su --login #{USER_NAME} --command 'dropdb #{user}'", ssh, true, dry)
228
+ self.exec("su --login #{USER_NAME} --command 'dropuser #{user}'", ssh, true, dry)
229
+ end
230
+ end
231
+
232
+ def self.createExtensions(settings, db, extensions, ssh)
233
+ self.executeRemotely(settings, ssh) do |ssh|
234
+ extensions.each do |extension|
235
+ self.executeSQL("CREATE EXTENSION #{extension}", db, ssh, true)
236
+ end
237
+ end
238
+ end
239
+
240
+ def self.executeRemotely(settings, ssh = nil)
241
+ settings['HostName'] = 'localhost' unless settings['HostName']
242
+ if settings['HostName'] == 'localhost'
243
+ yield(ssh)
244
+ else
245
+ self.sshStart("ssh://#{settings['HostName']}/") do |ssh|
246
+ yield(ssh)
247
+ end
248
+ end
249
+ end
250
+
56
251
  def self.createUserAndDBOverSSH(user, password, ssh)
57
252
  self.sshExec!(ssh, "su --login #{USER_NAME} --command 'createuser #{user}'", true)
58
253
  self.sshExec!(ssh, "su --login #{USER_NAME} --command 'createdb --owner=#{user} #{user}'", true)
59
- cmd = " su --login #{USER_NAME} --command ' psql -c \"ALTER USER #{user} WITH PASSWORD \\'#{password}\\';\"'"
60
- self.sshExec!(ssh, cmd)
254
+ if password
255
+ sql = "ALTER USER #{user} WITH PASSWORD '#{password}'"
256
+ self.executeSQL(sql, nil, ssh)
257
+ end
258
+ end
259
+
260
+ def self.importSQL(owner, db, sqlFile, ssh = nil)
261
+ if ssh
262
+ self.sshExec!(ssh, "echo \"SET ROLE '#{owner}';\" > /tmp/postgres_import.sql")
263
+ self.sshExec!(ssh, "cat #{sqlFile} >> /tmp/postgres_import.sql")
264
+ cmd = "su --login #{USER_NAME} --command 'psql #{db} < /tmp/postgres_import.sql'"
265
+ self.sshExec!(ssh, cmd)
266
+ else
267
+ # TODO
268
+ end
269
+ end
270
+
271
+ def self.executeSQL(sql, db, ssh = nil, allowFailure = false, options = [])
272
+ if ssh
273
+ db = 'postgres' unless db
274
+ cmd = " su --login #{USER_NAME} --command ' psql #{options.join(' ')} --dbname=#{db} --command=\"#{sql.gsub("'", "'\"'\"'")};\"'"
275
+ self.sshExec!(ssh, cmd, allowFailure)
276
+ else
277
+ # TODO
278
+ end
61
279
  end
62
280
 
63
281
  def pgsqlDir(distroID)