octopize.deploy_tool 0.1.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
@@ -30,6 +30,8 @@ import yaml
30
30
  from jinja2 import Environment, FileSystemLoader
31
31
 
32
32
  from octopize_avatar_deploy.download_templates import (
33
+ REQUIRED_DOCKER_FILES,
34
+ REQUIRED_TEMPLATE_FILES,
33
35
  LocalTemplateProvider,
34
36
  download_templates,
35
37
  )
@@ -187,7 +189,9 @@ class DeploymentConfigurator:
187
189
 
188
190
  output_path = self.output_dir / output_name
189
191
  output_path.parent.mkdir(parents=True, exist_ok=True)
190
- output_path.write_text(rendered)
192
+ # Ensure trailing newline for pre-commit end-of-file-fixer
193
+ content = rendered if rendered.endswith("\n") else rendered + "\n"
194
+ output_path.write_text(content)
191
195
 
192
196
  self.printer.print_success(f"Generated: {output_path}")
193
197
  except Exception as e:
@@ -215,13 +219,32 @@ class DeploymentConfigurator:
215
219
  "authentik/octopize-avatar-blueprint.yaml",
216
220
  )
217
221
 
218
- # Copy docker-compose.yml from templates (it may be templated in the future)
222
+ # Copy docker-compose.yml from templates
219
223
  docker_compose_src = self.templates_dir / "docker-compose.yml"
220
224
  docker_compose_dst = self.output_dir / "docker-compose.yml"
221
225
  if docker_compose_src.exists():
222
226
  shutil.copy2(docker_compose_src, docker_compose_dst)
223
227
  self.printer.print_success(f"Generated: {docker_compose_dst}")
224
228
 
229
+ # Copy authentik custom templates (email templates)
230
+ custom_templates_src = self.templates_dir / "authentik" / "custom-templates"
231
+ custom_templates_dst = self.output_dir / "authentik" / "custom-templates"
232
+ if custom_templates_src.exists():
233
+ custom_templates_dst.mkdir(parents=True, exist_ok=True)
234
+ for template_file in custom_templates_src.glob("*.html"):
235
+ shutil.copy2(template_file, custom_templates_dst / template_file.name)
236
+ self.printer.print_success(f"Copied: email templates to {custom_templates_dst}")
237
+
238
+ # Copy authentik branding files
239
+ branding_src = self.templates_dir / "authentik" / "branding"
240
+ branding_dst = self.output_dir / "authentik" / "branding"
241
+ if branding_src.exists():
242
+ branding_dst.mkdir(parents=True, exist_ok=True)
243
+ for branding_file in branding_src.glob("*"):
244
+ if branding_file.is_file():
245
+ shutil.copy2(branding_file, branding_dst / branding_file.name)
246
+ self.printer.print_success(f"Copied: branding files to {branding_dst}")
247
+
225
248
  self.printer.print()
226
249
  self.printer.print_success("Configuration files generated successfully!")
227
250
 
@@ -263,7 +286,7 @@ class DeploymentConfigurator:
263
286
  # Check for existing state and prompt to resume or restart
264
287
  if self.state and self.state.has_started() and not self.state.is_complete():
265
288
  if interactive:
266
- self.state.print_status()
289
+ self.state.print_status(self.printer)
267
290
  self.printer.print_header("")
268
291
  response = self.input_gatherer.prompt_yes_no(
269
292
  "Resume from where you left off?", default=True
@@ -481,16 +504,19 @@ class DeploymentRunner:
481
504
  Returns:
482
505
  True if templates are available, False otherwise
483
506
  """
484
- from octopize_avatar_deploy.download_templates import REQUIRED_FILES
485
-
486
507
  if not self.templates_dir.exists():
487
508
  if self.verbose:
488
509
  self.printer.print_error(f"Templates directory not found: {self.templates_dir}")
489
510
  return False
490
511
 
491
- # Check for required files
512
+ # Check for required template files
492
513
  missing_files = []
493
- for filename in REQUIRED_FILES:
514
+ for filename in REQUIRED_TEMPLATE_FILES:
515
+ if not (self.templates_dir / filename).exists():
516
+ missing_files.append(filename)
517
+
518
+ # Check for required docker files
519
+ for filename in REQUIRED_DOCKER_FILES:
494
520
  if not (self.templates_dir / filename).exists():
495
521
  missing_files.append(filename)
496
522
 
@@ -501,8 +527,9 @@ class DeploymentRunner:
501
527
  )
502
528
  return False
503
529
 
530
+ total_files = len(REQUIRED_TEMPLATE_FILES) + len(REQUIRED_DOCKER_FILES)
504
531
  if self.verbose:
505
- self.printer.print_success(f"Found all {len(REQUIRED_FILES)} required template files")
532
+ self.printer.print_success(f"Found all {total_files} required template files")
506
533
 
507
534
  # Validate template version compatibility
508
535
  return self._validate_template_version()
@@ -1,63 +1,47 @@
1
- # Default Configuration Values for Avatar Deployment
2
- # Version: 1.0.0 (MAJOR.MINOR.PATCH)
3
- # Compatible with script version: >=1.0.0,<2.0.0
4
-
5
- version: "1.0.0"
6
-
7
- # Image versions - updated regularly with new releases
1
+ version: 1.0.0
8
2
  images:
9
- api: "2.20.1"
10
- web: "0.40.0"
11
- pdfgenerator: "latest"
12
- seaweedfs: "0.2.0"
13
- authentik: "2025.10.2"
14
-
15
- # Email configuration defaults
3
+ api: 2.47.0
4
+ web: 0.52.0
5
+ pdfgenerator: 0.2.3
6
+ seaweedfs: 0.4.0
7
+ authentik: 2025.12.3
16
8
  email:
17
- provider: "aws" # 'aws' or 'smtp'
9
+ provider: aws
18
10
  smtp:
19
- host: "email-smtp.eu-west-3.amazonaws.com"
20
- port: "587"
21
- use_tls: "true"
22
- start_tls: "false"
23
- verify: "true"
24
- sender_email: "noreply@octopize.io"
25
-
26
- # Telemetry configuration (for usage analytics)
11
+ host: email-smtp.eu-west-3.amazonaws.com
12
+ port: '587'
13
+ use_tls: 'true'
14
+ start_tls: 'false'
15
+ verify: 'true'
16
+ sender_email: noreply@octopize.io
27
17
  telemetry:
28
- enabled: true # Ask user if they want to enable telemetry
29
- endpoint_url: "https://s3.fr-par.scw.cloud"
30
- region: "fr-par"
31
-
32
- # Application settings (defaults when no preset is selected)
18
+ enabled: true
19
+ endpoint_url: https://s3.fr-par.scw.cloud
20
+ region: fr-par
33
21
  application:
34
- use_console_logging: "false"
35
- log_level: "INFO"
36
- sentry_enabled: "true"
37
- email_authentication: "true"
38
-
39
- # Deployment presets - pre-configured settings for common scenarios
22
+ use_console_logging: 'false'
23
+ log_level: INFO
24
+ sentry_enabled: 'true'
25
+ email_authentication: 'true'
40
26
  presets:
41
27
  default:
42
- description: "Production-ready configuration with telemetry and monitoring"
28
+ description: Production-ready configuration with telemetry and monitoring
43
29
  application:
44
- use_console_logging: "false"
45
- sentry_enabled: "true"
30
+ use_console_logging: 'false'
31
+ sentry_enabled: 'true'
46
32
  telemetry:
47
33
  enabled: true
48
-
49
34
  dev-mode:
50
- description: "Development mode with console logging, no external services"
35
+ description: Development mode with console logging, no external services
51
36
  application:
52
- use_console_logging: "true"
53
- sentry_enabled: "false"
37
+ use_console_logging: 'true'
38
+ sentry_enabled: 'false'
54
39
  telemetry:
55
40
  enabled: false
56
-
57
41
  airgapped:
58
- description: "Air-gapped deployment without external monitoring or telemetry"
42
+ description: Air-gapped deployment without external monitoring or telemetry
59
43
  application:
60
- use_console_logging: "false"
61
- sentry_enabled: "false"
44
+ use_console_logging: 'false'
45
+ sentry_enabled: 'false'
62
46
  telemetry:
63
47
  enabled: false
@@ -15,7 +15,7 @@ GITHUB_RAW_BASE = "https://raw.githubusercontent.com/octopize/avatar-deployment"
15
15
  DEFAULT_BRANCH = "main"
16
16
 
17
17
  # Files to download from the docker/templates/ directory
18
- REQUIRED_FILES = [
18
+ REQUIRED_TEMPLATE_FILES = [
19
19
  ".env.template",
20
20
  "nginx.conf.template",
21
21
  "docker-compose.yml",
@@ -23,6 +23,19 @@ REQUIRED_FILES = [
23
23
  "authentik/octopize-avatar-blueprint.yaml.j2",
24
24
  ]
25
25
 
26
+ # Additional files to download from docker/ directory (not in templates/)
27
+ REQUIRED_DOCKER_FILES = [
28
+ "authentik/custom-templates/email_account_confirmation.html",
29
+ "authentik/custom-templates/email_account_exists.html",
30
+ "authentik/custom-templates/email_account_invitation.html",
31
+ "authentik/custom-templates/email_forgotten_password.html",
32
+ "authentik/custom-templates/email_password_changed.html",
33
+ "authentik/custom-templates/email_password_reset.html",
34
+ "authentik/branding/favicon.ico",
35
+ "authentik/branding/logo.png",
36
+ "authentik/branding/background.png",
37
+ ]
38
+
26
39
 
27
40
  class TemplateProvider(ABC):
28
41
  """Abstract base class for template providers."""
@@ -37,12 +50,26 @@ class TemplateProvider(ABC):
37
50
  self.verbose = verbose
38
51
 
39
52
  @abstractmethod
40
- def provide_file(self, filename: str, destination: Path) -> bool:
53
+ def provide_template_file(self, filename: str, destination: Path) -> bool:
54
+ """
55
+ Provide a single template file to the destination.
56
+
57
+ Args:
58
+ filename: Name of file to provide (from docker/templates/)
59
+ destination: Local path where file should be saved
60
+
61
+ Returns:
62
+ True if successful, False otherwise
63
+ """
64
+ pass
65
+
66
+ @abstractmethod
67
+ def provide_docker_file(self, filename: str, destination: Path) -> bool:
41
68
  """
42
- Provide a single file to the destination.
69
+ Provide a single docker file to the destination.
43
70
 
44
71
  Args:
45
- filename: Name of file to provide
72
+ filename: Name of file to provide (from docker/)
46
73
  destination: Local path where file should be saved
47
74
 
48
75
  Returns:
@@ -52,7 +79,7 @@ class TemplateProvider(ABC):
52
79
 
53
80
  def provide_all(self, output_dir: Path) -> bool:
54
81
  """
55
- Provide all required template files.
82
+ Provide all required template and docker files.
56
83
 
57
84
  Args:
58
85
  output_dir: Directory where files should be saved
@@ -67,9 +94,17 @@ class TemplateProvider(ABC):
67
94
  print(f"\nProviding templates to {output_dir}/")
68
95
  print("=" * 60)
69
96
 
70
- for filename in REQUIRED_FILES:
97
+ # Provide template files
98
+ for filename in REQUIRED_TEMPLATE_FILES:
99
+ destination = output_dir / filename
100
+ if not self.provide_template_file(filename, destination):
101
+ success = False
102
+ print(f"⚠ Warning: Failed to provide {filename}")
103
+
104
+ # Provide docker files (authentik branding, email templates, etc.)
105
+ for filename in REQUIRED_DOCKER_FILES:
71
106
  destination = output_dir / filename
72
- if not self.provide_file(filename, destination):
107
+ if not self.provide_docker_file(filename, destination):
73
108
  success = False
74
109
  print(f"⚠ Warning: Failed to provide {filename}")
75
110
 
@@ -92,7 +127,10 @@ class TemplateProvider(ABC):
92
127
  True if all required files exist
93
128
  """
94
129
  output_dir = Path(output_dir)
95
- for filename in REQUIRED_FILES:
130
+ for filename in REQUIRED_TEMPLATE_FILES:
131
+ if not (output_dir / filename).exists():
132
+ return False
133
+ for filename in REQUIRED_DOCKER_FILES:
96
134
  if not (output_dir / filename).exists():
97
135
  return False
98
136
  return True
@@ -111,11 +149,12 @@ class GitHubTemplateProvider(TemplateProvider):
111
149
  """
112
150
  super().__init__(verbose=verbose)
113
151
  self.branch = branch
114
- self.base_url = f"{GITHUB_RAW_BASE}/{branch}/docker/templates"
152
+ self.templates_base_url = f"{GITHUB_RAW_BASE}/{branch}/docker/templates"
153
+ self.docker_base_url = f"{GITHUB_RAW_BASE}/{branch}/docker"
115
154
 
116
- def provide_file(self, filename: str, destination: Path) -> bool:
155
+ def provide_template_file(self, filename: str, destination: Path) -> bool:
117
156
  """
118
- Download a single file from GitHub.
157
+ Download a single template file from GitHub.
119
158
 
120
159
  Args:
121
160
  filename: Name of file to download (in docker/templates/ directory)
@@ -124,7 +163,7 @@ class GitHubTemplateProvider(TemplateProvider):
124
163
  Returns:
125
164
  True if successful, False otherwise
126
165
  """
127
- url = f"{self.base_url}/{filename}"
166
+ url = f"{self.templates_base_url}/{filename}"
128
167
 
129
168
  if self.verbose:
130
169
  print(f"Downloading {filename}...")
@@ -151,21 +190,43 @@ class GitHubTemplateProvider(TemplateProvider):
151
190
  print(f" ✗ Failed: {e}")
152
191
  return False
153
192
 
154
- def check_cached_templates(self, output_dir: Path) -> bool:
193
+ def provide_docker_file(self, filename: str, destination: Path) -> bool:
155
194
  """
156
- Check if templates are already cached locally.
195
+ Download a single docker file from GitHub.
157
196
 
158
197
  Args:
159
- output_dir: Directory to check for cached templates
198
+ filename: Name of file to download (in docker/ directory)
199
+ destination: Local path where file should be saved
160
200
 
161
201
  Returns:
162
- True if all required files exist
202
+ True if successful, False otherwise
163
203
  """
164
- output_dir = Path(output_dir)
165
- for filename in REQUIRED_FILES:
166
- if not (output_dir / filename).exists():
167
- return False
168
- return True
204
+ url = f"{self.docker_base_url}/{filename}"
205
+
206
+ if self.verbose:
207
+ print(f"Downloading {filename}...")
208
+ print(f" URL: {url}")
209
+ print(f" Destination: {destination}")
210
+
211
+ try:
212
+ with urllib.request.urlopen(url, timeout=10) as response:
213
+ content = response.read()
214
+
215
+ # Ensure parent directory exists
216
+ destination.parent.mkdir(parents=True, exist_ok=True)
217
+
218
+ # Write file
219
+ destination.write_bytes(content)
220
+
221
+ if self.verbose:
222
+ print(f" ✓ Downloaded {len(content)} bytes")
223
+
224
+ return True
225
+
226
+ except Exception as e:
227
+ if self.verbose:
228
+ print(f" ✗ Failed: {e}")
229
+ return False
169
230
 
170
231
 
171
232
  class LocalTemplateProvider(TemplateProvider):
@@ -176,18 +237,20 @@ class LocalTemplateProvider(TemplateProvider):
176
237
  Initialize local template provider.
177
238
 
178
239
  Args:
179
- source_dir: Local directory containing template files
240
+ source_dir: Local directory containing template files (docker/templates/)
180
241
  verbose: Print progress information
181
242
  """
182
243
  super().__init__(verbose=verbose)
183
244
  self.source_dir = Path(source_dir)
245
+ # Parent of templates is docker/
246
+ self.docker_dir = self.source_dir.parent
184
247
 
185
- def provide_file(self, filename: str, destination: Path) -> bool:
248
+ def provide_template_file(self, filename: str, destination: Path) -> bool:
186
249
  """
187
- Copy a single file from source to destination.
250
+ Copy a single template file from source to destination.
188
251
 
189
252
  Args:
190
- filename: Name of file to copy
253
+ filename: Name of file to copy (from docker/templates/)
191
254
  destination: Local path where file should be saved
192
255
 
193
256
  Returns:
@@ -220,6 +283,44 @@ class LocalTemplateProvider(TemplateProvider):
220
283
  print(f" ✗ Failed: {e}")
221
284
  return False
222
285
 
286
+ def provide_docker_file(self, filename: str, destination: Path) -> bool:
287
+ """
288
+ Copy a single docker file from source to destination.
289
+
290
+ Args:
291
+ filename: Name of file to copy (from docker/)
292
+ destination: Local path where file should be saved
293
+
294
+ Returns:
295
+ True if successful, False otherwise
296
+ """
297
+ source = self.docker_dir / filename
298
+
299
+ if self.verbose:
300
+ print(f"Copying {filename}...")
301
+ print(f" Source: {source}")
302
+ print(f" Destination: {destination}")
303
+
304
+ try:
305
+ if not source.exists():
306
+ raise FileNotFoundError(f"Source file not found: {source}")
307
+
308
+ # Ensure parent directory exists
309
+ destination.parent.mkdir(parents=True, exist_ok=True)
310
+
311
+ # Copy file
312
+ shutil.copy2(source, destination)
313
+
314
+ if self.verbose:
315
+ print(f" ✓ Copied {source.stat().st_size} bytes")
316
+
317
+ return True
318
+
319
+ except Exception as e:
320
+ if self.verbose:
321
+ print(f" ✗ Failed: {e}")
322
+ return False
323
+
223
324
 
224
325
  def download_templates(
225
326
  output_dir: Path,
@@ -116,21 +116,28 @@ class DeploymentState:
116
116
  total = len(self.steps)
117
117
  return f"{completed}/{total} steps completed"
118
118
 
119
- def print_status(self) -> None:
120
- """Print current status."""
121
- print("\n" + "=" * 60)
122
- print("Deployment Configuration Status")
123
- print("=" * 60)
124
- print(f"\n{self.get_progress_summary()}\n")
119
+ def print_status(self, printer=None) -> None:
120
+ """
121
+ Print current status.
122
+
123
+ Args:
124
+ printer: Optional printer to use for output. If None, uses built-in print().
125
+ """
126
+ _print = printer.print if printer else print
127
+
128
+ _print("\n" + "=" * 60)
129
+ _print("Deployment Configuration Status")
130
+ _print("=" * 60)
131
+ _print(f"\n{self.get_progress_summary()}\n")
125
132
 
126
133
  for step in self.steps:
127
134
  status = self.get_step_status(step)
128
135
  icon = "✓" if status == "completed" else "○" if status == "not-started" else "◐"
129
- print(f" {icon} {step.replace('_', ' ').title()}: {status}")
136
+ _print(f" {icon} {step.replace('_', ' ').title()}: {status}")
130
137
 
131
138
  if self.is_complete():
132
- print("\n✓ Configuration is complete!")
139
+ _print("\n✓ Configuration is complete!")
133
140
  elif self.has_started():
134
141
  next_step = self.get_next_step()
135
142
  if next_step:
136
- print(f"\n→ Next step: {next_step.replace('_', ' ').title()}")
143
+ _print(f"\n→ Next step: {next_step.replace('_', ' ').title()}")
@@ -19,11 +19,16 @@ class RequiredConfigStep(DeploymentStep):
19
19
 
20
20
  # Public URL - Required
21
21
  if "PUBLIC_URL" in self.config:
22
- config["PUBLIC_URL"] = self.config["PUBLIC_URL"]
22
+ public_url = self.config["PUBLIC_URL"]
23
23
  elif self.interactive:
24
- config["PUBLIC_URL"] = self.prompt("Public URL (domain name, e.g., avatar.example.com)")
24
+ public_url = self.prompt("Public URL (domain name, e.g., avatar.example.com)")
25
25
  else:
26
- config["PUBLIC_URL"] = ""
26
+ public_url = ""
27
+
28
+ # Normalize PUBLIC_URL to strip protocol and store just the domain
29
+ if public_url:
30
+ public_url = public_url.replace("https://", "").replace("http://", "").rstrip("/")
31
+ config["PUBLIC_URL"] = public_url
27
32
 
28
33
  # Environment name - Required
29
34
  if "ENV_NAME" in self.config:
@@ -50,9 +50,10 @@ class TelemetryStep(DeploymentStep):
50
50
  def generate_secrets(self) -> dict[str, str]:
51
51
  """Generate telemetry-related secrets."""
52
52
  # Only generate secrets if telemetry is enabled
53
- if self.config["TELEMETRY_S3_ENDPOINT_URL"]:
54
- return {
55
- "telemetry_s3_access_key_id": "",
56
- "telemetry_s3_secret_access_key": "",
57
- }
53
+
54
+ # The user will have to provide these if telemetry is enabled
55
+ return {
56
+ "telemetry_s3_access_key_id": "",
57
+ "telemetry_s3_secret_access_key": "",
58
+ }
58
59
  return {}
@@ -9,7 +9,7 @@ import re
9
9
  from pathlib import Path
10
10
 
11
11
  # Script version (semantic versioning: MAJOR.MINOR.PATCH)
12
- SCRIPT_VERSION = "2.1.0"
12
+ SCRIPT_VERSION = "2.6.0"
13
13
 
14
14
 
15
15
  class VersionError(Exception):
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: octopize.deploy_tool
3
+ Version: 0.3.0
4
+ Summary: Deployment configuration tool for Octopize Avatar platform
5
+ Project-URL: Homepage, https://octopize.io
6
+ Project-URL: Documentation, https://docs.octopize.io/docs/deploying/self-hosted
7
+ Project-URL: Repository, https://github.com/octopize/avatar-deployment
8
+ Project-URL: Issues, https://github.com/octopize/avatar-deployment/issues
9
+ Author-email: Octopize <contact@octopize.io>
10
+ License: MIT
11
+ Keywords: avatar,configuration,deployment,octopize
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: System :: Installation/Setup
19
+ Classifier: Topic :: System :: Systems Administration
20
+ Requires-Python: >=3.13
21
+ Requires-Dist: jinja2>=3.1.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: rich>=13.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy; extra == 'dev'
26
+ Requires-Dist: pytest>=7.0; extra == 'dev'
27
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Octopize Avatar Deployment Tool
31
+
32
+ Automated configuration tool for deploying Octopize Avatar platform using Docker Compose.
33
+
34
+ ## Quick Start
35
+
36
+ ```bash
37
+ # Install
38
+ pip install octopize-avatar-deploy
39
+
40
+ # Run interactive configuration
41
+ octopize-avatar-deploy --output-dir /app/avatar
42
+
43
+ # Deploy
44
+ cd /app/avatar
45
+ docker compose down --volumes --remove-orphans # Clean old containers if redeploying
46
+ docker compose up -d
47
+ ```
48
+
49
+ ## Usage Options
50
+
51
+ ### Interactive Mode (Default)
52
+
53
+ ```bash
54
+ octopize-avatar-deploy --output-dir /app/avatar
55
+ ```
56
+
57
+ ### Non-Interactive Mode
58
+
59
+ ```bash
60
+ # Create config file
61
+ cat > config.yaml << EOF
62
+ PUBLIC_URL: avatar.example.com
63
+ ENV_NAME: prod
64
+ ORGANIZATION_NAME: MyCompany
65
+ EOF
66
+
67
+ # Run with config
68
+ octopize-avatar-deploy --output-dir /app/avatar --config config.yaml --non-interactive
69
+ ```
70
+
71
+ ## Command Line Options
72
+
73
+ ```
74
+ --output-dir DIR Output directory (default: current directory)
75
+ --config FILE YAML configuration file
76
+ --non-interactive Non-interactive mode (requires config file)
77
+ --template-from PATH Use local templates instead of downloading from GitHub
78
+ --save-config Save configuration to deployment-config.yaml
79
+ --verbose Show detailed output
80
+ ```
81
+
82
+ ## What Gets Generated
83
+
84
+ ```
85
+ /app/avatar/
86
+ ├── .env # Environment configuration
87
+ ├── docker-compose.yml # Docker services
88
+ ├── nginx/nginx.conf # Nginx config
89
+ ├── authentik/
90
+ │ ├── octopize-avatar-blueprint.yaml
91
+ │ ├── custom-templates/ # Email templates
92
+ │ └── branding/ # Logo, favicon, background
93
+ └── .secrets/ # Generated secrets (22 files)
94
+ ```
95
+
96
+ ## Deployment Steps
97
+
98
+ 1. **Generate configuration:**
99
+
100
+ ```bash
101
+ octopize-avatar-deploy --output-dir /app/avatar
102
+ ```
103
+
104
+ 2. **Review generated files:**
105
+
106
+ ```bash
107
+ cd /app/avatar
108
+ cat .env
109
+ ls -la .secrets/
110
+ ```
111
+
112
+ 3. **Add TLS certificates (production):**
113
+
114
+ ```bash
115
+ mkdir -p tls
116
+ cp /path/to/fullchain.pem tls/
117
+ cp /path/to/privkey.pem tls/
118
+ ```
119
+
120
+ 4. **Start services:**
121
+
122
+ ```bash
123
+ docker compose down --volumes --remove-orphans
124
+ docker compose up -d
125
+ ```
126
+
127
+ 5. **Verify deployment:**
128
+
129
+ ```bash
130
+ docker compose ps
131
+ docker compose logs -f
132
+ curl https://avatar.example.com/api/health
133
+ ```
134
+
135
+ ## Troubleshooting
136
+
137
+ ### "bind source path does not exist" error
138
+
139
+ Old containers from previous deployment. Solution:
140
+
141
+ ```bash
142
+ docker compose down --volumes --remove-orphans
143
+ docker compose up -d
144
+ ```
145
+
146
+ ### Templates not downloading
147
+
148
+ ```bash
149
+ rm -rf .avatar-templates/
150
+ octopize-avatar-deploy --output-dir /app/avatar --verbose
151
+ ```
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ # Clone
157
+ git clone https://github.com/octopize/avatar-deployment
158
+ cd avatar-deployment/deployment-tool
159
+
160
+ # Install dependencies
161
+ just install
162
+
163
+ # Run tests
164
+ just test-all
165
+
166
+ # Run locally
167
+ just run-interactive-local
168
+ ```
@@ -1,12 +1,12 @@
1
1
  octopize_avatar_deploy/__init__.py,sha256=5RK4GI9PpEGMfABm3nZ2rEDyohQ0W_Hvn_cHQBBnAsw,651
2
2
  octopize_avatar_deploy/cli_test_harness.py,sha256=wIMKQ3VPuJDg6LVQcJVQA8WYbKOEwNwUPBD2swl5WkU,6703
3
- octopize_avatar_deploy/configure.py,sha256=BTyX0gzp-L3XpCR81VsCS5oubv6m57ltwqf7pUFVyks,24657
4
- octopize_avatar_deploy/defaults.yaml,sha256=-64srbym-Ex_kPCFq6DkZet8icBCkjadOrycEDAQUas,1649
5
- octopize_avatar_deploy/download_templates.py,sha256=x5ylpv1TfgbHga2AM1qZpJYrT8PxNlL2bLMj9tCsxJA,7152
3
+ octopize_avatar_deploy/configure.py,sha256=BnNFFVAmA64gCV1df9bVpL3QM5xBMCB_mIOBGn-Zqls,26174
4
+ octopize_avatar_deploy/defaults.yaml,sha256=sZWjUOza8XXYTT7m2mcUWADXHaFqefH_jv50ySpWBX0,1144
5
+ octopize_avatar_deploy/download_templates.py,sha256=86oG4356rciqbEzmJSjHhwUEt0MZhKA4FBPUQbzWL2U,10797
6
6
  octopize_avatar_deploy/input_gatherer.py,sha256=HztvV1_nK2ZWjtZWVoQcwQNjAtJbPJT0dDQ5l_GpSWU,11287
7
7
  octopize_avatar_deploy/printer.py,sha256=3MdaL_eXk283z7BmZM94buvYeGfEvE9hAgaBgq0MoNs,6659
8
- octopize_avatar_deploy/state_manager.py,sha256=dEZF8pSifHdlyF8_YNQLzXPpFFSx5fDk4hhUcKJHuxA,4893
9
- octopize_avatar_deploy/version_compat.py,sha256=0TlsBr7zWUD9TsTuNpLkKmshrbWpyPb1pL1Z0MKtqlc,10559
8
+ octopize_avatar_deploy/state_manager.py,sha256=3IstFyh-esorqmxOx0Bie6z3gAtHNolSlcJa0knUZSE,5090
9
+ octopize_avatar_deploy/version_compat.py,sha256=kgtU-344Sas9c3vXX2_fm1ZW0XI5i-KWO7v_fVsswEk,10559
10
10
  octopize_avatar_deploy/steps/__init__.py,sha256=bITxXeDG9bvAyJ4EGUESb_uX0UTLHou98Jd8TjBrO_o,626
11
11
  octopize_avatar_deploy/steps/authentik.py,sha256=6iSJUb18GRmz0SRYR0VzZGqPmhFRV1YZb1M9kS5XUUk,1460
12
12
  octopize_avatar_deploy/steps/authentik_blueprint.py,sha256=fWM7F9V2gCv7Lq9vRE4YKdOfD4Bw7PTn8Iakc8FkMpw,2786
@@ -14,11 +14,11 @@ octopize_avatar_deploy/steps/base.py,sha256=91Ph8bGJRFa2ceKrpXlN3IOsFEX2F7hR3p6t
14
14
  octopize_avatar_deploy/steps/database.py,sha256=9-RKxz0h2HdTSJyPdoW3iQ6veHVLH8aSfKxiAGUsqU4,1182
15
15
  octopize_avatar_deploy/steps/email.py,sha256=9yz9qx7vBSt8Dfg-7CZ3yM2kfIoWimyyUyiGmAUP7LQ,3139
16
16
  octopize_avatar_deploy/steps/logging.py,sha256=ks10nx0Mf0ZX9lygdIG4zoWFmAupJ528DbDRGM-PQnU,856
17
- octopize_avatar_deploy/steps/required.py,sha256=cKAZsgmtNJ7xQ9txza3TaJ1e562iTJpg5MsjK7US9Kk,3103
17
+ octopize_avatar_deploy/steps/required.py,sha256=08xxCDpMwYiKCSq4rlQhWEiF0aoZCAPatIF_x-o4Ecs,3309
18
18
  octopize_avatar_deploy/steps/storage.py,sha256=4-GVz8cMFY_fP4m1XR0yakGXfqpAGT3ZCY-lGB2uBG4,972
19
- octopize_avatar_deploy/steps/telemetry.py,sha256=Xo5sNDROBJGeZyLzum6aYCCYovebbNQvxeQVmILp54A,1878
19
+ octopize_avatar_deploy/steps/telemetry.py,sha256=9ikriAzn0OJr4zxW-saYfvhW4GJp0WDomtzhOQU7yb8,1880
20
20
  octopize_avatar_deploy/steps/user.py,sha256=3FSeEVjTHFieLGN2PqUwKzwIqe4vcx5eTw651Vg66ug,2825
21
- octopize_deploy_tool-0.1.0.dist-info/METADATA,sha256=C3w4-GaxFR59B7VGxnaTcLsnfLA_jR57fc89Yq4GVK0,9502
22
- octopize_deploy_tool-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
23
- octopize_deploy_tool-0.1.0.dist-info/entry_points.txt,sha256=owTMr4VgBceyp-KPS3f4CEsiKkVQJAx84PRpjS8G5cw,81
24
- octopize_deploy_tool-0.1.0.dist-info/RECORD,,
21
+ octopize_deploy_tool-0.3.0.dist-info/METADATA,sha256=nuRD0VPZss-GpL8qO0z6zXBfX6kLaHdTP48tX06FG38,4113
22
+ octopize_deploy_tool-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
23
+ octopize_deploy_tool-0.3.0.dist-info/entry_points.txt,sha256=A0f3VrMyKxoPfL_jsk7VnQUy5NbbC9r1pe-y9zczPDQ,79
24
+ octopize_deploy_tool-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ octopize-deploy-tool = octopize_avatar_deploy.configure:main
@@ -1,346 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: octopize.deploy_tool
3
- Version: 0.1.0
4
- Summary: Deployment configuration tool for Octopize Avatar platform
5
- Project-URL: Homepage, https://octopize.io
6
- Project-URL: Documentation, https://docs.octopize.io/docs/deploying/self-hosted
7
- Project-URL: Repository, https://github.com/octopize/avatar-deployment
8
- Project-URL: Issues, https://github.com/octopize/avatar-deployment/issues
9
- Author-email: Octopize <contact@octopize.io>
10
- License: MIT
11
- Keywords: avatar,configuration,deployment,octopize
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Intended Audience :: System Administrators
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Operating System :: OS Independent
17
- Classifier: Programming Language :: Python :: 3.13
18
- Classifier: Topic :: System :: Installation/Setup
19
- Classifier: Topic :: System :: Systems Administration
20
- Requires-Python: >=3.13
21
- Requires-Dist: jinja2>=3.1.0
22
- Requires-Dist: pyyaml>=6.0
23
- Requires-Dist: rich>=13.0.0
24
- Provides-Extra: dev
25
- Requires-Dist: mypy; extra == 'dev'
26
- Requires-Dist: pytest>=7.0; extra == 'dev'
27
- Requires-Dist: ruff>=0.1.0; extra == 'dev'
28
- Description-Content-Type: text/markdown
29
-
30
- # Octopize Avatar Deployment Tool
31
-
32
- Automated configuration tool for deploying Octopize Avatar platform using Docker Compose.
33
-
34
- ## Overview
35
-
36
- This tool simplifies Avatar deployment by:
37
- - **📦 Standalone package** - No need to clone the entire repository
38
- - **⬇️ Downloads templates** automatically from GitHub
39
- - **🎯 Deployment presets** - dev-mode, production, airgapped configurations
40
- - **🔐 Secure secrets generation** - Automatic creation of encryption keys
41
- - **✅ Stateless by design** - Minimal bundled dependencies
42
- - **🔄 Resumable configuration** - State management for interrupted setups
43
-
44
- ## Architecture
45
-
46
- ### What's Bundled vs Downloaded
47
-
48
- **Bundled in PyPI Package:**
49
- - `configure.py` - Main configuration logic
50
- - `state_manager.py` - State management for resuming
51
- - `download_templates.py` - Template downloader
52
- - `defaults.yaml` - Default values and presets
53
-
54
- **Downloaded from GitHub (on-demand):**
55
- - `.env.template` - Environment configuration template
56
- - `nginx.conf.template` - Nginx configuration template
57
- - `docker-compose.yml` - Docker services definition
58
- - `.template-version` - Template version information
59
- - Other deployment files
60
-
61
- These templates are located in `docker/templates/` in the avatar-deployment repository.
62
-
63
- This design means you can install and run the tool without cloning the repository!
64
-
65
- ## Quick Start
66
-
67
- ### Option 1: Using uvx (Recommended - After PyPI Publication)
68
-
69
- ```bash
70
- uvx octopize-avatar-deploy --output-dir /app/avatar
71
- ```
72
-
73
- ### Option 2: Using pip
74
-
75
- ```bash
76
- pip install octopize-avatar-deploy
77
- octopize-avatar-deploy --output-dir /app/avatar
78
- ```
79
-
80
- ### Option 3: From Source with uv
81
-
82
- ```bash
83
- # Sparse clone (only deployment-tool directory)
84
- git clone --depth 1 --filter=blob:none --sparse https://github.com/octopize/avatar-deployment
85
- cd avatar-deployment
86
- git sparse-checkout set deployment-tool
87
-
88
- # Run with uv
89
- cd deployment-tool
90
- uv run configure.py --output-dir /app/avatar
91
- ```
92
-
93
- ## Deployment Presets
94
-
95
- Choose a preset to quickly configure for your environment:
96
-
97
- ### `default` - Production Configuration
98
- - Console logging: **disabled** (use structured logs)
99
- - Sentry monitoring: **enabled**
100
- - Telemetry: **enabled**
101
- - Best for: Production deployments with monitoring
102
-
103
- ### `dev-mode` - Development Configuration
104
- - Console logging: **enabled** (see logs in terminal)
105
- - Sentry monitoring: **disabled**
106
- - Telemetry: **disabled**
107
- - Best for: Local development and testing
108
-
109
- ### `airgapped` - Air-Gapped Deployment
110
- - Console logging: **disabled**
111
- - Sentry monitoring: **disabled** (no external connections)
112
- - Telemetry: **disabled** (no external connections)
113
- - Best for: Secure, isolated environments
114
-
115
- ### `custom` - Manual Configuration
116
- - Configure all options interactively
117
- - Best for: Specific requirements
118
-
119
- ## Usage
120
-
121
- ### Interactive Mode with Preset
122
-
123
- ```bash
124
- octopize-avatar-deploy --output-dir /app/avatar --preset dev-mode
125
- ```
126
-
127
- The tool will:
128
- 1. Download latest templates from GitHub
129
- 2. Apply preset configuration
130
- 3. Prompt for required values (PUBLIC_URL, ENV_NAME)
131
- 4. Generate configuration files
132
-
133
- ### Non-Interactive Mode
134
-
135
- ```bash
136
- # Create config file
137
- cat > my-config.yaml << EOF
138
- PUBLIC_URL: avatar.mycompany.com
139
- ENV_NAME: mycompany-prod
140
- AVATAR_API_VERSION: 2.20.1
141
- AVATAR_WEB_VERSION: 0.40.0
142
- MAIL_PROVIDER: smtp
143
- SMTP_HOST: mail.mycompany.com
144
- EOF
145
-
146
- # Run with config
147
- octopize-avatar-deploy \
148
- --config my-config.yaml \
149
- --preset default \
150
- --non-interactive \
151
- --output-dir /app/avatar
152
- ```
153
-
154
- ### Advanced Options
155
-
156
- ```bash
157
- octopize-avatar-deploy \
158
- --output-dir /app/avatar \
159
- --preset dev-mode \
160
- --download-branch main \ # Git branch to download from
161
- --skip-download \ # Use cached templates
162
- --save-config \ # Save config to deployment-config.yaml
163
- --verbose # Show detailed progress
164
- ```
165
-
166
- ## Command Line Options
167
-
168
- ```
169
- --output-dir DIR Output directory (default: current directory)
170
- --preset NAME Use preset: default, dev-mode, airgapped
171
- --config FILE YAML configuration file
172
- --non-interactive Non-interactive mode (use config/defaults)
173
- --auth-type TYPE Authentication: email or username (default: email)
174
- --save-config Save config to deployment-config.yaml
175
- --download-branch BRANCH Git branch for templates (default: main)
176
- --skip-download Use cached templates
177
- --verbose Detailed output
178
- ```
179
-
180
- ## What Gets Generated
181
-
182
- After running the tool, you'll have:
183
-
184
- ```
185
- /app/avatar/
186
- ├── .env # Environment configuration
187
- ├── nginx.conf # Nginx reverse proxy config
188
- ├── .secrets/ # Generated secrets (gitignored)
189
- │ ├── db_password
190
- │ ├── authentik_secret_key
191
- │ ├── avatar_api_encryption_key
192
- │ └── ...
193
- ├── docker-compose.yml # Downloaded from GitHub
194
- └── .avatar-templates/ # Cached templates (auto-downloaded)
195
- ├── .env.template
196
- ├── nginx.conf.template
197
- ├── docker-compose.yml
198
- └── .template-version
199
- ```
200
-
201
- ## Configuration Presets in Detail
202
-
203
- Presets are defined in `defaults.yaml`:
204
-
205
- ```yaml
206
- presets:
207
- default:
208
- description: "Production-ready with telemetry and monitoring"
209
- application:
210
- use_console_logging: "false"
211
- sentry_enabled: "true"
212
- telemetry:
213
- enabled: true
214
-
215
- dev-mode:
216
- description: "Development with console logging"
217
- application:
218
- use_console_logging: "true"
219
- sentry_enabled: "false"
220
- telemetry:
221
- enabled: false
222
-
223
- airgapped:
224
- description: "No external monitoring/telemetry"
225
- application:
226
- use_console_logging: "false"
227
- sentry_enabled: "false"
228
- telemetry:
229
- enabled: false
230
- ```
231
-
232
- You can override preset values during interactive configuration.
233
-
234
- ## Template Download Mechanism
235
-
236
- Templates are downloaded from GitHub on first run:
237
-
238
- 1. **Check cache** - `.avatar-templates/` directory
239
- 2. **Download if needed** - From `github.com/octopize/avatar-deployment`
240
- 3. **Use cached** - On subsequent runs (unless `--skip-download` is used)
241
-
242
- This ensures:
243
- - ✅ Always get latest templates (from specified branch)
244
- - ✅ Offline support (once cached)
245
- - ✅ No repository cloning required
246
- - ✅ Minimal package size
247
-
248
- ## State Management
249
-
250
- The tool saves progress to `.deployment-state.yaml` allowing you to:
251
-
252
- - **Resume interrupted configurations**
253
- - **Track which steps completed**
254
- - **Avoid re-entering values**
255
-
256
- Steps:
257
- 1. Collect required config
258
- 2. Collect optional config
259
- 3. Generate .env file
260
- 4. Generate nginx.conf
261
- 5. Generate secrets
262
- 6. Prompt for user secrets (optional)
263
- 7. Finalize
264
-
265
- ```bash
266
- # If interrupted, just run again:
267
- octopize-avatar-deploy --output-dir /app/avatar
268
-
269
- # Tool will ask: "Continue from where you left off? [Y/n]"
270
- ```
271
-
272
- ## Troubleshooting
273
-
274
- ### Templates not downloading
275
-
276
- ```bash
277
- # Force re-download
278
- rm -rf .avatar-templates/
279
- octopize-avatar-deploy --output-dir /app/avatar --verbose
280
- ```
281
-
282
- ### Use specific Git branch
283
-
284
- ```bash
285
- # Download from development branch
286
- octopize-avatar-deploy \
287
- --output-dir /app/avatar \
288
- --download-branch develop \
289
- --verbose
290
- ```
291
-
292
- ### Offline mode
293
-
294
- ```bash
295
- # Download templates once
296
- octopize-avatar-deploy --output-dir /app/avatar
297
-
298
- # Then use cached versions
299
- octopize-avatar-deploy --output-dir /app/avatar --skip-download
300
- ```
301
-
302
- ## Development
303
-
304
- ### Project Structure
305
-
306
- ```
307
- deployment-tool/
308
- ├── configure.py # Main script (bundled)
309
- ├── state_manager.py # State management (bundled)
310
- ├── download_templates.py # Template downloader (bundled)
311
- ├── defaults.yaml # Defaults and presets (bundled)
312
- ├── tests/ # Test suite
313
- ├── README.md
314
- └── pyproject.toml # Package configuration
315
- ```
316
-
317
- ### Running Tests
318
-
319
- ```bash
320
- cd deployment-tool
321
- pytest tests/
322
- ```
323
-
324
- ### Building Package
325
-
326
- ```bash
327
- pip install build
328
- python -m build
329
- ```
330
-
331
- ## Related Documentation
332
-
333
- - [Deployment Guide](../deploying-on-single-instance.md)
334
- - [Docker Compose Configuration](../docker-compose.yml)
335
- - [Migration Guide](../MIGRATION_GUIDE.md)
336
-
337
- ## Support
338
-
339
- For issues and questions:
340
- - Email: help@octopize.io
341
- - Documentation: https://docs.octopize.io
342
- - Repository: https://github.com/octopize/avatar-deployment
343
-
344
- ## License
345
-
346
- Apache License v2.0
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- octopize-avatar-deploy = octopize_avatar_deploy.configure:main