djaploy 1.2.9__tar.gz → 1.3.1__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.
Files changed (92) hide show
  1. {djaploy-1.2.9 → djaploy-1.3.1}/PKG-INFO +2 -2
  2. {djaploy-1.2.9 → djaploy-1.3.1}/README.md +1 -1
  3. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/rclone/infra/djaploy_hooks.py +48 -23
  4. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/config.py +1 -0
  5. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/infra/djaploy_hooks.py +5 -0
  6. djaploy-1.3.1/djaploy/version.py +1 -0
  7. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy.egg-info/PKG-INFO +2 -2
  8. {djaploy-1.2.9 → djaploy-1.3.1}/pyproject.toml +1 -1
  9. djaploy-1.2.9/djaploy/version.py +0 -1
  10. {djaploy-1.2.9 → djaploy-1.3.1}/LICENSE +0 -0
  11. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/__init__.py +0 -0
  12. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/app.py +0 -0
  13. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/__init__.py +0 -0
  14. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/borg/__init__.py +0 -0
  15. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/borg/infra/__init__.py +0 -0
  16. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/borg/infra/djaploy_hooks.py +0 -0
  17. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/janitor/__init__.py +0 -0
  18. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/janitor/apps.py +0 -0
  19. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/janitor/infra/__init__.py +0 -0
  20. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/janitor/infra/commands/createjanitoruser.py +0 -0
  21. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/janitor/infra/djaploy_hooks.py +0 -0
  22. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/nginx/__init__.py +0 -0
  23. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/nginx/apps.py +0 -0
  24. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/nginx/infra/__init__.py +0 -0
  25. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/nginx/infra/djaploy_hooks.py +0 -0
  26. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/rclone/__init__.py +0 -0
  27. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/rclone/apps.py +0 -0
  28. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/rclone/infra/__init__.py +0 -0
  29. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/sync_certs/__init__.py +0 -0
  30. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/sync_certs/apps.py +0 -0
  31. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/sync_certs/infra/__init__.py +0 -0
  32. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/sync_certs/infra/djaploy_hooks.py +0 -0
  33. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/systemd/__init__.py +0 -0
  34. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/systemd/apps.py +0 -0
  35. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/systemd/infra/__init__.py +0 -0
  36. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/systemd/infra/djaploy_hooks.py +0 -0
  37. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/tailscale/__init__.py +0 -0
  38. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/tailscale/apps.py +0 -0
  39. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/tailscale/infra/__init__.py +0 -0
  40. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/tailscale/infra/djaploy_hooks.py +0 -0
  41. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/versioning/__init__.py +0 -0
  42. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/versioning/apps.py +0 -0
  43. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/versioning/infra/__init__.py +0 -0
  44. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/apps/versioning/infra/djaploy_hooks.py +0 -0
  45. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/artifact.py +0 -0
  46. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/bin/__init__.py +0 -0
  47. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/bin/django_pyinfra.py +0 -0
  48. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/bin/gunicornherder.py +0 -0
  49. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/builtin_hooks.py +0 -0
  50. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/certificates.py +0 -0
  51. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/changelog.py +0 -0
  52. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/__init__.py +0 -0
  53. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/_utils.py +0 -0
  54. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/activate.py +0 -0
  55. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/configure.py +0 -0
  56. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/deploy.py +0 -0
  57. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/restore.py +0 -0
  58. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/rollback.py +0 -0
  59. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/status.py +0 -0
  60. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/commands/sync_certs.py +0 -0
  61. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/deploy.py +0 -0
  62. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/discovery.py +0 -0
  63. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/hooks.py +0 -0
  64. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/infra/__init__.py +0 -0
  65. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/infra/bluegreen.py +0 -0
  66. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/infra/templates.py +0 -0
  67. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/infra/utils.py +0 -0
  68. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/__init__.py +0 -0
  69. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/commands/__init__.py +0 -0
  70. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/commands/djaploy.py +0 -0
  71. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/commands/restore_backup.py +0 -0
  72. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/commands/sync_certs.py +0 -0
  73. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/commands/update_certs.py +0 -0
  74. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/commands/verify.py +0 -0
  75. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/management/utils.py +0 -0
  76. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/notifications.py +0 -0
  77. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/utils.py +0 -0
  78. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy/versioning.py +0 -0
  79. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy.egg-info/SOURCES.txt +0 -0
  80. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy.egg-info/dependency_links.txt +0 -0
  81. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy.egg-info/entry_points.txt +0 -0
  82. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy.egg-info/requires.txt +0 -0
  83. {djaploy-1.2.9 → djaploy-1.3.1}/djaploy.egg-info/top_level.txt +0 -0
  84. {djaploy-1.2.9 → djaploy-1.3.1}/setup.cfg +0 -0
  85. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_bluegreen.py +0 -0
  86. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_borg.py +0 -0
  87. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_config.py +0 -0
  88. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_deploy_scripts.py +0 -0
  89. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_discovery.py +0 -0
  90. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_gunicornherder.py +0 -0
  91. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_hooks.py +0 -0
  92. {djaploy-1.2.9 → djaploy-1.3.1}/tests/test_versioning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 1.2.9
3
+ Version: 1.3.1
4
4
  Summary: Modular Django deployment system based on pyinfra
5
5
  Author-email: Johanna Mae Dimayuga <johanna@techco.fi>
6
6
  License: MIT License
@@ -204,7 +204,7 @@ All deployment configuration lives on `djaploy.config.HostConfig`. Commonly used
204
204
  | `shared_resources` | — | Extra paths symlinked from `shared/` |
205
205
  | `db_dir` | — | External database directory template |
206
206
  | `gunicorn_conf` | — | `workers`, `timeout`, `umask`, `wsgi_module`, `health_check_*` |
207
- | `nginx_conf` | — | `server_name`, `listen`, `client_max_body_size`, `custom` |
207
+ | `nginx_conf` | — | `server_name`, `listen`, `client_max_body_size`, `static_path`, `media_path`, `custom` |
208
208
  | `core_conf` | — | `poetry_no_root`, `exclude_groups`, `poetry_lock`, `databases` |
209
209
  | `versioning_conf` / `notifications_conf` | — | See [Release Notifications & Versioning](#release-notifications--versioning) |
210
210
  | `backup` / `borg_backup` | — | `BackupConfig` / `BorgBackupConfig` |
@@ -142,7 +142,7 @@ All deployment configuration lives on `djaploy.config.HostConfig`. Commonly used
142
142
  | `shared_resources` | — | Extra paths symlinked from `shared/` |
143
143
  | `db_dir` | — | External database directory template |
144
144
  | `gunicorn_conf` | — | `workers`, `timeout`, `umask`, `wsgi_module`, `health_check_*` |
145
- | `nginx_conf` | — | `server_name`, `listen`, `client_max_body_size`, `custom` |
145
+ | `nginx_conf` | — | `server_name`, `listen`, `client_max_body_size`, `static_path`, `media_path`, `custom` |
146
146
  | `core_conf` | — | `poetry_no_root`, `exclude_groups`, `poetry_lock`, `databases` |
147
147
  | `versioning_conf` / `notifications_conf` | — | See [Release Notifications & Versioning](#release-notifications--versioning) |
148
148
  | `backup` / `borg_backup` | — | `BackupConfig` / `BorgBackupConfig` |
@@ -162,9 +162,17 @@ cleanup() {{
162
162
  # Set trap to cleanup on exit
163
163
  trap cleanup EXIT
164
164
 
165
+ # Retention enforcement (defined here, run before the dump so it still
166
+ # executes even if a later backup step fails)
167
+ {_generate_retention_function(retention_days, host_name)}
168
+
165
169
  # Start backup
166
170
  log_message "Starting backup for {host_name}"
167
171
 
172
+ # Enforce retention up front so stale backups are pruned on every run,
173
+ # independent of whether the dump/upload below succeeds.
174
+ enforce_retention || true
175
+
168
176
  # Create temporary backup directory
169
177
  mkdir -p "$TEMP_BACKUP_DIR"
170
178
 
@@ -174,6 +182,23 @@ log_message "Creating consistent database backups"
174
182
  # List of databases to backup
175
183
  DATABASES=({db_array})
176
184
 
185
+ # Fail fast (with a clear message) if there isn't enough scratch space. VACUUM
186
+ # INTO writes a full copy of every DB, then those are tarred — so we need room
187
+ # for roughly the total DB size plus the archive. The threshold scales from the
188
+ # live DB sizes (1.5x), so it is correct on any host without a hard-coded value.
189
+ REQUIRED_KB=0
190
+ for DB in "${{DATABASES[@]}}"; do
191
+ if [ -f "$DB_DIR/$DB" ]; then
192
+ REQUIRED_KB=$((REQUIRED_KB + $(du -k "$DB_DIR/$DB" | cut -f1)))
193
+ fi
194
+ done
195
+ REQUIRED_KB=$((REQUIRED_KB * 3 / 2))
196
+ AVAIL_KB=$(df --output=avail "$(dirname "$TEMP_BACKUP_DIR")" | tail -1)
197
+ if [ "${{AVAIL_KB:-0}}" -lt "$REQUIRED_KB" ]; then
198
+ log_message "ERROR: only $((AVAIL_KB/1024))MB free, need ~$((REQUIRED_KB/1024))MB for backup scratch space — aborting DB dump"
199
+ exit 1
200
+ fi
201
+
177
202
  for DB in "${{DATABASES[@]}}"; do
178
203
  if [ -f "$DB_DIR/$DB" ]; then
179
204
  log_message "Backing up database: $DB"
@@ -296,36 +321,36 @@ else
296
321
  log_message "Media directory not found or empty at $MEDIA_DIR, skipping media backup"
297
322
  fi
298
323
 
299
- # Clean up old backups based on retention policy
300
- {_generate_retention_cleanup(retention_days, host_name)}
324
+ # (Retention was already enforced at the start of this run via enforce_retention.)
301
325
 
302
326
  log_message "Backup completed successfully for {host_name}"
303
327
  '''
304
328
 
305
329
 
306
- def _generate_retention_cleanup(retention_days: int, host_name: str) -> str:
307
- """Generate the retention cleanup section of the backup script."""
330
+ def _generate_retention_function(retention_days: int, host_name: str) -> str:
331
+ """Generate an ``enforce_retention()`` shell function for the backup script.
332
+
333
+ Defined near the top of the script and invoked *before* the database dump
334
+ so retention runs on every invocation even if a later step fails. Previously
335
+ the cleanup was the final step, guarded by ``set -e`` and multiple ``exit 1``
336
+ branches, so any dump/upload failure skipped it and old backups accumulated
337
+ without bound (this is what filled the linnahovi disk).
338
+ """
308
339
  if retention_days <= 0:
309
- return ""
340
+ return "enforce_retention() { :; } # retention disabled (retention_days<=0)"
310
341
  return (
311
- 'log_message "Cleaning up backup folders older than {days} days"\n'
312
- '\n'
313
- '# Calculate cutoff date\n'
314
- 'CUTOFF_DATE=$(date -d "{days} days ago" +"%Y-%m-%d")\n'
315
- '\n'
316
- '# List all date folders and delete those older than cutoff\n'
317
- 'rclone lsf "${{REMOTE_NAME}}:{host}/" --dirs-only --config "$RCLONE_CONFIG" | while read -r folder; do\n'
318
- ' # Remove trailing slash\n'
319
- ' folder_name="${{folder%/}}"\n'
320
- '\n'
321
- ' # Check if folder name matches date format and is older than cutoff\n'
322
- ' if [[ "$folder_name" =~ ^[0-9]{{4}}-[0-9]{{2}}-[0-9]{{2}}$ ]] && [[ "$folder_name" < "$CUTOFF_DATE" ]]; then\n'
323
- ' log_message "Deleting old backup folder: $folder_name"\n'
324
- ' rclone purge "${{REMOTE_NAME}}:{host}/$folder_name" --config "$RCLONE_CONFIG" --quiet\n'
325
- ' fi\n'
326
- 'done\n'
327
- '\n'
328
- 'log_message "Cleanup completed"'
342
+ 'enforce_retention() {{\n'
343
+ ' log_message "Enforcing retention: removing remote folders older than {days} days"\n'
344
+ ' local cutoff; cutoff=$(date -d "{days} days ago" +"%Y-%m-%d")\n'
345
+ ' rclone lsf "${{REMOTE_NAME}}:{host}/" --dirs-only --config "$RCLONE_CONFIG" 2>>"$LOG_FILE" | while read -r folder; do\n'
346
+ ' local name="${{folder%/}}"\n'
347
+ ' if [[ "$name" =~ ^[0-9]{{4}}-[0-9]{{2}}-[0-9]{{2}}$ ]] && [[ "$name" < "$cutoff" ]]; then\n'
348
+ ' log_message "Deleting old backup folder: $name"\n'
349
+ ' rclone purge "${{REMOTE_NAME}}:{host}/$name" --config "$RCLONE_CONFIG" --quiet 2>>"$LOG_FILE" \\\n'
350
+ ' || log_message "WARN: purge failed for $name"\n'
351
+ ' fi\n'
352
+ ' done\n'
353
+ '}}'
329
354
  ).format(days=retention_days, host=host_name)
330
355
 
331
356
 
@@ -176,6 +176,7 @@ class HostConfig(tuple, metaclass=HostConfigMetaclass):
176
176
  manage_py_path: str = "manage.py" # Relative path to manage.py in the artifact
177
177
  db_dir: Optional[str] = None # External database directory template
178
178
  generate_local_settings: bool = False # Generate local.py with DATABASES, ALLOWED_HOSTS, etc.
179
+ secret_key: Optional[str] = None # Django SECRET_KEY value (e.g. OpSecret) injected into generated local.py
179
180
  shared_resources: Optional[List[str]] = None # Paths to symlink from shared/
180
181
 
181
182
  # Per-module configuration (merged with defaults)
@@ -562,6 +562,11 @@ def generate_local_settings(host_data, artifact_path):
562
562
 
563
563
  lines.append("DEBUG = False\n")
564
564
 
565
+ secret_key = getattr(host_data, "secret_key", None)
566
+ if secret_key:
567
+ # str() resolves an OpSecret to its value; repr safely quotes/escapes it.
568
+ lines.append(f"SECRET_KEY = {str(secret_key)!r}\n")
569
+
565
570
  if is_zero_downtime(host_data) or is_bluegreen(host_data):
566
571
  static_path, media_path = get_static_media_paths(host_data)
567
572
  lines.append(f'STATIC_ROOT = "{static_path}"\n')
@@ -0,0 +1 @@
1
+ __version__ = "1.3.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 1.2.9
3
+ Version: 1.3.1
4
4
  Summary: Modular Django deployment system based on pyinfra
5
5
  Author-email: Johanna Mae Dimayuga <johanna@techco.fi>
6
6
  License: MIT License
@@ -204,7 +204,7 @@ All deployment configuration lives on `djaploy.config.HostConfig`. Commonly used
204
204
  | `shared_resources` | — | Extra paths symlinked from `shared/` |
205
205
  | `db_dir` | — | External database directory template |
206
206
  | `gunicorn_conf` | — | `workers`, `timeout`, `umask`, `wsgi_module`, `health_check_*` |
207
- | `nginx_conf` | — | `server_name`, `listen`, `client_max_body_size`, `custom` |
207
+ | `nginx_conf` | — | `server_name`, `listen`, `client_max_body_size`, `static_path`, `media_path`, `custom` |
208
208
  | `core_conf` | — | `poetry_no_root`, `exclude_groups`, `poetry_lock`, `databases` |
209
209
  | `versioning_conf` / `notifications_conf` | — | See [Release Notifications & Versioning](#release-notifications--versioning) |
210
210
  | `backup` / `borg_backup` | — | `BackupConfig` / `BorgBackupConfig` |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "djaploy"
3
- version = "1.2.9"
3
+ version = "1.3.1"
4
4
  description = "Modular Django deployment system based on pyinfra"
5
5
  authors = [
6
6
  {name = "Johanna Mae Dimayuga", email = "johanna@techco.fi"}
@@ -1 +0,0 @@
1
- __version__ = "1.2.9"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes