Skip to content

Commit 977912c

Browse files
committed
Add S3 database backups
1 parent 5a71d4e commit 977912c

16 files changed

Lines changed: 718 additions & 27 deletions

File tree

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,44 @@ DB_USER=myapp_user # PostgreSQL/MySQL user to create
616616
DB_PASSWORD=${DB_PASSWORD:-} # PostgreSQL/MySQL password from env or .env
617617
DB_SQLITE_PATH= # Optional; defaults to $REMOTE_PATH/shared/database.sqlite
618618
REDIS_SETUP_ENABLED=false # Set true to install/configure Redis on localhost
619+
620+
# === Database backups to S3 ===
621+
DB_BACKUP_ENABLED=false # Set true to configure scheduled backups
622+
DB_BACKUP_S3_BUCKET= # S3 bucket for uploaded backup files
623+
DB_BACKUP_S3_PREFIX=myapp # Optional path prefix inside the bucket
624+
DB_BACKUP_SCHEDULE=daily # hourly, daily, weekly, or systemd OnCalendar
625+
DB_BACKUP_RETENTION_DAYS=14 # Local compressed backup retention
626+
DB_BACKUP_S3_ENDPOINT= # Optional S3-compatible endpoint
627+
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
628+
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
629+
AWS_DEFAULT_REGION=eu-west-1
630+
```
631+
632+
### Database Backups
633+
634+
ShipNode can install a small remote backup script and a systemd timer that dumps the configured PostgreSQL, MySQL, or SQLite database, compresses it, and uploads it to S3.
635+
636+
```bash
637+
DB_BACKUP_ENABLED=true
638+
DB_BACKUP_S3_BUCKET=my-backups
639+
DB_BACKUP_S3_PREFIX=myapp/production
640+
DB_BACKUP_SCHEDULE=daily
641+
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
642+
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
643+
AWS_DEFAULT_REGION=eu-west-1
619644
```
620645

646+
Put real AWS/S3 credentials in `.env`, upload them with `shipnode env`, then configure backups:
647+
648+
```bash
649+
shipnode setup
650+
shipnode backup setup
651+
shipnode backup run
652+
shipnode backup status
653+
```
654+
655+
Use `DB_BACKUP_S3_ENDPOINT` for S3-compatible storage such as Cloudflare R2, MinIO, or DigitalOcean Spaces. `DB_BACKUP_RETENTION_DAYS` controls local compressed files on the server; use your bucket lifecycle policy for S3 retention.
656+
621657
### Supported Frameworks
622658

623659
Auto-detected from your `package.json`:

build.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ MODULES=(
1515
"lib/core.sh"
1616
"lib/release.sh"
1717
"lib/database.sh"
18+
"lib/backup.sh"
1819
"lib/users.sh"
1920
"lib/framework.sh"
2021
"lib/validation.sh"
@@ -31,6 +32,7 @@ MODULES=(
3132
"lib/commands/rollback.sh"
3233
"lib/commands/migrate.sh"
3334
"lib/commands/env.sh"
35+
"lib/commands/backup.sh"
3436
"lib/commands/help.sh"
3537
"lib/commands/main.sh"
3638
)

docs/advanced/architecture.md

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This document describes the internal architecture and module organization of Shi
66

77
ShipNode is organized as a modular bash project to improve maintainability, testability, and collaboration. The codebase is split into focused modules, each with a single responsibility.
88

9-
**Total: ~8,350 lines** across 26 modules.
9+
**Total: ~8,350 lines** across 32 modules.
1010

1111
## Directory Structure
1212

@@ -18,6 +18,7 @@ shipnode/
1818
│ ├── pkg-manager.sh # Package manager detection + PM2 template generation (236 lines)
1919
│ ├── release.sh # Release management with metadata tracking (263 lines)
2020
│ ├── database.sh # Database and Redis operations
21+
│ ├── backup.sh # Database backup operations
2122
│ ├── users.sh # User provisioning helpers (352 lines)
2223
│ ├── framework.sh # Framework detection (259 lines)
2324
│ ├── validation.sh # Input validation (287 lines)
@@ -37,6 +38,7 @@ shipnode/
3738
│ ├── rollback.sh # Rollback command (86 lines)
3839
│ ├── migrate.sh # Migrate command (92 lines)
3940
│ ├── env.sh # Environment upload (42 lines)
41+
│ ├── backup.sh # Database backup commands
4042
│ ├── eject.sh # Eject PM2/Caddy templates (266 lines)
4143
│ ├── metrics.sh # PM2 resource monitoring (12 lines)
4244
│ ├── config-cmd.sh # Config show/validate/path (137 lines)
@@ -64,32 +66,34 @@ Modules are loaded in a specific order to ensure dependencies are available:
6466
2. **pkg-manager.sh** - Depends on core.sh
6567
3. **release.sh** - Depends on core.sh
6668
4. **database.sh** - Depends on core.sh
67-
5. **users.sh** - Depends on core.sh
68-
6. **framework.sh** - Depends on core.sh
69-
7. **validation.sh** - Depends on core.sh
70-
8. **prompts.sh** - Depends on core.sh
71-
9. **templates.sh** - Depends on core.sh
72-
10. **commands/config.sh** - Depends on core.sh
73-
11. **commands/users-yaml.sh** - Depends on core.sh, validation.sh
74-
12. **commands/user.sh** - Depends on core.sh, users.sh, validation.sh
75-
13. **commands/mkpasswd.sh** - Depends on core.sh
76-
14. **commands/init.sh** - Depends on core.sh, framework.sh, validation.sh, prompts.sh
77-
15. **commands/setup.sh** - Depends on core.sh, release.sh, database.sh
78-
16. **commands/deploy.sh** - Depends on core.sh, release.sh, pkg-manager.sh
79-
17. **commands/doctor.sh** - Depends on core.sh
80-
18. **commands/status.sh** - Depends on core.sh
81-
19. **commands/unlock.sh** - Depends on core.sh, release.sh
82-
20. **commands/rollback.sh** - Depends on core.sh, release.sh
83-
21. **commands/migrate.sh** - Depends on core.sh, release.sh
84-
22. **commands/env.sh** - Depends on core.sh
85-
23. **commands/eject.sh** - Depends on core.sh
86-
24. **commands/metrics.sh** - Depends on core.sh
87-
25. **commands/config-cmd.sh** - Depends on core.sh
88-
26. **commands/upgrade.sh** - Depends on core.sh
89-
27. **commands/ci.sh** - Depends on core.sh
90-
28. **commands/harden.sh** - Depends on core.sh
91-
29. **commands/help.sh** - Depends on core.sh
92-
30. **commands/main.sh** - Depends on all other modules
69+
5. **backup.sh** - Depends on core.sh
70+
6. **users.sh** - Depends on core.sh
71+
7. **framework.sh** - Depends on core.sh
72+
8. **validation.sh** - Depends on core.sh
73+
9. **prompts.sh** - Depends on core.sh
74+
10. **templates.sh** - Depends on core.sh
75+
11. **commands/config.sh** - Depends on core.sh
76+
12. **commands/users-yaml.sh** - Depends on core.sh, validation.sh
77+
13. **commands/user.sh** - Depends on core.sh, users.sh, validation.sh
78+
14. **commands/mkpasswd.sh** - Depends on core.sh
79+
15. **commands/init.sh** - Depends on core.sh, framework.sh, validation.sh, prompts.sh
80+
16. **commands/setup.sh** - Depends on core.sh, release.sh, database.sh, backup.sh
81+
17. **commands/deploy.sh** - Depends on core.sh, release.sh, pkg-manager.sh
82+
18. **commands/doctor.sh** - Depends on core.sh
83+
19. **commands/status.sh** - Depends on core.sh
84+
20. **commands/unlock.sh** - Depends on core.sh, release.sh
85+
21. **commands/rollback.sh** - Depends on core.sh, release.sh
86+
22. **commands/migrate.sh** - Depends on core.sh, release.sh
87+
23. **commands/env.sh** - Depends on core.sh
88+
24. **commands/backup.sh** - Depends on core.sh, backup.sh
89+
25. **commands/eject.sh** - Depends on core.sh
90+
26. **commands/metrics.sh** - Depends on core.sh
91+
27. **commands/config-cmd.sh** - Depends on core.sh
92+
28. **commands/upgrade.sh** - Depends on core.sh
93+
29. **commands/ci.sh** - Depends on core.sh
94+
30. **commands/harden.sh** - Depends on core.sh
95+
31. **commands/help.sh** - Depends on core.sh
96+
32. **commands/main.sh** - Depends on all other modules
9397

9498
## Module Descriptions
9599

@@ -140,6 +144,16 @@ Modules are loaded in a specific order to ensure dependencies are available:
140144
- `setup_sqlite()` - Install SQLite and create database file
141145
- `setup_redis()` - Install and configure Redis
142146

147+
#### backup.sh
148+
149+
**Purpose:** Database backup configuration, remote script installation, and S3 upload operations
150+
151+
**Key Functions:**
152+
- `setup_database_backups()` - Install dependencies, write backup files, and configure timer
153+
- `run_database_backup()` - Run a backup immediately on the remote server
154+
- `list_database_backups()` - List uploaded backups in S3
155+
- `show_database_backup_status()` - Show systemd timer status, logs, and local backups
156+
143157
#### users.sh (352 lines)
144158

145159
**Purpose:** User provisioning helper functions
@@ -331,6 +345,13 @@ Modules are loaded in a specific order to ensure dependencies are available:
331345
**Key Functions:**
332346
- `cmd_env()` - Upload .env file to server
333347

348+
#### commands/backup.sh
349+
350+
**Purpose:** Database backup command router
351+
352+
**Key Functions:**
353+
- `cmd_backup()` - Route backup subcommands: setup, run, status, list
354+
334355
#### commands/ci.sh (362 lines)
335356

336357
**Purpose:** CI/CD integration commands

docs/guides/configuration/shipnode-conf.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,31 @@ DB_PASSWORD=${DB_PASSWORD:-}
101101

102102
`DB_TYPE` can be `postgresql`, `mysql`, or `sqlite`. PostgreSQL/MySQL setup installs the server if missing, starts/enables it, creates the database/user, and grants privileges. SQLite setup installs `sqlite3` and creates a database file at `DB_SQLITE_PATH`, defaulting to `$REMOTE_PATH/shared/database.sqlite`. Keep SQLite files under `shared/`; ShipNode rejects paths inside `current/` or `releases/`.
103103

104+
### Database Backups to S3
105+
106+
Enable backups when ShipNode should dump the configured database and upload compressed files to S3 on a schedule:
107+
108+
```bash
109+
DB_BACKUP_ENABLED=true
110+
DB_BACKUP_S3_BUCKET=my-backups
111+
DB_BACKUP_S3_PREFIX=myapp/production
112+
DB_BACKUP_SCHEDULE=daily
113+
DB_BACKUP_RETENTION_DAYS=14
114+
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
115+
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
116+
AWS_DEFAULT_REGION=eu-west-1
117+
```
118+
119+
Put the real `AWS_*` values in `.env`, upload them with `shipnode env`, then run:
120+
121+
```bash
122+
shipnode backup setup
123+
shipnode backup run
124+
shipnode backup status
125+
```
126+
127+
`DB_BACKUP_SCHEDULE` accepts `hourly`, `daily`, `weekly`, or a systemd `OnCalendar` expression. Set `DB_BACKUP_S3_ENDPOINT` for S3-compatible providers.
128+
104129
Enable Redis separately:
105130

106131
```bash

docs/reference/commands.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ All ShipNode commands with descriptions.
6060
| `shipnode env pull` | Download .env from server |
6161
| `shipnode env list` | List environment variables |
6262

63+
## Backup Commands
64+
65+
| Command | Description |
66+
|---------|-------------|
67+
| `shipnode backup setup` | Install/update database backup script and systemd timer |
68+
| `shipnode backup run` | Run a database backup immediately |
69+
| `shipnode backup status` | Show timer status, recent logs, and local backups |
70+
| `shipnode backup list` | List uploaded backups in S3 |
71+
6372
## User Commands
6473

6574
| Command | Description |

docs/reference/configuration-reference.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,37 @@ Run schema migrations from `.shipnode/pre-deploy.sh`; database creation and migr
129129

130130
Keep SQLite files under `shared/` so they survive release switches and cleanup. ShipNode rejects paths inside `$REMOTE_PATH/current/` or `$REMOTE_PATH/releases/`.
131131

132+
### Database Backups
133+
134+
When `DB_BACKUP_ENABLED=true`, `shipnode setup` and `shipnode backup setup` install a remote backup script at `$REMOTE_PATH/shared/shipnode-backup.sh` and enable a systemd timer. Backups are compressed locally, uploaded to S3, and can be run manually with `shipnode backup run`.
135+
136+
| Variable | Type | Default | Description |
137+
|----------|------|---------|-------------|
138+
| `DB_BACKUP_ENABLED` | boolean | `false` | Enable scheduled database backups |
139+
| `DB_BACKUP_S3_BUCKET` | string | - | S3 bucket where backups are uploaded |
140+
| `DB_BACKUP_S3_PREFIX` | string | app name | Optional S3 key prefix |
141+
| `DB_BACKUP_SCHEDULE` | string | `daily` | `hourly`, `daily`, `weekly`, or a systemd `OnCalendar` value |
142+
| `DB_BACKUP_RETENTION_DAYS` | number | `14` | Days to keep local compressed backups on the server |
143+
| `DB_BACKUP_S3_ENDPOINT` | string | - | Optional S3-compatible endpoint |
144+
| `AWS_ACCESS_KEY_ID` | string | - | S3 access key, preferably from `.env` |
145+
| `AWS_SECRET_ACCESS_KEY` | string | - | S3 secret key, preferably from `.env` |
146+
| `AWS_DEFAULT_REGION` | string | - | S3 region |
147+
148+
Example:
149+
150+
```bash
151+
DB_BACKUP_ENABLED=true
152+
DB_BACKUP_S3_BUCKET=my-backups
153+
DB_BACKUP_S3_PREFIX=myapp/production
154+
DB_BACKUP_SCHEDULE=daily
155+
DB_BACKUP_RETENTION_DAYS=14
156+
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
157+
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
158+
AWS_DEFAULT_REGION=eu-west-1
159+
```
160+
161+
Store real credentials in `.env`, run `shipnode env`, then run `shipnode backup setup`. S3 object retention should be managed with bucket lifecycle rules; ShipNode only prunes local compressed files.
162+
132163
### Advanced
133164

134165
| Variable | Type | Description |

0 commit comments

Comments
 (0)