pactown 0.1.4__py3-none-any.whl → 0.1.47__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.
@@ -0,0 +1,458 @@
1
+ """
2
+ User isolation module for pactown.
3
+
4
+ Provides Linux user-based sandbox isolation for multi-tenant SaaS:
5
+ - Create isolated Linux users per SaaS user
6
+ - Run sandboxes under isolated user accounts
7
+ - Easy migration of projects between hosts
8
+ - Resource limits via cgroups (optional)
9
+ """
10
+
11
+ import grp
12
+ import os
13
+ import pwd
14
+ import re
15
+ import shutil
16
+ import subprocess
17
+ import logging
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import Dict, List, Optional, Any
21
+ from threading import Lock
22
+
23
+ logger = logging.getLogger("pactown.isolation")
24
+
25
+
26
+ def _sanitize_gecos(value: str) -> str:
27
+ v = str(value or "")
28
+ v = v.replace(":", "_")
29
+ v = re.sub(r"[\x00\r\n\t]", " ", v)
30
+ v = re.sub(r"\s+", " ", v).strip()
31
+ if not v:
32
+ v = "pactown-user"
33
+ return v[:200]
34
+
35
+
36
+ @dataclass
37
+ class IsolatedUser:
38
+ """Represents an isolated Linux user for sandbox execution."""
39
+ saas_user_id: str
40
+ linux_username: str
41
+ linux_uid: int
42
+ linux_gid: int
43
+ home_dir: Path
44
+ created_at: float = field(default_factory=lambda: __import__('time').time())
45
+
46
+ def to_dict(self) -> dict:
47
+ return {
48
+ "saas_user_id": self.saas_user_id,
49
+ "linux_username": self.linux_username,
50
+ "linux_uid": self.linux_uid,
51
+ "linux_gid": self.linux_gid,
52
+ "home_dir": str(self.home_dir),
53
+ "created_at": self.created_at,
54
+ }
55
+
56
+
57
+ class UserIsolationManager:
58
+ """
59
+ Manages isolated Linux users for sandbox execution.
60
+
61
+ Each SaaS user gets a dedicated Linux user account:
62
+ - Username: pactown_<hash(saas_user_id)>
63
+ - Home dir: /home/pactown_users/<username>
64
+ - All sandboxes run under this user
65
+
66
+ Benefits:
67
+ - Process isolation (different UIDs)
68
+ - File system isolation (home directories)
69
+ - Easy migration (tar user's home dir)
70
+ - Resource limits via cgroups
71
+ """
72
+
73
+ PREFIX = "pactown_"
74
+ BASE_UID = 60000 # Start UIDs from 60000
75
+ BASE_GID = 60000
76
+
77
+ def __init__(
78
+ self,
79
+ users_base: Path = Path("/home/pactown_users"),
80
+ enable_cgroups: bool = False,
81
+ ):
82
+ self.users_base = users_base
83
+ self.enable_cgroups = enable_cgroups
84
+ self._users: Dict[str, IsolatedUser] = {}
85
+ self._lock = Lock()
86
+ self._next_uid = self.BASE_UID
87
+
88
+ # Create base directory if running as root
89
+ if os.geteuid() == 0:
90
+ self.users_base.mkdir(parents=True, exist_ok=True)
91
+
92
+ # Load existing users
93
+ self._load_existing_users()
94
+
95
+ def can_isolate(self) -> tuple[bool, str]:
96
+ if os.geteuid() != 0:
97
+ return False, "not running as root"
98
+ if shutil.which("useradd") is None:
99
+ return False, "missing 'useradd' (install 'passwd'/'shadow' tools)"
100
+ if shutil.which("groupadd") is None:
101
+ return False, "missing 'groupadd' (install 'passwd'/'shadow' tools)"
102
+ try:
103
+ self.users_base.mkdir(parents=True, exist_ok=True)
104
+ except Exception as e:
105
+ return False, f"cannot create users_base={self.users_base}: {e}"
106
+ try:
107
+ if not os.access(self.users_base, os.W_OK | os.X_OK):
108
+ return False, f"users_base not writable: {self.users_base}"
109
+ except Exception as e:
110
+ return False, f"cannot check permissions for users_base={self.users_base}: {e}"
111
+ return True, ""
112
+
113
+ def _load_existing_users(self):
114
+ """Load existing pactown users from system."""
115
+ try:
116
+ for entry in pwd.getpwall():
117
+ if entry.pw_name.startswith(self.PREFIX):
118
+ # Extract saas_user_id from comment field or username
119
+ saas_id = entry.pw_gecos or entry.pw_name[len(self.PREFIX):]
120
+ self._users[saas_id] = IsolatedUser(
121
+ saas_user_id=saas_id,
122
+ linux_username=entry.pw_name,
123
+ linux_uid=entry.pw_uid,
124
+ linux_gid=entry.pw_gid,
125
+ home_dir=Path(entry.pw_dir),
126
+ )
127
+ if entry.pw_uid >= self._next_uid:
128
+ self._next_uid = entry.pw_uid + 1
129
+ except Exception as e:
130
+ logger.warning(f"Could not load existing users: {e}")
131
+
132
+ def _generate_username(self, saas_user_id: str) -> str:
133
+ """Generate Linux username from SaaS user ID."""
134
+ import hashlib
135
+ hash_suffix = hashlib.sha256(saas_user_id.encode()).hexdigest()[:8]
136
+ return f"{self.PREFIX}{hash_suffix}"
137
+
138
+ def get_or_create_user(self, saas_user_id: str) -> IsolatedUser:
139
+ """Get or create an isolated Linux user for a SaaS user."""
140
+ with self._lock:
141
+ if saas_user_id in self._users:
142
+ return self._users[saas_user_id]
143
+
144
+ # Create new user
145
+ username = self._generate_username(saas_user_id)
146
+ uid = self._next_uid
147
+ gid = self._next_uid
148
+ home_dir = self.users_base / username
149
+
150
+ # If user already exists (deterministic username), reuse it.
151
+ try:
152
+ existing = pwd.getpwnam(username)
153
+ user = IsolatedUser(
154
+ saas_user_id=saas_user_id,
155
+ linux_username=existing.pw_name,
156
+ linux_uid=existing.pw_uid,
157
+ linux_gid=existing.pw_gid,
158
+ home_dir=Path(existing.pw_dir),
159
+ )
160
+ self._users[saas_user_id] = user
161
+ if existing.pw_uid >= self._next_uid:
162
+ self._next_uid = existing.pw_uid + 1
163
+ logger.info(
164
+ "Reusing existing Linux user %s (uid=%s) for %s",
165
+ user.linux_username,
166
+ user.linux_uid,
167
+ saas_user_id,
168
+ )
169
+ return user
170
+ except KeyError:
171
+ pass
172
+
173
+ # Check if we can create users (requires root)
174
+ if os.geteuid() != 0:
175
+ # Non-root mode: create virtual user for tracking
176
+ logger.warning(f"Not running as root, creating virtual user for {saas_user_id}")
177
+ user = IsolatedUser(
178
+ saas_user_id=saas_user_id,
179
+ linux_username=username,
180
+ linux_uid=os.getuid(), # Use current user
181
+ linux_gid=os.getgid(),
182
+ home_dir=home_dir,
183
+ )
184
+ self._users[saas_user_id] = user
185
+
186
+ # Create home directory anyway
187
+ try:
188
+ home_dir.mkdir(parents=True, exist_ok=True)
189
+ except Exception as e:
190
+ fallback_base = Path("/tmp/pactown_users")
191
+ fallback_home = fallback_base / username
192
+ logger.warning(
193
+ "Could not create users_base home_dir=%s (%s); falling back to %s",
194
+ home_dir,
195
+ e,
196
+ fallback_home,
197
+ )
198
+ fallback_home.mkdir(parents=True, exist_ok=True)
199
+ user.home_dir = fallback_home
200
+ return user
201
+
202
+ can_isolate, reason = self.can_isolate()
203
+ if not can_isolate:
204
+ raise RuntimeError(f"User isolation unavailable: {reason}")
205
+
206
+ try:
207
+ # Create group
208
+ try:
209
+ grp.getgrnam(username)
210
+ except KeyError:
211
+ res = subprocess.run(
212
+ ["groupadd", "-g", str(gid), username],
213
+ check=True,
214
+ capture_output=True,
215
+ text=True,
216
+ )
217
+ if res.stdout:
218
+ logger.debug("groupadd stdout: %s", res.stdout.strip())
219
+ if res.stderr:
220
+ logger.debug("groupadd stderr: %s", res.stderr.strip())
221
+
222
+ # Create user
223
+ safe_comment = _sanitize_gecos(saas_user_id)
224
+ res = subprocess.run(
225
+ [
226
+ "useradd",
227
+ "-u",
228
+ str(uid),
229
+ "-g",
230
+ str(gid),
231
+ "-d",
232
+ str(home_dir),
233
+ "-m",
234
+ "-s",
235
+ "/bin/bash",
236
+ "-c",
237
+ safe_comment,
238
+ username,
239
+ ],
240
+ check=True,
241
+ capture_output=True,
242
+ text=True,
243
+ )
244
+ if res.stdout:
245
+ logger.debug("useradd stdout: %s", res.stdout.strip())
246
+ if res.stderr:
247
+ logger.debug("useradd stderr: %s", res.stderr.strip())
248
+
249
+ logger.info(f"Created Linux user {username} (uid={uid}) for {saas_user_id}")
250
+
251
+ except subprocess.CalledProcessError as e:
252
+ stderr = getattr(e, "stderr", None)
253
+ stdout = getattr(e, "stdout", None)
254
+ logger.error(
255
+ "Failed to create isolated user for %s: cmd=%s returncode=%s stdout=%s stderr=%s",
256
+ saas_user_id,
257
+ getattr(e, "cmd", None),
258
+ getattr(e, "returncode", None),
259
+ (stdout.strip() if isinstance(stdout, str) else stdout),
260
+ (stderr.strip() if isinstance(stderr, str) else stderr),
261
+ )
262
+ raise RuntimeError(
263
+ f"Failed to create isolated user (saas_user_id={saas_user_id}, username={username}): {e}"
264
+ )
265
+
266
+ user = IsolatedUser(
267
+ saas_user_id=saas_user_id,
268
+ linux_username=username,
269
+ linux_uid=uid,
270
+ linux_gid=gid,
271
+ home_dir=home_dir,
272
+ )
273
+
274
+ self._users[saas_user_id] = user
275
+ self._next_uid += 1
276
+
277
+ return user
278
+
279
+ def get_user(self, saas_user_id: str) -> Optional[IsolatedUser]:
280
+ """Get isolated user without creating."""
281
+ return self._users.get(saas_user_id)
282
+
283
+ def get_sandbox_path(self, saas_user_id: str, service_id: str) -> Path:
284
+ """Get sandbox path for a specific service under user's home."""
285
+ user = self.get_or_create_user(saas_user_id)
286
+ sandbox_dir = user.home_dir / "sandboxes" / service_id
287
+ sandbox_dir.mkdir(parents=True, exist_ok=True)
288
+
289
+ # Set ownership if root
290
+ if os.geteuid() == 0:
291
+ os.chown(sandbox_dir, user.linux_uid, user.linux_gid)
292
+
293
+ return sandbox_dir
294
+
295
+ def run_as_user(
296
+ self,
297
+ saas_user_id: str,
298
+ command: str,
299
+ cwd: Path,
300
+ env: Optional[Dict[str, str]] = None,
301
+ ) -> subprocess.Popen:
302
+ """
303
+ Run a command as the isolated user.
304
+
305
+ Returns subprocess.Popen for the running process.
306
+ """
307
+ user = self.get_or_create_user(saas_user_id)
308
+
309
+ full_env = os.environ.copy()
310
+ full_env["HOME"] = str(user.home_dir)
311
+ full_env["USER"] = user.linux_username
312
+ full_env["LOGNAME"] = user.linux_username
313
+ if env:
314
+ full_env.update(env)
315
+
316
+ def set_user():
317
+ """Pre-exec function to switch user."""
318
+ if os.geteuid() == 0:
319
+ os.setgid(user.linux_gid)
320
+ os.setuid(user.linux_uid)
321
+
322
+ process = subprocess.Popen(
323
+ command,
324
+ shell=True,
325
+ cwd=str(cwd),
326
+ env=full_env,
327
+ stdout=subprocess.PIPE,
328
+ stderr=subprocess.PIPE,
329
+ preexec_fn=set_user if os.geteuid() == 0 else None,
330
+ )
331
+
332
+ logger.info(f"Started process {process.pid} as user {user.linux_username}")
333
+ return process
334
+
335
+ def list_users(self) -> List[IsolatedUser]:
336
+ """List all isolated users."""
337
+ return list(self._users.values())
338
+
339
+ def get_user_stats(self, saas_user_id: str) -> Dict[str, Any]:
340
+ """Get stats for a user's sandboxes."""
341
+ user = self.get_user(saas_user_id)
342
+ if not user:
343
+ return {"error": "User not found"}
344
+
345
+ sandboxes_dir = user.home_dir / "sandboxes"
346
+ if not sandboxes_dir.exists():
347
+ return {
348
+ "user": user.to_dict(),
349
+ "sandbox_count": 0,
350
+ "total_size_mb": 0,
351
+ }
352
+
353
+ sandbox_count = len(list(sandboxes_dir.iterdir()))
354
+ total_size = sum(
355
+ f.stat().st_size
356
+ for f in sandboxes_dir.rglob("*")
357
+ if f.is_file()
358
+ )
359
+
360
+ return {
361
+ "user": user.to_dict(),
362
+ "sandbox_count": sandbox_count,
363
+ "total_size_mb": total_size / (1024 * 1024),
364
+ }
365
+
366
+ def export_user_data(self, saas_user_id: str, output_path: Path) -> bool:
367
+ """
368
+ Export user's data for migration.
369
+
370
+ Creates a tar.gz archive of the user's home directory.
371
+ """
372
+ user = self.get_user(saas_user_id)
373
+ if not user or not user.home_dir.exists():
374
+ return False
375
+
376
+ try:
377
+ subprocess.run(
378
+ ["tar", "-czf", str(output_path), "-C", str(user.home_dir.parent), user.linux_username],
379
+ check=True,
380
+ capture_output=True,
381
+ )
382
+ logger.info(f"Exported user {saas_user_id} to {output_path}")
383
+ return True
384
+ except subprocess.CalledProcessError as e:
385
+ logger.error(f"Failed to export user: {e}")
386
+ return False
387
+
388
+ def import_user_data(self, saas_user_id: str, archive_path: Path) -> bool:
389
+ """
390
+ Import user's data from migration archive.
391
+ """
392
+ user = self.get_or_create_user(saas_user_id)
393
+
394
+ try:
395
+ # Extract to user's home
396
+ subprocess.run(
397
+ ["tar", "-xzf", str(archive_path), "-C", str(self.users_base)],
398
+ check=True,
399
+ capture_output=True,
400
+ )
401
+
402
+ # Fix ownership
403
+ if os.geteuid() == 0:
404
+ subprocess.run(
405
+ ["chown", "-R", f"{user.linux_uid}:{user.linux_gid}", str(user.home_dir)],
406
+ check=True,
407
+ capture_output=True,
408
+ )
409
+
410
+ logger.info(f"Imported user {saas_user_id} from {archive_path}")
411
+ return True
412
+ except subprocess.CalledProcessError as e:
413
+ logger.error(f"Failed to import user: {e}")
414
+ return False
415
+
416
+ def delete_user(self, saas_user_id: str, delete_home: bool = True) -> bool:
417
+ """Delete an isolated user."""
418
+ user = self.get_user(saas_user_id)
419
+ if not user:
420
+ return False
421
+
422
+ try:
423
+ if os.geteuid() == 0:
424
+ # Delete Linux user
425
+ cmd = ["userdel"]
426
+ if delete_home:
427
+ cmd.append("-r")
428
+ cmd.append(user.linux_username)
429
+ subprocess.run(cmd, check=True, capture_output=True)
430
+ subprocess.run(
431
+ ["groupdel", user.linux_username],
432
+ capture_output=True, # May fail if group doesn't exist
433
+ )
434
+ elif delete_home and user.home_dir.exists():
435
+ # Non-root: just delete home directory
436
+ shutil.rmtree(user.home_dir)
437
+
438
+ del self._users[saas_user_id]
439
+ logger.info(f"Deleted user {saas_user_id}")
440
+ return True
441
+
442
+ except subprocess.CalledProcessError as e:
443
+ logger.error(f"Failed to delete user: {e}")
444
+ return False
445
+
446
+
447
+ # Global isolation manager instance
448
+ _isolation_manager: Optional[UserIsolationManager] = None
449
+
450
+
451
+ def get_isolation_manager() -> UserIsolationManager:
452
+ """Get global isolation manager instance."""
453
+ global _isolation_manager
454
+ if _isolation_manager is None:
455
+ default_base = "/home/pactown_users" if os.geteuid() == 0 else "/tmp/pactown_users"
456
+ users_base = Path(os.environ.get("PACTOWN_USERS_BASE", default_base))
457
+ _isolation_manager = UserIsolationManager(users_base=users_base)
458
+ return _isolation_manager
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pactown
3
- Version: 0.1.4
3
+ Version: 0.1.47
4
4
  Summary: Decentralized Service Ecosystem Orchestrator - Build interconnected microservices from Markdown using markpact
5
5
  Project-URL: Homepage, https://github.com/wronai/pactown
6
6
  Project-URL: Repository, https://github.com/wronai/pactown
@@ -30,6 +30,8 @@ Requires-Dist: pyyaml>=6.0
30
30
  Requires-Dist: rich>=13.0
31
31
  Requires-Dist: uvicorn>=0.20.0
32
32
  Requires-Dist: watchfiles>=0.20.0
33
+ Provides-Extra: all
34
+ Requires-Dist: lolm>=0.1.6; extra == 'all'
33
35
  Provides-Extra: dev
34
36
  Requires-Dist: build; extra == 'dev'
35
37
  Requires-Dist: bump2version>=1.0; extra == 'dev'
@@ -38,6 +40,8 @@ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
38
40
  Requires-Dist: pytest>=7.0; extra == 'dev'
39
41
  Requires-Dist: ruff>=0.1; extra == 'dev'
40
42
  Requires-Dist: twine; extra == 'dev'
43
+ Provides-Extra: llm
44
+ Requires-Dist: lolm>=0.1.6; extra == 'llm'
41
45
  Description-Content-Type: text/markdown
42
46
 
43
47
  ![img.png](img.png)
@@ -74,6 +78,7 @@ Pactown enables you to compose multiple independent markpact projects into a uni
74
78
 
75
79
  ## Key Features
76
80
 
81
+ ### Core Features
77
82
  - **🔗 Service Composition** – Combine multiple markpact READMEs into one ecosystem
78
83
  - **📦 Local Registry** – Store and share markpact artifacts across projects
79
84
  - **🔄 Dependency Resolution** – Automatic startup order based on service dependencies
@@ -84,14 +89,43 @@ Pactown enables you to compose multiple independent markpact projects into a uni
84
89
  - **🔍 Service Discovery** – Name-based service lookup, no hardcoded URLs
85
90
  - **⚡ Config Generator** – Auto-generate config from folder of READMEs
86
91
 
87
- ## Documentation
92
+ ### New in v0.4.0
93
+ - **⚡ Fast Start** – Dependency caching for millisecond startup times ([docs](docs/FAST_START.md))
94
+ - **🛡️ Security Policy** – Rate limiting, user profiles, anomaly logging ([docs](docs/SECURITY_POLICY.md))
95
+ - **👤 User Isolation** – Linux user-based sandbox isolation for multi-tenant SaaS ([docs](docs/USER_ISOLATION.md))
96
+ - **📊 Detailed Logging** – Structured logs with error capture ([docs](docs/LOGGING.md))
97
+
98
+ ---
99
+
100
+ ## 📚 Documentation
101
+
102
+ ### Quick Navigation
103
+
104
+ | Category | Documents |
105
+ |----------|-----------|
106
+ | **Getting Started** | [Quick Start](#quick-start) · [Installation](#installation) · [Commands](#commands) |
107
+ | **Core Concepts** | [Specification](docs/SPECIFICATION.md) · [Configuration](docs/CONFIGURATION.md) · [Network](docs/NETWORK.md) |
108
+ | **Deployment** | [Deployment Guide](docs/DEPLOYMENT.md) · [Quadlet/VPS](docs/QUADLET.md) · [Generator](docs/GENERATOR.md) |
109
+ | **Security** | [Security Policy](docs/SECURITY_POLICY.md) · [Quadlet Security](docs/SECURITY.md) · [User Isolation](docs/USER_ISOLATION.md) |
110
+ | **Performance** | [Fast Start](docs/FAST_START.md) · [Logging](docs/LOGGING.md) |
111
+ | **Comparisons** | [vs Cloudflare Workers](docs/CLOUDFLARE_WORKERS_COMPARISON.md) |
112
+
113
+ ### All Documentation
88
114
 
89
115
  | Document | Description |
90
116
  |----------|-------------|
91
117
  | [Specification](docs/SPECIFICATION.md) | Architecture and design |
92
118
  | [Configuration](docs/CONFIGURATION.md) | YAML config reference |
119
+ | [Deployment](docs/DEPLOYMENT.md) | Production deployment guide (Compose/Kubernetes/Quadlet) |
93
120
  | [Network](docs/NETWORK.md) | Dynamic ports & service discovery |
94
121
  | [Generator](docs/GENERATOR.md) | Auto-generate configs |
122
+ | [Quadlet](docs/QUADLET.md) | Podman Quadlet deployment for VPS production |
123
+ | [Security](docs/SECURITY.md) | Quadlet security hardening and injection test suite |
124
+ | [Security Policy](docs/SECURITY_POLICY.md) | Rate limiting, user profiles, resource monitoring |
125
+ | [Fast Start](docs/FAST_START.md) | Dependency caching for fast startup |
126
+ | [User Isolation](docs/USER_ISOLATION.md) | Linux user-based sandbox isolation |
127
+ | [Logging](docs/LOGGING.md) | Structured logging and error capture |
128
+ | [Cloudflare Workers comparison](docs/CLOUDFLARE_WORKERS_COMPARISON.md) | When to use Pactown vs Cloudflare Workers |
95
129
 
96
130
  ### Source Code Reference
97
131
 
@@ -102,7 +136,29 @@ Pactown enables you to compose multiple independent markpact projects into a uni
102
136
  | [`resolver.py`](src/pactown/resolver.py) | Dependency resolution |
103
137
  | [`network.py`](src/pactown/network.py) | Port allocation & discovery |
104
138
  | [`generator.py`](src/pactown/generator.py) | Config file generator |
139
+ | [`service_runner.py`](src/pactown/service_runner.py) | High-level service runner API |
140
+ | [`security.py`](src/pactown/security.py) | Security policy & rate limiting |
141
+ | [`fast_start.py`](src/pactown/fast_start.py) | Dependency caching & fast startup |
142
+ | [`user_isolation.py`](src/pactown/user_isolation.py) | Linux user isolation for multi-tenant |
143
+ | [`sandbox_manager.py`](src/pactown/sandbox_manager.py) | Sandbox lifecycle management |
105
144
  | [`registry/`](src/pactown/registry/) | Local artifact registry |
145
+ | [`deploy/`](src/pactown/deploy/) | Deployment backends (Docker, Podman, K8s, Quadlet) |
146
+
147
+ ---
148
+
149
+ ## 🎯 Examples
150
+
151
+ | Example | What it shows |
152
+ |---------|---------------|
153
+ | [`examples/saas-platform/`](examples/saas-platform/) | Complete SaaS with Web + API + Database + Gateway |
154
+ | [`examples/quadlet-vps/`](examples/quadlet-vps/) | VPS setup and Quadlet workflow |
155
+ | [`examples/email-llm-responder/`](examples/email-llm-responder/) | Email automation with LLM integration |
156
+ | [`examples/api-gateway-webhooks/`](examples/api-gateway-webhooks/) | API gateway / webhook handler |
157
+ | [`examples/realtime-notifications/`](examples/realtime-notifications/) | WebSocket + SSE real-time notifications |
158
+ | [`examples/microservices/`](examples/microservices/) | Multi-language microservices |
159
+ | [`examples/fast-start-demo/`](examples/fast-start-demo/) | **NEW:** Fast startup with dependency caching |
160
+ | [`examples/security-policy/`](examples/security-policy/) | **NEW:** Rate limiting and user profiles |
161
+ | [`examples/user-isolation/`](examples/user-isolation/) | **NEW:** Multi-tenant user isolation |
106
162
 
107
163
  ## Installation
108
164
 
@@ -145,31 +201,31 @@ services:
145
201
 
146
202
  Each service is a standard markpact README:
147
203
 
148
- ```markdown
204
+ ````markdown
149
205
  # API Service
150
206
 
151
207
  REST API for the application.
152
208
 
153
209
  ---
154
210
 
155
- \`\`\`markpact:deps python
211
+ ```python markpact:deps
156
212
  fastapi
157
213
  uvicorn
158
- \`\`\`
214
+ ```
159
215
 
160
- \`\`\`markpact:file python path=main.py
216
+ ```python markpact:file path=main.py
161
217
  from fastapi import FastAPI
162
218
  app = FastAPI()
163
219
 
164
220
  @app.get("/health")
165
221
  def health():
166
222
  return {"status": "ok"}
167
- \`\`\`
223
+ ```
168
224
 
169
- \`\`\`markpact:run python
225
+ ```bash markpact:run
170
226
  uvicorn main:app --port ${MARKPACT_PORT:-8001}
171
- \`\`\`
172
227
  ```
228
+ ````
173
229
 
174
230
  ### 3. Start the ecosystem
175
231
 
@@ -0,0 +1,36 @@
1
+ pactown/__init__.py,sha256=mmmdTjBpjrS1yjksg5JEQEbQhwQhXa-v_FkCqqu5xrI,4417
2
+ pactown/cli.py,sha256=EoQxC5A82-kz72jO97SbPzaeDvjumpIUFyRzhbSK7jU,29731
3
+ pactown/config.py,sha256=gDPa1Mp721W61fWxZexQTOM2-vFESoeeWGaei9SacdE,5333
4
+ pactown/events.py,sha256=BRjUY8a9r4WKedqZK1LzC9ln3dFUnlBRVEa9kqUnSrA,36955
5
+ pactown/fast_start.py,sha256=077Z789IJwOt2OcDl4LDQdCz9xuvZmLNKBFpstg3j-E,17141
6
+ pactown/generator.py,sha256=Sqjn0t-D23jT-BMsMQ6x0V3jnkwf0NtU_wDkLUuUXi4,5805
7
+ pactown/llm.py,sha256=1iwLGxX8ShxqBBRt0t2ILjH3RokADpXx2un317jraDo,14852
8
+ pactown/markpact_blocks.py,sha256=C3ew5KKGYLsi4LFZ_FMotDKzFLfGJ70qPR8h3d3uxAw,1402
9
+ pactown/network.py,sha256=7u6Rt8ZvCXppAE0ONKTrq0j60T1dZvgZP3LCxrMk-so,8577
10
+ pactown/orchestrator.py,sha256=hZ4tmdcD2nTLG9HIUu_t5NhJId7-u8Cx64lGfIrEZrk,15888
11
+ pactown/parallel.py,sha256=vz1MQqZNH6bQN803ch48Xc5zwaMrXTZrPhLu4cEAU14,8095
12
+ pactown/platform.py,sha256=YpGA5Fb0_lIh4FKKlHs63GNbcp3cA-doJmG1FIGJypY,4442
13
+ pactown/resolver.py,sha256=mR6gqDRuM-MXlzcuqK_GF8i9HuOj8uNS9ZoJxLSHs30,5615
14
+ pactown/runner_api.py,sha256=V6eomMxQUyhU4I5W4p4t9EMph4aYHn3rhxend0M9v9s,17625
15
+ pactown/sandbox_manager.py,sha256=joB6Y20cyLfgXlJEw2yElR_6UoszoRTroo1lTD6hkQc,26795
16
+ pactown/security.py,sha256=XlrzFUd85W9xTLK-KVPgiXpYknGUFe48hyNi6ZY_EqM,24945
17
+ pactown/service_runner.py,sha256=NR9Ydx8qZkNLDyhc46s3lFPViriHDMe2F1KC5if4O4E,45067
18
+ pactown/user_isolation.py,sha256=MMa7-maCdr7kSpEvIpaHrD8v54Vf_d6f1Ywv8FtJV2E,16645
19
+ pactown/deploy/__init__.py,sha256=N0cBpIShIWTOqn9XOWbqdDy6poFsnyIEPHUyTtB76TM,813
20
+ pactown/deploy/base.py,sha256=wuO2HgOc8BQibbqQcEqYF3YuopMiUULf0qnJfxzPWec,7740
21
+ pactown/deploy/compose.py,sha256=O7_M6Nc6Nniw70CoQ_9OIOEZpAEuTHABk85HPaL0QzE,11044
22
+ pactown/deploy/docker.py,sha256=Y6C4wW89t-qyYZetR2PDF6KLq3KWyzMmJRexzOBrJ7k,9315
23
+ pactown/deploy/kubernetes.py,sha256=S3hDrAEOvOJgu4qQ7TizKBD46M9DBuWocmlUdF49iKY,14567
24
+ pactown/deploy/podman.py,sha256=Zdu5aLLecdGVKsyF0GZAxRddNnNyqfABgKvBhCaUKnk,12152
25
+ pactown/deploy/quadlet.py,sha256=BcELmjhXmChKDIO0odnMO7prJOrPzDX-0xOb91_-IvQ,29505
26
+ pactown/deploy/quadlet_api.py,sha256=kM6A79LFG-FhG6w5iuP-wV0fcPbWx4kGhTJJu_5_MI8,17489
27
+ pactown/deploy/quadlet_shell.py,sha256=sYZeuz_W1RL00Ncg3sDDI5-E-h9Uo78m3rdYJ0KZcO8,18720
28
+ pactown/registry/__init__.py,sha256=1nv3JsKStu1aPl9Jy6pEJTFg2yk6srayNIas4KjfSKs,278
29
+ pactown/registry/client.py,sha256=kgIp9zQ_Jh_GxGAEgrsVnDNz5mYsmcf-8NvsOU9f0Ik,7750
30
+ pactown/registry/models.py,sha256=CpUUORDAJbDbLU5KZ3F0MjluX8EAZRYOS4xINFPEf9g,5316
31
+ pactown/registry/server.py,sha256=tnwrX6AXDDHHaxiW2bX3N_FqbbZUSFgTYZ1cB43ggp0,6161
32
+ pactown-0.1.47.dist-info/METADATA,sha256=N5yma7rPhyOh9sKeoNza1kDv5hKsvRCWXKWVU1elm0Y,15503
33
+ pactown-0.1.47.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
+ pactown-0.1.47.dist-info/entry_points.txt,sha256=UH1jCyqsE6rPgxYvU1ZKOFlWZCNK0yi953y44r8W-VE,195
35
+ pactown-0.1.47.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
36
+ pactown-0.1.47.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ pactown = pactown.cli:main
3
+ pactown-quadlet-api = pactown.deploy.quadlet_api:run_api
4
+ pactown-registry = pactown.registry.server:main
5
+ pactown-runner-api = pactown.runner_api:main
@@ -1,24 +0,0 @@
1
- pactown/__init__.py,sha256=w9Wv0Ssx_yWabMDy1JJh9oa4Vk7Uj_gDQvdo3cOXbeo,640
2
- pactown/cli.py,sha256=P7h2-iigtV5JHN6xX9vdKd0nmbr8GFNdXavKYwiUbHs,12431
3
- pactown/config.py,sha256=4i0A_9AtRCy3khV4jSgIos5YnVdkALWpL27NlbKpkNA,5393
4
- pactown/generator.py,sha256=TBC3ElDNXo-vDj6n4nDGRynVNo1rdfeMp1hYzHjfOkI,5936
5
- pactown/network.py,sha256=JRr6zcn2jsr2c-rIDh5mTutPau28Y-JkROS5VeHB1D4,8013
6
- pactown/orchestrator.py,sha256=um2EcdNBT7UYG16T2MO-JVaDPGn3uJ67UkofLIHDy8Q,16610
7
- pactown/parallel.py,sha256=GpnAijFFXcqvz08QgUEpJdDoeRMQ-Fq_VxrbRmyNeqo,8362
8
- pactown/resolver.py,sha256=CXss7EgjkMk2J_6wUP_Dx6m4BYTbFD7dMhMfXwpf1Do,5839
9
- pactown/sandbox_manager.py,sha256=SrAsF6E6bM2-javMhSoLwKHiOtq6VnD3tIa7Nwlktkc,11086
10
- pactown/deploy/__init__.py,sha256=GP-Bon4XtUsvNpvVShlJ7cxV8leOv2Pk2bhcHxMpPBs,473
11
- pactown/deploy/base.py,sha256=8rIyq4wl1QSXeQqWA9DTvr9d70dae2WhA4BXHtgm8fg,7535
12
- pactown/deploy/compose.py,sha256=eBDn_8QPUpnBQOw6S7fA-xS-bzYAEd9XKuKWDwz2kQ4,11427
13
- pactown/deploy/docker.py,sha256=5MQOdH78KnyrNRq4sCPNIA8cjifU8uHT7gbkQA-zosA,9633
14
- pactown/deploy/kubernetes.py,sha256=1z5HVhGc1K-JoNpIp2mu5OU3ufz8Fx4BH2clQjutGWI,14826
15
- pactown/deploy/podman.py,sha256=U8cTSTKfiHSCBbQvqhicYXO3XJpdu4f2kURo62Xp2sA,12574
16
- pactown/registry/__init__.py,sha256=_OPop2RWt8cjdsV6EYCIXEkuxS4PjWm72gMiwUkCE-U,278
17
- pactown/registry/client.py,sha256=EoD8PTUPmJeqSe1OLuFO8M_XbEyFox2L9E3oKO2SAy4,7933
18
- pactown/registry/models.py,sha256=wlL9Jsl60Gf27pUe8ej-oWSxRBlFOReLsr9W7joKiPI,5293
19
- pactown/registry/server.py,sha256=chLI1EUhlayeoaJzMnmHyjvsSU0y2JKwOLJnle0DyuQ,6303
20
- pactown-0.1.4.dist-info/METADATA,sha256=tXziMYrGKzF8_DXr31aZqfUOpH4g_7zqoJPe39tgDXA,11891
21
- pactown-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
22
- pactown-0.1.4.dist-info/entry_points.txt,sha256=v8Wxhuh8J_-OT6Y5FjE48dzbRGXUiznz0kUxSk5gtM0,93
23
- pactown-0.1.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
24
- pactown-0.1.4.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- pactown = pactown.cli:main
3
- pactown-registry = pactown.registry.server:main