xenfra 0.1.7__py3-none-any.whl → 0.1.8__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.
xenfra/models.py ADDED
@@ -0,0 +1,54 @@
1
+ from enum import Enum
2
+ from datetime import datetime
3
+ from pydantic import BaseModel, Field
4
+
5
+ class DeploymentStatus(str, Enum):
6
+ PENDING = "pending"
7
+ IN_PROGRESS = "in_progress"
8
+ SUCCESS = "success"
9
+ FAILED = "failed"
10
+
11
+ class SourceType(str, Enum):
12
+ LOCAL = "local"
13
+ GIT = "git"
14
+
15
+ class Deployment(BaseModel):
16
+ id: str = Field(..., description="Unique identifier for the deployment")
17
+ projectId: str = Field(..., description="Identifier of the project being deployed")
18
+ status: DeploymentStatus = Field(..., description="Current status of the deployment")
19
+ source: str = Field(..., description="Source of the deployment (e.g., 'cli', 'api')")
20
+ created_at: datetime = Field(..., description="Timestamp when the deployment was created")
21
+ finished_at: datetime | None = Field(None, description="Timestamp when the deployment finished")
22
+
23
+ class DeploymentRecord(BaseModel):
24
+ deployment_id: str = Field(..., description="Unique identifier for this deployment instance.")
25
+ timestamp: datetime = Field(..., description="Timestamp of when the deployment succeeded.")
26
+ source_type: SourceType = Field(..., description="The type of the source code (local or git).")
27
+ source_identifier: str = Field(..., description="The identifier for the source (commit SHA for git, archive path for local).")
28
+
29
+ class BalanceRead(BaseModel):
30
+ month_to_date_balance: str
31
+ account_balance: str
32
+ month_to_date_usage: str
33
+ generated_at: str
34
+ error: str | None = None
35
+
36
+ class DropletCostRead(BaseModel):
37
+ id: int
38
+ name: str
39
+ ip_address: str
40
+ status: str
41
+ size_slug: str
42
+ monthly_price: float
43
+
44
+ class ProjectRead(BaseModel):
45
+ id: int
46
+ name: str
47
+ ip_address: str | None = None
48
+ status: str
49
+ region: str
50
+ size_slug: str
51
+ estimated_monthly_cost: float | None = None
52
+ created_at: datetime
53
+
54
+
xenfra/recipes.py CHANGED
@@ -1,121 +1,23 @@
1
- def generate_stack(context, is_dockerized=False):
1
+ from jinja2 import Environment, FileSystemLoader
2
+ from pathlib import Path
3
+
4
+ def generate_stack(context: dict):
2
5
  """
3
- Generates a startup script.
4
- INCLUDES 'AGGRESSIVE MODE' to kill background updates.
6
+ Generates a cloud-init startup script from a Jinja2 template.
7
+
8
+ Args:
9
+ context: A dictionary containing information for rendering the template,
10
+ e.g., {'domain': 'example.com', 'email': 'user@example.com'}
5
11
  """
6
- project_type = context.get("type", "pip")
7
- packages = context.get("packages", "")
8
-
9
- script = """#!/bin/bash
10
- export DEBIAN_FRONTEND=noninteractive
11
- LOG="/root/setup.log"
12
- touch $LOG
13
-
14
- echo "--------------------------------" >> $LOG
15
- echo "🧘 XENFRA: Context-Aware Boot" >> $LOG
16
- echo "--------------------------------" >> $LOG
17
-
18
- # Create App Directory
19
- mkdir -p /root/app
20
- cd /root/app
21
-
22
- # --- AGGRESSIVE FIX: KILL BACKGROUND UPDATES ---
23
- echo "⚔️ [0/6] Stopping Background Updates..." >> $LOG
24
- systemctl stop unattended-upgrades.service
25
- systemctl stop apt-daily.service
26
- systemctl stop apt-daily-upgrade.service
27
- systemctl kill --kill-who=all apt-daily.service
28
- systemctl kill --kill-who=all apt-daily-upgrade.service
29
-
30
- # Force remove locks if they exist (The Nuclear Option)
31
- rm -f /var/lib/dpkg/lock*
32
- rm -f /var/lib/apt/lists/lock
33
- rm -f /var/cache/apt/archives/lock
34
- dpkg --configure -a
35
- # -----------------------------------------------
36
-
37
- # 1. System Updates
38
- echo "🔄 [1/6] Refreshing Package Lists..." >> $LOG
39
- apt-get update
40
- apt-get install -y python3-pip python3-venv git curl
41
-
42
- # 2. Install Docker & Compose
43
- echo "🐳 [2/6] Installing Docker..." >> $LOG
44
- apt-get install -y docker.io || (curl -fsSL https://get.docker.com | sh)
45
- echo "🎶 [3/6] Installing Docker Compose..." >> $LOG
46
- apt-get install -y docker-compose-v2
47
- """
48
-
49
- if is_dockerized:
50
- script += """
51
- # --- DOCKERIZED DEPLOYMENT ---
52
- echo "📦 [4/6] Installing Caddy..." >> $LOG
53
- apt-get install -y debian-keyring debian-archive-keyring apt-transport-https
54
- curl -LsSf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
55
- curl -LsSf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
56
- apt-get update
57
- apt-get install -y caddy
58
-
59
- # --- Stop and disable global Caddy to prevent port conflicts ---
60
- echo "🛑 Stopping global Caddy service to free up ports..." >> $LOG
61
- systemctl stop caddy || true
62
- systemctl disable caddy || true
63
- # ------------------------------------------------------------------
64
-
65
- echo "🚀 [5/6] Starting application with Docker Compose..." >> $LOG
66
- cd /root/app
67
- docker-compose -f /root/app/docker-compose.yml up -d
68
-
69
- echo "🔒 [6/6] Reloading Caddy..." >> $LOG
70
- # Caddy will automatically use the Caddyfile from the mounted volume in docker-compose
71
- # We just need to make sure it's running. Docker-compose handles this.
72
- # A system reload might be useful if Caddy was installed as a host service.
73
- systemctl reload caddy || systemctl start caddy
74
- """
75
- else:
76
- # 3. Virtual Environment Setup (Non-Dockerized)
77
- if project_type == "uv":
78
- script += """
79
- # --- STANDARD NON-DOCKER DEPLOYMENT (UV) ---
80
- echo "⚡ [4/6] Installing UV..." >> $LOG
81
- curl -LsSf https://astral.sh/uv/install.sh | sh
82
- source $HOME/.local/bin/env
83
- export PATH="/root/.local/bin:$PATH"
84
-
85
- echo "⚡ [5/6] Creating UV Venv..." >> $LOG
86
- uv venv /root/venv
87
- source /root/venv/bin/activate
88
- """
89
- else:
90
- script += """
91
- # --- STANDARD NON-DOCKER DEPLOYMENT (PIP) ---
92
- echo "🐍 [4/6] Creating Python Venv..." >> $LOG
93
- python3 -m venv /root/venv
94
- source /root/venv/bin/activate
95
- echo "🐍 [5/6] Upgrading Pip..." >> $LOG
96
- pip install --upgrade pip
97
- """
98
- # 4. Install Packages (Non-Dockerized)
99
- if packages:
100
- if project_type == "uv":
101
- script += f"""
102
- echo "📦 [6/6] UV Installing Deps: {packages}..." >> $LOG
103
- source /root/venv/bin/activate
104
- uv pip install {packages}
105
- """
106
- else:
107
- script += f"""
108
- echo "📦 [6/6] Pip Installing Deps: {packages}..." >> $LOG
109
- source /root/venv/bin/activate
110
- pip install {packages}
111
- """
112
- else:
113
- script += 'echo "ℹ️ [6/6] No packages detected." >> $LOG\n'
114
-
115
- # Finish
116
- script += """
117
-
118
- echo "✅ SETUP COMPLETE" >> $LOG
119
- touch /root/setup_complete
120
- """
12
+ # Path to the templates directory
13
+ template_dir = Path(__file__).parent / "templates"
14
+ env = Environment(loader=FileSystemLoader(template_dir))
15
+
16
+ template = env.get_template("cloud-init.sh.j2")
17
+
18
+ # The non-dockerized logic has been removed as we are focusing on
19
+ # a purely Docker-based deployment strategy for simplicity and scalability.
20
+ # The context will contain all necessary variables for the template.
21
+ script = template.render(context)
22
+
121
23
  return script
xenfra/security.py ADDED
@@ -0,0 +1,58 @@
1
+ # src/xenfra/security.py
2
+
3
+ from datetime import datetime, timedelta, timezone
4
+ from typing import Optional
5
+ from passlib.context import CryptContext
6
+ from jose import JWTError, jwt
7
+ from cryptography.fernet import Fernet
8
+
9
+ from xenfra.config import settings
10
+
11
+ # --- Configuration ---
12
+ SECRET_KEY = settings.SECRET_KEY
13
+ ALGORITHM = "HS256"
14
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30
15
+
16
+ # This key MUST be 32 url-safe base64-encoded bytes.
17
+ fernet = Fernet(settings.ENCRYPTION_KEY.encode())
18
+
19
+ # --- Password Hashing ---
20
+ # Explicitly use passlib.backends.bcrypt
21
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
22
+
23
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
24
+ return pwd_context.verify(plain_password, hashed_password)
25
+
26
+ def get_password_hash(password: str) -> str:
27
+ # bcrypt passwords cannot be longer than 72 bytes. Truncate if necessary.
28
+ # Note: Frontend should also enforce password length limits.
29
+ if len(password.encode('utf-8')) > 72:
30
+ password = password[:72]
31
+ return pwd_context.hash(password)
32
+
33
+ # --- JWT Handling ---
34
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
35
+ to_encode = data.copy()
36
+ if expires_delta:
37
+ expire = datetime.now(timezone.utc) + expires_delta
38
+ else:
39
+ expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
40
+ to_encode.update({"exp": expire})
41
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
42
+ return encoded_jwt
43
+
44
+ def decode_token(token: str) -> Optional[dict]:
45
+ try:
46
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
47
+ return payload
48
+ except JWTError:
49
+ return None
50
+
51
+ # --- Token Encryption ---
52
+ def encrypt_token(token: str) -> str:
53
+ """Encrypts a token using Fernet symmetric encryption."""
54
+ return fernet.encrypt(token.encode()).decode()
55
+
56
+ def decrypt_token(encrypted_token: str) -> str:
57
+ """Decrypts a token."""
58
+ return fernet.decrypt(encrypted_token.encode()).decode()
@@ -0,0 +1,25 @@
1
+ # Dockerfile template for Python web applications
2
+ FROM {{ python_version | default('python:3.11-slim') }}
3
+
4
+ WORKDIR /app
5
+
6
+ # Install uv, our preferred package manager
7
+ RUN apt-get update && apt-get install -y curl && \
8
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
9
+ apt-get remove -y curl && \
10
+ apt-get clean && \
11
+ rm -rf /var/lib/apt/lists/*
12
+
13
+ COPY requirements.txt .
14
+
15
+ # Install dependencies
16
+ RUN /root/.cargo/bin/uv pip install --system --no-cache -r requirements.txt
17
+
18
+ COPY . .
19
+
20
+ # Expose the application port
21
+ EXPOSE {{ port | default(8000) }}
22
+
23
+ # The command to run the application will be in docker-compose.yml
24
+ # This allows for more flexibility
25
+
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ export DEBIAN_FRONTEND=noninteractive
3
+ LOG="/root/setup.log"
4
+ touch $LOG
5
+
6
+ echo "--------------------------------" >> $LOG
7
+ echo "🧘 XENFRA: Context-Aware Boot" >> $LOG
8
+ echo "--------------------------------" >> $LOG
9
+
10
+ # Create App Directory
11
+ mkdir -p /root/app
12
+ cd /root/app
13
+
14
+ # --- AGGRESSIVE FIX: KILL BACKGROUND UPDATES ---
15
+ echo "⚔️ [0/6] Stopping Background Updates..." >> $LOG
16
+ systemctl stop unattended-upgrades.service || true
17
+ systemctl stop apt-daily.service || true
18
+ systemctl stop apt-daily-upgrade.service || true
19
+ systemctl kill --kill-who=all apt-daily.service || true
20
+ systemctl kill --kill-who=all apt-daily-upgrade.service || true
21
+
22
+ # Force remove locks if they exist
23
+ rm -f /var/lib/dpkg/lock*
24
+ rm -f /var/lib/apt/lists/lock
25
+ rm -f /var/cache/apt/archives/lock
26
+ dpkg --configure -a || true
27
+ # -----------------------------------------------
28
+
29
+ # 1. System Updates
30
+ echo "🔄 [1/6] Refreshing Package Lists..." >> $LOG
31
+ apt-get update
32
+ apt-get install -y python3-pip git curl
33
+
34
+ # 2. Install Docker & Compose
35
+ echo "🐳 [2/6] Installing Docker..." >> $LOG
36
+ apt-get install -y docker.io || (curl -fsSL https://get.docker.com | sh)
37
+ echo "🎶 [3/6] Installing Docker Compose..." >> $LOG
38
+ apt-get install -y docker-compose-v2
39
+
40
+ # --- DOCKERIZED DEPLOYMENT ---
41
+ echo "📦 [4/6] Installing Caddy..." >> $LOG
42
+ apt-get install -y debian-keyring debian-archive-keyring apt-transport-https
43
+ curl -LsSf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
44
+ curl -LsSf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
45
+ apt-get update
46
+ apt-get install -y caddy
47
+
48
+ {% if domain %}
49
+ # Dynamically generate Caddyfile content
50
+ echo "🔒 Writing Caddyfile for {{ domain }}..." >> $LOG
51
+ cat << EOF > /etc/caddy/Caddyfile
52
+ {{ domain }}:80, {{ domain }}:443 {
53
+ reverse_proxy localhost:{{ port | default(8000) }}
54
+ tls {{ email }}
55
+ }
56
+ EOF
57
+ {% endif %}
58
+
59
+ echo "🚀 [5/6] Starting application with Docker Compose..." >> $LOG
60
+ cd /root/app
61
+ docker-compose -f /root/app/docker-compose.yml up -d
62
+
63
+ {% if domain %}
64
+ echo "🔒 [6/6] Starting Caddy..." >> $LOG
65
+ systemctl restart caddy
66
+ {% else %}
67
+ echo "✅ [6/6] Skipping Caddy start (no domain specified)." >> $LOG
68
+ {% endif %}
69
+
70
+ # Finish
71
+ echo "✅ SETUP SCRIPT COMPLETE" >> $LOG
72
+ touch /root/setup_complete
@@ -0,0 +1,33 @@
1
+ # docker-compose.yml template
2
+ version: '3.8'
3
+
4
+ services:
5
+ app:
6
+ build: .
7
+ ports:
8
+ - "{{ port | default(8000) }}:{{ port | default(8000) }}"
9
+ volumes:
10
+ - .:/app
11
+ command: {{ command }}
12
+ {% if database == 'postgres' %}
13
+ depends_on:
14
+ - db
15
+ environment:
16
+ - DATABASE_URL=postgresql://{{ db_user | default('user') }}:{{ db_password | default('password') }}@db:5432/{{ db_name | default('appdb') }}
17
+ {% endif %}
18
+
19
+ {% if database == 'postgres' %}
20
+ db:
21
+ image: postgres:15-alpine
22
+ volumes:
23
+ - postgres_data:/var/lib/postgresql/data/
24
+ environment:
25
+ - POSTGRES_USER={{ db_user | default('user') }}
26
+ - POSTGRES_PASSWORD={{ db_password | default('password') }}
27
+ - POSTGRES_DB={{ db_name | default('appdb') }}
28
+ {% endif %}
29
+
30
+ volumes:
31
+ {% if database == 'postgres' %}
32
+ postgres_data:
33
+ {% endif %}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: xenfra
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: A 'Zen Mode' infrastructure engine for Python developers.
5
5
  Author: xenfra-cloud
6
6
  Author-email: xenfra-cloud <xenfracloud@gmail.com>
@@ -15,6 +15,19 @@ Requires-Dist: fabric>=3.2.2
15
15
  Requires-Dist: python-digitalocean>=1.17.0
16
16
  Requires-Dist: python-dotenv>=1.2.1
17
17
  Requires-Dist: rich>=14.2.0
18
+ Requires-Dist: fastapi>=0.110.0
19
+ Requires-Dist: uvicorn[standard]>=0.27.1
20
+ Requires-Dist: click>=8.1.7
21
+ Requires-Dist: sqlmodel>=0.0.16
22
+ Requires-Dist: psycopg2-binary>=2.9.9
23
+ Requires-Dist: python-jose[cryptography]>=3.3.0
24
+ Requires-Dist: passlib>=1.7.4
25
+ Requires-Dist: httpx>=0.27.0
26
+ Requires-Dist: pyyaml>=6.0.1
27
+ Requires-Dist: python-multipart>=0.0.21
28
+ Requires-Dist: bcrypt==4.0.1
29
+ Requires-Dist: jinja2>=3.1.3
30
+ Requires-Dist: pytest>=9.0.2
18
31
  Requires-Dist: pytest>=8.0.0 ; extra == 'test'
19
32
  Requires-Dist: pytest-mock>=3.12.0 ; extra == 'test'
20
33
  Requires-Python: >=3.13
@@ -35,83 +48,37 @@ It handles the complexity of server provisioning, context-aware configuration, D
35
48
  * **Clients as the Face**: Frontends like the default CLI (`xenfra.cli`) are thin, stateless clients responsible only for user interaction.
36
49
  * **Zen Mode**: If a server setup fails due to common issues like a locked package manager, the Engine automatically fixes it without exposing raw errors to the user.
37
50
 
38
- ## 🚀 Quickstart: As a Library
51
+ ## 🚀 Quickstart
39
52
 
40
- This demonstrates the power of Xenfra's engine for programmatic infrastructure management.
53
+ Using the Xenfra CLI involves a simple workflow: **Configure**, **Initialize**, and then **Deploy & Manage**.
41
54
 
42
- ### 1. Installation
55
+ ### 1. Configure
43
56
 
44
- ```bash
45
- # Install from PyPI (once published)
46
- pip install xenfra
47
- ```
48
-
49
- ### 2. Prerequisites
50
-
51
- Ensure your DigitalOcean API token is available as an environment variable:
57
+ Xenfra needs your DigitalOcean API token to manage infrastructure on your behalf. Export it as an environment variable:
52
58
 
53
59
  ```bash
54
- export DIGITAL_OCEAN_TOKEN="your_secret_token_here"
60
+ export DIGITAL_OCEAN_TOKEN="dop_v1_your_secret_token_here"
55
61
  ```
56
62
 
57
- ### 3. Programmatic Usage
58
-
59
- ```python
60
- from xenfra.engine import InfraEngine
63
+ ### 2. Initialize
61
64
 
62
- # Optional: a logger to receive status updates
63
- def my_logger(message):
64
- print(f"[My App] {message}")
65
-
66
- try:
67
- # The engine automatically finds and validates the API token
68
- engine = InfraEngine()
69
-
70
- # Define the server and deploy
71
- result = engine.deploy_server(
72
- name="my-app-server",
73
- region="nyc3",
74
- size="s-1vcpu-1gb",
75
- image="ubuntu-24-04-x64",
76
- logger=my_logger
77
- )
78
-
79
- print(f"🎉 Deployment successful! IP Address: {result.get('ip')}")
80
-
81
- except Exception as e:
82
- print(f"❌ Deployment failed: {e}")
83
- ```
84
-
85
- ## 💻 CLI Usage
86
-
87
- Xenfra also provides a beautiful, interactive CLI for manual control.
88
-
89
- ### 1. Installation
65
+ Navigate to your project's root directory and run the `init` command. This command scans your project, asks a few questions, and creates a `xenfra.yaml` configuration file.
90
66
 
91
67
  ```bash
92
- pip install xenfra
68
+ xenfra init
93
69
  ```
70
+ You should review the generated `xenfra.yaml` and commit it to your repository.
94
71
 
95
- ### 2. Configuration
72
+ ### 3. Deploy & Manage
96
73
 
97
- Create a `.env` file in your project root with your DigitalOcean token:
98
-
99
- ```env
100
- DIGITAL_OCEAN_TOKEN=dop_v1_your_token_here
101
- ```
74
+ Once your project is initialized, you can use the following commands to manage your application:
102
75
 
103
- ### 3. Run the CLI
104
-
105
- Once installed, simply run the `xenfra` command:
106
-
107
- ```bash
108
- xenfra
109
- ```
76
+ * **`xenfra deploy`**: Deploys your application based on the settings in `xenfra.yaml`.
77
+ * **`xenfra list`**: Instantly lists all your deployed projects from a local cache.
78
+ * Use `xenfra list --refresh` to force a sync with your cloud provider.
79
+ * **`xenfra logs`**: Streams real-time logs from a selected project.
80
+ * **`xenfra destroy`**: Decommissions and deletes a deployed project.
110
81
 
111
- This will launch the interactive menu where you can:
112
- - **🚀 Deploy New Server**: A guided workflow to provision and deploy your application.
113
- - **📋 List Active Servers**: View your current DigitalOcean droplets.
114
- - **🧨 Destroy a Server**: Decommission servers you no longer need.
115
82
 
116
83
  ## 📦 Supported Frameworks & Features
117
84
 
@@ -0,0 +1,25 @@
1
+ xenfra/__init__.py,sha256=ja0-Vc9T61EXZnPeX84Fi-UOjoGAWu5r90_1t-0WjVw,46
2
+ xenfra/api/auth.py,sha256=WxepMKeF8Xnf9imqYip_EyGT0xFXgKoAhFIYx7uqWvA,1848
3
+ xenfra/api/billing.py,sha256=hoviKgpzjMoWcz0KW7I5HUhwLKPlRov5-3TIqzbdpAs,2871
4
+ xenfra/api/connections.py,sha256=IQfOA9jZV6Gcz0_E69Sgp3poAMQKwGwn19GV84Sn61I,7328
5
+ xenfra/api/main.py,sha256=psdVAOKsSM9vMmTSdZBuGceJNG50mqtJFbfa3UrTpmo,6392
6
+ xenfra/api/webhooks.py,sha256=Ek--AVzzKxvukway-vLt_wprBMaQjuWy7q35BbUKeiA,6367
7
+ xenfra/cli/main.py,sha256=1RiznGbrlaSDw37nD8i7wrpXMQsHnNuqk1YOYzxWdII,7841
8
+ xenfra/config.py,sha256=g7ahyI4kbYITRcliCbzXCRfKJ3uh8E2VMjmpR-n51s8,621
9
+ xenfra/db/models.py,sha256=nRqbG9cbcX8LWz2Jr4inKysAq47yALcxNl3_UYEpSoQ,1305
10
+ xenfra/db/session.py,sha256=Q03RIiPlYBtPD-qVHlTqQtacnOPTZnOy6_f9p6lABPU,510
11
+ xenfra/dependencies.py,sha256=pQ7bFNZHC24chMxRa_69mu2fIqcoc8DG9MD1POSGgjA,1211
12
+ xenfra/dockerizer.py,sha256=5qzgqw2MZrBwI4IFzXPJZ6k3F3IRdFnX-KGIpv5cjXc,3126
13
+ xenfra/engine.py,sha256=fzdBIW1CmOQtTETjlaMtltt0cgH8r4HN7ImH-s2Wnzk,11793
14
+ xenfra/mcp_client.py,sha256=IxkTC3NPHvPJn-2dl5BUHQ3d-A9MBiKP9fIftzhmCuA,5756
15
+ xenfra/models.py,sha256=9Dvr1_kb29LJIR0a-uwx_tsCWt3GZeko6FKQtYY8F4g,1852
16
+ xenfra/recipes.py,sha256=q5ilpDzGuUM6GyvlW97pOpPS9nzQAJGI4pgzSU_URik,862
17
+ xenfra/security.py,sha256=w7aIFnHDHOH2zDjYD_LnJnSE01NeGeoAh_V8hC1HipY,2003
18
+ xenfra/templates/Dockerfile.j2,sha256=apWts895OOoUYwj_fOa6OiylFB5m8zFEYvJ1Nki32YM,664
19
+ xenfra/templates/cloud-init.sh.j2,sha256=Ln7XO4rwaHgauevMKfkxAgBFNLHPnWv-TnJl3NJbU8c,2359
20
+ xenfra/templates/docker-compose.yml.j2,sha256=zKUT2cd_FrxXvRxE-vAAjuQk3-nLNQjRe-StkhAWRQA,860
21
+ xenfra/utils.py,sha256=aGXjJm-pwVCHuCn5UBdrxRcYvM8aJwHQ1kihl7gcxiM,2387
22
+ xenfra-0.1.8.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
23
+ xenfra-0.1.8.dist-info/entry_points.txt,sha256=O6JNPm-inoMzmW29WPYl6jHHlWCrcs8ShHEo-CXvERs,49
24
+ xenfra-0.1.8.dist-info/METADATA,sha256=hebQ0mFii9qF8Gs7PeVgzbBizBZ8qQ7GuFA0FycJTT4,4128
25
+ xenfra-0.1.8.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ xenfra = xenfra.cli.main:main
3
+