active_postgres 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c54d617bfe700f38f1be17b6ff66e630bde27185b0dfdbeae52ef9472a25fd2
4
- data.tar.gz: 92b80059f95ce83b62de7b047c0827906314d43a737360b32c88de67b4b2b11f
3
+ metadata.gz: cccb8eff8b3ff2ae0f3413bef89b81a1b5b487e474222a9ff3e85fc74e57c5e9
4
+ data.tar.gz: 05a21ba9f1a29b6b5d451b3c059fd6a15135b8d6ecd7bad7d6232e6c30a5d63a
5
5
  SHA512:
6
- metadata.gz: 2a55cf101289b0c3a7f88494b79abd6bd61d2f47d9946308da3612dc5be5334013d455c08ef923202679a14f0aba1a1749c33e8164c85f744e5ff32d2fe99776
7
- data.tar.gz: 1c07e9b4fa3d29a6d11cf960d2836c930d64adca20104b8e2911b2d828e0c19b9e2e20d9b46d4491f22ea9b59b7cc56838c648b1dc1fc40351faece92043bfdd
6
+ metadata.gz: '05819a2a73506fb087831e9ad69b6901b8bd6f14f0559a8e08d8c7fbbbff5c20eabc616bf0c862dd95757dab18a52666d8c0c1c00a63dc23b2094849160ce80b'
7
+ data.tar.gz: 2733384099851358c38788c41f77768e7a781cd99b7492aa4c10c85758a520167ad954657e0842d7b55681b2f932515933e887706ba3e2d39a50c44fdbd1e7e5
@@ -11,11 +11,16 @@ module ActivePostgres
11
11
  # See: create_application_user_and_database method called from deployment flow
12
12
 
13
13
  # Install on standbys
14
- # If repmgr is enabled, only install packages (cluster will be cloned by repmgr)
15
- # If repmgr is disabled, install everything including cluster creation
16
14
  config.standby_hosts.each do |host|
17
15
  if config.component_enabled?(:repmgr)
18
- install_packages_only(host)
16
+ # Check if cluster already exists (config update vs fresh install)
17
+ if cluster_exists?(host)
18
+ # Existing cluster - just update configs
19
+ update_configs_on_host(host)
20
+ else
21
+ # Fresh install - only install packages, repmgr will clone the cluster
22
+ install_packages_only(host)
23
+ end
19
24
  else
20
25
  install_on_host(host, is_primary: false)
21
26
  end
@@ -62,6 +67,10 @@ module ActivePostgres
62
67
  else
63
68
  component_config[:postgresql] || {}
64
69
  end
70
+
71
+ # Substitute ${private_ip} with the host's actual private IP
72
+ private_ip = config.replication_host_for(host)
73
+ pg_config = substitute_private_ip(pg_config, private_ip)
65
74
  _ = pg_config # Used in ERB template
66
75
 
67
76
  upload_template(host, 'postgresql.conf.erb', "/etc/postgresql/#{config.version}/main/postgresql.conf", binding,
@@ -87,11 +96,99 @@ module ActivePostgres
87
96
  optimal_settings.merge(user_postgresql)
88
97
  end
89
98
 
99
+ def substitute_private_ip(pg_config, private_ip)
100
+ pg_config.transform_values do |value|
101
+ if value.is_a?(String)
102
+ value.gsub('${private_ip}', private_ip)
103
+ else
104
+ value
105
+ end
106
+ end
107
+ end
108
+
90
109
  def install_packages_only(host)
91
110
  puts " Installing packages on #{host} (cluster will be created by repmgr)..."
92
111
  ssh_executor.install_postgres(host, config.version)
93
112
  end
94
113
 
114
+ def cluster_exists?(host)
115
+ exists = false
116
+ version = config.version
117
+ ssh_executor.execute_on_host(host) do
118
+ exists = test(:sudo, 'test', '-d', "/var/lib/postgresql/#{version}/main/base")
119
+ end
120
+ exists
121
+ end
122
+
123
+ def update_configs_on_host(host)
124
+ puts " Updating configs on #{host}..."
125
+
126
+ component_config = config.component_config(:core)
127
+
128
+ pg_config = if config.component_enabled?(:performance_tuning)
129
+ tuned = calculate_tuned_settings(host, component_config)
130
+ # Standbys must have replication-critical settings >= primary
131
+ ensure_standby_compatible(tuned)
132
+ else
133
+ component_config[:postgresql] || {}
134
+ end
135
+
136
+ private_ip = config.replication_host_for(host)
137
+ pg_config = substitute_private_ip(pg_config, private_ip)
138
+ _ = pg_config
139
+
140
+ upload_template(host, 'postgresql.conf.erb', "/etc/postgresql/#{config.version}/main/postgresql.conf", binding,
141
+ owner: 'postgres:postgres')
142
+ upload_template(host, 'pg_hba.conf.erb', "/etc/postgresql/#{config.version}/main/pg_hba.conf", binding,
143
+ owner: 'postgres:postgres')
144
+
145
+ ssh_executor.restart_postgres(host, config.version)
146
+ end
147
+
148
+ def ensure_standby_compatible(pg_config)
149
+ primary_settings = get_primary_replication_settings
150
+ return pg_config if primary_settings.empty?
151
+
152
+ adjustments = []
153
+
154
+ %i[max_connections max_worker_processes max_wal_senders
155
+ max_prepared_transactions max_locks_per_transaction].each do |setting|
156
+ primary_val = primary_settings[setting]
157
+ next unless primary_val
158
+
159
+ current_val = pg_config[setting]
160
+ if current_val.nil? || current_val.to_i < primary_val.to_i
161
+ adjustments << "#{setting}: #{current_val || 'unset'} → #{primary_val}"
162
+ pg_config[setting] = primary_val
163
+ end
164
+ end
165
+
166
+ if adjustments.any?
167
+ puts " ⚠️ Adjusting standby settings to match primary minimum:"
168
+ adjustments.each { |adj| puts " #{adj}" }
169
+ end
170
+
171
+ pg_config
172
+ end
173
+
174
+ def get_primary_replication_settings
175
+ @primary_replication_settings ||= begin
176
+ settings = {}
177
+ sql = "SELECT name || '=' || setting FROM pg_settings WHERE name IN ('max_connections', 'max_worker_processes', 'max_wal_senders', 'max_prepared_transactions', 'max_locks_per_transaction')"
178
+ result = ssh_executor.run_sql(config.primary_host, sql)
179
+ result.strip.split("\n").each do |line|
180
+ name, val = line.split('=')
181
+ next unless name && val
182
+
183
+ settings[name.strip.to_sym] = val.strip.to_i
184
+ end
185
+ settings
186
+ rescue StandardError => e
187
+ puts " Warning: Could not get primary settings: #{e.message}"
188
+ {}
189
+ end
190
+ end
191
+
95
192
  def create_app_user_and_database(host)
96
193
  app_user = config.app_user
97
194
  app_database = config.app_database
@@ -14,7 +14,6 @@ module ActivePostgres
14
14
  end
15
15
 
16
16
  def restart
17
- # SSL doesn't have its own service, restart PostgreSQL
18
17
  ssh_executor.restart_postgres(config.primary_host)
19
18
 
20
19
  config.standby_hosts.each do |host|
@@ -37,7 +36,6 @@ module ActivePostgres
37
36
 
38
37
  ssh_executor.ensure_postgres_user(host)
39
38
 
40
- # Ensure the PostgreSQL config directory exists
41
39
  ssh_executor.execute_on_host(host) do
42
40
  execute :sudo, 'mkdir', '-p', "/etc/postgresql/#{version}/main"
43
41
  execute :sudo, 'chown', 'postgres:postgres', "/etc/postgresql/#{version}/main"
@@ -47,17 +45,31 @@ module ActivePostgres
47
45
  ssl_key = secrets.resolve('ssl_key')
48
46
 
49
47
  if ssl_cert && ssl_key
50
- puts ' Using SSL certificates from secrets...'
51
- ssh_executor.upload_file(host, ssl_cert, "/etc/postgresql/#{version}/main/server.crt", mode: '644',
52
- owner: 'postgres:postgres')
53
- ssh_executor.upload_file(host, ssl_key, "/etc/postgresql/#{version}/main/server.key", mode: '600',
54
- owner: 'postgres:postgres')
48
+ install_custom_cert(host, ssl_cert, ssl_key, ssl_config)
55
49
  else
56
50
  puts ' Generating self-signed SSL certificates...'
57
51
  generate_self_signed_cert(host, ssl_config)
58
52
  end
59
53
  end
60
54
 
55
+ def install_custom_cert(host, ssl_cert, ssl_key, _ssl_config)
56
+ version = config.version
57
+ ssl_chain = secrets.resolve('ssl_chain')
58
+
59
+ puts ' Using SSL certificates from secrets...'
60
+
61
+ full_cert = if ssl_chain
62
+ "#{ssl_cert.strip}\n#{ssl_chain.strip}\n"
63
+ else
64
+ ssl_cert
65
+ end
66
+
67
+ ssh_executor.upload_file(host, full_cert, "/etc/postgresql/#{version}/main/server.crt",
68
+ mode: '644', owner: 'postgres:postgres')
69
+ ssh_executor.upload_file(host, ssl_key, "/etc/postgresql/#{version}/main/server.key",
70
+ mode: '600', owner: 'postgres:postgres')
71
+ end
72
+
61
73
  def generate_self_signed_cert(host, ssl_config)
62
74
  version = config.version
63
75
  cert_path = "/etc/postgresql/#{version}/main/server.crt"
@@ -75,11 +75,11 @@ production:
75
75
  enabled: false # Enable for HA with standbys
76
76
 
77
77
  # Secrets: Using Rails credentials (update via: rails credentials:edit)
78
- # The $(command) syntax allows executing commands to fetch secrets
78
+ # The rails_credentials: prefix fetches values from Rails.application.credentials
79
79
  secrets:
80
- superuser_password: $(rails runner "puts Rails.application.credentials.dig(:postgres, :superuser_password)")
81
- replication_password: $(rails runner "puts Rails.application.credentials.dig(:postgres, :replication_password)")
82
- repmgr_password: $(rails runner "puts Rails.application.credentials.dig(:postgres, :repmgr_password)")
83
- pgbouncer_password: $(rails runner "puts Rails.application.credentials.dig(:postgres, :password)")
84
- app_password: $(rails runner "puts Rails.application.credentials.dig(:postgres, :password)")
80
+ superuser_password: rails_credentials:postgres.superuser_password
81
+ replication_password: rails_credentials:postgres.replication_password
82
+ repmgr_password: rails_credentials:postgres.repmgr_password
83
+ pgbouncer_password: rails_credentials:postgres.password
84
+ app_password: rails_credentials:postgres.password
85
85
 
@@ -280,7 +280,7 @@ module ActivePostgres
280
280
  keys_only: true,
281
281
  forward_agent: false,
282
282
  auth_methods: ['publickey'],
283
- verify_host_key: :never,
283
+ verify_host_key: :accept_new,
284
284
  timeout: 10,
285
285
  number_of_password_prompts: 0
286
286
  }
@@ -1,3 +1,3 @@
1
1
  module ActivePostgres
2
- VERSION = '0.5.0'.freeze
2
+ VERSION = '0.6.0'.freeze
3
3
  end
@@ -336,6 +336,24 @@ namespace :postgres do
336
336
  end
337
337
 
338
338
  namespace :setup do
339
+ desc 'Setup only core PostgreSQL (updates postgresql.conf and pg_hba.conf)'
340
+ task core: :environment do
341
+ require 'active_postgres'
342
+
343
+ config = ActivePostgres::Configuration.load
344
+ installer = ActivePostgres::Installer.new(config)
345
+ installer.setup_component('core')
346
+ end
347
+
348
+ desc 'Setup only SSL certificates'
349
+ task ssl: :environment do
350
+ require 'active_postgres'
351
+
352
+ config = ActivePostgres::Configuration.load
353
+ installer = ActivePostgres::Installer.new(config)
354
+ installer.setup_component('ssl')
355
+ end
356
+
339
357
  desc 'Setup only PgBouncer'
340
358
  task pgbouncer: :environment do
341
359
  require 'active_postgres'
@@ -508,6 +526,18 @@ namespace :postgres do
508
526
  key_valid = test('[ -f /etc/postgresql/*/main/server.key ]')
509
527
  if cert_valid && key_valid
510
528
  info ' Certificates: Present ✅'
529
+ cert_issuer = begin
530
+ capture(:sudo, 'openssl', 'x509', '-in', "/etc/postgresql/#{config.version}/main/server.crt",
531
+ '-noout', '-issuer', '2>/dev/null').strip
532
+ rescue StandardError
533
+ nil
534
+ end
535
+ if cert_issuer
536
+ issuer_o = cert_issuer.match(/O\s*=\s*"?([^",\/]+)"?/)&.captures&.first
537
+ issuer_cn = cert_issuer.match(/CN\s*=\s*([^,\/]+)/)&.captures&.first
538
+ issuer_name = issuer_o || issuer_cn || cert_issuer.sub('issuer=', '')
539
+ info " Issuer: #{issuer_name.strip}"
540
+ end
511
541
  results[:passed] << "#{label}: SSL enabled with certificates"
512
542
  else
513
543
  warn ' Certificates: Missing ⚠️'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_postgres
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BoringCache