active_postgres 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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +23 -0
  3. data/README.md +158 -0
  4. data/exe/activepostgres +5 -0
  5. data/lib/active_postgres/cli.rb +157 -0
  6. data/lib/active_postgres/cluster_deployment_flow.rb +85 -0
  7. data/lib/active_postgres/component_resolver.rb +24 -0
  8. data/lib/active_postgres/components/base.rb +38 -0
  9. data/lib/active_postgres/components/core.rb +158 -0
  10. data/lib/active_postgres/components/extensions.rb +99 -0
  11. data/lib/active_postgres/components/monitoring.rb +55 -0
  12. data/lib/active_postgres/components/pgbackrest.rb +94 -0
  13. data/lib/active_postgres/components/pgbouncer.rb +137 -0
  14. data/lib/active_postgres/components/repmgr.rb +651 -0
  15. data/lib/active_postgres/components/ssl.rb +86 -0
  16. data/lib/active_postgres/configuration.rb +190 -0
  17. data/lib/active_postgres/connection_pooler.rb +429 -0
  18. data/lib/active_postgres/credentials.rb +17 -0
  19. data/lib/active_postgres/deployment_flow.rb +154 -0
  20. data/lib/active_postgres/error_handler.rb +185 -0
  21. data/lib/active_postgres/failover.rb +83 -0
  22. data/lib/active_postgres/generators/active_postgres/install_generator.rb +186 -0
  23. data/lib/active_postgres/health_checker.rb +244 -0
  24. data/lib/active_postgres/installer.rb +114 -0
  25. data/lib/active_postgres/log_sanitizer.rb +67 -0
  26. data/lib/active_postgres/logger.rb +125 -0
  27. data/lib/active_postgres/performance_tuner.rb +246 -0
  28. data/lib/active_postgres/rails/database_config.rb +174 -0
  29. data/lib/active_postgres/rails/migration_guard.rb +25 -0
  30. data/lib/active_postgres/railtie.rb +28 -0
  31. data/lib/active_postgres/retry_helper.rb +80 -0
  32. data/lib/active_postgres/rollback_manager.rb +140 -0
  33. data/lib/active_postgres/secrets.rb +86 -0
  34. data/lib/active_postgres/ssh_executor.rb +288 -0
  35. data/lib/active_postgres/standby_deployment_flow.rb +122 -0
  36. data/lib/active_postgres/validator.rb +143 -0
  37. data/lib/active_postgres/version.rb +3 -0
  38. data/lib/active_postgres.rb +67 -0
  39. data/lib/tasks/postgres.rake +855 -0
  40. data/lib/tasks/rolling_update.rake +258 -0
  41. data/lib/tasks/rotate_credentials.rake +193 -0
  42. data/templates/pg_hba.conf.erb +47 -0
  43. data/templates/pgbackrest.conf.erb +43 -0
  44. data/templates/pgbouncer.ini.erb +55 -0
  45. data/templates/postgresql.conf.erb +157 -0
  46. data/templates/repmgr.conf.erb +40 -0
  47. metadata +224 -0
@@ -0,0 +1,258 @@
1
+ namespace :postgres do
2
+ namespace :update do
3
+ desc 'Rolling update PostgreSQL version (zero downtime)'
4
+ task :version, [:new_version] => :environment do |_t, args|
5
+ require 'active_postgres'
6
+
7
+ new_version = args[:new_version]
8
+ unless new_version
9
+ puts 'Usage: rake postgres:update:version[18]'
10
+ puts 'Example: rake postgres:update:version[18] (upgrade from 16 to 18)'
11
+ exit 1
12
+ end
13
+
14
+ config = ActivePostgres::Configuration.load
15
+ ssh_executor = ActivePostgres::SSHExecutor.new(config)
16
+ current_version = config.version
17
+
18
+ if current_version.to_s == new_version.to_s
19
+ puts "Already running version #{new_version}"
20
+ exit 0
21
+ end
22
+
23
+ puts "šŸ”„ Rolling update: PostgreSQL #{current_version} → #{new_version}"
24
+ puts ''
25
+ puts 'This will:'
26
+ puts ' 1. Update standby to new version'
27
+ puts ' 2. Verify standby health'
28
+ puts ' 3. Promote standby to primary (brief switchover ~5-10s)'
29
+ puts ' 4. Update old primary to new version'
30
+ puts ' 5. Optionally switchback to original primary'
31
+ puts ''
32
+ puts 'āš ļø IMPORTANT: Test this in staging first!'
33
+ puts ''
34
+ print 'Continue? (yes/no): '
35
+ response = $stdin.gets.chomp.downcase
36
+ exit 0 unless %w[yes y].include?(response)
37
+
38
+ primary = config.primary_host
39
+ standby = config.standby_hosts.first
40
+
41
+ unless standby
42
+ puts 'āŒ No standby configured - cannot do rolling update'
43
+ puts 'For single-node updates, use: rake postgres:update:in_place'
44
+ exit 1
45
+ end
46
+
47
+ puts "\nšŸ“‹ Cluster Status:"
48
+ puts " Primary: #{primary} (version #{current_version})"
49
+ puts " Standby: #{standby} (version #{current_version})"
50
+ puts ''
51
+
52
+ puts '=' * 60
53
+ puts 'STEP 1: Update standby to new version'
54
+ puts '=' * 60
55
+
56
+ puts "\nUpdating #{standby}..."
57
+ ssh_executor.execute_on_host(standby) do
58
+ execute :sudo, 'systemctl', 'stop', "postgresql@#{current_version}-main"
59
+ execute :sudo, 'apt-get', 'update'
60
+ execute :sudo, 'apt-get', 'install', '-y', "postgresql-#{new_version}", "postgresql-contrib-#{new_version}"
61
+
62
+ puts "Upgrading cluster from #{current_version} to #{new_version}..."
63
+ execute :sudo, 'pg_upgradecluster', current_version.to_s, 'main'
64
+ execute :sudo, 'pg_dropcluster', '--stop', current_version.to_s, 'main'
65
+ end
66
+
67
+ puts "āœ“ Standby upgraded to version #{new_version}"
68
+ puts ''
69
+
70
+ puts '=' * 60
71
+ puts 'STEP 2: Verify standby health'
72
+ puts '=' * 60
73
+
74
+ Rake::Task['postgres:verify'].invoke
75
+ puts ''
76
+
77
+ puts '=' * 60
78
+ puts 'STEP 3: Promote standby to primary'
79
+ puts '=' * 60
80
+
81
+ puts 'āš ļø Brief downtime during switchover (~5-10 seconds)'
82
+ puts ''
83
+ print 'Promote standby? (yes/no): '
84
+ response = $stdin.gets.chomp.downcase
85
+ exit 0 unless %w[yes y].include?(response)
86
+
87
+ Rake::Task['postgres:repmgr:promote'].invoke(standby)
88
+ puts ''
89
+
90
+ puts 'āœ“ Switchover complete'
91
+ puts " New primary: #{standby} (version #{new_version})"
92
+ puts " Old primary: #{primary} (version #{current_version})"
93
+ puts ''
94
+
95
+ puts '=' * 60
96
+ puts 'STEP 4: Update old primary'
97
+ puts '=' * 60
98
+
99
+ puts "\nUpdating #{primary}..."
100
+ ssh_executor.execute_on_host(primary) do
101
+ execute :sudo, 'systemctl', 'stop', "postgresql@#{current_version}-main"
102
+ execute :sudo, 'apt-get', 'update'
103
+ execute :sudo, 'apt-get', 'install', '-y', "postgresql-#{new_version}", "postgresql-contrib-#{new_version}"
104
+
105
+ puts "Upgrading cluster from #{current_version} to #{new_version}..."
106
+ execute :sudo, 'pg_upgradecluster', current_version.to_s, 'main'
107
+ execute :sudo, 'pg_dropcluster', '--stop', current_version.to_s, 'main'
108
+ end
109
+
110
+ puts "āœ“ All nodes upgraded to version #{new_version}"
111
+ puts ''
112
+
113
+ puts '=' * 60
114
+ puts 'āœ… Rolling update complete!'
115
+ puts '=' * 60
116
+ puts ''
117
+ puts "Cluster is now running PostgreSQL #{new_version}"
118
+ puts "Current primary: #{standby}"
119
+ puts ''
120
+ puts 'šŸ“‹ Next steps:'
121
+ puts " 1. Update config/postgres.yml: version: #{new_version}"
122
+ puts ' 2. Test application thoroughly'
123
+ puts " 3. Optionally switchback: rake postgres:repmgr:promote[#{primary}]"
124
+ puts ' 4. Commit config changes'
125
+ end
126
+
127
+ desc 'Patch current PostgreSQL version (zero downtime)'
128
+ task patch: :environment do
129
+ require 'active_postgres'
130
+
131
+ config = ActivePostgres::Configuration.load
132
+ ssh_executor = ActivePostgres::SSHExecutor.new(config)
133
+ version = config.version
134
+
135
+ primary = config.primary_host
136
+ standby = config.standby_hosts.first
137
+
138
+ unless standby
139
+ puts 'āŒ No standby configured - cannot do rolling patch'
140
+ puts 'For single-node patching, use: rake postgres:update:in_place_patch'
141
+ exit 1
142
+ end
143
+
144
+ puts 'šŸ”„ Rolling security patch update'
145
+ puts ''
146
+ puts 'This will:'
147
+ puts ' 1. Patch standby and restart'
148
+ puts ' 2. Verify standby health'
149
+ puts ' 3. Promote standby (brief switchover ~5s)'
150
+ puts ' 4. Patch old primary'
151
+ puts ' 5. Switchback'
152
+ puts ''
153
+ print 'Continue? (yes/no): '
154
+ response = $stdin.gets.chomp.downcase
155
+ exit 0 unless %w[yes y].include?(response)
156
+
157
+ puts "\nšŸ“‹ Step 1: Patch standby #{standby}"
158
+ ssh_executor.execute_on_host(standby) do
159
+ execute :sudo, 'apt-get', 'update'
160
+ execute :sudo, 'apt-get', 'install', '--only-upgrade', '-y', "postgresql-#{version}"
161
+ execute :sudo, 'systemctl', 'restart', "postgresql@#{version}-main"
162
+ end
163
+ puts 'āœ“ Standby patched and restarted'
164
+ sleep 5
165
+
166
+ puts "\nšŸ“‹ Step 2: Promote standby"
167
+ Rake::Task['postgres:repmgr:promote'].invoke(standby)
168
+ sleep 5
169
+
170
+ puts "\nšŸ“‹ Step 3: Patch old primary #{primary}"
171
+ ssh_executor.execute_on_host(primary) do
172
+ execute :sudo, 'apt-get', 'update'
173
+ execute :sudo, 'apt-get', 'install', '--only-upgrade', '-y', "postgresql-#{version}"
174
+ execute :sudo, 'systemctl', 'restart', "postgresql@#{version}-main"
175
+ end
176
+ puts 'āœ“ Old primary patched'
177
+ sleep 5
178
+
179
+ puts "\nšŸ“‹ Step 4: Switchback to #{primary}"
180
+ Rake::Task['postgres:repmgr:promote'].invoke(primary)
181
+
182
+ puts ''
183
+ puts 'āœ… Security patches applied!'
184
+ puts ' Total downtime: ~10 seconds (during switchovers)'
185
+ end
186
+
187
+ desc 'In-place update (requires downtime)'
188
+ task :in_place, [:new_version] => :environment do |_t, args|
189
+ require 'active_postgres'
190
+
191
+ new_version = args[:new_version]
192
+ unless new_version
193
+ puts 'Usage: rake postgres:update:in_place[18]'
194
+ exit 1
195
+ end
196
+
197
+ config = ActivePostgres::Configuration.load
198
+ ssh_executor = ActivePostgres::SSHExecutor.new(config)
199
+ current_version = config.version
200
+ host = config.primary_host
201
+
202
+ puts 'āš ļø WARNING: In-place update requires downtime'
203
+ puts ' For zero-downtime updates, use a standby server'
204
+ puts ''
205
+ puts "Updating PostgreSQL #{current_version} → #{new_version} on #{host}"
206
+ puts ''
207
+ print 'Continue? (yes/no): '
208
+ response = $stdin.gets.chomp.downcase
209
+ exit 0 unless %w[yes y].include?(response)
210
+
211
+ puts "\nšŸ”„ Starting in-place upgrade..."
212
+ ssh_executor.execute_on_host(host) do
213
+ execute :sudo, 'systemctl', 'stop', "postgresql@#{current_version}-main"
214
+ execute :sudo, 'apt-get', 'update'
215
+ execute :sudo, 'apt-get', 'install', '-y', "postgresql-#{new_version}"
216
+
217
+ execute :sudo, 'pg_upgradecluster', current_version.to_s, 'main'
218
+ execute :sudo, 'pg_dropcluster', '--stop', current_version.to_s, 'main'
219
+ execute :sudo, 'systemctl', 'start', "postgresql@#{new_version}-main"
220
+ end
221
+
222
+ puts "āœ… Upgraded to PostgreSQL #{new_version}"
223
+ puts " Update config/postgres.yml: version: #{new_version}"
224
+ end
225
+ end
226
+
227
+ namespace :repmgr do
228
+ desc 'Promote standby to primary (switchover)'
229
+ task :promote, [:host] => :environment do |_t, args|
230
+ require 'active_postgres'
231
+
232
+ host = args[:host]
233
+ unless host
234
+ puts 'Usage: rake postgres:repmgr:promote[host]'
235
+ exit 1
236
+ end
237
+
238
+ config = ActivePostgres::Configuration.load
239
+ ssh_executor = ActivePostgres::SSHExecutor.new(config)
240
+
241
+ puts "šŸ”„ Promoting #{host} to primary..."
242
+ puts 'ā±ļø Expected downtime: 5-10 seconds'
243
+ puts ''
244
+
245
+ ssh_executor.execute_on_host(host) do
246
+ execute :sudo, '-u', 'postgres', 'repmgr', 'standby', 'promote'
247
+ end
248
+
249
+ sleep 3
250
+
251
+ puts 'āœ… Promotion complete!'
252
+ puts " New primary: #{host}"
253
+ puts ''
254
+ puts 'šŸ“‹ Verify cluster:'
255
+ puts ' rake postgres:verify'
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,193 @@
1
+ namespace :postgres do
2
+ namespace :credentials do
3
+ desc 'Rotate app user password (zero downtime)'
4
+ task :rotate, [:new_password] => :environment do |_t, args|
5
+ require 'active_postgres'
6
+
7
+ config = ActivePostgres::Configuration.load
8
+ ssh_executor = ActivePostgres::SSHExecutor.new(config)
9
+ ActivePostgres::Secrets.new(config)
10
+
11
+ new_password = args[:new_password]
12
+ unless new_password
13
+ puts 'Usage: rake postgres:credentials:rotate[new_password]'
14
+ puts 'Or generate random: rake postgres:credentials:rotate_random'
15
+ exit 1
16
+ end
17
+
18
+ app_user = config.app_user
19
+ host = config.primary_host
20
+
21
+ puts "Rotating password for user '#{app_user}' on #{host}..."
22
+ puts 'āš ļø IMPORTANT: Update Rails credentials after this completes!'
23
+ puts ''
24
+
25
+ ssh_executor.execute_on_host(host) do
26
+ escaped_password = new_password.gsub("'", "''")
27
+
28
+ sql = "ALTER USER #{app_user} WITH PASSWORD '#{escaped_password}';"
29
+ upload! StringIO.new(sql), '/tmp/rotate_password.sql'
30
+ execute :chmod, '644', '/tmp/rotate_password.sql'
31
+ execute :sudo, '-u', 'postgres', 'psql', '-f', '/tmp/rotate_password.sql'
32
+ execute :rm, '-f', '/tmp/rotate_password.sql'
33
+
34
+ puts "āœ“ Updated PostgreSQL password for #{app_user}"
35
+ end
36
+
37
+ if config.component_enabled?(:pgbouncer)
38
+ puts 'Updating PgBouncer userlist...'
39
+
40
+ ssh_executor.execute_on_host(host) do
41
+ postgres_user = config.postgres_user
42
+ userlist_entries = []
43
+
44
+ [postgres_user, app_user].compact.uniq.each do |user|
45
+ sql = <<~SQL.strip
46
+ SELECT concat('"', rolname, '" "', rolpassword, '"')
47
+ FROM pg_authid
48
+ WHERE rolname = '#{user}'
49
+ SQL
50
+
51
+ upload! StringIO.new(sql), '/tmp/get_user_hash.sql'
52
+ execute :chmod, '644', '/tmp/get_user_hash.sql'
53
+ user_hash = capture(:sudo, '-u', postgres_user, 'psql', '-t', '-f', '/tmp/get_user_hash.sql').strip
54
+ execute :rm, '-f', '/tmp/get_user_hash.sql'
55
+
56
+ userlist_entries << user_hash if user_hash && !user_hash.empty?
57
+ end
58
+
59
+ if userlist_entries.any?
60
+ userlist_content = "#{userlist_entries.join("\n")}\n"
61
+ upload! StringIO.new(userlist_content), '/tmp/userlist.txt'
62
+ execute :sudo, 'mv', '/tmp/userlist.txt', '/etc/pgbouncer/userlist.txt'
63
+ execute :sudo, 'chmod', '640', '/etc/pgbouncer/userlist.txt'
64
+ execute :sudo, 'chown', 'postgres:postgres', '/etc/pgbouncer/userlist.txt'
65
+ execute :sudo, 'systemctl', 'reload', 'pgbouncer'
66
+
67
+ puts 'āœ“ Updated PgBouncer userlist and reloaded (zero downtime)'
68
+ end
69
+ end
70
+ end
71
+
72
+ puts ''
73
+ puts 'āœ… Password rotation complete!'
74
+ puts ''
75
+ puts 'šŸ“‹ Next steps:'
76
+ puts '1. Update Rails credentials:'
77
+ puts ' rails credentials:edit'
78
+ puts ''
79
+ puts ' Add/update:'
80
+ puts ' postgres:'
81
+ puts " password: \"#{new_password}\""
82
+ puts ''
83
+ puts '2. Restart Rails app to use new password'
84
+ puts ' cap production deploy:restart'
85
+ puts ''
86
+ puts 'āš ļø Old password still works until Rails restarts'
87
+ end
88
+
89
+ desc 'Rotate app user password with random generated password (zero downtime)'
90
+ task rotate_random: :environment do
91
+ require 'securerandom'
92
+ new_password = SecureRandom.base64(32)
93
+
94
+ puts 'šŸ” Generated secure random password'
95
+ puts ''
96
+
97
+ Rake::Task['postgres:credentials:rotate'].invoke(new_password)
98
+ end
99
+
100
+ desc 'Rotate all passwords (app, repmgr, superuser) - zero downtime'
101
+ task rotate_all: :environment do
102
+ require 'active_postgres'
103
+ require 'securerandom'
104
+
105
+ config = ActivePostgres::Configuration.load
106
+ ssh_executor = ActivePostgres::SSHExecutor.new(config)
107
+
108
+ users = [
109
+ { name: config.app_user, credential_key: 'password' },
110
+ { name: config.repmgr_user, credential_key: 'repmgr_password' },
111
+ { name: 'postgres', credential_key: 'superuser_password' }
112
+ ]
113
+
114
+ new_passwords = {}
115
+ host = config.primary_host
116
+
117
+ puts 'šŸ” Rotating all PostgreSQL passwords...'
118
+ puts ''
119
+
120
+ users.each do |user_info|
121
+ username = user_info[:name]
122
+ new_password = SecureRandom.base64(32)
123
+ new_passwords[user_info[:credential_key]] = new_password
124
+
125
+ puts "Rotating #{username}..."
126
+
127
+ ssh_executor.execute_on_host(host) do
128
+ escaped_password = new_password.gsub("'", "''")
129
+
130
+ sql = "ALTER USER #{username} WITH PASSWORD '#{escaped_password}';"
131
+ upload! StringIO.new(sql), '/tmp/rotate_password.sql'
132
+ execute :chmod, '644', '/tmp/rotate_password.sql'
133
+ execute :sudo, '-u', 'postgres', 'psql', '-f', '/tmp/rotate_password.sql'
134
+ execute :rm, '-f', '/tmp/rotate_password.sql'
135
+ end
136
+
137
+ puts "āœ“ Updated #{username}"
138
+ end
139
+
140
+ if config.component_enabled?(:pgbouncer)
141
+ puts ''
142
+ puts 'Updating PgBouncer userlist...'
143
+
144
+ ssh_executor.execute_on_host(host) do
145
+ postgres_user = 'postgres'
146
+ userlist_entries = []
147
+
148
+ users.map { |u| u[:name] }.compact.uniq.each do |user|
149
+ sql = <<~SQL.strip
150
+ SELECT concat('"', rolname, '" "', rolpassword, '"')
151
+ FROM pg_authid
152
+ WHERE rolname = '#{user}'
153
+ SQL
154
+
155
+ upload! StringIO.new(sql), '/tmp/get_user_hash.sql'
156
+ execute :chmod, '644', '/tmp/get_user_hash.sql'
157
+ user_hash = capture(:sudo, '-u', postgres_user, 'psql', '-t', '-f', '/tmp/get_user_hash.sql').strip
158
+ execute :rm, '-f', '/tmp/get_user_hash.sql'
159
+
160
+ userlist_entries << user_hash if user_hash && !user_hash.empty?
161
+ end
162
+
163
+ if userlist_entries.any?
164
+ userlist_content = "#{userlist_entries.join("\n")}\n"
165
+ execute :sudo, 'tee', '/etc/pgbouncer/userlist.txt', stdin: StringIO.new(userlist_content)
166
+ execute :sudo, 'chmod', '640', '/etc/pgbouncer/userlist.txt'
167
+ execute :sudo, 'chown', 'postgres:postgres', '/etc/pgbouncer/userlist.txt'
168
+ execute :sudo, 'systemctl', 'reload', 'pgbouncer'
169
+
170
+ puts 'āœ“ PgBouncer userlist updated and reloaded'
171
+ end
172
+ end
173
+ end
174
+
175
+ puts ''
176
+ puts 'āœ… All passwords rotated!'
177
+ puts ''
178
+ puts 'šŸ“‹ New passwords to add to Rails credentials:'
179
+ puts ''
180
+ puts 'rails credentials:edit'
181
+ puts ''
182
+ puts 'postgres:'
183
+ new_passwords.each do |key, password|
184
+ puts " #{key}: \"#{password}\""
185
+ end
186
+ puts ''
187
+ puts 'āš ļø Save these passwords securely before continuing!'
188
+ puts ''
189
+ puts 'After updating credentials:'
190
+ puts ' cap production deploy:restart'
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,47 @@
1
+ # PostgreSQL Client Authentication Configuration File
2
+ # Generated by active_postgres
3
+ <%
4
+ core_config = config.component_config(:core)
5
+ pg_hba_rules = core_config[:pg_hba] || []
6
+
7
+ # Detect network from primary private_ip
8
+ detected_network = if config.primary && config.primary['private_ip']
9
+ ip_parts = config.primary['private_ip'].split('.')
10
+ "#{ip_parts[0]}.#{ip_parts[1]}.0.0/16"
11
+ else
12
+ '10.8.0.0/16' # Default to common VPN/private network
13
+ end
14
+
15
+ # Default rules if none specified
16
+ if pg_hba_rules.empty?
17
+ pg_hba_rules = [
18
+ {type: 'local', database: 'all', user: 'all', method: 'peer'},
19
+ {type: 'host', database: 'all', user: 'all', address: '127.0.0.1/32', method: 'scram-sha-256'},
20
+ {type: 'host', database: 'all', user: 'all', address: detected_network, method: 'scram-sha-256'}
21
+ ]
22
+ end
23
+ %>
24
+
25
+ # TYPE DATABASE USER ADDRESS METHOD
26
+
27
+ <% pg_hba_rules.each do |rule| %>
28
+ <% if rule[:type] == 'local' %>
29
+ <%= rule[:type] %> <%= rule[:database] %> <%= rule[:user] %> <%= rule[:method] %>
30
+ <% else %>
31
+ <%= rule[:type] %> <%= rule[:database] %> <%= rule[:user] %> <%= rule[:address] %> <%= rule[:method] %>
32
+ <% end %>
33
+ <% end %>
34
+
35
+ <% if config.component_enabled?(:repmgr) && !pg_hba_rules.any? { |r| r[:database] == 'replication' } %>
36
+ # Replication connections (auto-added for repmgr)
37
+ <%
38
+ # Find the network rule (not 127.0.0.1) for replication, or fall back to 10.8.0.0/24
39
+ repmgr_network = pg_hba_rules.find { |r| r[:type] == 'host' && r[:address] && !r[:address].start_with?('127.0.0.1') }&.dig(:address) || '10.8.0.0/24'
40
+ repmgr_user = config.repmgr_user
41
+ repmgr_db = config.repmgr_database
42
+ %>
43
+ host replication <%= repmgr_user %> <%= repmgr_network %> scram-sha-256
44
+ host <%= repmgr_db %> <%= repmgr_user %> <%= repmgr_network %> scram-sha-256
45
+ <% end %>
46
+
47
+
@@ -0,0 +1,43 @@
1
+ [global]
2
+ <% if pgbackrest_config[:repo_type] == 's3' %>
3
+ repo1-type=s3
4
+ repo1-path=<%= pgbackrest_config[:repo_path] || '/backups' %>
5
+ repo1-s3-bucket=<%= pgbackrest_config[:s3_bucket] %>
6
+ repo1-s3-region=<%= pgbackrest_config[:s3_region] || 'us-east-1' %>
7
+ <% if pgbackrest_config[:s3_endpoint] %>
8
+ # Custom S3-compatible endpoint (DigitalOcean Spaces, Backblaze B2, Wasabi, MinIO, etc.)
9
+ repo1-s3-endpoint=<%= pgbackrest_config[:s3_endpoint] %>
10
+ repo1-s3-uri-style=<%= pgbackrest_config[:s3_uri_style] || 'path' %>
11
+ <% else %>
12
+ # AWS S3
13
+ repo1-s3-endpoint=s3.<%= pgbackrest_config[:s3_region] || 'us-east-1' %>.amazonaws.com
14
+ <% end %>
15
+ <% if secrets_obj.resolve('s3_access_key') %>
16
+ repo1-s3-key=<%= secrets_obj.resolve('s3_access_key') %>
17
+ repo1-s3-key-secret=<%= secrets_obj.resolve('s3_secret_key') %>
18
+ <% end %>
19
+ <% else %>
20
+ # Local storage
21
+ repo1-path=<%= pgbackrest_config[:repo_path] || '/var/lib/pgbackrest' %>
22
+ <% end %>
23
+
24
+ # Retention
25
+ repo1-retention-full=<%= pgbackrest_config[:retention_full] || 7 %>
26
+ repo1-retention-archive=<%= pgbackrest_config[:retention_archive] || 14 %>
27
+
28
+ # Compression
29
+ compress-type=lz4
30
+ compress-level=3
31
+
32
+ <% if pgbackrest_config[:encryption] %>
33
+ # Encryption (recommended for cloud storage)
34
+ repo1-cipher-type=aes-256-cbc
35
+ repo1-cipher-pass=<%= secrets_obj.resolve('backup_encryption_key') %>
36
+ <% end %>
37
+
38
+ [main]
39
+ pg1-path=/var/lib/postgresql/<%= config.version %>/main
40
+ pg1-port=5432
41
+ pg1-socket-path=/var/run/postgresql
42
+
43
+
@@ -0,0 +1,55 @@
1
+ [databases]
2
+ * = host=<%= pgbouncer_config[:database_host] || '127.0.0.1' %> port=<%= pgbouncer_config[:database_port] || 5432 %>
3
+
4
+ [pgbouncer]
5
+ # Network settings
6
+ listen_port = <%= pgbouncer_config[:listen_port] || 6432 %>
7
+ listen_addr = <%= pgbouncer_config[:listen_addr] || '*' %>
8
+ unix_socket_dir = <%= pgbouncer_config[:unix_socket_dir] || '/var/run/postgresql' %>
9
+
10
+ # Authentication
11
+ auth_type = <%= pgbouncer_config[:auth_type] || 'scram-sha-256' %>
12
+ auth_file = <%= pgbouncer_config[:auth_file] || '/etc/pgbouncer/userlist.txt' %>
13
+
14
+ # Pool settings (automatically calculated based on PostgreSQL max_connections)
15
+ pool_mode = <%= pgbouncer_config[:pool_mode] || 'transaction' %>
16
+ max_client_conn = <%= pgbouncer_config[:max_client_conn] || 1000 %>
17
+ default_pool_size = <%= pgbouncer_config[:default_pool_size] || 25 %>
18
+ <% if pgbouncer_config[:min_pool_size] %>
19
+ min_pool_size = <%= pgbouncer_config[:min_pool_size] %>
20
+ <% end %>
21
+ reserve_pool_size = <%= pgbouncer_config[:reserve_pool_size] || 5 %>
22
+ reserve_pool_timeout = <%= pgbouncer_config[:reserve_pool_timeout] || 5 %>
23
+ <% if pgbouncer_config[:max_db_connections] %>
24
+ max_db_connections = <%= pgbouncer_config[:max_db_connections] %>
25
+ <% end %>
26
+ <% if pgbouncer_config[:max_user_connections] %>
27
+ max_user_connections = <%= pgbouncer_config[:max_user_connections] %>
28
+ <% end %>
29
+
30
+ # Connection behavior
31
+ server_reset_query = <%= pgbouncer_config[:server_reset_query] || 'DISCARD ALL' %>
32
+ server_lifetime = <%= pgbouncer_config[:server_lifetime] || 3600 %>
33
+ server_idle_timeout = <%= pgbouncer_config[:server_idle_timeout] || 600 %>
34
+ server_connect_timeout = <%= pgbouncer_config[:server_connect_timeout] || 15 %>
35
+ client_idle_timeout = <%= pgbouncer_config[:client_idle_timeout] || 0 %>
36
+ client_login_timeout = <%= pgbouncer_config[:client_login_timeout] || 60 %>
37
+ query_timeout = <%= pgbouncer_config[:query_timeout] || 0 %>
38
+ query_wait_timeout = <%= pgbouncer_config[:query_wait_timeout] || 120 %>
39
+
40
+ # Logging and administration
41
+ admin_users = <%= pgbouncer_config[:admin_users] || config.postgres_user %>
42
+ stats_users = <%= pgbouncer_config[:stats_users] || config.postgres_user %>
43
+ log_connections = <%= pgbouncer_config[:log_connections] || 1 %>
44
+ log_disconnections = <%= pgbouncer_config[:log_disconnections] || 1 %>
45
+ log_pooler_errors = <%= pgbouncer_config[:log_pooler_errors] || 1 %>
46
+
47
+ # Process management
48
+ <% if pgbouncer_config[:pidfile] %>
49
+ pidfile = <%= pgbouncer_config[:pidfile] %>
50
+ <% end %>
51
+ <% if pgbouncer_config[:logfile] %>
52
+ logfile = <%= pgbouncer_config[:logfile] %>
53
+ <% end %>
54
+
55
+