152 lines
4.2 KiB
Python
152 lines
4.2 KiB
Python
"""
|
|
Configuration and environment management for Elemes CLI.
|
|
Multi-OS compatible using pathlib.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
# Core directory paths resolved portably across Linux, macOS, Windows
|
|
CLI_DIR = Path(__file__).resolve().parent
|
|
SCRIPT_DIR = CLI_DIR.parent
|
|
PARENT_DIR = SCRIPT_DIR.parent
|
|
PROJECT_NAME = PARENT_DIR.name
|
|
|
|
EXAMPLES_DIR = SCRIPT_DIR / "examples"
|
|
ENV_FILE = PARENT_DIR / ".env"
|
|
LAST_RUN_FILE = SCRIPT_DIR / ".last_run"
|
|
|
|
# Regex for filtering noisy stderr lines from podman-compose
|
|
NOISE_RE = re.compile(
|
|
r"^(podman-compose version:|\[.*--version.*\]$|using podman version:|podman exec |exit code: )"
|
|
)
|
|
|
|
|
|
def read_env(env_path: Optional[Path] = None) -> Dict[str, str]:
|
|
"""
|
|
Parse .env file without shell sourcing, preserving values cleanly.
|
|
Compatible with Windows and Unix newlines.
|
|
"""
|
|
path = env_path or ENV_FILE
|
|
env_vars: Dict[str, str] = {}
|
|
if not path.is_file():
|
|
return env_vars
|
|
|
|
try:
|
|
content = path.read_text(encoding="utf-8")
|
|
for line in content.splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if "=" in line:
|
|
key, val = line.split("=", 1)
|
|
key = key.strip()
|
|
val = val.strip()
|
|
# Remove enclosing matching quotes if present
|
|
if (val.startswith('"') and val.endswith('"')) or (
|
|
val.startswith("'") and val.endswith("'")
|
|
):
|
|
val = val[1:-1]
|
|
env_vars[key] = val
|
|
except Exception:
|
|
pass
|
|
return env_vars
|
|
|
|
|
|
def env_val(key: str, default: str = "", env_path: Optional[Path] = None) -> str:
|
|
"""Read a single configuration value from .env."""
|
|
env = read_env(env_path)
|
|
return env.get(key, default)
|
|
|
|
|
|
def generate_ts_config() -> None:
|
|
"""
|
|
Generate lms-tail.json from template based on VELXIO_MODE.
|
|
Prepares Tailscale Serve config before starting containers.
|
|
"""
|
|
template_path = SCRIPT_DIR / "config" / "lms-tail.json.template"
|
|
output_path = SCRIPT_DIR / "config" / "lms-tail.json"
|
|
|
|
if not template_path.is_file():
|
|
return
|
|
|
|
mode = env_val("VELXIO_MODE", "local").lower()
|
|
if mode == "remote":
|
|
velxio_host = env_val("VELXIO_HOST", "velxio-dev")
|
|
target = f"http://{velxio_host}:80/"
|
|
else:
|
|
target = "http://127.0.0.1:80/"
|
|
|
|
try:
|
|
content = template_path.read_text(encoding="utf-8")
|
|
new_content = content.replace("__VELXIO_PROXY_TARGET__", target)
|
|
output_path.write_text(new_content, encoding="utf-8")
|
|
except Exception as e:
|
|
print(f"⚠️ Gagal membuat {output_path.name}: {e}")
|
|
|
|
|
|
def elemes_services() -> List[str]:
|
|
"""
|
|
List of elemes services to start based on VELXIO_MODE.
|
|
Empty list = start all services (local mode).
|
|
Non-empty = start only listed services (remote mode).
|
|
"""
|
|
mode = env_val("VELXIO_MODE", "local").lower()
|
|
if mode == "remote":
|
|
return [
|
|
"elemes-ts",
|
|
"postgres",
|
|
"elemes",
|
|
"compiler-worker",
|
|
"elemes-frontend",
|
|
"flowchart",
|
|
]
|
|
return []
|
|
|
|
|
|
def get_run_timestamp() -> str:
|
|
"""Format current time like date '+%Y-%m-%d %H:%M:%S %Z'."""
|
|
now = datetime.now().astimezone()
|
|
# Format: 2026-09-06 05:30:00 WIB
|
|
return now.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
|
|
|
|
def record_last_run(command_str: str) -> str:
|
|
"""Record execution command and time into .last_run."""
|
|
run_time = get_run_timestamp()
|
|
try:
|
|
LAST_RUN_FILE.write_text(
|
|
f"command: {command_str}\ntime: {run_time}\n",
|
|
encoding="utf-8",
|
|
)
|
|
except Exception:
|
|
pass
|
|
return run_time
|
|
|
|
|
|
# Verbose mode state
|
|
_VERBOSE: bool = False
|
|
|
|
|
|
def is_verbose() -> bool:
|
|
"""Return whether verbose mode is currently active."""
|
|
return _VERBOSE
|
|
|
|
|
|
def set_verbose(val: bool) -> None:
|
|
"""Set verbose mode globally."""
|
|
global _VERBOSE
|
|
_VERBOSE = val
|
|
|
|
|
|
def toggle_verbose() -> bool:
|
|
"""Toggle verbose mode and return new state."""
|
|
global _VERBOSE
|
|
_VERBOSE = not _VERBOSE
|
|
return _VERBOSE
|