@sysnee/pgs 0.1.7-rc.5 → 0.1.7-rc.7

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.
package/README.md CHANGED
@@ -1,151 +1,164 @@
1
1
  # PostgreSQL Multi-Tenant Instance Manager
2
2
 
3
- Dynamic PostgreSQL multi-tenant management system providing complete database isolation by creating dedicated PostgreSQL instances per tenant, with intelligent routing via HAProxy.
3
+ Dynamic PostgreSQL multi-tenant management system providing complete database isolation by creating dedicated PostgreSQL instances per tenant, with intelligent SNI-based routing via Traefik v3.
4
4
 
5
- > 📖 **For detailed project definition, architecture, and comparison with similar solutions, see [PROJECT.md](./PROJECT.md)**
5
+ > 📖 **For detailed project definition, architecture, and comparison with similar solutions, see [docs/PROJECT.md](./docs/PROJECT.md)**
6
+
7
+ ## Quick Start
8
+
9
+ ### Initial Setup
10
+
11
+ ```bash
12
+ pgs setup
13
+ ```
14
+
15
+ This creates the configuration directory at `~/.sysnee-config/` with:
16
+ - `docker-compose.yml` - Container orchestration
17
+ - `traefik.yml` - Traefik static configuration
18
+ - `dynamic.yml` - Traefik dynamic routing configuration
19
+ - `tenant-access.json` - Access control
20
+ - `certs/` - SSL certificates directory
21
+
22
+ ### SSL Certificate Setup
23
+
24
+ Place your wildcard SSL certificate in the certs directory:
25
+
26
+ ```bash
27
+ # Using Let's Encrypt
28
+ sudo cp /etc/letsencrypt/live/pgs.YOUR-DOMAIN.com/fullchain.pem ~/.sysnee-config/certs/
29
+ sudo cp /etc/letsencrypt/live/pgs.YOUR-DOMAIN.com/privkey.pem ~/.sysnee-config/certs/
30
+ sudo chown $USER:$USER ~/.sysnee-config/certs/*
31
+ ```
32
+
33
+ ### DNS Configuration
34
+
35
+ Add a wildcard DNS record pointing to your server:
36
+
37
+ ```
38
+ *.pgs.YOUR-DOMAIN.com → YOUR_SERVER_IP
39
+ ```
6
40
 
7
41
  ## Usage
8
42
 
9
43
  ### Create a new tenant instance
10
44
 
11
45
  ```bash
12
- npm run create <tenant-id> [--password <password>]
46
+ pgs create <tenant-id> [--password <password>]
13
47
  ```
14
48
 
15
49
  Example:
16
50
  ```bash
17
- npm run create tenant1
18
- npm run create tenant2 --password mycustompass
51
+ pgs create tenant1
52
+ pgs create tenant2 --password mycustompass
19
53
  ```
20
54
 
21
- New tenants are created with external access **disabled** by default.
55
+ New tenants are created with external access **enabled** by default.
22
56
 
23
57
  ### List all tenants
24
58
 
25
59
  ```bash
26
- npm run list
60
+ pgs list
27
61
  ```
28
62
 
29
- Shows all tenants with their external access status.
63
+ Shows all tenants with their hostnames and external access status.
30
64
 
31
65
  ### Remove a tenant
32
66
 
33
67
  ```bash
34
- npm run remove <tenant-id>
68
+ pgs remove <tenant-id>
35
69
  ```
36
70
 
37
71
  ### Start services
38
72
 
39
73
  ```bash
40
- npm run start # Start all services (including HAProxy)
41
- npm run start -- --tenant tenant1 # Start specific tenant
74
+ pgs start # Start all services (including Traefik)
75
+ pgs start <tenant-id> # Start specific tenant
42
76
  ```
43
77
 
44
78
  ### Stop services
45
79
 
46
80
  ```bash
47
- npm run stop # Stop all services
48
- npm run stop -- --tenant tenant1 # Stop specific tenant
81
+ pgs stop # Stop all services
82
+ pgs stop <tenant-id> # Stop specific tenant
83
+ ```
84
+
85
+ ### Access Control
86
+
87
+ ```bash
88
+ pgs enable-access <tenant-id> # Enable external access
89
+ pgs disable-access <tenant-id> # Disable external access
49
90
  ```
50
91
 
51
92
  ## How it works
52
93
 
53
- - HAProxy listens on port 5432 and routes connections based on the PostgreSQL username
54
- - Each tenant connects using their tenant ID as the username (e.g., `tecnolab`)
94
+ - **Traefik v3** listens on port 5432 with TLS/SSL enabled
95
+ - Routes connections based on **SNI (Server Name Indication)** hostname
96
+ - Each tenant connects using their unique hostname (e.g., `tenant1-abc123.pgs.domain.com`)
55
97
  - External access is controlled via `tenant-access.json`
56
98
  - Tenants are isolated in their own PostgreSQL containers on a Docker bridge network
57
- - Only HAProxy has external port mapping; PostgreSQL containers are internal only
99
+ - Only Traefik has external port mapping; PostgreSQL containers are internal only
58
100
 
59
101
  ## Connection
60
102
 
61
- After creating a tenant and enabling external access:
103
+ After creating a tenant:
62
104
 
63
105
  ```bash
64
- psql -h localhost -p 5432 -U <tenant-id> -d <tenant-id>
65
- ```
66
-
67
- ## Project Definition
68
-
69
- ### What It Is
70
-
71
- A **dynamic PostgreSQL multi-tenant management system** that provides complete database isolation by creating dedicated PostgreSQL instances per tenant. The system uses HAProxy with custom PostgreSQL protocol parsing to route connections intelligently while maintaining complete tenant isolation at the database server level.
72
-
73
- ### Core Concept
106
+ # Using psql
107
+ psql "postgresql://postgres:PASSWORD@TENANT-ID.pgs.YOUR-DOMAIN.com:5432/DATABASE?sslmode=require"
74
108
 
75
- Instead of sharing a single PostgreSQL instance with multiple databases (shared database architecture), this system creates **one PostgreSQL container per tenant**, ensuring:
76
-
77
- - **Complete Data Isolation**: Each tenant has its own PostgreSQL process and data directory
78
- - **Independent Scaling**: Resources can be allocated per tenant
79
- - **Enhanced Security**: No risk of cross-tenant data access
80
- - **Simplified Operations**: Each tenant can be managed independently
81
-
82
- ### Key Features
83
-
84
- 1. **Dynamic Tenant Provisioning**
85
- - Create new PostgreSQL instances on-demand
86
- - Automatic volume and network configuration
87
- - Custom initialization scripts per tenant
88
-
89
- 2. **Intelligent Routing**
90
- - HAProxy parses PostgreSQL protocol to extract username
91
- - Routes connections to correct tenant backend automatically
92
- - Single external port (5432) for all tenants
93
-
94
- 3. **Access Control**
95
- - Per-tenant external access enable/disable
96
- - Secure by default (access disabled on creation)
97
- - Runtime access control without service restart
109
+ # Example
110
+ psql "postgresql://postgres:mypass@tenant1-abc123.pgs.us-central-1.sysnee.com:5432/tenant1-abc123?sslmode=require"
111
+ ```
98
112
 
99
- 4. **Complete Isolation**
100
- - Separate Docker containers per tenant
101
- - Isolated volumes for data persistence
102
- - Network isolation via Docker bridge network
103
- - No shared processes or memory
113
+ ### DBeaver / GUI Clients
104
114
 
105
- 5. **Zero-Downtime Operations**
106
- - Graceful HAProxy reloads
107
- - Independent tenant management
108
- - No impact on other tenants during operations
115
+ 1. **Host**: `tenant-id.pgs.your-domain.com`
116
+ 2. **Port**: `5432`
117
+ 3. **Database**: `tenant-id`
118
+ 4. **Username**: `postgres`
119
+ 5. **Password**: (your password)
120
+ 6. **SSL**: Enable SSL, set mode to `require`
109
121
 
110
122
  ## Architecture
111
123
 
112
124
  ```
113
- ┌─────────────────────────────────────────────────────────┐
114
- External Access
115
- (localhost:5432)
116
- └──────────────────────┬──────────────────────────────────┘
117
-
118
-
119
- ┌─────────────────────────────────────────────────────────┐
120
- HAProxy Proxy
121
- ┌──────────────────────────────────────────────────┐
122
- │ │ Frontend: postgres_frontend
123
- │ │ - Listens on port 5432
124
- │ │ - Parses PostgreSQL protocol (Lua script)
125
- │ │ - Extracts username from startup packet
126
- │ │ - Checks tenant-access.json for permissions
127
- └──────────────────────────────────────────────────┘
128
- └──────────────────────┬──────────────────────────────────┘
129
-
130
- ┌──────────────┼──────────────┐
131
-
132
-
133
- ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
134
- Backend │Backend │Backend
135
- pgs_tenant1 pgs_tenant2 pgs_tenant3
136
- └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
137
- │ │ │
138
- ▼ ▼ ▼
139
- ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
140
- │ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │
141
- Container 1 Container 2 Container 3
142
- │ │ │ │
143
- Port: 5432 Port: 5432 Port: 5432 │
144
- (internal) (internal) (internal)
145
- │ │ │ │
146
- Volume: Volume: Volume:
147
- pgdata_1 pgdata_2 pgdata_3
148
- └──────────────┘ └──────────────┘ └──────────────┘
125
+ ┌─────────────────────────────────────────────────────────────────┐
126
+ External Access
127
+ (tenant-id.pgs.domain.com:5432 + TLS/SNI)
128
+ └───────────────────────────┬─────────────────────────────────────┘
129
+
130
+
131
+ ┌─────────────────────────────────────────────────────────────────┐
132
+ Traefik v3 Proxy
133
+ ┌───────────────────────────────────────────────────────────┐
134
+ │ │ EntryPoint: postgres (port 5432)
135
+ │ │ - TLS termination with wildcard certificate
136
+ │ │ - PostgreSQL STARTTLS protocol support
137
+ │ │ - SNI-based routing (HostSNI rule)
138
+ │ │ - Dynamic configuration via dynamic.yml
139
+ └───────────────────────────────────────────────────────────┘
140
+ └───────────────────────────┬─────────────────────────────────────┘
141
+
142
+ ┌──────────────────┼──────────────────┐
143
+
144
+
145
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
146
+ TCP Router TCP Router TCP Router
147
+ tenant1 tenant2 tenant3
148
+ │ HostSNI() │ │ HostSNI() │ │ HostSNI() │
149
+ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
150
+ │ │ │
151
+ ▼ ▼ ▼
152
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
153
+ PostgreSQL PostgreSQL PostgreSQL
154
+ Container 1 Container 2 Container 3
155
+ │ │
156
+ Port: 5432 Port: 5432 Port: 5432
157
+ (internal) (internal) (internal)
158
+
159
+ Volume: Volume: Volume:
160
+ pgdata_1 │ │ pgdata_2 │ │ pgdata_3 │
161
+ └──────────────┘ └──────────────┘ └──────────────┘
149
162
  ```
150
163
 
151
164
  ## Technical Implementation
@@ -155,22 +168,16 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
155
168
  1. **Manager Script (manager.js)**
156
169
  - Node.js CLI tool for tenant lifecycle management
157
170
  - Dynamically generates docker-compose.yml entries
158
- - Manages HAProxy configuration
171
+ - Manages Traefik dynamic configuration
159
172
  - Controls tenant access permissions
160
173
 
161
- 2. **HAProxy Reverse Proxy**
162
- - TCP-level load balancer and router
163
- - Custom Lua script for PostgreSQL protocol parsing
164
- - Routes based on extracted username
165
- - Per-tenant access control
166
-
167
- 3. **PostgreSQL Protocol Parser (pg-route.lua)**
168
- - Parses binary PostgreSQL startup packet
169
- - Extracts username and connection parameters
170
- - Handles SSL negotiation
171
- - Enforces access control policies
174
+ 2. **Traefik v3 Reverse Proxy**
175
+ - TCP-level routing with TLS termination
176
+ - Native PostgreSQL STARTTLS protocol support
177
+ - SNI-based tenant routing
178
+ - Dynamic configuration without restarts
172
179
 
173
- 4. **Docker Infrastructure**
180
+ 3. **Docker Infrastructure**
174
181
  - Separate container per tenant
175
182
  - Bridge network for internal communication
176
183
  - Persistent volumes for data
@@ -178,13 +185,22 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
178
185
 
179
186
  ### Connection Flow
180
187
 
181
- 1. Client connects to `localhost:5432` with username `tenant_id`
182
- 2. HAProxy receives connection and invokes Lua script
183
- 3. Script parses PostgreSQL startup packet and extracts username
184
- 4. Script checks `tenant-access.json` for permission
185
- 5. If allowed, routes to backend `pgs_{tenant_id}`
186
- 6. Backend forwards to PostgreSQL container on internal network
187
- 7. Connection established with complete isolation
188
+ 1. Client connects to `tenant-id.pgs.domain.com:5432` with `sslmode=require`
189
+ 2. Traefik receives connection and initiates PostgreSQL STARTTLS handshake
190
+ 3. Client sends TLS ClientHello with SNI (hostname)
191
+ 4. Traefik extracts SNI and matches against configured routers
192
+ 5. If tenant has access enabled, routes to backend `pgs_{tenant_id}:5432`
193
+ 6. Connection established with complete isolation
194
+
195
+ ### Why Traefik v3?
196
+
197
+ PostgreSQL uses a non-standard TLS negotiation (STARTTLS):
198
+ 1. Client sends PostgreSQL SSLRequest packet
199
+ 2. Server responds 'S' (SSL supported)
200
+ 3. Client sends TLS ClientHello with SNI
201
+ 4. TLS handshake completes
202
+
203
+ **Traefik v3** is one of the few proxies that natively understands this PostgreSQL-specific flow, allowing SNI-based routing for PostgreSQL connections.
188
204
 
189
205
  ## Comparison with Similar Solutions
190
206
 
@@ -195,65 +211,29 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
195
211
  - Multiple databases/schemas per instance
196
212
  - Shared processes and memory
197
213
  - Risk of cross-tenant data access
198
- - Less isolation
199
214
 
200
215
  **This Solution:**
201
216
  - Multiple PostgreSQL instances
202
217
  - One instance per tenant
203
218
  - Complete process isolation
204
219
  - Zero risk of cross-tenant access
205
- - Maximum isolation
206
220
 
207
221
  ### Similar Open Source Solutions
208
222
 
209
- #### 1. **PgBouncer**
210
- - **Purpose**: Connection pooling, not tenant isolation
211
- - **Difference**: Pools connections to single instance; this creates separate instances
212
- - **Use Case**: Different - PgBouncer optimizes connections; this isolates tenants
213
-
214
- #### 2. **Citus**
215
- - **Purpose**: PostgreSQL extension for distributed PostgreSQL
216
- - **Difference**: Shards data across nodes; this creates separate instances per tenant
217
- - **Use Case**: Horizontal scaling vs. tenant isolation
218
-
219
- #### 3. **Patroni + HAProxy**
220
- - **Purpose**: High availability and load balancing
221
- - **Difference**: Replicates single database; this creates isolated instances
222
- - **Use Case**: HA for single database vs. multi-tenant isolation
223
-
224
- #### 4. **Schema-based Multi-tenancy**
225
- - **Purpose**: Share database, separate schemas
226
- - **Difference**: Shared instance; this uses separate instances
227
- - **Use Case**: Resource efficiency vs. complete isolation
223
+ | Solution | Purpose | Difference |
224
+ |----------|---------|------------|
225
+ | **PgBouncer** | Connection pooling | Pools to single instance; this creates separate instances |
226
+ | **Citus** | Distributed PostgreSQL | Shards data; this isolates tenants completely |
227
+ | **Patroni** | High availability | Replicates single DB; this creates isolated instances |
228
+ | **RLS** | Row-level security | Logic-based separation; this uses infrastructure isolation |
228
229
 
229
- #### 5. **Row-level Security (RLS)**
230
- - **Purpose**: Application-level tenant isolation
231
- - **Difference**: Logic-based separation; this uses infrastructure isolation
232
- - **Use Case**: Application isolation vs. infrastructure isolation
230
+ ### Unique Aspects
233
231
 
234
- ### Unique Aspects of This Solution
235
-
236
- 1. **Instance-per-tenant at infrastructure level**
237
- - Not just database or schema separation
238
- - Complete process and memory isolation
239
-
240
- 2. **Dynamic provisioning with single external port**
241
- - No need for port management
242
- - Automatic routing based on connection parameters
243
-
244
- 3. **Protocol-aware routing**
245
- - Parses PostgreSQL binary protocol
246
- - Routes before connection completion
247
- - Handles SSL negotiation
248
-
249
- 4. **Runtime access control**
250
- - Enable/disable tenant access without restart
251
- - No downtime for access changes
252
-
253
- 5. **Docker-native architecture**
254
- - Leverages container isolation
255
- - Simple deployment and scaling
256
- - Resource limits per tenant
232
+ 1. **Instance-per-tenant** - Complete process and memory isolation
233
+ 2. **SNI-based routing** - Single port, automatic hostname-based routing
234
+ 3. **TLS by default** - Secure connections required
235
+ 4. **Dynamic provisioning** - Create tenants on-demand via CLI
236
+ 5. **Docker-native** - Simple deployment and resource limits per tenant
257
237
 
258
238
  ## Use Cases
259
239
 
@@ -263,52 +243,39 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
263
243
  - **Healthcare/Finance** applications with compliance requirements
264
244
  - **Multi-tenant platforms** needing independent scaling
265
245
  - **Development/Testing** environments with isolated databases
266
- - **Legacy application migration** requiring tenant separation
267
246
 
268
247
  ### Not Ideal For
269
248
 
270
249
  - Thousands of tenants (resource overhead)
271
- - Shared resource requirements
272
250
  - Simple multi-tenant applications without strict isolation needs
273
251
  - Environments requiring minimal resource usage
274
252
 
275
- ## Advantages
276
-
277
- ✅ **Maximum Isolation**: Complete process and data separation
278
- ✅ **Security**: Zero risk of cross-tenant data access
279
- ✅ **Flexibility**: Independent scaling and management per tenant
280
- ✅ **Simplicity**: Single external port, automatic routing
281
- ✅ **Compliance**: Easier to meet regulatory requirements
282
- ✅ **Debugging**: Isolated environments simplify troubleshooting
283
-
284
- ## Trade-offs
285
-
286
- ⚠️ **Resource Usage**: Higher memory/CPU per tenant
287
- ⚠️ **Management Overhead**: More containers to manage
288
- ⚠️ **Scaling Limits**: Practical limit on number of tenants per host
289
- ⚠️ **Backup Complexity**: Need to backup multiple instances
290
-
291
253
  ## Technology Stack
292
254
 
293
255
  - **Runtime**: Node.js (ES Modules)
294
256
  - **Container Orchestration**: Docker Compose
295
- - **Reverse Proxy**: HAProxy with Lua scripting
257
+ - **Reverse Proxy**: Traefik v3 (PostgreSQL STARTTLS + SNI routing)
296
258
  - **Database**: PostgreSQL 18+
297
- - **Protocol Parsing**: Custom Lua script
298
- - **Configuration**: YAML (docker-compose.yml), JSON (tenant-access.json)
259
+ - **TLS**: Wildcard SSL certificate
260
+ - **Configuration**: YAML (docker-compose.yml, traefik.yml, dynamic.yml), JSON (tenant-access.json)
261
+
262
+ ## Requirements
263
+
264
+ - Docker & Docker Compose
265
+ - Node.js 18+
266
+ - Wildcard SSL certificate for your domain
267
+ - Wildcard DNS record pointing to your server
299
268
 
300
269
  ## Future Enhancements
301
270
 
302
271
  - [ ] Health checks and automatic failover
303
272
  - [ ] Backup/restore automation per tenant
304
- - [ ] Resource limits (CPU/memory) per tenant
305
273
  - [ ] Monitoring and metrics collection
306
274
  - [ ] Tenant migration tools
307
275
  - [ ] Kubernetes support
308
276
  - [ ] Connection pooling per tenant
309
- - [ ] SSL/TLS termination
277
+ - [ ] Web dashboard
310
278
 
311
279
  ## License & Status
312
280
 
313
- This is a custom solution built for specific multi-tenant requirements. It combines open-source tools (HAProxy, PostgreSQL, Docker) with custom routing logic to achieve instance-per-tenant isolation with intelligent connection routing.
314
-
281
+ This is a custom solution built for specific multi-tenant requirements. It combines open-source tools (Traefik, PostgreSQL, Docker) with SNI-based routing to achieve instance-per-tenant isolation with intelligent connection routing.
@@ -8,9 +8,11 @@ Para transformar esta solução em um produto comercial viável (PaaS de Postgre
8
8
  2. **Alta Disponibilidade**: Sem replicação → Patroni/pg_auto_failover
9
9
  3. **Backup/Recovery**: Inexistente → pgBackRest + S3
10
10
  4. **Monitoramento**: Básico → Prometheus + Grafana
11
- 5. **Segurança**: Melhorias necessárias → Vault + SSL/TLS
11
+ 5. **Segurança**: TLS/SNI implementado via Traefik v3 Adicionar Vault para secrets
12
12
  6. **API**: CLI apenas → REST API completa
13
13
 
14
+ > **Nota**: A solução atual usa Traefik v3 para roteamento SNI com suporte nativo ao protocolo PostgreSQL STARTTLS.
15
+
14
16
  ---
15
17
 
16
18
  ## Abordagem por Fase
@@ -32,8 +34,8 @@ Host 2 (Região A)
32
34
  ├── docker-compose-3.yml # Tenants 101-150
33
35
  └── docker-compose-4.yml # Tenants 151-200
34
36
 
35
- Load Balancer (HAProxy/Nginx)
36
- ├── Roteia para host correto baseado em tenant_id hash
37
+ Load Balancer (Traefik v3)
38
+ ├── Roteia para host correto baseado em SNI (hostname)
37
39
  └── Health checks de todos os hosts
38
40
  ```
39
41
 
@@ -141,12 +143,12 @@ pgs_tenant1_replica:
141
143
  PATRONI_ROLE: replica
142
144
  ```
143
145
 
144
- #### 2. HAProxy Redundante
146
+ #### 2. Traefik Redundante
145
147
 
146
148
  ```
147
- HAProxy 1 ──┐
149
+ Traefik 1 ──┐
148
150
  ├── Keepalived VIP (Virtual IP)
149
- HAProxy 2 ──┘
151
+ Traefik 2 ──┘
150
152
  ```
151
153
 
152
154
  #### 3. Backup com pgBackRest
@@ -301,7 +303,7 @@ spec:
301
303
  ```yaml
302
304
  # docker-compose.yml com profiles e múltiplos arquivos
303
305
  services:
304
- haproxy:
306
+ traefik:
305
307
  # ...
306
308
 
307
309
  pgs_tenant1:
@@ -340,7 +342,7 @@ API centralizada gerencia:
340
342
 
341
343
  ```
342
344
  Orquestração: Docker Compose (melhorado)
343
- Proxy: HAProxy
345
+ Proxy: Traefik v3 (SNI routing + PostgreSQL STARTTLS)
344
346
  Backup: pg_dump + AWS S3
345
347
  Monitoring: Prometheus + Grafana
346
348
  Secrets: HashiCorp Vault (básico)
@@ -352,7 +354,7 @@ Database: PostgreSQL 18+
352
354
 
353
355
  ```
354
356
  Orquestração: Kubernetes
355
- Proxy: Nginx Ingress + HAProxy
357
+ Proxy: Traefik IngressRoute (TCP + SNI) ou Nginx Ingress
356
358
  Backup: pgBackRest + S3/GCS
357
359
  Monitoring: Prometheus + Grafana + Alertmanager
358
360
  Secrets: HashiCorp Vault (completo)
@@ -4,6 +4,8 @@
4
4
 
5
5
  Esta análise identifica os pontos críticos que precisam ser endereçados para transformar esta solução em um produto comercial viável.
6
6
 
7
+ > **Nota**: A solução atual utiliza **Traefik v3** para roteamento baseado em SNI (Server Name Indication) com suporte nativo ao protocolo PostgreSQL STARTTLS. Isso permite roteamento por hostname com TLS obrigatório.
8
+
7
9
  ---
8
10
 
9
11
  ## 🔴 PROBLEMAS CRÍTICOS
@@ -118,8 +120,8 @@ Melhorias possíveis:
118
120
  - Sem backup automático em tempo real
119
121
  - RTO (Recovery Time Objective) alto
120
122
 
121
- **b) HAProxy como SPOF**
122
- - Se HAProxy cair, todos os tenants ficam inacessíveis
123
+ **b) Traefik como SPOF**
124
+ - Se Traefik cair, todos os tenants ficam inacessíveis
123
125
  - Sem redundância do proxy
124
126
 
125
127
  #### Soluções Necessárias:
@@ -137,12 +139,12 @@ Primary (writable) ──streaming──> Standby (read-only)
137
139
  ↓ failover ↑ promotion
138
140
  ```
139
141
 
140
- **2. HAProxy Redundância**
142
+ **2. Traefik Redundância**
141
143
  ```
142
- - Múltiplas instâncias HAProxy
144
+ - Múltiplas instâncias Traefik
143
145
  - Keepalived para VIP (Virtual IP)
144
- - Health checks entre HAProxy instances
145
- - Load balancing do HAProxy
146
+ - Health checks entre Traefik instances
147
+ - Load balancing do Traefik
146
148
  ```
147
149
 
148
150
  **3. Multi-Region (Futuro)**
@@ -296,7 +298,7 @@ Implementação:
296
298
  **2. SSL/TLS**
297
299
  ```
298
300
  - Certificados SSL para conexões
299
- - TLS entre HAProxy e PostgreSQL
301
+ - TLS entre Traefik e PostgreSQL (interno)
300
302
  - Certificate management automático (Let's Encrypt)
301
303
  - TLS 1.2+ obrigatório
302
304
  ```
@@ -557,7 +559,7 @@ resource "managed_postgres_tenant" "example" {
557
559
  ```
558
560
  ✅ Alta Disponibilidade
559
561
  - Replicação PostgreSQL (Patroni)
560
- - HAProxy redundante (Keepalived)
562
+ - Traefik redundante (Keepalived)
561
563
  - Auto-failover
562
564
 
563
565
  ✅ Backup Avançado
@@ -30,12 +30,12 @@
30
30
 
31
31
  **Problema**: Zero redundância
32
32
  - 1 container PostgreSQL = 1 ponto de falha
33
- - HAProxy sem redundância
33
+ - Traefik sem redundância
34
34
  - Sem failover automático
35
35
 
36
36
  **Solução**:
37
37
  - Replicação PostgreSQL (Patroni/pg_auto_failover)
38
- - HAProxy redundante com Keepalived
38
+ - Traefik redundante com Keepalived
39
39
  - Auto-failover configurado
40
40
 
41
41
  ### 3. Backup e Recovery - ESSENCIAL
@@ -60,14 +60,13 @@
60
60
 
61
61
  ### 5. Segurança
62
62
 
63
- **Problemas**:
64
- - Senhas em texto plano
65
- - Sem SSL/TLS
66
- - Sem secret management
63
+ **Status Atual** ✅:
64
+ - TLS/SSL implementado via Traefik v3
65
+ - Roteamento SNI (hostname-based)
66
+ - Certificado wildcard
67
67
 
68
- **Solução**:
68
+ **Melhorias Necessárias**:
69
69
  - HashiCorp Vault para secrets
70
- - SSL/TLS obrigatório
71
70
  - Rotação automática de senhas
72
71
  - Network policies
73
72
 
@@ -103,7 +102,8 @@
103
102
  ✅ API REST
104
103
  ✅ Monitoring básico (Prometheus + Grafana)
105
104
  ✅ Secrets management básico
106
- ✅ SSL/TLS
105
+ ✅ SSL/TLS via Traefik v3 (IMPLEMENTADO)
106
+ ✅ SNI-based routing (IMPLEMENTADO)
107
107
  ```
108
108
 
109
109
  **Custo estimado**: $5K-15K (infraestrutura + desenvolvimento)
package/docs/PROJECT.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ### What It Is
6
6
 
7
- A **dynamic PostgreSQL multi-tenant management system** that provides complete database isolation by creating dedicated PostgreSQL instances per tenant. The system uses HAProxy with custom PostgreSQL protocol parsing to route connections intelligently while maintaining complete tenant isolation at the database server level.
7
+ A **dynamic PostgreSQL multi-tenant management system** that provides complete database isolation by creating dedicated PostgreSQL instances per tenant. The system uses **Traefik v3** with native PostgreSQL STARTTLS support to route connections based on SNI (Server Name Indication), enabling hostname-based tenant routing with TLS security.
8
8
 
9
9
  ### Core Concept
10
10
 
@@ -22,15 +22,16 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
22
22
  - Automatic volume and network configuration
23
23
  - Custom initialization scripts per tenant
24
24
 
25
- 2. **Intelligent Routing**
26
- - HAProxy parses PostgreSQL protocol to extract username
27
- - Routes connections to correct tenant backend automatically
25
+ 2. **SNI-Based Routing**
26
+ - Traefik v3 handles PostgreSQL STARTTLS protocol
27
+ - Routes connections based on hostname (SNI)
28
28
  - Single external port (5432) for all tenants
29
+ - TLS/SSL encryption required
29
30
 
30
31
  3. **Access Control**
31
32
  - Per-tenant external access enable/disable
32
- - Secure by default (access disabled on creation)
33
33
  - Runtime access control without service restart
34
+ - Dynamic Traefik configuration updates
34
35
 
35
36
  4. **Complete Isolation**
36
37
  - Separate Docker containers per tenant
@@ -39,49 +40,50 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
39
40
  - No shared processes or memory
40
41
 
41
42
  5. **Zero-Downtime Operations**
42
- - Graceful HAProxy reloads
43
+ - Traefik dynamic configuration (no restarts needed)
43
44
  - Independent tenant management
44
45
  - No impact on other tenants during operations
45
46
 
46
47
  ## Architecture
47
48
 
48
49
  ```
49
- ┌─────────────────────────────────────────────────────────┐
50
- External Access
51
- (localhost:5432)
52
- └──────────────────────┬──────────────────────────────────┘
53
-
54
-
55
- ┌─────────────────────────────────────────────────────────┐
56
- HAProxy Proxy
57
- ┌──────────────────────────────────────────────────┐
58
- │ │ Frontend: postgres_frontend
59
- │ │ - Listens on port 5432
60
- │ │ - Parses PostgreSQL protocol (Lua script)
61
- │ │ - Extracts username from startup packet
62
- │ │ - Checks tenant-access.json for permissions
63
- └──────────────────────────────────────────────────┘
64
- └──────────────────────┬──────────────────────────────────┘
65
-
66
- ┌──────────────┼──────────────┐
67
-
68
-
69
- ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
70
- Backend │Backend │Backend
71
- pgs_tenant1 pgs_tenant2 pgs_tenant3
72
- └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
73
- │ │ │
74
- ▼ ▼ ▼
75
- ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
76
- │ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │
77
- Container 1 Container 2 Container 3
78
- │ │ │ │
79
- Port: 5432 Port: 5432 Port: 5432 │
80
- (internal) (internal) (internal)
81
- │ │ │ │
82
- Volume: Volume: Volume:
83
- pgdata_1 pgdata_2 pgdata_3
84
- └──────────────┘ └──────────────┘ └──────────────┘
50
+ ┌─────────────────────────────────────────────────────────────────┐
51
+ External Access
52
+ (tenant-id.pgs.domain.com:5432 + TLS/SNI)
53
+ └───────────────────────────┬─────────────────────────────────────┘
54
+
55
+
56
+ ┌─────────────────────────────────────────────────────────────────┐
57
+ Traefik v3 Proxy
58
+ ┌───────────────────────────────────────────────────────────┐
59
+ │ │ EntryPoint: postgres (port 5432)
60
+ │ │ - TLS termination with wildcard certificate
61
+ │ │ - PostgreSQL STARTTLS protocol support
62
+ │ │ - SNI-based routing (HostSNI rule)
63
+ │ │ - Dynamic configuration via dynamic.yml
64
+ └───────────────────────────────────────────────────────────┘
65
+ └───────────────────────────┬─────────────────────────────────────┘
66
+
67
+ ┌──────────────────┼──────────────────┐
68
+
69
+
70
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
71
+ TCP Router TCP Router TCP Router
72
+ tenant1 tenant2 tenant3
73
+ │ HostSNI() │ │ HostSNI() │ │ HostSNI() │
74
+ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
75
+ │ │ │
76
+ ▼ ▼ ▼
77
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
78
+ PostgreSQL PostgreSQL PostgreSQL
79
+ Container 1 Container 2 Container 3
80
+ │ │
81
+ Port: 5432 Port: 5432 Port: 5432
82
+ (internal) (internal) (internal)
83
+
84
+ Volume: Volume: Volume:
85
+ pgdata_1 │ │ pgdata_2 │ │ pgdata_3 │
86
+ └──────────────┘ └──────────────┘ └──────────────┘
85
87
  ```
86
88
 
87
89
  ## Technical Implementation
@@ -91,37 +93,88 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
91
93
  1. **Manager Script (manager.js)**
92
94
  - Node.js CLI tool for tenant lifecycle management
93
95
  - Dynamically generates docker-compose.yml entries
94
- - Manages HAProxy configuration
96
+ - Manages Traefik dynamic configuration (dynamic.yml)
95
97
  - Controls tenant access permissions
96
98
 
97
- 2. **HAProxy Reverse Proxy**
98
- - TCP-level load balancer and router
99
- - Custom Lua script for PostgreSQL protocol parsing
100
- - Routes based on extracted username
101
- - Per-tenant access control
99
+ 2. **Traefik v3 Reverse Proxy**
100
+ - TCP-level routing with TLS termination
101
+ - Native PostgreSQL STARTTLS protocol support
102
+ - SNI-based routing via HostSNI() rule
103
+ - Dynamic configuration without restarts
102
104
 
103
- 3. **PostgreSQL Protocol Parser (pg-route.lua)**
104
- - Parses binary PostgreSQL startup packet
105
- - Extracts username and connection parameters
106
- - Handles SSL negotiation
107
- - Enforces access control policies
108
-
109
- 4. **Docker Infrastructure**
105
+ 3. **Docker Infrastructure**
110
106
  - Separate container per tenant
111
107
  - Bridge network for internal communication
112
108
  - Persistent volumes for data
113
109
  - Isolated execution environments
114
110
 
111
+ ### Why Traefik v3 for PostgreSQL?
112
+
113
+ PostgreSQL uses a non-standard TLS negotiation called STARTTLS:
114
+
115
+ ```
116
+ Standard TLS (HTTPS):
117
+ Client → TLS ClientHello (with SNI) → Server
118
+
119
+ PostgreSQL STARTTLS:
120
+ Client → SSLRequest (PG protocol) → Server
121
+ Server → 'S' (SSL supported) → Client
122
+ Client → TLS ClientHello (with SNI) → Server
123
+ ```
124
+
125
+ Most SNI proxies expect TLS ClientHello as the first packet. PostgreSQL sends its own protocol first, then TLS.
126
+
127
+ **Traefik v3** is one of the few proxies that natively understands this PostgreSQL-specific flow:
128
+ - Detects PostgreSQL SSLRequest
129
+ - Responds with 'S'
130
+ - Captures TLS ClientHello with SNI
131
+ - Routes based on hostname
132
+
115
133
  ### Connection Flow
116
134
 
117
- 1. Client connects to `localhost:5432` with username `tenant_id`
118
- 2. HAProxy receives connection and invokes Lua script
119
- 3. Script parses PostgreSQL startup packet and extracts username
120
- 4. Script checks `tenant-access.json` for permission
121
- 5. If allowed, routes to backend `pgs_{tenant_id}`
122
- 6. Backend forwards to PostgreSQL container on internal network
135
+ 1. Client connects to `tenant-id.pgs.domain.com:5432` with `sslmode=require`
136
+ 2. Traefik receives connection and detects PostgreSQL SSLRequest
137
+ 3. Traefik responds 'S' (SSL supported)
138
+ 4. Client sends TLS ClientHello with SNI (hostname)
139
+ 5. Traefik extracts SNI and matches against configured routers in dynamic.yml
140
+ 6. If tenant has access enabled, routes to backend `pgs_{tenant_id}:5432`
123
141
  7. Connection established with complete isolation
124
142
 
143
+ ### Configuration Files
144
+
145
+ **traefik.yml** (Static Configuration):
146
+ ```yaml
147
+ entryPoints:
148
+ postgres:
149
+ address: ":5432"
150
+
151
+ providers:
152
+ file:
153
+ filename: /etc/traefik/dynamic.yml
154
+ watch: true
155
+ ```
156
+
157
+ **dynamic.yml** (Dynamic Configuration - Auto-generated):
158
+ ```yaml
159
+ tcp:
160
+ routers:
161
+ router_tenant1:
162
+ entryPoints:
163
+ - postgres
164
+ rule: "HostSNI(`tenant1-abc123.pgs.domain.com`)"
165
+ service: svc_tenant1
166
+ tls: {}
167
+ services:
168
+ svc_tenant1:
169
+ loadBalancer:
170
+ servers:
171
+ - address: "pgs_tenant1-abc123:5432"
172
+ tls:
173
+ certificates:
174
+ - certFile: /etc/traefik/certs/fullchain.pem
175
+ keyFile: /etc/traefik/certs/privkey.pem
176
+ ```
177
+
125
178
  ## Comparison with Similar Solutions
126
179
 
127
180
  ### Shared Database Architecture
@@ -152,7 +205,7 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
152
205
  - **Difference**: Shards data across nodes; this creates separate instances per tenant
153
206
  - **Use Case**: Horizontal scaling vs. tenant isolation
154
207
 
155
- #### 3. **Patroni + HAProxy**
208
+ #### 3. **Patroni + Traefik/HAProxy**
156
209
  - **Purpose**: High availability and load balancing
157
210
  - **Difference**: Replicates single database; this creates isolated instances
158
211
  - **Use Case**: HA for single database vs. multi-tenant isolation
@@ -173,18 +226,18 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
173
226
  - Not just database or schema separation
174
227
  - Complete process and memory isolation
175
228
 
176
- 2. **Dynamic provisioning with single external port**
229
+ 2. **SNI-based routing with single external port**
177
230
  - No need for port management
178
- - Automatic routing based on connection parameters
231
+ - Automatic routing based on hostname
232
+ - TLS/SSL security by default
179
233
 
180
- 3. **Protocol-aware routing**
181
- - Parses PostgreSQL binary protocol
182
- - Routes before connection completion
183
- - Handles SSL negotiation
234
+ 3. **PostgreSQL STARTTLS support**
235
+ - Traefik v3 handles non-standard PG protocol
236
+ - Proper SNI extraction after PG handshake
184
237
 
185
238
  4. **Runtime access control**
186
239
  - Enable/disable tenant access without restart
187
- - No downtime for access changes
240
+ - Dynamic Traefik configuration
188
241
 
189
242
  5. **Docker-native architecture**
190
243
  - Leverages container isolation
@@ -211,9 +264,9 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
211
264
  ## Advantages
212
265
 
213
266
  ✅ **Maximum Isolation**: Complete process and data separation
214
- ✅ **Security**: Zero risk of cross-tenant data access
267
+ ✅ **Security**: Zero risk of cross-tenant data access + TLS encryption
215
268
  ✅ **Flexibility**: Independent scaling and management per tenant
216
- ✅ **Simplicity**: Single external port, automatic routing
269
+ ✅ **Simplicity**: Single external port, automatic SNI routing
217
270
  ✅ **Compliance**: Easier to meet regulatory requirements
218
271
  ✅ **Debugging**: Isolated environments simplify troubleshooting
219
272
 
@@ -223,15 +276,16 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
223
276
  ⚠️ **Management Overhead**: More containers to manage
224
277
  ⚠️ **Scaling Limits**: Practical limit on number of tenants per host
225
278
  ⚠️ **Backup Complexity**: Need to backup multiple instances
279
+ ⚠️ **SSL Required**: Clients must support TLS with SNI
226
280
 
227
281
  ## Technology Stack
228
282
 
229
283
  - **Runtime**: Node.js (ES Modules)
230
284
  - **Container Orchestration**: Docker Compose
231
- - **Reverse Proxy**: HAProxy with Lua scripting
285
+ - **Reverse Proxy**: Traefik v3 (PostgreSQL STARTTLS + SNI routing)
232
286
  - **Database**: PostgreSQL 18+
233
- - **Protocol Parsing**: Custom Lua script
234
- - **Configuration**: YAML (docker-compose.yml), JSON (tenant-access.json)
287
+ - **TLS**: Wildcard SSL certificate
288
+ - **Configuration**: YAML (docker-compose.yml, traefik.yml, dynamic.yml), JSON (tenant-access.json)
235
289
 
236
290
  ## Future Enhancements
237
291
 
@@ -242,9 +296,8 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
242
296
  - [ ] Tenant migration tools
243
297
  - [ ] Kubernetes support
244
298
  - [ ] Connection pooling per tenant
245
- - [ ] SSL/TLS termination
299
+ - [ ] Web dashboard
246
300
 
247
301
  ## License & Status
248
302
 
249
- This is a custom solution built for specific multi-tenant requirements. It combines open-source tools (HAProxy, PostgreSQL, Docker) with custom routing logic to achieve instance-per-tenant isolation with intelligent connection routing.
250
-
303
+ This is a custom solution built for specific multi-tenant requirements. It combines open-source tools (Traefik v3, PostgreSQL, Docker) with SNI-based routing to achieve instance-per-tenant isolation with intelligent connection routing.
package/manager.js CHANGED
@@ -62,7 +62,7 @@ function removeTenantAccess(tenantId) {
62
62
 
63
63
  // ==================== Traefik Configuration ====================
64
64
 
65
- function generateTraefikDynamicConfig() {
65
+ function generateTraefikDynamicConfig(tenantsManifests = {}) {
66
66
  console.debug('Generating Traefik dynamic config')
67
67
  const doc = loadCompose();
68
68
  const access = loadTenantAccess();
@@ -75,6 +75,7 @@ function generateTraefikDynamicConfig() {
75
75
  const dynamicConfig = {
76
76
  tcp: {
77
77
  routers: {},
78
+ middlewares: {},
78
79
  services: {}
79
80
  },
80
81
  tls: {
@@ -96,15 +97,37 @@ function generateTraefikDynamicConfig() {
96
97
  }
97
98
 
98
99
  const routerName = `router_${tenantId}`.replace(/-/g, '_');
100
+ const middlewareName = `ipwhitelist_${tenantId}`.replace(/-/g, '_');
99
101
  const svcName = `svc_${tenantId}`.replace(/-/g, '_');
100
102
 
101
- dynamicConfig.tcp.routers[routerName] = {
103
+ const router = {
102
104
  entryPoints: ['postgres'],
103
- rule: `HostSNI(\`${tenantId}.pgs.us-central-1.sysnee.com\`)`,
105
+ rule: `HostSNI(\`${tenantId}.pgs.br-sp-1.sysnee.com\`)`,
104
106
  service: svcName,
105
107
  tls: {}
106
108
  };
107
109
 
110
+ const manifest = tenantsManifests[tenantId];
111
+ if (manifest && manifest.firewall && manifest.firewall.rules) {
112
+ const sourceRanges = manifest.firewall.rules
113
+ .filter(rule => rule.type === 'allow')
114
+ .map(rule => rule.source);
115
+
116
+ if (sourceRanges.length > 0) {
117
+ router.middlewares = [middlewareName];
118
+
119
+ if (!dynamicConfig.tcp.middlewares[middlewareName]) {
120
+ dynamicConfig.tcp.middlewares[middlewareName] = {
121
+ ipAllowList: {
122
+ sourceRange: sourceRanges
123
+ }
124
+ };
125
+ }
126
+ }
127
+ }
128
+
129
+ dynamicConfig.tcp.routers[routerName] = router;
130
+
108
131
  dynamicConfig.tcp.services[svcName] = {
109
132
  loadBalancer: {
110
133
  servers: [
@@ -209,11 +232,23 @@ function ensureNetwork(doc) {
209
232
  }
210
233
 
211
234
  function initialSetup() {
235
+ installDocker()
212
236
  createInitialFiles()
213
237
  ensureDockerPrivilegies()
214
238
  console.log('All ready!')
215
239
  }
216
240
 
241
+ function installDocker() {
242
+ console.log('Installing Docker...')
243
+ execSync('curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh', { stdio: 'inherit' })
244
+ execSync('rm get-docker.sh')
245
+ console.log('Docker installed successfully')
246
+
247
+ console.log('Installing Docker Compose plugin...')
248
+ execSync('sudo apt-get install -y docker-compose-plugin', { stdio: 'inherit' })
249
+ console.log('Docker Compose plugin installed successfully')
250
+ }
251
+
217
252
  function ensureDockerPrivilegies() {
218
253
  execSync('sudo usermod -aG docker $USER && newgrp docker')
219
254
  writeFileSync(SETUP_STATUS_PATH, 'container:ok')
@@ -282,7 +317,7 @@ function checkSetupStatus() {
282
317
  }
283
318
 
284
319
  function createTenant(tenantId, options = {}) {
285
- const { version = '18', password, limits = DEFAULT_LIMITS } = options;
320
+ const { version = '18', password, limits = DEFAULT_LIMITS, manifest = null } = options;
286
321
  const doc = loadCompose();
287
322
 
288
323
  if (doc.services && doc.services[`pgs_${tenantId}`]) {
@@ -311,19 +346,17 @@ function createTenant(tenantId, options = {}) {
311
346
 
312
347
  saveCompose(doc);
313
348
 
314
- // Add tenant to access control
315
349
  setTenantAccess(tenantId, true);
316
350
 
317
- // Regenerate Traefik config and update service
318
- generateTraefikDynamicConfig();
351
+ const manifests = manifest ? { [tenantId]: manifest } : {};
352
+ generateTraefikDynamicConfig(manifests);
319
353
  updateTraefikService();
320
354
 
321
- // start the tenant
322
355
  executeCommand(`docker compose up -d ${serviceName}`.trim());
323
356
 
324
357
  console.log(`✓ Created tenant: ${tenantId}`);
325
358
  console.log(` Service: ${serviceName}`);
326
- console.log(` Host: ${tenantId}.pgs.us-central-1.sysnee.com`);
359
+ console.log(` Host: ${tenantId}.pgs.br-sp-1.sysnee.com`);
327
360
  console.log(` Port: 5432`);
328
361
  console.log(` Database: ${tenantId}`);
329
362
  console.log(` Password: ${password}`);
@@ -364,13 +397,13 @@ function listTenants() {
364
397
  console.log('─'.repeat(90));
365
398
  tenants.forEach(t => {
366
399
  const accessStr = t.access ? '✓ enabled' : '✗ disabled';
367
- const host = `${t.tenantId}.pgs.us-central-1.sysnee.com`;
400
+ const host = `${t.tenantId}.pgs.br-sp-1.sysnee.com`;
368
401
  console.log(` ${t.tenantId.padEnd(25)} ${host.padEnd(45)} ${accessStr}`);
369
402
  });
370
403
  console.log('─'.repeat(90));
371
404
  }
372
405
 
373
- function enableTenantAccess(tenantId) {
406
+ function enableTenantAccess(tenantId, manifests = {}) {
374
407
  const doc = loadCompose();
375
408
  const serviceName = `pgs_${tenantId}`;
376
409
 
@@ -379,12 +412,12 @@ function enableTenantAccess(tenantId) {
379
412
  }
380
413
 
381
414
  setTenantAccess(tenantId, true);
382
- generateTraefikDynamicConfig();
415
+ generateTraefikDynamicConfig(manifests);
383
416
 
384
417
  console.log(`✓ External access enabled for tenant: ${tenantId}`);
385
418
  }
386
419
 
387
- function disableTenantAccess(tenantId) {
420
+ function disableTenantAccess(tenantId, manifests = {}) {
388
421
  const doc = loadCompose();
389
422
  const serviceName = `pgs_${tenantId}`;
390
423
 
@@ -393,12 +426,12 @@ function disableTenantAccess(tenantId) {
393
426
  }
394
427
 
395
428
  setTenantAccess(tenantId, false);
396
- generateTraefikDynamicConfig();
429
+ generateTraefikDynamicConfig(manifests);
397
430
 
398
431
  console.log(`✓ External access disabled for tenant: ${tenantId}`);
399
432
  }
400
433
 
401
- function removeTenant(tenantId) {
434
+ function removeTenant(tenantId, manifests = {}) {
402
435
  const doc = loadCompose();
403
436
  const serviceName = `pgs_${tenantId}`;
404
437
 
@@ -421,7 +454,7 @@ function removeTenant(tenantId) {
421
454
  saveCompose(doc);
422
455
 
423
456
  removeTenantAccess(tenantId);
424
- generateTraefikDynamicConfig();
457
+ generateTraefikDynamicConfig(manifests);
425
458
  updateTraefikService();
426
459
 
427
460
  console.log(`✓ Removed tenant: ${tenantId}`);
@@ -478,7 +511,8 @@ program
478
511
  const tenantOptions = {
479
512
  password: null,
480
513
  version: null,
481
- limits: null
514
+ limits: null,
515
+ manifest: null
482
516
  }
483
517
 
484
518
  if (opts.file) {
@@ -492,6 +526,7 @@ program
492
526
 
493
527
  tenantOptions.limits = optsFromManifest.shared_limits
494
528
  tenantOptions.version = optsFromManifest.version
529
+ tenantOptions.manifest = optsFromManifest
495
530
  } else {
496
531
  tenantOptions.version = opts.version;
497
532
  tenantOptions.limits = {
@@ -526,10 +561,17 @@ program
526
561
  .command('remove')
527
562
  .description('Remove a tenant instance')
528
563
  .argument('<tenant-id>', 'Tenant identifier')
529
- .action((tenantId) => {
564
+ .option('-f, --file <file>', 'Manifest file path (optional, for firewall rules)')
565
+ .action((tenantId, opts) => {
530
566
  checkSetupStatus()
531
567
  try {
532
- removeTenant(tenantId);
568
+ const manifests = {};
569
+ if (opts.file) {
570
+ const manifestFilePath = path.resolve(opts.file)
571
+ const manifest = JSON.parse(readFileSync(manifestFilePath))
572
+ manifests[tenantId] = manifest;
573
+ }
574
+ removeTenant(tenantId, manifests);
533
575
  } catch (error) {
534
576
  console.error(`Error: ${error.message}`);
535
577
  process.exit(1);
@@ -560,10 +602,17 @@ program
560
602
  .command('enable-access')
561
603
  .description('Enable external access for a tenant')
562
604
  .argument('<tenant-id>', 'Tenant identifier')
563
- .action((tenantId) => {
605
+ .option('-f, --file <file>', 'Manifest file path (optional, for firewall rules)')
606
+ .action((tenantId, opts) => {
564
607
  checkSetupStatus()
565
608
  try {
566
- enableTenantAccess(tenantId);
609
+ const manifests = {};
610
+ if (opts.file) {
611
+ const manifestFilePath = path.resolve(opts.file)
612
+ const manifest = JSON.parse(readFileSync(manifestFilePath))
613
+ manifests[tenantId] = manifest;
614
+ }
615
+ enableTenantAccess(tenantId, manifests);
567
616
  } catch (error) {
568
617
  console.error(`Error: ${error.message}`);
569
618
  process.exit(1);
@@ -574,10 +623,17 @@ program
574
623
  .command('disable-access')
575
624
  .description('Disable external access for a tenant')
576
625
  .argument('<tenant-id>', 'Tenant identifier')
577
- .action((tenantId) => {
626
+ .option('-f, --file <file>', 'Manifest file path (optional, for firewall rules)')
627
+ .action((tenantId, opts) => {
578
628
  checkSetupStatus()
579
629
  try {
580
- disableTenantAccess(tenantId);
630
+ const manifests = {};
631
+ if (opts.file) {
632
+ const manifestFilePath = path.resolve(opts.file)
633
+ const manifest = JSON.parse(readFileSync(manifestFilePath))
634
+ manifests[tenantId] = manifest;
635
+ }
636
+ disableTenantAccess(tenantId, manifests);
581
637
  } catch (error) {
582
638
  console.error(`Error: ${error.message}`);
583
639
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sysnee/pgs",
3
- "version": "0.1.7-rc.5",
3
+ "version": "0.1.7-rc.7",
4
4
  "description": "Dynamic PostgreSQL service instance manager",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "stop": "node manager.js stop",
15
15
  "enable-access": "node manager.js enable-access",
16
16
  "disable-access": "node manager.js disable-access",
17
- "reload-haproxy": "node manager.js reload-haproxy"
17
+ "setup": "node manager.js setup"
18
18
  },
19
19
  "dependencies": {
20
20
  "js-yaml": "^4.1.0",