active_postgres 0.9.4 → 0.9.6

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.
@@ -287,39 +287,39 @@ namespace :postgres do
287
287
  puts "\nTip: set RAILS_ENV=production for production targets"
288
288
 
289
289
  puts "\nSetup & Maintenance"
290
- puts " rake postgres:setup # Deploy HA cluster (CLEAN=true for fresh install)"
291
- puts " rake postgres:purge # Destroy cluster (DESTRUCTIVE)"
292
- puts " rake postgres:setup:core # PostgreSQL config (postgresql.conf, pg_hba.conf)"
293
- puts " rake postgres:setup:repmgr # HA + failover (repmgr)"
294
- puts " rake postgres:setup:pgbouncer # PgBouncer pooling"
295
- puts " rake postgres:setup:pgbackrest # Backups (pgBackRest)"
296
- puts " rake postgres:setup:monitoring # postgres_exporter"
297
- puts " rake postgres:setup:ssl # SSL certs"
290
+ puts ' rake postgres:setup # Deploy HA cluster (CLEAN=true for fresh install)'
291
+ puts ' rake postgres:purge # Destroy cluster (DESTRUCTIVE)'
292
+ puts ' rake postgres:setup:core # PostgreSQL config (postgresql.conf, pg_hba.conf)'
293
+ puts ' rake postgres:setup:repmgr # HA + failover (repmgr)'
294
+ puts ' rake postgres:setup:pgbouncer # PgBouncer pooling'
295
+ puts ' rake postgres:setup:pgbackrest # Backups (pgBackRest)'
296
+ puts ' rake postgres:setup:monitoring # postgres_exporter'
297
+ puts ' rake postgres:setup:ssl # SSL certs'
298
298
 
299
299
  puts "\nStatus & Health"
300
- puts " rake postgres:status # Cluster status (SSH by default)"
301
- puts " rake postgres:overview # Control tower overview"
302
- puts " rake postgres:nodes # Topology view"
303
- puts " rake postgres:verify # Comprehensive checklist"
304
- puts " ACTIVE_POSTGRES_STATUS_MODE=ssh|direct rake postgres:status"
300
+ puts ' rake postgres:status # Cluster status (SSH by default)'
301
+ puts ' rake postgres:overview # Control tower overview'
302
+ puts ' rake postgres:nodes # Topology view'
303
+ puts ' rake postgres:verify # Comprehensive checklist'
304
+ puts ' ACTIVE_POSTGRES_STATUS_MODE=ssh|direct rake postgres:status'
305
305
 
306
306
  puts "\nBackups"
307
- puts " rake postgres:backup:full # Full backup"
308
- puts " rake postgres:backup:incremental # Incremental backup"
309
- puts " rake postgres:backup:list # List backups"
310
- puts " rake postgres:backup:restore[ID] # Restore backup set"
311
- puts " rake postgres:backup:restore_at[\"YYYY-MM-DD HH:MM:SS\",promote] # PITR"
307
+ puts ' rake postgres:backup:full # Full backup'
308
+ puts ' rake postgres:backup:incremental # Incremental backup'
309
+ puts ' rake postgres:backup:list # List backups'
310
+ puts ' rake postgres:backup:restore[ID] # Restore backup set'
311
+ puts ' rake postgres:backup:restore_at["YYYY-MM-DD HH:MM:SS",promote] # PITR'
312
312
 
313
313
  puts "\nMigrations"
314
- puts " rake postgres:migrate # Run migrations on primary only"
314
+ puts ' rake postgres:migrate # Run migrations on primary only'
315
315
 
316
316
  puts "\nPgBouncer"
317
- puts " rake postgres:pgbouncer:update_userlist[users] # Update userlist"
318
- puts " rake postgres:pgbouncer:stats # Service status + stats"
317
+ puts ' rake postgres:pgbouncer:update_userlist[users] # Update userlist'
318
+ puts ' rake postgres:pgbouncer:stats # Service status + stats'
319
319
 
320
320
  puts "\nTests"
321
- puts " rake postgres:test:replication[rows] # Replication stress test"
322
- puts " rake postgres:test:pgbouncer[connections] # PgBouncer load test"
321
+ puts ' rake postgres:test:replication[rows] # Replication stress test'
322
+ puts ' rake postgres:test:pgbouncer[connections] # PgBouncer load test'
323
323
 
324
324
  puts
325
325
  end
@@ -380,7 +380,7 @@ namespace :postgres do
380
380
  end
381
381
 
382
382
  desc 'Restore to a point in time (PITR)'
383
- task :restore_at, [:target_time, :target_action] => :environment do |_t, args|
383
+ task :restore_at, %i[target_time target_action] => :environment do |_t, args|
384
384
  require 'active_postgres'
385
385
 
386
386
  unless args[:target_time]
@@ -607,8 +607,8 @@ namespace :postgres do
607
607
  nil
608
608
  end
609
609
  if cert_issuer
610
- issuer_o = cert_issuer.match(/O\s*=\s*"?([^",\/]+)"?/)&.captures&.first
611
- issuer_cn = cert_issuer.match(/CN\s*=\s*([^,\/]+)/)&.captures&.first
610
+ issuer_o = cert_issuer.match(%r{O\s*=\s*"?([^",/]+)"?})&.captures&.first
611
+ issuer_cn = cert_issuer.match(%r{CN\s*=\s*([^,/]+)})&.captures&.first
612
612
  issuer_name = issuer_o || issuer_cn || cert_issuer.sub('issuer=', '')
613
613
  info " Issuer: #{issuer_name.strip}"
614
614
  end
@@ -57,6 +57,8 @@ log-path=/var/log/pgbackrest
57
57
 
58
58
  # Process settings
59
59
  process-max=<%= pgbackrest_config[:process_max] || 2 %>
60
+ archive-async=y
61
+ spool-path=/var/spool/pgbackrest
60
62
 
61
63
  # Start/Stop (for consistent backups)
62
64
  start-fast=y
@@ -68,4 +70,3 @@ pg1-port=5432
68
70
  pg1-socket-path=/var/run/postgresql
69
71
  pg1-user=<%= config.postgres_user %>
70
72
 
71
-
@@ -36,6 +36,8 @@ client_idle_timeout = <%= pgbouncer_config[:client_idle_timeout] || 0 %>
36
36
  client_login_timeout = <%= pgbouncer_config[:client_login_timeout] || 60 %>
37
37
  query_timeout = <%= pgbouncer_config[:query_timeout] || 0 %>
38
38
  query_wait_timeout = <%= pgbouncer_config[:query_wait_timeout] || 120 %>
39
+ dns_max_ttl = <%= pgbouncer_config[:dns_max_ttl] || 5 %>
40
+ dns_nxdomain_ttl = <%= pgbouncer_config[:dns_nxdomain_ttl] || 15 %>
39
41
 
40
42
  # Logging and administration
41
43
  admin_users = <%= pgbouncer_config[:admin_users] || config.postgres_user %>
@@ -62,4 +64,3 @@ pidfile = <%= pgbouncer_config[:pidfile] %>
62
64
  logfile = <%= pgbouncer_config[:logfile] %>
63
65
  <% end %>
64
66
 
65
-
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PRIMARY_RECORD="<%= primary_record %>"
5
+ PGBOUNCER_INI="/etc/pgbouncer/pgbouncer.ini"
6
+ PGBOUNCER_SERVICE="pgbouncer"
7
+ STATE_DIR="/var/lib/active-postgres"
8
+ STATE_FILE="${STATE_DIR}/pgbouncer-primary-ip"
9
+ LOG_TAG="pgbouncer-follow-primary"
10
+
11
+ resolved_ip=$(getent ahostsv4 "$PRIMARY_RECORD" | awk '{print $1; exit}' || true)
12
+ if [[ -z "$resolved_ip" ]]; then
13
+ /usr/bin/logger -t "$LOG_TAG" "Could not resolve ${PRIMARY_RECORD}"
14
+ exit 1
15
+ fi
16
+
17
+ current_host=$(awk -F'host=' '/^\\* = host=/{print $2}' "$PGBOUNCER_INI" | awk '{print $1}')
18
+ previous_ip=""
19
+ if [[ -f "$STATE_FILE" ]]; then
20
+ previous_ip=$(cat "$STATE_FILE")
21
+ fi
22
+
23
+ changed=0
24
+ if [[ "$current_host" != "$PRIMARY_RECORD" ]]; then
25
+ /usr/bin/sed -i -E "s/^(\\* = host=)[^ ]+/\\1${PRIMARY_RECORD}/" "$PGBOUNCER_INI"
26
+ changed=1
27
+ fi
28
+
29
+ if [[ "$previous_ip" != "$resolved_ip" ]]; then
30
+ changed=1
31
+ fi
32
+
33
+ /usr/bin/install -d -m 0755 "$STATE_DIR"
34
+ printf "%s\n" "$resolved_ip" > "$STATE_FILE"
35
+
36
+ if [[ "$changed" == "1" ]]; then
37
+ /usr/bin/systemctl restart "$PGBOUNCER_SERVICE"
38
+ /usr/bin/logger -t "$LOG_TAG" "Refreshed PgBouncer primary target ${PRIMARY_RECORD} (${resolved_ip})"
39
+ fi
@@ -46,12 +46,43 @@ if [[ ${#tar_paths[@]} -eq 0 ]]; then
46
46
  exit 0
47
47
  fi
48
48
 
49
- tar -czf "$archive" -C / "${tar_paths[@]}"
49
+ set +e
50
+ tar --warning=no-file-changed --ignore-failed-read -czf "$archive" -C / "${tar_paths[@]}"
51
+ tar_status=$?
52
+ set -e
50
53
 
51
- destination="s3://${AWS_BUCKET}/${PREFIX%/}/${NODE}/${DATE_PATH}/$(basename "$archive")"
52
- aws_args=(s3 cp "$archive" "$destination" --only-show-errors)
53
- if [[ -n "${AWS_ENDPOINT_URL:-}" ]]; then
54
- aws_args+=(--endpoint-url "$AWS_ENDPOINT_URL")
54
+ if [[ $tar_status -gt 1 ]]; then
55
+ exit "$tar_status"
55
56
  fi
56
57
 
57
- aws "${aws_args[@]}"
58
+ if [[ ! -s "$archive" ]]; then
59
+ exit 0
60
+ fi
61
+
62
+ destination="s3://${AWS_BUCKET}/${PREFIX%/}/${NODE}/${DATE_PATH}/$(basename "$archive")"
63
+ if command -v aws >/dev/null 2>&1; then
64
+ aws_args=(s3 cp "$archive" "$destination" --only-show-errors)
65
+ if [[ -n "${AWS_ENDPOINT_URL:-}" ]]; then
66
+ aws_args+=(--endpoint-url "$AWS_ENDPOINT_URL")
67
+ fi
68
+
69
+ aws "${aws_args[@]}"
70
+ elif command -v s3cmd >/dev/null 2>&1; then
71
+ s3cmd_args=(
72
+ --access_key="$AWS_ACCESS_KEY_ID"
73
+ --secret_key="$AWS_SECRET_ACCESS_KEY"
74
+ --region="${AWS_DEFAULT_REGION:-us-east-1}"
75
+ --no-progress
76
+ )
77
+
78
+ if [[ -n "${AWS_ENDPOINT_URL:-}" ]]; then
79
+ endpoint="${AWS_ENDPOINT_URL#https://}"
80
+ endpoint="${endpoint#http://}"
81
+ s3cmd_args+=(--host="$endpoint" --host-bucket="${endpoint}/%(bucket)")
82
+ fi
83
+
84
+ s3cmd "${s3cmd_args[@]}" put "$archive" "$destination"
85
+ else
86
+ echo "missing aws or s3cmd command for PostgreSQL log archive" >&2
87
+ exit 1
88
+ fi
@@ -52,6 +52,11 @@ archive_command = '<%= pg_config[:archive_command] %>'
52
52
  <% elsif config.component_enabled?(:pgbackrest) %>
53
53
  archive_command = 'pgbackrest --stanza=main archive-push %p'
54
54
  <% end %>
55
+ <% if pg_config[:restore_command] %>
56
+ restore_command = '<%= pg_config[:restore_command] %>'
57
+ <% elsif config.component_enabled?(:pgbackrest) %>
58
+ restore_command = 'pgbackrest --pg1-path=/var/lib/postgresql/<%= config.version %>/main --stanza=main archive-get %f "%p"'
59
+ <% end %>
55
60
  <% if pg_config[:shared_memory_type] %>
56
61
  shared_memory_type = <%= pg_config[:shared_memory_type] %>
57
62
  <% end %>
@@ -17,13 +17,13 @@ REPMGR_DB=$(grep -oP "dbname=\K[^ ']+" "$REPMGR_CONF" | head -1)
17
17
  REPMGR_USER=$(grep -oP "user=\K[^ ']+" "$REPMGR_CONF" 2>/dev/null | head -1)
18
18
  REPMGR_USER="${REPMGR_USER:-repmgr}"
19
19
 
20
- cluster_data=$(psql -h /var/run/postgresql -d "$REPMGR_DB" -tAF',' -c "SELECT type, conninfo FROM repmgr.nodes WHERE active = true" 2>/dev/null || true)
20
+ cluster_data=$(psql -h /var/run/postgresql -d "$REPMGR_DB" -tAF',' -c "SELECT type, conninfo FROM repmgr.nodes WHERE active = true ORDER BY node_id" 2>/dev/null || true)
21
21
  if [[ -z "$cluster_data" ]]; then
22
22
  exit 0
23
23
  fi
24
24
 
25
25
  primary_host=$(printf "%s\n" "$cluster_data" | awk -F',' '$1 == "primary" {print $2; exit}' | sed -n 's/.*host=\([^ ]*\).*/\1/p')
26
- standby_hosts=$(printf "%s\n" "$cluster_data" | awk -F',' '$1 == "standby" {print $2}' | sed -n 's/.*host=\([^ ]*\).*/\1/p' | sort -u)
26
+ standby_hosts=$(printf "%s\n" "$cluster_data" | awk -F',' '$1 == "standby" {print $2; exit}' | sed -n 's/.*host=\([^ ]*\).*/\1/p')
27
27
 
28
28
  if [[ -z "$primary_host" ]]; then
29
29
  exit 0
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.9.4
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - BoringCache
@@ -29,35 +29,35 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.3'
32
+ version: '1.4'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.3'
39
+ version: '1.4'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: pg
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.0'
46
+ version: '1.6'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.0'
53
+ version: '1.6'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: railties
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '6.0'
60
+ version: '8.1'
61
61
  - - "<"
62
62
  - !ruby/object:Gem::Version
63
63
  version: '9.0'
@@ -67,7 +67,7 @@ dependencies:
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: '6.0'
70
+ version: '8.1'
71
71
  - - "<"
72
72
  - !ruby/object:Gem::Version
73
73
  version: '9.0'
@@ -77,70 +77,28 @@ dependencies:
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '1.21'
80
+ version: '1.25'
81
81
  type: :runtime
82
82
  prerelease: false
83
83
  version_requirements: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '1.21'
87
+ version: '1.25'
88
88
  - !ruby/object:Gem::Dependency
89
89
  name: thor
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '1.3'
94
+ version: '1.5'
95
95
  type: :runtime
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: '1.3'
102
- - !ruby/object:Gem::Dependency
103
- name: minitest
104
- requirement: !ruby/object:Gem::Requirement
105
- requirements:
106
- - - "~>"
107
- - !ruby/object:Gem::Version
108
- version: '5.0'
109
- type: :development
110
- prerelease: false
111
- version_requirements: !ruby/object:Gem::Requirement
112
- requirements:
113
- - - "~>"
114
- - !ruby/object:Gem::Version
115
- version: '5.0'
116
- - !ruby/object:Gem::Dependency
117
- name: rubocop
118
- requirement: !ruby/object:Gem::Requirement
119
- requirements:
120
- - - "~>"
121
- - !ruby/object:Gem::Version
122
- version: '1.50'
123
- type: :development
124
- prerelease: false
125
- version_requirements: !ruby/object:Gem::Requirement
126
- requirements:
127
- - - "~>"
128
- - !ruby/object:Gem::Version
129
- version: '1.50'
130
- - !ruby/object:Gem::Dependency
131
- name: simplecov
132
- requirement: !ruby/object:Gem::Requirement
133
- requirements:
134
- - - "~>"
135
- - !ruby/object:Gem::Version
136
- version: '0.22'
137
- type: :development
138
- prerelease: false
139
- version_requirements: !ruby/object:Gem::Requirement
140
- requirements:
141
- - - "~>"
142
- - !ruby/object:Gem::Version
143
- version: '0.22'
101
+ version: '1.5'
144
102
  description: Production-grade PostgreSQL HA with deep Rails integration. Automated
145
103
  deployment, replication, failover, and monitoring.
146
104
  email:
@@ -150,8 +108,10 @@ executables:
150
108
  extensions: []
151
109
  extra_rdoc_files: []
152
110
  files:
111
+ - CHANGELOG.md
153
112
  - LICENSE
154
113
  - README.md
114
+ - SECURITY.md
155
115
  - exe/activepostgres
156
116
  - lib/active_postgres.rb
157
117
  - lib/active_postgres/cli.rb
@@ -199,6 +159,7 @@ files:
199
159
  - templates/pgbouncer-follow-primary.service.erb
200
160
  - templates/pgbouncer-follow-primary.timer.erb
201
161
  - templates/pgbouncer.ini.erb
162
+ - templates/pgbouncer_follow_dns_primary.sh.erb
202
163
  - templates/pgbouncer_follow_primary.sh.erb
203
164
  - templates/postgres_log_archive.sh.erb
204
165
  - templates/postgresql.conf.erb
@@ -208,10 +169,10 @@ homepage: https://github.com/boringcache/active_postgres
208
169
  licenses:
209
170
  - MIT
210
171
  metadata:
211
- homepage_uri: https://github.com/boringcache/active_postgres
212
172
  source_code_uri: https://github.com/boringcache/active_postgres
213
173
  documentation_uri: https://github.com/boringcache/active_postgres/blob/main/README.md
214
174
  changelog_uri: https://github.com/boringcache/active_postgres/blob/main/CHANGELOG.md
175
+ bug_tracker_uri: https://github.com/boringcache/active_postgres/issues
215
176
  rubygems_mfa_required: 'true'
216
177
  rdoc_options: []
217
178
  require_paths:
@@ -220,14 +181,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
220
181
  requirements:
221
182
  - - ">="
222
183
  - !ruby/object:Gem::Version
223
- version: 3.3.0
184
+ version: 4.0.0
224
185
  required_rubygems_version: !ruby/object:Gem::Requirement
225
186
  requirements:
226
187
  - - ">="
227
188
  - !ruby/object:Gem::Version
228
189
  version: '0'
229
190
  requirements: []
230
- rubygems_version: 3.6.9
191
+ rubygems_version: 4.0.16
231
192
  specification_version: 4
232
193
  summary: PostgreSQL High Availability for Rails, made simple
233
194
  test_files: []