pg-r2-backup 1.0.4__tar.gz

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.
@@ -0,0 +1,305 @@
1
+ Metadata-Version: 2.4
2
+ Name: pg-r2-backup
3
+ Version: 1.0.4
4
+ Summary: PostgreSQL backup tool for Cloudflare R2 (S3 Compatuble)
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: boto3
8
+ Requires-Dist: python-dotenv
9
+ Requires-Dist: schedule
10
+ Requires-Dist: py7zr
11
+
12
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
13
+ ![Python](https://img.shields.io/badge/python-3.12-blue)
14
+ ![Storage](https://img.shields.io/badge/storage-S3--compatible-orange)
15
+ ![Database](https://img.shields.io/badge/database-PostgreSQL-336791)
16
+ ![Deploy](https://img.shields.io/badge/deploy-Railway-purple)
17
+ ![Docker](https://img.shields.io/badge/docker-supported-blue)
18
+
19
+ # Postgres-to-R2 Backup (S3-Compatible)
20
+
21
+ A lightweight automation service that creates scheduled PostgreSQL backups and securely uploads them to **S3-compatible object storage**
22
+ such as **Cloudflare R2, AWS S3, Wasabi, Backblaze B2, or MinIO**.
23
+ Designed specifically as a **Railway deployment template**, with built-in support for Docker and cron scheduling.
24
+
25
+ ---
26
+
27
+ ## โœจ Features
28
+
29
+ - ๐Ÿ“ฆ **Automated Backups** โ€” scheduled daily or hourly PostgreSQL backups
30
+ - ๐Ÿ” **Optional Encryption** โ€” gzip compression or 7z encryption with password
31
+ - โ˜๏ธ **Cloudflare R2 Integration** โ€” seamless S3-compatible storage support
32
+ - ๐Ÿงน **Retention Policy** โ€” automatically delete old backups
33
+ - ๐Ÿ”— **Flexible Database URLs** โ€” supports private and public PostgreSQL connection URLs
34
+ - โšก **Optimized Performance** โ€” parallel pg_dump and multipart S3 uploads
35
+ - ๐Ÿณ **Docker Ready** โ€” portable, lightweight container
36
+ - ๐Ÿš€ **Railway Template First** โ€” no fork required for normal usage
37
+ - ๐Ÿชฃ **S3-Compatible Storage** โ€” works with R2, AWS S3, Wasabi, B2, MinIO
38
+
39
+ ---
40
+
41
+ ## ๐Ÿš€ Deployment on Railway
42
+
43
+ 1. Click the **Deploy on Railway** button below
44
+ 2. Railway will create a new project using the latest version of this repository
45
+ 3. Add the required environment variables in the Railway dashboard
46
+ 4. (Optional) Configure a cron job for your desired backup schedule
47
+
48
+ [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/postgres-to-r2-backup?referralCode=nIQTyp&utm_medium=integration&utm_source=template&utm_campaign=generic)
49
+
50
+ ---
51
+
52
+ ## ๐Ÿ”ง Environment Variables (S3-Compatible)
53
+
54
+ ```env
55
+ DATABASE_URL= # PostgreSQL database URL (private)
56
+ DATABASE_PUBLIC_URL= # Public PostgreSQL URL (optional)
57
+ USE_PUBLIC_URL=false # Set true to use DATABASE_PUBLIC_URL
58
+
59
+ DUMP_FORMAT=dump # sql | plain | dump | custom | tar
60
+ FILENAME_PREFIX=backup # Backup filename prefix
61
+ MAX_BACKUPS=7 # Number of backups to retain
62
+
63
+ R2_ENDPOINT= # S3 endpoint URL
64
+ R2_BUCKET_NAME= # Bucket name
65
+ R2_ACCESS_KEY= # Access key
66
+ R2_SECRET_KEY= # Secret key
67
+ S3_REGION=us-east-1 # Required for AWS S3 (ignored by R2/MinIO)
68
+
69
+ BACKUP_PASSWORD= # Optional: enables 7z encryption
70
+ BACKUP_TIME=00:00 # Daily backup time (UTC, HH:MM)
71
+ ```
72
+
73
+ > Variable names use `R2_*` for historical reasons, but **any S3-compatible provider** can be used by changing the endpoint and credentials.
74
+ > For AWS S3 users: ensure `S3_REGION` matches your bucketโ€™s region.
75
+
76
+ ---
77
+
78
+ ## โ˜๏ธ Supported S3-Compatible Providers
79
+
80
+ This project uses the **standard AWS S3 API via boto3**, and works with:
81
+
82
+ - Cloudflare R2 (recommended)
83
+ - AWS S3
84
+ - Wasabi
85
+ - Backblaze B2 (S3 API)
86
+ - MinIO (self-hosted)
87
+
88
+ ### Example Endpoints
89
+
90
+ | Provider | Endpoint Example |
91
+ |--------|------------------|
92
+ | Cloudflare R2 | `https://<accountid>.r2.cloudflarestorage.com` |
93
+ | AWS S3 | `https://s3.amazonaws.com` |
94
+ | Wasabi | `https://s3.wasabisys.com` |
95
+ | Backblaze B2 | `https://s3.us-west-004.backblazeb2.com` |
96
+ | MinIO | `http://localhost:9000` |
97
+
98
+ ---
99
+
100
+ ## โฐ Railway Cron Jobs
101
+
102
+ You can configure the backup schedule using **Railway Cron Jobs**:
103
+
104
+ 1. Open your Railway project
105
+ 2. Go to **Deployments โ†’ Cron**
106
+ 3. Add a cron job targeting this service
107
+
108
+ ### Common Cron Expressions
109
+
110
+ | Schedule | Cron Expression | Description |
111
+ |--------|----------------|------------|
112
+ | Hourly | `0 * * * *` | Every hour |
113
+ | Daily | `0 0 * * *` | Once per day (UTC midnight) |
114
+ | Twice Daily | `0 */12 * * *` | Every 12 hours |
115
+ | Weekly | `0 0 * * 0` | Every Sunday |
116
+ | Monthly | `0 0 1 * *` | First day of the month |
117
+
118
+ **Tips**
119
+ - All cron times are **UTC**
120
+ - Use https://crontab.guru to validate expressions
121
+ - Adjust `MAX_BACKUPS` to match your schedule
122
+
123
+ > If you use Railway Cron Jobs, the service will start once per execution.
124
+ > In this case, the internal scheduler is ignored after startup.
125
+
126
+ ---
127
+
128
+ ## ๐Ÿ–ฅ๏ธ Running Locally or on Other Platforms
129
+
130
+ It can run on **any platform** that supports:
131
+ - Python 3.9+
132
+ - `pg_dump` (PostgreSQL client tools)
133
+ - Environment variables
134
+ - Long-running background processes or cron
135
+
136
+ > Docker images use **Python 3.12** by default.
137
+ > Local execution supports **Python 3.9+**.
138
+
139
+ ### Supported Environments
140
+
141
+ - Local machine (Linux / macOS / Windows*)
142
+ - VPS (Netcup, Hetzner, DigitalOcean, etc.)
143
+ - Docker containers
144
+ - Other PaaS providers (Heroku, Fly.io, Render, etc.)
145
+
146
+ > *Windows is supported when `pg_dump` is installed and available in PATH.*
147
+
148
+ ### Local Requirements
149
+
150
+ - Python 3.9+
151
+ - PostgreSQL client tools (`pg_dump`)
152
+ - pip
153
+
154
+ ### Run Manually (Local)
155
+
156
+ ```bash
157
+ pip install -r requirements.txt
158
+ python main.py
159
+ ```
160
+
161
+ ### Run with Docker (Optional)
162
+
163
+ Build and run the image locally:
164
+
165
+ ```bash
166
+ docker build -t postgres-to-r2-backup .
167
+ docker run --env-file .env postgres-to-r2-backup
168
+ ```
169
+
170
+ > Ensure the container is allowed to run continuously when not using an external cron scheduler.
171
+
172
+ > All scheduling uses **UTC** by default (e.g. Malaysia UTC+8 โ†’ set `BACKUP_TIME=16:00` for midnight).
173
+
174
+ ### Run from Prebuilt Docker Image
175
+
176
+ If you downloaded a prebuilt Docker image archive (`.tar` or `.tar.gz`), you can run it without building locally:
177
+
178
+ ```bash
179
+ # Extract the archive (if compressed)
180
+ tar -xzf postgres-to-r2-backup_v1.0.4.tar.gz
181
+
182
+ # Load the image into Docker
183
+ docker load -i postgres-to-r2-backup_v1.0.4.tar
184
+
185
+ # Run the container
186
+ docker run --env-file .env postgres-to-r2-backup:v1.0.4
187
+ ```
188
+
189
+ > Prebuilt images are architecture-specific (amd64 / arm64).
190
+
191
+ ---
192
+
193
+ ## ๐Ÿงฐ Using the CLI (Global Installation)
194
+
195
+ This project can also be used as a standalone CLI tool, installable via pip, in addition to running as a Railway or Docker service.
196
+
197
+ ### Install via pip
198
+
199
+ ```bash
200
+ pip install pg-r2-backup
201
+ ```
202
+
203
+ ### Requirements
204
+
205
+ - Python 3.9+
206
+ - PostgreSQL client tools (`pg_dump`) installed and available in PATH
207
+
208
+ ### Quick Start (CLI)
209
+
210
+ ```bash
211
+ mkdir backups
212
+ cd backups
213
+
214
+ pg-r2-backup init # creates .env from .env.example
215
+ pg-r2-backup doctor # checks environment and dependencies
216
+ pg-r2-backup run # runs a backup immediately
217
+ ```
218
+
219
+ ### CLI Commands
220
+
221
+ ```bash
222
+ pg-r2-backup run # Run backup immediately
223
+ pg-r2-backup doctor # Check environment & dependencies
224
+ pg-r2-backup config show # Show current configuration
225
+ pg-r2-backup init # Create .env from .env.example
226
+ pg-r2-backup schedule # Show scheduling examples
227
+ pg-r2-backup --version
228
+ ```
229
+
230
+ ### Environment Variable Resolution (CLI)
231
+
232
+ When running via the CLI, environment variables are resolved in the following order:
233
+
234
+ 1. A `.env` file in the current working directory (or parent directory)
235
+ 2. System environment variables
236
+
237
+ This allows different folders to maintain separate backup configurations.
238
+
239
+ ### Scheduling Backups (CLI)
240
+
241
+ The CLI does not run a background scheduler. Use your operating system or platform scheduler instead.
242
+
243
+ **Linux / macOS (cron)**
244
+
245
+ ```bash
246
+ 0 0 * * * pg-r2-backup run
247
+ ```
248
+
249
+ **Windows (Task Scheduler)**
250
+
251
+ - Program: `pg-r2-backup`
252
+ - Arguments: `run`
253
+ - Start in: folder containing `.env` (working directory)
254
+
255
+ **Railway / Docker**
256
+
257
+ Use the platform's built-in scheduler (recommended).
258
+
259
+ ๐Ÿ’ก **Tip**
260
+ Run `pg-r2-backup schedule` at any time to see scheduling examples.
261
+
262
+ ---
263
+
264
+ ## ๐Ÿ” Security
265
+
266
+ - **Do not expose PostgreSQL directly to the public internet.**
267
+ If your database is not on a private network, use a secure tunnel instead.
268
+
269
+ - **Recommended: Cloudflare Tunnel**
270
+ When using a public database URL, it is strongly recommended to connect via a secure tunnel such as **Cloudflare Tunnel** rather than opening database ports.
271
+
272
+ - **Protect credentials**
273
+ Store all secrets (database URLs, R2 keys, encryption passwords) using environment variables.
274
+ Never commit `.env` files to version control.
275
+
276
+ - **Encrypted backups (optional)**
277
+ Set `BACKUP_PASSWORD` to enable encrypted backups using 7z before uploading to S3-compatible storage.
278
+
279
+ - **Least privilege access**
280
+ Use a PostgreSQL user with read-only access where possible, and restrict R2 credentials to the required bucket only.
281
+
282
+ ---
283
+
284
+ ## ๐Ÿ›  Development & Contributions
285
+
286
+ Fork this repository **only if you plan to**:
287
+
288
+ - Modify the backup logic
289
+ - Add features or integrations
290
+ - Submit pull requests
291
+ - Run locally for development
292
+
293
+ ---
294
+
295
+ ## โ“ FAQ
296
+
297
+ **Why only DATABASE_URL?**
298
+ This matches how most modern platforms expose PostgreSQL credentials.
299
+ Support for separate DB variables may be added if there is demand.
300
+
301
+ ## ๐Ÿ“œ License
302
+
303
+ This project is open source under the **MIT License**.
304
+
305
+ You are free to use, modify, and distribute it with attribution.
@@ -0,0 +1,294 @@
1
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
2
+ ![Python](https://img.shields.io/badge/python-3.12-blue)
3
+ ![Storage](https://img.shields.io/badge/storage-S3--compatible-orange)
4
+ ![Database](https://img.shields.io/badge/database-PostgreSQL-336791)
5
+ ![Deploy](https://img.shields.io/badge/deploy-Railway-purple)
6
+ ![Docker](https://img.shields.io/badge/docker-supported-blue)
7
+
8
+ # Postgres-to-R2 Backup (S3-Compatible)
9
+
10
+ A lightweight automation service that creates scheduled PostgreSQL backups and securely uploads them to **S3-compatible object storage**
11
+ such as **Cloudflare R2, AWS S3, Wasabi, Backblaze B2, or MinIO**.
12
+ Designed specifically as a **Railway deployment template**, with built-in support for Docker and cron scheduling.
13
+
14
+ ---
15
+
16
+ ## โœจ Features
17
+
18
+ - ๐Ÿ“ฆ **Automated Backups** โ€” scheduled daily or hourly PostgreSQL backups
19
+ - ๐Ÿ” **Optional Encryption** โ€” gzip compression or 7z encryption with password
20
+ - โ˜๏ธ **Cloudflare R2 Integration** โ€” seamless S3-compatible storage support
21
+ - ๐Ÿงน **Retention Policy** โ€” automatically delete old backups
22
+ - ๐Ÿ”— **Flexible Database URLs** โ€” supports private and public PostgreSQL connection URLs
23
+ - โšก **Optimized Performance** โ€” parallel pg_dump and multipart S3 uploads
24
+ - ๐Ÿณ **Docker Ready** โ€” portable, lightweight container
25
+ - ๐Ÿš€ **Railway Template First** โ€” no fork required for normal usage
26
+ - ๐Ÿชฃ **S3-Compatible Storage** โ€” works with R2, AWS S3, Wasabi, B2, MinIO
27
+
28
+ ---
29
+
30
+ ## ๐Ÿš€ Deployment on Railway
31
+
32
+ 1. Click the **Deploy on Railway** button below
33
+ 2. Railway will create a new project using the latest version of this repository
34
+ 3. Add the required environment variables in the Railway dashboard
35
+ 4. (Optional) Configure a cron job for your desired backup schedule
36
+
37
+ [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/postgres-to-r2-backup?referralCode=nIQTyp&utm_medium=integration&utm_source=template&utm_campaign=generic)
38
+
39
+ ---
40
+
41
+ ## ๐Ÿ”ง Environment Variables (S3-Compatible)
42
+
43
+ ```env
44
+ DATABASE_URL= # PostgreSQL database URL (private)
45
+ DATABASE_PUBLIC_URL= # Public PostgreSQL URL (optional)
46
+ USE_PUBLIC_URL=false # Set true to use DATABASE_PUBLIC_URL
47
+
48
+ DUMP_FORMAT=dump # sql | plain | dump | custom | tar
49
+ FILENAME_PREFIX=backup # Backup filename prefix
50
+ MAX_BACKUPS=7 # Number of backups to retain
51
+
52
+ R2_ENDPOINT= # S3 endpoint URL
53
+ R2_BUCKET_NAME= # Bucket name
54
+ R2_ACCESS_KEY= # Access key
55
+ R2_SECRET_KEY= # Secret key
56
+ S3_REGION=us-east-1 # Required for AWS S3 (ignored by R2/MinIO)
57
+
58
+ BACKUP_PASSWORD= # Optional: enables 7z encryption
59
+ BACKUP_TIME=00:00 # Daily backup time (UTC, HH:MM)
60
+ ```
61
+
62
+ > Variable names use `R2_*` for historical reasons, but **any S3-compatible provider** can be used by changing the endpoint and credentials.
63
+ > For AWS S3 users: ensure `S3_REGION` matches your bucketโ€™s region.
64
+
65
+ ---
66
+
67
+ ## โ˜๏ธ Supported S3-Compatible Providers
68
+
69
+ This project uses the **standard AWS S3 API via boto3**, and works with:
70
+
71
+ - Cloudflare R2 (recommended)
72
+ - AWS S3
73
+ - Wasabi
74
+ - Backblaze B2 (S3 API)
75
+ - MinIO (self-hosted)
76
+
77
+ ### Example Endpoints
78
+
79
+ | Provider | Endpoint Example |
80
+ |--------|------------------|
81
+ | Cloudflare R2 | `https://<accountid>.r2.cloudflarestorage.com` |
82
+ | AWS S3 | `https://s3.amazonaws.com` |
83
+ | Wasabi | `https://s3.wasabisys.com` |
84
+ | Backblaze B2 | `https://s3.us-west-004.backblazeb2.com` |
85
+ | MinIO | `http://localhost:9000` |
86
+
87
+ ---
88
+
89
+ ## โฐ Railway Cron Jobs
90
+
91
+ You can configure the backup schedule using **Railway Cron Jobs**:
92
+
93
+ 1. Open your Railway project
94
+ 2. Go to **Deployments โ†’ Cron**
95
+ 3. Add a cron job targeting this service
96
+
97
+ ### Common Cron Expressions
98
+
99
+ | Schedule | Cron Expression | Description |
100
+ |--------|----------------|------------|
101
+ | Hourly | `0 * * * *` | Every hour |
102
+ | Daily | `0 0 * * *` | Once per day (UTC midnight) |
103
+ | Twice Daily | `0 */12 * * *` | Every 12 hours |
104
+ | Weekly | `0 0 * * 0` | Every Sunday |
105
+ | Monthly | `0 0 1 * *` | First day of the month |
106
+
107
+ **Tips**
108
+ - All cron times are **UTC**
109
+ - Use https://crontab.guru to validate expressions
110
+ - Adjust `MAX_BACKUPS` to match your schedule
111
+
112
+ > If you use Railway Cron Jobs, the service will start once per execution.
113
+ > In this case, the internal scheduler is ignored after startup.
114
+
115
+ ---
116
+
117
+ ## ๐Ÿ–ฅ๏ธ Running Locally or on Other Platforms
118
+
119
+ It can run on **any platform** that supports:
120
+ - Python 3.9+
121
+ - `pg_dump` (PostgreSQL client tools)
122
+ - Environment variables
123
+ - Long-running background processes or cron
124
+
125
+ > Docker images use **Python 3.12** by default.
126
+ > Local execution supports **Python 3.9+**.
127
+
128
+ ### Supported Environments
129
+
130
+ - Local machine (Linux / macOS / Windows*)
131
+ - VPS (Netcup, Hetzner, DigitalOcean, etc.)
132
+ - Docker containers
133
+ - Other PaaS providers (Heroku, Fly.io, Render, etc.)
134
+
135
+ > *Windows is supported when `pg_dump` is installed and available in PATH.*
136
+
137
+ ### Local Requirements
138
+
139
+ - Python 3.9+
140
+ - PostgreSQL client tools (`pg_dump`)
141
+ - pip
142
+
143
+ ### Run Manually (Local)
144
+
145
+ ```bash
146
+ pip install -r requirements.txt
147
+ python main.py
148
+ ```
149
+
150
+ ### Run with Docker (Optional)
151
+
152
+ Build and run the image locally:
153
+
154
+ ```bash
155
+ docker build -t postgres-to-r2-backup .
156
+ docker run --env-file .env postgres-to-r2-backup
157
+ ```
158
+
159
+ > Ensure the container is allowed to run continuously when not using an external cron scheduler.
160
+
161
+ > All scheduling uses **UTC** by default (e.g. Malaysia UTC+8 โ†’ set `BACKUP_TIME=16:00` for midnight).
162
+
163
+ ### Run from Prebuilt Docker Image
164
+
165
+ If you downloaded a prebuilt Docker image archive (`.tar` or `.tar.gz`), you can run it without building locally:
166
+
167
+ ```bash
168
+ # Extract the archive (if compressed)
169
+ tar -xzf postgres-to-r2-backup_v1.0.4.tar.gz
170
+
171
+ # Load the image into Docker
172
+ docker load -i postgres-to-r2-backup_v1.0.4.tar
173
+
174
+ # Run the container
175
+ docker run --env-file .env postgres-to-r2-backup:v1.0.4
176
+ ```
177
+
178
+ > Prebuilt images are architecture-specific (amd64 / arm64).
179
+
180
+ ---
181
+
182
+ ## ๐Ÿงฐ Using the CLI (Global Installation)
183
+
184
+ This project can also be used as a standalone CLI tool, installable via pip, in addition to running as a Railway or Docker service.
185
+
186
+ ### Install via pip
187
+
188
+ ```bash
189
+ pip install pg-r2-backup
190
+ ```
191
+
192
+ ### Requirements
193
+
194
+ - Python 3.9+
195
+ - PostgreSQL client tools (`pg_dump`) installed and available in PATH
196
+
197
+ ### Quick Start (CLI)
198
+
199
+ ```bash
200
+ mkdir backups
201
+ cd backups
202
+
203
+ pg-r2-backup init # creates .env from .env.example
204
+ pg-r2-backup doctor # checks environment and dependencies
205
+ pg-r2-backup run # runs a backup immediately
206
+ ```
207
+
208
+ ### CLI Commands
209
+
210
+ ```bash
211
+ pg-r2-backup run # Run backup immediately
212
+ pg-r2-backup doctor # Check environment & dependencies
213
+ pg-r2-backup config show # Show current configuration
214
+ pg-r2-backup init # Create .env from .env.example
215
+ pg-r2-backup schedule # Show scheduling examples
216
+ pg-r2-backup --version
217
+ ```
218
+
219
+ ### Environment Variable Resolution (CLI)
220
+
221
+ When running via the CLI, environment variables are resolved in the following order:
222
+
223
+ 1. A `.env` file in the current working directory (or parent directory)
224
+ 2. System environment variables
225
+
226
+ This allows different folders to maintain separate backup configurations.
227
+
228
+ ### Scheduling Backups (CLI)
229
+
230
+ The CLI does not run a background scheduler. Use your operating system or platform scheduler instead.
231
+
232
+ **Linux / macOS (cron)**
233
+
234
+ ```bash
235
+ 0 0 * * * pg-r2-backup run
236
+ ```
237
+
238
+ **Windows (Task Scheduler)**
239
+
240
+ - Program: `pg-r2-backup`
241
+ - Arguments: `run`
242
+ - Start in: folder containing `.env` (working directory)
243
+
244
+ **Railway / Docker**
245
+
246
+ Use the platform's built-in scheduler (recommended).
247
+
248
+ ๐Ÿ’ก **Tip**
249
+ Run `pg-r2-backup schedule` at any time to see scheduling examples.
250
+
251
+ ---
252
+
253
+ ## ๐Ÿ” Security
254
+
255
+ - **Do not expose PostgreSQL directly to the public internet.**
256
+ If your database is not on a private network, use a secure tunnel instead.
257
+
258
+ - **Recommended: Cloudflare Tunnel**
259
+ When using a public database URL, it is strongly recommended to connect via a secure tunnel such as **Cloudflare Tunnel** rather than opening database ports.
260
+
261
+ - **Protect credentials**
262
+ Store all secrets (database URLs, R2 keys, encryption passwords) using environment variables.
263
+ Never commit `.env` files to version control.
264
+
265
+ - **Encrypted backups (optional)**
266
+ Set `BACKUP_PASSWORD` to enable encrypted backups using 7z before uploading to S3-compatible storage.
267
+
268
+ - **Least privilege access**
269
+ Use a PostgreSQL user with read-only access where possible, and restrict R2 credentials to the required bucket only.
270
+
271
+ ---
272
+
273
+ ## ๐Ÿ›  Development & Contributions
274
+
275
+ Fork this repository **only if you plan to**:
276
+
277
+ - Modify the backup logic
278
+ - Add features or integrations
279
+ - Submit pull requests
280
+ - Run locally for development
281
+
282
+ ---
283
+
284
+ ## โ“ FAQ
285
+
286
+ **Why only DATABASE_URL?**
287
+ This matches how most modern platforms expose PostgreSQL credentials.
288
+ Support for separate DB variables may be added if there is demand.
289
+
290
+ ## ๐Ÿ“œ License
291
+
292
+ This project is open source under the **MIT License**.
293
+
294
+ You are free to use, modify, and distribute it with attribution.
File without changes
@@ -0,0 +1,166 @@
1
+ import argparse
2
+ import shutil
3
+ import os
4
+ import sys
5
+ import textwrap
6
+ import importlib.metadata
7
+
8
+ from main import run_backup
9
+
10
+ def get_version():
11
+ try:
12
+ return importlib.metadata.version("pg-r2-backup")
13
+ except importlib.metadata.PackageNotFoundError:
14
+ return "dev"
15
+
16
+
17
+ def mask(value, show=4):
18
+ if not value:
19
+ return ""
20
+ if len(value) <= show:
21
+ return "*" * len(value)
22
+ return value[:show] + "*" * (len(value) - show)
23
+
24
+ def doctor():
25
+ print("pg-r2-backup doctor\n")
26
+
27
+ if shutil.which("pg_dump") is None:
28
+ print("[FAIL] pg_dump not found in PATH")
29
+ else:
30
+ print("[OK] pg_dump found")
31
+
32
+ required_envs = [
33
+ "DATABASE_URL",
34
+ "R2_ACCESS_KEY",
35
+ "R2_SECRET_KEY",
36
+ "R2_BUCKET_NAME",
37
+ "R2_ENDPOINT",
38
+ ]
39
+
40
+ missing = [e for e in required_envs if not os.environ.get(e)]
41
+
42
+ if missing:
43
+ print("\n[FAIL] Missing environment variables:")
44
+ for m in missing:
45
+ print(f" - {m}")
46
+ else:
47
+ print("\n[OK] Required environment variables set")
48
+
49
+ use_public = os.environ.get("USE_PUBLIC_URL", "false").lower() == "true"
50
+ print(f"\nDatabase URL mode : {'public' if use_public else 'private'}")
51
+
52
+ if os.environ.get("BACKUP_PASSWORD"):
53
+ print("Compression : 7z (encrypted)")
54
+ else:
55
+ print("Compression : gzip")
56
+
57
+ print("\nDoctor check complete.")
58
+
59
+
60
+ def config_show():
61
+ print("pg-r2-backup config\n")
62
+
63
+ config = {
64
+ "USE_PUBLIC_URL": os.environ.get("USE_PUBLIC_URL", "false"),
65
+ "DUMP_FORMAT": os.environ.get("DUMP_FORMAT", "dump"),
66
+ "FILENAME_PREFIX": os.environ.get("FILENAME_PREFIX", "backup"),
67
+ "MAX_BACKUPS": os.environ.get("MAX_BACKUPS", "7"),
68
+ "BACKUP_TIME": os.environ.get("BACKUP_TIME", "00:00"),
69
+ "R2_BUCKET_NAME": os.environ.get("R2_BUCKET_NAME", ""),
70
+ "R2_ENDPOINT": os.environ.get("R2_ENDPOINT", ""),
71
+ "R2_ACCESS_KEY": mask(os.environ.get("R2_ACCESS_KEY")),
72
+ "R2_SECRET_KEY": mask(os.environ.get("R2_SECRET_KEY")),
73
+ }
74
+
75
+ for k, v in config.items():
76
+ print(f"{k:<16} : {v}")
77
+
78
+
79
+ def init_env():
80
+ if os.path.exists(".env"):
81
+ print("[ERROR] .env already exists")
82
+ return
83
+
84
+ example = ".env.example"
85
+ if not os.path.exists(example):
86
+ print("[ERROR] .env.example not found")
87
+ return
88
+
89
+ shutil.copy(example, ".env")
90
+ print("[SUCCESS] .env created from .env.example")
91
+ print("Edit the file before running backups.")
92
+
93
+
94
+ def schedule_info():
95
+ print(textwrap.dedent("""
96
+ pg-r2-backup scheduling
97
+
98
+ Linux / macOS (cron):
99
+ 0 0 * * * pg-r2-backup run
100
+
101
+ Windows (Task Scheduler):
102
+ Program : pg-r2-backup
103
+ Args : run
104
+ Start in: folder containing .env (working directory)
105
+
106
+ Railway / Docker:
107
+ Use the platform scheduler
108
+ """).strip())
109
+
110
+ def main():
111
+ parser = argparse.ArgumentParser(
112
+ prog="pg-r2-backup",
113
+ description="PostgreSQL backup tool for Cloudflare R2",
114
+ formatter_class=argparse.RawDescriptionHelpFormatter,
115
+ epilog=textwrap.dedent("""
116
+ Examples:
117
+ pg-r2-backup doctor
118
+ pg-r2-backup run
119
+ pg-r2-backup config show
120
+ pg-r2-backup init
121
+ pg-r2-backup schedule
122
+ """)
123
+ )
124
+
125
+ parser.add_argument(
126
+ "--version",
127
+ action="version",
128
+ version=f"%(prog)s {get_version()}"
129
+ )
130
+
131
+ subparsers = parser.add_subparsers(dest="command")
132
+
133
+ subparsers.add_parser("run", help="Run backup immediately")
134
+ subparsers.add_parser("doctor", help="Check environment & dependencies")
135
+ subparsers.add_parser("schedule", help="Show scheduling examples")
136
+
137
+ config_parser = subparsers.add_parser("config", help="Show configuration")
138
+ config_sub = config_parser.add_subparsers(dest="subcommand")
139
+ config_sub.add_parser("show", help="Show current configuration")
140
+
141
+ subparsers.add_parser("init", help="Create .env from .env.example")
142
+
143
+ args = parser.parse_args()
144
+
145
+ if args.command == "run":
146
+ run_backup()
147
+
148
+ elif args.command == "doctor":
149
+ doctor()
150
+
151
+ elif args.command == "config" and args.subcommand == "show":
152
+ config_show()
153
+
154
+ elif args.command == "init":
155
+ init_env()
156
+
157
+ elif args.command == "schedule":
158
+ schedule_info()
159
+
160
+ else:
161
+ parser.print_help()
162
+ sys.exit(1)
163
+
164
+
165
+ if __name__ == "__main__":
166
+ main()
@@ -0,0 +1,188 @@
1
+ import os
2
+ import subprocess
3
+ import boto3
4
+ from boto3.session import Config
5
+ from datetime import datetime, timezone
6
+ from boto3.s3.transfer import TransferConfig
7
+ from dotenv import load_dotenv, find_dotenv
8
+ import time
9
+ import schedule
10
+ import py7zr
11
+ import shutil
12
+ import gzip
13
+
14
+ load_dotenv(find_dotenv(usecwd=True), override=True)
15
+
16
+ ## ENV
17
+
18
+ DATABASE_URL = os.environ.get("DATABASE_URL")
19
+ DATABASE_PUBLIC_URL = os.environ.get("DATABASE_PUBLIC_URL")
20
+ R2_ACCESS_KEY = os.environ.get("R2_ACCESS_KEY")
21
+ R2_SECRET_KEY = os.environ.get("R2_SECRET_KEY")
22
+ R2_BUCKET_NAME = os.environ.get("R2_BUCKET_NAME")
23
+ R2_ENDPOINT = os.environ.get("R2_ENDPOINT")
24
+ MAX_BACKUPS = int(os.environ.get("MAX_BACKUPS", 7))
25
+ BACKUP_PREFIX = os.environ.get("BACKUP_PREFIX", "")
26
+ FILENAME_PREFIX = os.environ.get("FILENAME_PREFIX", "backup")
27
+ DUMP_FORMAT = os.environ.get("DUMP_FORMAT", "dump")
28
+ BACKUP_PASSWORD = os.environ.get("BACKUP_PASSWORD")
29
+ USE_PUBLIC_URL = os.environ.get("USE_PUBLIC_URL", "false").lower() == "true"
30
+ BACKUP_TIME = os.environ.get("BACKUP_TIME", "00:00")
31
+ S3_REGION = os.environ.get("S3_REGION", "us-east-1")
32
+
33
+ def log(msg):
34
+ print(msg, flush=True)
35
+
36
+ ## Validate BACKUP_TIME
37
+ try:
38
+ hour, minute = BACKUP_TIME.split(":")
39
+ if not (0 <= int(hour) <= 23 and 0 <= int(minute) <= 59):
40
+ raise ValueError
41
+ except ValueError:
42
+ log("[WARNING] Invalid BACKUP_TIME format. Using default: 00:00")
43
+ BACKUP_TIME = "00:00"
44
+
45
+ def get_database_url():
46
+ if USE_PUBLIC_URL:
47
+ if not DATABASE_PUBLIC_URL:
48
+ raise ValueError("[ERROR] DATABASE_PUBLIC_URL not set but USE_PUBLIC_URL=true!")
49
+ return DATABASE_PUBLIC_URL
50
+
51
+ if not DATABASE_URL:
52
+ raise ValueError("[ERROR] DATABASE_URL not set!")
53
+ return DATABASE_URL
54
+
55
+ def gzip_compress(src):
56
+ dst = src + ".gz"
57
+ with open(src, "rb") as f_in:
58
+ with gzip.open(dst, "wb") as f_out:
59
+ shutil.copyfileobj(f_in, f_out)
60
+ return dst
61
+
62
+ def run_backup():
63
+ if shutil.which("pg_dump") is None:
64
+ log("[ERROR] pg_dump not found. Install postgresql-client.")
65
+ return
66
+
67
+ database_url = get_database_url()
68
+ log(f"[INFO] Using {'public' if USE_PUBLIC_URL else 'private'} database URL")
69
+
70
+ format_map = {
71
+ "sql": ("p", "sql"),
72
+ "plain": ("p", "sql"),
73
+ "dump": ("c", "dump"),
74
+ "custom": ("c", "dump"),
75
+ "tar": ("t", "tar")
76
+ }
77
+ pg_format, ext = format_map.get(DUMP_FORMAT.lower(), ("c", "dump"))
78
+
79
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
80
+ backup_file = f"{FILENAME_PREFIX}_{timestamp}.{ext}"
81
+
82
+ compressed_file = (
83
+ f"{backup_file}.7z" if BACKUP_PASSWORD else f"{backup_file}.gz"
84
+ )
85
+
86
+ compressed_file_r2 = f"{BACKUP_PREFIX}{compressed_file}"
87
+
88
+ ## Create backup
89
+ try:
90
+ log(f"[INFO] Creating backup {backup_file}")
91
+
92
+ dump_cmd = [
93
+ "pg_dump",
94
+ f"--dbname={database_url}",
95
+ "-F", pg_format,
96
+ "--no-owner",
97
+ "--no-acl",
98
+ "-f", backup_file
99
+ ]
100
+
101
+ subprocess.run(dump_cmd, check=True)
102
+
103
+ if BACKUP_PASSWORD:
104
+ log("[INFO] Encrypting backup with 7z...")
105
+ with py7zr.SevenZipFile(compressed_file, "w", password=BACKUP_PASSWORD) as archive:
106
+ archive.write(backup_file)
107
+ log("[SUCCESS] Backup encrypted successfully")
108
+ else:
109
+ log("[INFO] Compressing backup with gzip...")
110
+ gzip_compress(backup_file)
111
+ log("[SUCCESS] Backup compressed successfully")
112
+
113
+ except subprocess.CalledProcessError as e:
114
+ log(f"[ERROR] Backup creation failed: {e}")
115
+ return
116
+ finally:
117
+ if os.path.exists(backup_file):
118
+ os.remove(backup_file)
119
+
120
+ ## Upload to R2
121
+ if os.path.exists(compressed_file):
122
+ size = os.path.getsize(compressed_file)
123
+ log(f"[INFO] Final backup size: {size / 1024 / 1024:.2f} MB")
124
+
125
+ try:
126
+ client = boto3.client(
127
+ "s3",
128
+ endpoint_url=R2_ENDPOINT,
129
+ aws_access_key_id=R2_ACCESS_KEY,
130
+ aws_secret_access_key=R2_SECRET_KEY,
131
+ region_name=S3_REGION,
132
+ config=Config(
133
+ s3={"addressing_style": "path"}
134
+ )
135
+ )
136
+
137
+ config = TransferConfig(
138
+ multipart_threshold=8 * 1024 * 1024,
139
+ multipart_chunksize=8 * 1024 * 1024,
140
+ max_concurrency=4,
141
+ use_threads=True
142
+ )
143
+
144
+ client.upload_file(
145
+ compressed_file,
146
+ R2_BUCKET_NAME,
147
+ compressed_file_r2,
148
+ Config=config
149
+ )
150
+
151
+ log(f"[SUCCESS] Backup uploaded: {compressed_file_r2}")
152
+
153
+ objects = client.list_objects_v2(
154
+ Bucket=R2_BUCKET_NAME,
155
+ Prefix=BACKUP_PREFIX
156
+ )
157
+
158
+ if "Contents" in objects:
159
+ backups = sorted(
160
+ objects["Contents"],
161
+ key=lambda x: x["LastModified"],
162
+ reverse=True
163
+ )
164
+
165
+ for obj in backups[MAX_BACKUPS:]:
166
+ client.delete_object(
167
+ Bucket=R2_BUCKET_NAME,
168
+ Key=obj["Key"]
169
+ )
170
+ log(f"[INFO] Deleted old backup: {obj['Key']}")
171
+
172
+ except Exception as e:
173
+ log(f"[ERROR] R2 operation failed: {e}")
174
+ finally:
175
+ if os.path.exists(compressed_file):
176
+ os.remove(compressed_file)
177
+
178
+ if __name__ == "__main__":
179
+ log("[INFO] Starting backup scheduler...")
180
+ log(f"[INFO] Scheduled backup time: {BACKUP_TIME} UTC")
181
+
182
+ schedule.every().day.at(BACKUP_TIME).do(run_backup)
183
+
184
+ run_backup()
185
+
186
+ while True:
187
+ schedule.run_pending()
188
+ time.sleep(60)
@@ -0,0 +1,305 @@
1
+ Metadata-Version: 2.4
2
+ Name: pg-r2-backup
3
+ Version: 1.0.4
4
+ Summary: PostgreSQL backup tool for Cloudflare R2 (S3 Compatuble)
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: boto3
8
+ Requires-Dist: python-dotenv
9
+ Requires-Dist: schedule
10
+ Requires-Dist: py7zr
11
+
12
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
13
+ ![Python](https://img.shields.io/badge/python-3.12-blue)
14
+ ![Storage](https://img.shields.io/badge/storage-S3--compatible-orange)
15
+ ![Database](https://img.shields.io/badge/database-PostgreSQL-336791)
16
+ ![Deploy](https://img.shields.io/badge/deploy-Railway-purple)
17
+ ![Docker](https://img.shields.io/badge/docker-supported-blue)
18
+
19
+ # Postgres-to-R2 Backup (S3-Compatible)
20
+
21
+ A lightweight automation service that creates scheduled PostgreSQL backups and securely uploads them to **S3-compatible object storage**
22
+ such as **Cloudflare R2, AWS S3, Wasabi, Backblaze B2, or MinIO**.
23
+ Designed specifically as a **Railway deployment template**, with built-in support for Docker and cron scheduling.
24
+
25
+ ---
26
+
27
+ ## โœจ Features
28
+
29
+ - ๐Ÿ“ฆ **Automated Backups** โ€” scheduled daily or hourly PostgreSQL backups
30
+ - ๐Ÿ” **Optional Encryption** โ€” gzip compression or 7z encryption with password
31
+ - โ˜๏ธ **Cloudflare R2 Integration** โ€” seamless S3-compatible storage support
32
+ - ๐Ÿงน **Retention Policy** โ€” automatically delete old backups
33
+ - ๐Ÿ”— **Flexible Database URLs** โ€” supports private and public PostgreSQL connection URLs
34
+ - โšก **Optimized Performance** โ€” parallel pg_dump and multipart S3 uploads
35
+ - ๐Ÿณ **Docker Ready** โ€” portable, lightweight container
36
+ - ๐Ÿš€ **Railway Template First** โ€” no fork required for normal usage
37
+ - ๐Ÿชฃ **S3-Compatible Storage** โ€” works with R2, AWS S3, Wasabi, B2, MinIO
38
+
39
+ ---
40
+
41
+ ## ๐Ÿš€ Deployment on Railway
42
+
43
+ 1. Click the **Deploy on Railway** button below
44
+ 2. Railway will create a new project using the latest version of this repository
45
+ 3. Add the required environment variables in the Railway dashboard
46
+ 4. (Optional) Configure a cron job for your desired backup schedule
47
+
48
+ [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/postgres-to-r2-backup?referralCode=nIQTyp&utm_medium=integration&utm_source=template&utm_campaign=generic)
49
+
50
+ ---
51
+
52
+ ## ๐Ÿ”ง Environment Variables (S3-Compatible)
53
+
54
+ ```env
55
+ DATABASE_URL= # PostgreSQL database URL (private)
56
+ DATABASE_PUBLIC_URL= # Public PostgreSQL URL (optional)
57
+ USE_PUBLIC_URL=false # Set true to use DATABASE_PUBLIC_URL
58
+
59
+ DUMP_FORMAT=dump # sql | plain | dump | custom | tar
60
+ FILENAME_PREFIX=backup # Backup filename prefix
61
+ MAX_BACKUPS=7 # Number of backups to retain
62
+
63
+ R2_ENDPOINT= # S3 endpoint URL
64
+ R2_BUCKET_NAME= # Bucket name
65
+ R2_ACCESS_KEY= # Access key
66
+ R2_SECRET_KEY= # Secret key
67
+ S3_REGION=us-east-1 # Required for AWS S3 (ignored by R2/MinIO)
68
+
69
+ BACKUP_PASSWORD= # Optional: enables 7z encryption
70
+ BACKUP_TIME=00:00 # Daily backup time (UTC, HH:MM)
71
+ ```
72
+
73
+ > Variable names use `R2_*` for historical reasons, but **any S3-compatible provider** can be used by changing the endpoint and credentials.
74
+ > For AWS S3 users: ensure `S3_REGION` matches your bucketโ€™s region.
75
+
76
+ ---
77
+
78
+ ## โ˜๏ธ Supported S3-Compatible Providers
79
+
80
+ This project uses the **standard AWS S3 API via boto3**, and works with:
81
+
82
+ - Cloudflare R2 (recommended)
83
+ - AWS S3
84
+ - Wasabi
85
+ - Backblaze B2 (S3 API)
86
+ - MinIO (self-hosted)
87
+
88
+ ### Example Endpoints
89
+
90
+ | Provider | Endpoint Example |
91
+ |--------|------------------|
92
+ | Cloudflare R2 | `https://<accountid>.r2.cloudflarestorage.com` |
93
+ | AWS S3 | `https://s3.amazonaws.com` |
94
+ | Wasabi | `https://s3.wasabisys.com` |
95
+ | Backblaze B2 | `https://s3.us-west-004.backblazeb2.com` |
96
+ | MinIO | `http://localhost:9000` |
97
+
98
+ ---
99
+
100
+ ## โฐ Railway Cron Jobs
101
+
102
+ You can configure the backup schedule using **Railway Cron Jobs**:
103
+
104
+ 1. Open your Railway project
105
+ 2. Go to **Deployments โ†’ Cron**
106
+ 3. Add a cron job targeting this service
107
+
108
+ ### Common Cron Expressions
109
+
110
+ | Schedule | Cron Expression | Description |
111
+ |--------|----------------|------------|
112
+ | Hourly | `0 * * * *` | Every hour |
113
+ | Daily | `0 0 * * *` | Once per day (UTC midnight) |
114
+ | Twice Daily | `0 */12 * * *` | Every 12 hours |
115
+ | Weekly | `0 0 * * 0` | Every Sunday |
116
+ | Monthly | `0 0 1 * *` | First day of the month |
117
+
118
+ **Tips**
119
+ - All cron times are **UTC**
120
+ - Use https://crontab.guru to validate expressions
121
+ - Adjust `MAX_BACKUPS` to match your schedule
122
+
123
+ > If you use Railway Cron Jobs, the service will start once per execution.
124
+ > In this case, the internal scheduler is ignored after startup.
125
+
126
+ ---
127
+
128
+ ## ๐Ÿ–ฅ๏ธ Running Locally or on Other Platforms
129
+
130
+ It can run on **any platform** that supports:
131
+ - Python 3.9+
132
+ - `pg_dump` (PostgreSQL client tools)
133
+ - Environment variables
134
+ - Long-running background processes or cron
135
+
136
+ > Docker images use **Python 3.12** by default.
137
+ > Local execution supports **Python 3.9+**.
138
+
139
+ ### Supported Environments
140
+
141
+ - Local machine (Linux / macOS / Windows*)
142
+ - VPS (Netcup, Hetzner, DigitalOcean, etc.)
143
+ - Docker containers
144
+ - Other PaaS providers (Heroku, Fly.io, Render, etc.)
145
+
146
+ > *Windows is supported when `pg_dump` is installed and available in PATH.*
147
+
148
+ ### Local Requirements
149
+
150
+ - Python 3.9+
151
+ - PostgreSQL client tools (`pg_dump`)
152
+ - pip
153
+
154
+ ### Run Manually (Local)
155
+
156
+ ```bash
157
+ pip install -r requirements.txt
158
+ python main.py
159
+ ```
160
+
161
+ ### Run with Docker (Optional)
162
+
163
+ Build and run the image locally:
164
+
165
+ ```bash
166
+ docker build -t postgres-to-r2-backup .
167
+ docker run --env-file .env postgres-to-r2-backup
168
+ ```
169
+
170
+ > Ensure the container is allowed to run continuously when not using an external cron scheduler.
171
+
172
+ > All scheduling uses **UTC** by default (e.g. Malaysia UTC+8 โ†’ set `BACKUP_TIME=16:00` for midnight).
173
+
174
+ ### Run from Prebuilt Docker Image
175
+
176
+ If you downloaded a prebuilt Docker image archive (`.tar` or `.tar.gz`), you can run it without building locally:
177
+
178
+ ```bash
179
+ # Extract the archive (if compressed)
180
+ tar -xzf postgres-to-r2-backup_v1.0.4.tar.gz
181
+
182
+ # Load the image into Docker
183
+ docker load -i postgres-to-r2-backup_v1.0.4.tar
184
+
185
+ # Run the container
186
+ docker run --env-file .env postgres-to-r2-backup:v1.0.4
187
+ ```
188
+
189
+ > Prebuilt images are architecture-specific (amd64 / arm64).
190
+
191
+ ---
192
+
193
+ ## ๐Ÿงฐ Using the CLI (Global Installation)
194
+
195
+ This project can also be used as a standalone CLI tool, installable via pip, in addition to running as a Railway or Docker service.
196
+
197
+ ### Install via pip
198
+
199
+ ```bash
200
+ pip install pg-r2-backup
201
+ ```
202
+
203
+ ### Requirements
204
+
205
+ - Python 3.9+
206
+ - PostgreSQL client tools (`pg_dump`) installed and available in PATH
207
+
208
+ ### Quick Start (CLI)
209
+
210
+ ```bash
211
+ mkdir backups
212
+ cd backups
213
+
214
+ pg-r2-backup init # creates .env from .env.example
215
+ pg-r2-backup doctor # checks environment and dependencies
216
+ pg-r2-backup run # runs a backup immediately
217
+ ```
218
+
219
+ ### CLI Commands
220
+
221
+ ```bash
222
+ pg-r2-backup run # Run backup immediately
223
+ pg-r2-backup doctor # Check environment & dependencies
224
+ pg-r2-backup config show # Show current configuration
225
+ pg-r2-backup init # Create .env from .env.example
226
+ pg-r2-backup schedule # Show scheduling examples
227
+ pg-r2-backup --version
228
+ ```
229
+
230
+ ### Environment Variable Resolution (CLI)
231
+
232
+ When running via the CLI, environment variables are resolved in the following order:
233
+
234
+ 1. A `.env` file in the current working directory (or parent directory)
235
+ 2. System environment variables
236
+
237
+ This allows different folders to maintain separate backup configurations.
238
+
239
+ ### Scheduling Backups (CLI)
240
+
241
+ The CLI does not run a background scheduler. Use your operating system or platform scheduler instead.
242
+
243
+ **Linux / macOS (cron)**
244
+
245
+ ```bash
246
+ 0 0 * * * pg-r2-backup run
247
+ ```
248
+
249
+ **Windows (Task Scheduler)**
250
+
251
+ - Program: `pg-r2-backup`
252
+ - Arguments: `run`
253
+ - Start in: folder containing `.env` (working directory)
254
+
255
+ **Railway / Docker**
256
+
257
+ Use the platform's built-in scheduler (recommended).
258
+
259
+ ๐Ÿ’ก **Tip**
260
+ Run `pg-r2-backup schedule` at any time to see scheduling examples.
261
+
262
+ ---
263
+
264
+ ## ๐Ÿ” Security
265
+
266
+ - **Do not expose PostgreSQL directly to the public internet.**
267
+ If your database is not on a private network, use a secure tunnel instead.
268
+
269
+ - **Recommended: Cloudflare Tunnel**
270
+ When using a public database URL, it is strongly recommended to connect via a secure tunnel such as **Cloudflare Tunnel** rather than opening database ports.
271
+
272
+ - **Protect credentials**
273
+ Store all secrets (database URLs, R2 keys, encryption passwords) using environment variables.
274
+ Never commit `.env` files to version control.
275
+
276
+ - **Encrypted backups (optional)**
277
+ Set `BACKUP_PASSWORD` to enable encrypted backups using 7z before uploading to S3-compatible storage.
278
+
279
+ - **Least privilege access**
280
+ Use a PostgreSQL user with read-only access where possible, and restrict R2 credentials to the required bucket only.
281
+
282
+ ---
283
+
284
+ ## ๐Ÿ›  Development & Contributions
285
+
286
+ Fork this repository **only if you plan to**:
287
+
288
+ - Modify the backup logic
289
+ - Add features or integrations
290
+ - Submit pull requests
291
+ - Run locally for development
292
+
293
+ ---
294
+
295
+ ## โ“ FAQ
296
+
297
+ **Why only DATABASE_URL?**
298
+ This matches how most modern platforms expose PostgreSQL credentials.
299
+ Support for separate DB variables may be added if there is demand.
300
+
301
+ ## ๐Ÿ“œ License
302
+
303
+ This project is open source under the **MIT License**.
304
+
305
+ You are free to use, modify, and distribute it with attribution.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ main.py
3
+ pyproject.toml
4
+ cli/__init__.py
5
+ cli/cli.py
6
+ pg_r2_backup.egg-info/PKG-INFO
7
+ pg_r2_backup.egg-info/SOURCES.txt
8
+ pg_r2_backup.egg-info/dependency_links.txt
9
+ pg_r2_backup.egg-info/entry_points.txt
10
+ pg_r2_backup.egg-info/requires.txt
11
+ pg_r2_backup.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pg-r2-backup = cli.cli:main
@@ -0,0 +1,4 @@
1
+ boto3
2
+ python-dotenv
3
+ schedule
4
+ py7zr
@@ -0,0 +1,2 @@
1
+ cli
2
+ main
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pg-r2-backup"
7
+ version = "1.0.4"
8
+ description = "PostgreSQL backup tool for Cloudflare R2 (S3 Compatuble)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+
12
+ dependencies = [
13
+ "boto3",
14
+ "python-dotenv",
15
+ "schedule",
16
+ "py7zr"
17
+ ]
18
+
19
+ [tool.setuptools]
20
+ packages = ["cli"]
21
+ py-modules = ["main"]
22
+
23
+ [project.scripts]
24
+ pg-r2-backup = "cli.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+