fix(aurora): harden Kling integration and surface config diagnostics
This commit is contained in:
11
.env.example
11
.env.example
@@ -59,6 +59,9 @@ DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||
# OpenAI API (optional)
|
||||
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# Anthropic Claude API (Sofiia agent)
|
||||
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# Notion integration (optional)
|
||||
NOTION_API_KEY=
|
||||
NOTION_VERSION=2022-06-28
|
||||
@@ -139,6 +142,14 @@ ENVIRONMENT=development
|
||||
# Enable debug mode (true/false)
|
||||
DEBUG=true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Aurora / Kling Integration
|
||||
# -----------------------------------------------------------------------------
|
||||
KLING_ACCESS_KEY=
|
||||
KLING_SECRET_KEY=
|
||||
KLING_BASE_URL=https://api.klingai.com
|
||||
KLING_TIMEOUT=60
|
||||
|
||||
# =============================================================================
|
||||
# SECRET GENERATION COMMANDS
|
||||
# =============================================================================
|
||||
|
||||
@@ -94,6 +94,10 @@ services:
|
||||
- AURORA_FORCE_CPU=false
|
||||
- AURORA_PREFER_MPS=true
|
||||
- AURORA_ENABLE_VIDEOTOOLBOX=true
|
||||
- KLING_ACCESS_KEY=${KLING_ACCESS_KEY:-}
|
||||
- KLING_SECRET_KEY=${KLING_SECRET_KEY:-}
|
||||
- KLING_BASE_URL=${KLING_BASE_URL:-https://api.klingai.com}
|
||||
- KLING_TIMEOUT=${KLING_TIMEOUT:-60}
|
||||
volumes:
|
||||
- sofiia-data:/data
|
||||
networks:
|
||||
|
||||
74
services/aurora-service/README.md
Normal file
74
services/aurora-service/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Aurora Service
|
||||
|
||||
`aurora-service` is a FastAPI scaffold for AISTALK media forensics workflows.
|
||||
|
||||
## API
|
||||
|
||||
- `POST /api/aurora/upload` (`multipart/form-data`)
|
||||
- fields: `file`, `mode` (`tactical|forensic`)
|
||||
- returns `job_id`
|
||||
- `GET /api/aurora/status/{job_id}`
|
||||
- `GET /api/aurora/jobs`
|
||||
- `GET /api/aurora/result/{job_id}`
|
||||
- `POST /api/aurora/cancel/{job_id}`
|
||||
- `POST /api/aurora/delete/{job_id}`
|
||||
- `GET /api/aurora/files/{job_id}/{file_name}`
|
||||
|
||||
## Notes
|
||||
|
||||
- Visual media (`video`, `photo`) run deterministic sequential enhancement with conservative defaults:
|
||||
- `frame -> pre-denoise -> deblur -> face restore (GFPGAN / CodeFormer-style fallback) -> Real-ESRGAN`
|
||||
- For `priority=faces`, pipeline can switch to ROI-only face processing (background preserved).
|
||||
- Score-driven candidate selection is enabled for forensic face workflows.
|
||||
- `audio` path remains scaffold (`Echo`) for now.
|
||||
- Forensic mode adds chain-of-custody artifacts and signature metadata.
|
||||
- Model weights are auto-downloaded to `AURORA_MODELS_DIR` on first run.
|
||||
|
||||
## Local run
|
||||
|
||||
```bash
|
||||
cd services/aurora-service
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 9401
|
||||
```
|
||||
|
||||
## Native macOS run (Apple Silicon)
|
||||
|
||||
```bash
|
||||
cd services/aurora-service
|
||||
./setup-native-macos.sh
|
||||
./start-native-macos.sh
|
||||
```
|
||||
|
||||
This profile enables:
|
||||
- `AURORA_FORCE_CPU=false`
|
||||
- `AURORA_PREFER_MPS=true`
|
||||
- `AURORA_ENABLE_VIDEOTOOLBOX=true`
|
||||
|
||||
### Runtime env vars
|
||||
|
||||
- `AURORA_DATA_DIR` (default: `/data/aurora`)
|
||||
- `AURORA_MODELS_DIR` (default: `/data/aurora/models`)
|
||||
- `AURORA_FORCE_CPU` (default: `true`)
|
||||
- `AURORA_PREFER_MPS` (default: `true`)
|
||||
- `AURORA_ENABLE_VIDEOTOOLBOX` (default: `true`)
|
||||
- `AURORA_FFMPEG_VIDEO_ENCODER` (optional override, e.g. `h264_videotoolbox`)
|
||||
- `KLING_ACCESS_KEY` / `KLING_SECRET_KEY` (required for Kling endpoints)
|
||||
- `KLING_BASE_URL` (default: `https://api.klingai.com`)
|
||||
- `KLING_TIMEOUT` (default: `60`)
|
||||
|
||||
## Autostart via launchd (macOS)
|
||||
|
||||
```bash
|
||||
cd services/aurora-service
|
||||
./launchd/install-launchd.sh
|
||||
```
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
./launchd/status-launchd.sh
|
||||
./launchd/uninstall-launchd.sh
|
||||
```
|
||||
|
||||
`install-launchd.sh` bootstraps the service immediately, so reboot is not required.
|
||||
273
services/aurora-service/app/kling.py
Normal file
273
services/aurora-service/app/kling.py
Normal file
@@ -0,0 +1,273 @@
|
||||
"""Kling AI API client for video generation and enhancement."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
KLING_ACCESS_KEY = os.getenv("KLING_ACCESS_KEY", "").strip()
|
||||
KLING_SECRET_KEY = os.getenv("KLING_SECRET_KEY", "").strip()
|
||||
KLING_BASE_URL = os.getenv("KLING_BASE_URL", "https://api.klingai.com")
|
||||
KLING_TIMEOUT = int(os.getenv("KLING_TIMEOUT", "60"))
|
||||
|
||||
|
||||
def _kling_sign(access_key: str, secret_key: str) -> str:
|
||||
"""Generate Kling AI Bearer token via HMAC-SHA256 JWT-style signing."""
|
||||
import base64
|
||||
header = base64.urlsafe_b64encode(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()).rstrip(b"=").decode()
|
||||
now = int(time.time())
|
||||
payload = base64.urlsafe_b64encode(json.dumps({
|
||||
"iss": access_key,
|
||||
"exp": now + 1800,
|
||||
"nbf": now - 5,
|
||||
}).encode()).rstrip(b"=").decode()
|
||||
msg = f"{header}.{payload}"
|
||||
sig = hmac.new(secret_key.encode(), msg.encode(), hashlib.sha256).digest()
|
||||
sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=").decode()
|
||||
return f"{msg}.{sig_b64}"
|
||||
|
||||
|
||||
def _kling_headers() -> Dict[str, str]:
|
||||
if not KLING_ACCESS_KEY or not KLING_SECRET_KEY:
|
||||
raise RuntimeError(
|
||||
"Kling credentials are not configured. Set KLING_ACCESS_KEY and KLING_SECRET_KEY."
|
||||
)
|
||||
token = _kling_sign(KLING_ACCESS_KEY, KLING_SECRET_KEY)
|
||||
return {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
|
||||
def _kling_request(method: str, path: str, body: Optional[Dict] = None, timeout: int = KLING_TIMEOUT) -> Dict[str, Any]:
|
||||
url = f"{KLING_BASE_URL}{path}"
|
||||
data = json.dumps(body).encode() if body else None
|
||||
req = urllib.request.Request(url, data=data, headers=_kling_headers(), method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
raw = resp.read().decode("utf-8")
|
||||
return json.loads(raw)
|
||||
except urllib.error.HTTPError as e:
|
||||
err_body = e.read().decode("utf-8", errors="replace")
|
||||
raise RuntimeError(f"Kling API {method} {path} → {e.code}: {err_body}") from e
|
||||
|
||||
|
||||
def _kling_request_with_fallback(
|
||||
method: str,
|
||||
paths: List[str],
|
||||
body: Optional[Dict] = None,
|
||||
timeout: int = KLING_TIMEOUT,
|
||||
) -> Dict[str, Any]:
|
||||
"""Try several endpoint variants to tolerate provider path drift/gateway prefixes."""
|
||||
last_error: Optional[str] = None
|
||||
tried: List[str] = []
|
||||
for path in paths:
|
||||
tried.append(path)
|
||||
try:
|
||||
return _kling_request(method, path, body=body, timeout=timeout)
|
||||
except RuntimeError as e:
|
||||
msg = str(e)
|
||||
last_error = msg
|
||||
# 404 likely means wrong endpoint path; try next candidate.
|
||||
if "→ 404:" in msg:
|
||||
continue
|
||||
# Non-404 errors are usually actionable immediately.
|
||||
raise
|
||||
raise RuntimeError(
|
||||
f"Kling API endpoint mismatch for {method}. Tried: {tried}. Last error: {last_error or 'unknown'}"
|
||||
)
|
||||
|
||||
|
||||
# ── Video Enhancement (Video-to-Video) ──────────────────────────────────────
|
||||
|
||||
def kling_video_enhance(
|
||||
*,
|
||||
video_url: Optional[str] = None,
|
||||
video_id: Optional[str] = None,
|
||||
prompt: str = "",
|
||||
negative_prompt: str = "noise, blur, artifacts",
|
||||
mode: str = "pro",
|
||||
duration: str = "5",
|
||||
cfg_scale: float = 0.5,
|
||||
callback_url: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Submit a video-to-video enhancement task to Kling AI.
|
||||
|
||||
Args:
|
||||
video_url: Direct URL to input video.
|
||||
video_id: Kling resource ID for previously uploaded video.
|
||||
prompt: Text guidance for enhancement.
|
||||
negative_prompt: Things to avoid.
|
||||
mode: 'std' or 'pro'.
|
||||
duration: '5' or '10' seconds.
|
||||
cfg_scale: 0.0-1.0, how strongly to follow prompt.
|
||||
callback_url: Webhook for completion notification.
|
||||
|
||||
Returns:
|
||||
Task response dict with task_id.
|
||||
"""
|
||||
if not video_url and not video_id:
|
||||
raise ValueError("Either video_url or video_id must be provided")
|
||||
|
||||
payload: Dict[str, Any] = {
|
||||
"model": f"kling-v1-5",
|
||||
"mode": mode,
|
||||
"duration": duration,
|
||||
"cfg_scale": cfg_scale,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
}
|
||||
if video_url:
|
||||
payload["video_url"] = video_url
|
||||
if video_id:
|
||||
payload["video_id"] = video_id
|
||||
if callback_url:
|
||||
payload["callback_url"] = callback_url
|
||||
|
||||
return _kling_request_with_fallback(
|
||||
"POST",
|
||||
["/v1/videos/video2video", "/kling/v1/videos/video2video"],
|
||||
body=payload,
|
||||
)
|
||||
|
||||
|
||||
def kling_video_generate(
|
||||
*,
|
||||
image_url: Optional[str] = None,
|
||||
image_id: Optional[str] = None,
|
||||
prompt: str,
|
||||
negative_prompt: str = "noise, blur, artifacts, distortion",
|
||||
model: str = "kling-v1-5",
|
||||
mode: str = "pro",
|
||||
duration: str = "5",
|
||||
cfg_scale: float = 0.5,
|
||||
aspect_ratio: str = "16:9",
|
||||
callback_url: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate video from image + prompt (image-to-video).
|
||||
|
||||
Args:
|
||||
image_url: Source still frame URL.
|
||||
image_id: Kling resource ID for previously uploaded image.
|
||||
prompt: Animation guidance.
|
||||
model: 'kling-v1', 'kling-v1-5', 'kling-v1-6'.
|
||||
mode: 'std' or 'pro'.
|
||||
duration: '5' or '10'.
|
||||
aspect_ratio: '16:9', '9:16', '1:1'.
|
||||
"""
|
||||
if not image_url and not image_id:
|
||||
raise ValueError("Either image_url or image_id must be provided")
|
||||
|
||||
payload: Dict[str, Any] = {
|
||||
"model": model,
|
||||
"mode": mode,
|
||||
"duration": duration,
|
||||
"cfg_scale": cfg_scale,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
}
|
||||
if image_url:
|
||||
payload["image"] = {"type": "url", "url": image_url}
|
||||
if image_id:
|
||||
payload["image"] = {"type": "id", "id": image_id}
|
||||
if callback_url:
|
||||
payload["callback_url"] = callback_url
|
||||
|
||||
return _kling_request_with_fallback(
|
||||
"POST",
|
||||
["/v1/videos/image2video", "/kling/v1/videos/image2video"],
|
||||
body=payload,
|
||||
)
|
||||
|
||||
|
||||
def kling_task_status(task_id: str) -> Dict[str, Any]:
|
||||
"""Get status of any Kling task by ID."""
|
||||
return _kling_request_with_fallback(
|
||||
"GET",
|
||||
[f"/v1/tasks/{task_id}", f"/kling/v1/tasks/{task_id}"],
|
||||
)
|
||||
|
||||
|
||||
def kling_video_task_status(task_id: str, endpoint: str = "video2video") -> Dict[str, Any]:
|
||||
"""Get status of a video task."""
|
||||
return _kling_request_with_fallback(
|
||||
"GET",
|
||||
[f"/v1/videos/{endpoint}/{task_id}", f"/kling/v1/videos/{endpoint}/{task_id}"],
|
||||
)
|
||||
|
||||
|
||||
def kling_list_models() -> Dict[str, Any]:
|
||||
"""List available Kling models."""
|
||||
return _kling_request_with_fallback(
|
||||
"GET",
|
||||
["/v1/models", "/kling/v1/models"],
|
||||
)
|
||||
|
||||
|
||||
def kling_upload_file(file_path: Path) -> Dict[str, Any]:
|
||||
"""Upload a local file to Kling storage and return resource_id."""
|
||||
import base64
|
||||
with open(file_path, "rb") as f:
|
||||
data = f.read()
|
||||
b64 = base64.b64encode(data).decode()
|
||||
suffix = file_path.suffix.lstrip(".").lower()
|
||||
mime_map = {
|
||||
"mp4": "video/mp4", "mov": "video/quicktime", "avi": "video/x-msvideo",
|
||||
"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png",
|
||||
}
|
||||
mime = mime_map.get(suffix, "application/octet-stream")
|
||||
payload = {
|
||||
"file": b64,
|
||||
"file_name": file_path.name,
|
||||
"content_type": mime,
|
||||
}
|
||||
return _kling_request_with_fallback(
|
||||
"POST",
|
||||
["/v1/files/upload", "/v1/files", "/kling/v1/files/upload", "/kling/v1/files"],
|
||||
body=payload,
|
||||
timeout=120,
|
||||
)
|
||||
|
||||
|
||||
def kling_poll_until_done(
|
||||
task_id: str,
|
||||
endpoint: str = "video2video",
|
||||
max_wait_sec: int = 600,
|
||||
poll_interval: int = 5,
|
||||
) -> Dict[str, Any]:
|
||||
"""Poll Kling task until completed/failed or timeout."""
|
||||
start = time.time()
|
||||
while True:
|
||||
status_resp = kling_video_task_status(task_id, endpoint)
|
||||
task = status_resp.get("data", {})
|
||||
state = task.get("task_status") or task.get("status") or "processing"
|
||||
|
||||
if state in ("succeed", "completed", "failed", "error"):
|
||||
return status_resp
|
||||
|
||||
elapsed = time.time() - start
|
||||
if elapsed >= max_wait_sec:
|
||||
raise TimeoutError(f"Kling task {task_id} timed out after {max_wait_sec}s (last status: {state})")
|
||||
|
||||
logger.debug("Kling task %s status=%s elapsed=%.0fs", task_id, state, elapsed)
|
||||
time.sleep(poll_interval)
|
||||
|
||||
|
||||
def kling_health_check() -> Dict[str, Any]:
|
||||
"""Quick connectivity check — returns status dict."""
|
||||
try:
|
||||
resp = _kling_request("GET", "/v1/models", timeout=10)
|
||||
return {"ok": True, "models": resp}
|
||||
except Exception as exc:
|
||||
return {"ok": False, "error": str(exc)}
|
||||
1327
services/aurora-service/app/main.py
Normal file
1327
services/aurora-service/app/main.py
Normal file
File diff suppressed because it is too large
Load Diff
113
services/aurora-service/launchd/install-launchd.sh
Executable file
113
services/aurora-service/launchd/install-launchd.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LABEL="${AURORA_LAUNCHD_LABEL:-com.daarion.aurora}"
|
||||
DOMAIN="gui/$(id -u)"
|
||||
LAUNCH_AGENTS_DIR="${HOME}/Library/LaunchAgents"
|
||||
PLIST_PATH="${LAUNCH_AGENTS_DIR}/${LABEL}.plist"
|
||||
START_SCRIPT="${ROOT_DIR}/start-native-macos.sh"
|
||||
|
||||
PORT_VALUE="${PORT:-9401}"
|
||||
DATA_DIR_VALUE="${AURORA_DATA_DIR:-${HOME}/.sofiia/aurora-data}"
|
||||
MODELS_DIR_VALUE="${AURORA_MODELS_DIR:-${DATA_DIR_VALUE}/models}"
|
||||
PUBLIC_BASE_URL_VALUE="${AURORA_PUBLIC_BASE_URL:-http://127.0.0.1:${PORT_VALUE}}"
|
||||
CORS_ORIGINS_VALUE="${AURORA_CORS_ORIGINS:-*}"
|
||||
FORCE_CPU_VALUE="${AURORA_FORCE_CPU:-false}"
|
||||
PREFER_MPS_VALUE="${AURORA_PREFER_MPS:-true}"
|
||||
ENABLE_VTB_VALUE="${AURORA_ENABLE_VIDEOTOOLBOX:-true}"
|
||||
KLING_ACCESS_KEY_VALUE="${KLING_ACCESS_KEY:-}"
|
||||
KLING_SECRET_KEY_VALUE="${KLING_SECRET_KEY:-}"
|
||||
KLING_BASE_URL_VALUE="${KLING_BASE_URL:-https://api.klingai.com}"
|
||||
KLING_TIMEOUT_VALUE="${KLING_TIMEOUT:-60}"
|
||||
|
||||
LOG_DIR="${DATA_DIR_VALUE}/logs"
|
||||
LOG_OUT="${LOG_DIR}/launchd.out.log"
|
||||
LOG_ERR="${LOG_DIR}/launchd.err.log"
|
||||
PATH_VALUE="${PATH:-/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}"
|
||||
|
||||
if [ ! -x "${START_SCRIPT}" ]; then
|
||||
echo "[aurora-launchd] missing start script: ${START_SCRIPT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "${ROOT_DIR}/.venv-macos/bin/python" ]; then
|
||||
echo "[aurora-launchd] .venv-macos is missing. Run ./setup-native-macos.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${LAUNCH_AGENTS_DIR}" "${LOG_DIR}" "${DATA_DIR_VALUE}" "${MODELS_DIR_VALUE}"
|
||||
|
||||
cat > "${PLIST_PATH}" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>${LABEL}</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${START_SCRIPT}</string>
|
||||
</array>
|
||||
|
||||
<key>WorkingDirectory</key>
|
||||
<string>${ROOT_DIR}</string>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>${LOG_OUT}</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>${LOG_ERR}</string>
|
||||
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>${PATH_VALUE}</string>
|
||||
<key>PYTHONUNBUFFERED</key>
|
||||
<string>1</string>
|
||||
<key>PORT</key>
|
||||
<string>${PORT_VALUE}</string>
|
||||
<key>AURORA_DATA_DIR</key>
|
||||
<string>${DATA_DIR_VALUE}</string>
|
||||
<key>AURORA_MODELS_DIR</key>
|
||||
<string>${MODELS_DIR_VALUE}</string>
|
||||
<key>AURORA_PUBLIC_BASE_URL</key>
|
||||
<string>${PUBLIC_BASE_URL_VALUE}</string>
|
||||
<key>AURORA_CORS_ORIGINS</key>
|
||||
<string>${CORS_ORIGINS_VALUE}</string>
|
||||
<key>AURORA_FORCE_CPU</key>
|
||||
<string>${FORCE_CPU_VALUE}</string>
|
||||
<key>AURORA_PREFER_MPS</key>
|
||||
<string>${PREFER_MPS_VALUE}</string>
|
||||
<key>AURORA_ENABLE_VIDEOTOOLBOX</key>
|
||||
<string>${ENABLE_VTB_VALUE}</string>
|
||||
<key>KLING_ACCESS_KEY</key>
|
||||
<string>${KLING_ACCESS_KEY_VALUE}</string>
|
||||
<key>KLING_SECRET_KEY</key>
|
||||
<string>${KLING_SECRET_KEY_VALUE}</string>
|
||||
<key>KLING_BASE_URL</key>
|
||||
<string>${KLING_BASE_URL_VALUE}</string>
|
||||
<key>KLING_TIMEOUT</key>
|
||||
<string>${KLING_TIMEOUT_VALUE}</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
chmod 644 "${PLIST_PATH}"
|
||||
|
||||
launchctl bootout "${DOMAIN}/${LABEL}" >/dev/null 2>&1 || true
|
||||
launchctl bootstrap "${DOMAIN}" "${PLIST_PATH}"
|
||||
launchctl enable "${DOMAIN}/${LABEL}" >/dev/null 2>&1 || true
|
||||
launchctl kickstart -k "${DOMAIN}/${LABEL}"
|
||||
|
||||
echo "[aurora-launchd] installed: ${PLIST_PATH}"
|
||||
echo "[aurora-launchd] active label: ${DOMAIN}/${LABEL}"
|
||||
echo "[aurora-launchd] logs: ${LOG_OUT} | ${LOG_ERR}"
|
||||
echo "[aurora-launchd] check: launchctl print ${DOMAIN}/${LABEL}"
|
||||
Reference in New Issue
Block a user