djaploy 0.9.1__tar.gz → 0.9.3__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 (48) hide show
  1. {djaploy-0.9.1 → djaploy-0.9.3}/PKG-INFO +1 -1
  2. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/config.py +11 -6
  3. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/core.py +53 -28
  4. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy.egg-info/PKG-INFO +1 -1
  5. {djaploy-0.9.1 → djaploy-0.9.3}/pyproject.toml +1 -1
  6. {djaploy-0.9.1 → djaploy-0.9.3}/LICENSE +0 -0
  7. {djaploy-0.9.1 → djaploy-0.9.3}/README.md +0 -0
  8. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/__init__.py +0 -0
  9. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/artifact.py +0 -0
  10. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/bin/__init__.py +0 -0
  11. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/bin/django_pyinfra.py +0 -0
  12. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/certificates.py +0 -0
  13. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/changelog.py +0 -0
  14. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/deploy.py +0 -0
  15. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/__init__.py +0 -0
  16. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/commands/__init__.py +0 -0
  17. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/commands/configureserver.py +0 -0
  18. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/commands/deploy.py +0 -0
  19. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/commands/rollback.py +0 -0
  20. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/commands/sync_certs.py +0 -0
  21. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/commands/update_certs.py +0 -0
  22. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/commands/verify.py +0 -0
  23. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/management/utils.py +0 -0
  24. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/__init__.py +0 -0
  25. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/base.py +0 -0
  26. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/cert_renewal.py +0 -0
  27. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/litestream.py +0 -0
  28. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/loader.py +0 -0
  29. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/nginx.py +0 -0
  30. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/rclone.py +0 -0
  31. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/sync_certs.py +0 -0
  32. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/systemd.py +0 -0
  33. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/tailscale.py +0 -0
  34. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/modules/versioning.py +0 -0
  35. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/notifications.py +0 -0
  36. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/utils.py +0 -0
  37. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/version.py +0 -0
  38. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy/versioning.py +0 -0
  39. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy.egg-info/SOURCES.txt +0 -0
  40. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy.egg-info/dependency_links.txt +0 -0
  41. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy.egg-info/entry_points.txt +0 -0
  42. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy.egg-info/requires.txt +0 -0
  43. {djaploy-0.9.1 → djaploy-0.9.3}/djaploy.egg-info/top_level.txt +0 -0
  44. {djaploy-0.9.1 → djaploy-0.9.3}/setup.cfg +0 -0
  45. {djaploy-0.9.1 → djaploy-0.9.3}/tests/test_config.py +0 -0
  46. {djaploy-0.9.1 → djaploy-0.9.3}/tests/test_core_module.py +0 -0
  47. {djaploy-0.9.1 → djaploy-0.9.3}/tests/test_deploy_scripts.py +0 -0
  48. {djaploy-0.9.1 → djaploy-0.9.3}/tests/test_systemd_module.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 0.9.1
3
+ Version: 0.9.3
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
@@ -71,7 +71,7 @@ class DjaployConfig:
71
71
  artifact_dir: str = "deployment"
72
72
  deployment_strategy: str = "in_place" # "in_place" or "zero_downtime"
73
73
  keep_releases: int = 5 # Number of releases to keep (zero_downtime only)
74
- # Paths (relative to app root) to symlink from shared/ into each release.
74
+ # Paths (relative to git/artifact root) to symlink from shared/ into each release.
75
75
  # When None (default), auto-detected from Django settings (STATIC_ROOT,
76
76
  # MEDIA_ROOT, PRIVATE_MEDIA_ROOT). Set explicitly to override or to [] to disable.
77
77
  # Note: venv is shared implicitly via the stable current/ symlink path.
@@ -118,8 +118,8 @@ class DjaployConfig:
118
118
  """Auto-detect shared resources from Django settings.
119
119
 
120
120
  Resolves STATIC_ROOT, MEDIA_ROOT and PRIVATE_MEDIA_ROOT relative to
121
- project_dir. Only includes paths that are inside the project directory
122
- (paths outside the app dir don't need symlinking).
121
+ git_dir (the artifact/release root). Only includes paths that are
122
+ inside the project directory (paths outside don't need symlinking).
123
123
  """
124
124
  resources = []
125
125
  try:
@@ -127,17 +127,22 @@ class DjaployConfig:
127
127
  if not django_settings.configured:
128
128
  return resources
129
129
 
130
- base_dir = Path(django_settings.BASE_DIR)
130
+ # Resolve relative to git_dir (artifact root) so paths match
131
+ # the release directory structure, not BASE_DIR which may be
132
+ # a subdirectory of the artifact.
133
+ artifact_root = self.git_dir or self.project_dir
134
+ if artifact_root is None:
135
+ return resources
131
136
 
132
137
  for setting_name in ('STATIC_ROOT', 'MEDIA_ROOT', 'PRIVATE_MEDIA_ROOT'):
133
138
  value = getattr(django_settings, setting_name, None)
134
139
  if value:
135
140
  abs_path = Path(value)
136
141
  try:
137
- rel = str(abs_path.relative_to(base_dir))
142
+ rel = str(abs_path.relative_to(artifact_root))
138
143
  resources.append(rel)
139
144
  except ValueError:
140
- pass # Outside BASE_DIR, no symlink needed
145
+ pass # Outside artifact root, no symlink needed
141
146
  except Exception:
142
147
  pass
143
148
  return resources
@@ -136,15 +136,19 @@ class CoreModule(BaseModule):
136
136
  _sudo=True,
137
137
  )
138
138
 
139
- # Create shared resource directories
139
+ # Create shared resource directories (mkdir -p for nested paths)
140
140
  shared_resources = getattr(project_config, 'shared_resources', [])
141
- for resource in shared_resources:
142
- if not resource.startswith('.'):
143
- files.directory(
144
- name=f"Create shared/{resource} directory",
145
- path=f"{app_path}/shared/{resource}",
146
- user=app_user,
147
- group=app_user,
141
+ if shared_resources:
142
+ mkdir_commands = [
143
+ f"mkdir -p {app_path}/shared/{resource}"
144
+ for resource in shared_resources
145
+ if not resource.startswith('.')
146
+ ]
147
+ if mkdir_commands:
148
+ mkdir_commands.append(f"chown -R {app_user}:{app_user} {app_path}/shared")
149
+ server.shell(
150
+ name="Create shared resource directories",
151
+ commands=mkdir_commands,
148
152
  _sudo=True,
149
153
  )
150
154
 
@@ -259,13 +263,35 @@ class CoreModule(BaseModule):
259
263
  def post_deploy(self, host_data: Dict[str, Any], project_config: Dict[str, Any], artifact_path: Path):
260
264
  """Run migrations and collectstatic after all modules have deployed their files"""
261
265
  app_user = getattr(host_data, 'app_user', None) or project_config.app_user
266
+
262
267
  if self._is_zero_downtime(project_config):
263
- app_path = f"{self._get_app_path(host_data, project_config)}/current"
268
+ # Run migrations and collectstatic via the stable build/ symlink
269
+ # so Poetry resolves the same virtualenv used during install.
270
+ # Old workers keep serving from current/ without interruption.
271
+ base_path = self._get_app_path(host_data, project_config)
272
+ build_path = f"{base_path}/build"
273
+ release_path = getattr(host.data, '_zero_downtime_release_path', None)
274
+ app_path = build_path if release_path else f"{base_path}/current"
275
+
276
+ self._run_migrations(app_user, app_path, project_config)
277
+ self._collect_static(app_user, app_path, project_config)
278
+
279
+ # Atomic symlink swap AFTER everything is ready.
280
+ # Old workers continue serving until USR2 triggers the handoff.
281
+ if release_path:
282
+ server.shell(
283
+ name="Swap current symlink to new release",
284
+ commands=[
285
+ f"ln -sfn {release_path} {base_path}/current.tmp && mv -Tf {base_path}/current.tmp {base_path}/current",
286
+ ],
287
+ _sudo=True,
288
+ _sudo_user=app_user,
289
+ _use_sudo_login=True,
290
+ )
264
291
  else:
265
292
  app_path = self._get_app_path(host_data, project_config)
266
-
267
- self._run_migrations(app_user, app_path, project_config)
268
- self._collect_static(app_user, app_path, project_config)
293
+ self._run_migrations(app_user, app_path, project_config)
294
+ self._collect_static(app_user, app_path, project_config)
269
295
 
270
296
  def rollback(self, host_data: Dict[str, Any], project_config: Dict[str, Any], release: str = None):
271
297
  """Roll back to a previous release by swapping the current symlink"""
@@ -433,27 +459,24 @@ class CoreModule(BaseModule):
433
459
  if getattr(host_data, 'pregenerate_certificates', False):
434
460
  self._generate_ssl_certificates(host_data, app_user)
435
461
 
436
- # Atomic symlink swap BEFORE installing deps so Poetry sees a stable
437
- # path (current/) and reuses the same virtualenv across releases.
438
- # Use mv -Tf for a truly atomic swap (ln -sfn unlinks then creates,
439
- # leaving a brief window where current doesn't exist).
462
+ # Install deps via a stable build/ symlink so Poetry reuses the same
463
+ # virtualenv across releases (Poetry keys venvs by directory path).
464
+ # The symlink swap to current/ is deferred to post_deploy so old
465
+ # workers keep serving from the previous release without interruption.
466
+ build_link = f"{app_path}/build"
440
467
  server.shell(
441
- name=f"Swap current symlink to {release_name}",
468
+ name="Create stable build symlink for Poetry",
442
469
  commands=[
443
- f"ln -sfn {release_path} {app_path}/current.tmp && mv -Tf {app_path}/current.tmp {app_path}/current",
470
+ f"ln -sfn {release_path} {build_link}",
444
471
  ],
445
472
  _sudo=True,
446
473
  _sudo_user=app_user,
447
474
  _use_sudo_login=True,
448
475
  )
476
+ self._install_dependencies(app_user, build_link, project_config)
449
477
 
450
- # Install deps via the stable current/ path.
451
- # Poetry keys virtualenvs by directory — using current/ means it
452
- # reuses the same venv across deploys (shared venv behavior).
453
- # Migrations and collectstatic are deferred to post_deploy so that
454
- # other modules (e.g. local_settings) can place config files first.
455
- current_path = f"{app_path}/current"
456
- self._install_dependencies(app_user, current_path, project_config)
478
+ # Store release path so other modules and post_deploy can access it
479
+ host.data._zero_downtime_release_path = release_path
457
480
 
458
481
  # Clean up old releases
459
482
  keep_releases = getattr(project_config, 'keep_releases', 5)
@@ -531,11 +554,13 @@ class CoreModule(BaseModule):
531
554
 
532
555
  python_version = project_config.python_version
533
556
 
557
+ poetry_bin = f"/home/{app_user}/.local/bin/poetry"
558
+
534
559
  commands = [
535
- # First configure Poetry to not use in-project virtualenvs on the server
536
- f"/home/{app_user}/.local/bin/poetry config virtualenvs.in-project false",
560
+ # Configure Poetry to not use in-project virtualenvs on the server
561
+ f"{poetry_bin} config virtualenvs.in-project false",
537
562
  # Ensure the virtualenv uses the configured Python version
538
- f"/home/{app_user}/.local/bin/poetry env use python{python_version}",
563
+ f"{poetry_bin} env use python{python_version}",
539
564
  ]
540
565
 
541
566
  # Optionally regenerate the lock file before installation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 0.9.1
3
+ Version: 0.9.3
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "djaploy"
3
- version = "0.9.1"
3
+ version = "0.9.3"
4
4
  description = "Modular Django deployment system based on pyinfra"
5
5
  authors = [
6
6
  {name = "Johanna Mae Dimayuga", email = "johanna@techco.fi"}
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