Implement install stages S3–S5: package, policy, and skills sync.
Attach/onboard sandbox from agents/hermes, additive OpenShell policy overlays, and nemohermes skill install for scaffold skills. No doctor/connect and no push.
This commit is contained in:
+10
-3
@@ -1,6 +1,6 @@
|
||||
# Host scripts
|
||||
|
||||
**Status:** S0b–S2 implemented. S3–S7 pending.
|
||||
**Status:** S0b–S5 implemented. S6–S7 pending.
|
||||
|
||||
All scripts wrap **`nemohermes` / `openshell` / Docker**. No parallel control API.
|
||||
|
||||
@@ -9,9 +9,11 @@ All scripts wrap **`nemohermes` / `openshell` / Docker**. No parallel control AP
|
||||
| Script | Role | Status |
|
||||
|--------|------|--------|
|
||||
| `bootstrap.sh` | Host prereqs; install Docker if missing | ✅ S0b |
|
||||
| `install.sh` | Staged installer (S0b–S2) | ✅ S0b–S2 |
|
||||
| `install.sh` | Staged installer (S0b–S5) | ✅ S0b–S5 |
|
||||
| `install/s1-env.sh` | S1: create/validate `.env` | ✅ S1 |
|
||||
| `install/s2-models.sh` | S2: model + vision config + smoke | ✅ S2 |
|
||||
| `install/s4-sandbox.sh` | S4: sandbox verify (attach) or onboard | ✅ S4 |
|
||||
| `install/s5-policy-skills.sh` | S5: policy overlays + skills sync | ✅ S5 |
|
||||
| `upgrade.sh` | Snapshot, pull pins, migrate, re-apply policy, doctor | ⏳ Pending |
|
||||
| `doctor.sh` | Health checks | ⏳ Pending |
|
||||
| `connect/*.sh` | Operator connect helpers (Square, QBO, Vagaro, channels) | ⏳ Pending |
|
||||
@@ -30,18 +32,23 @@ All scripts wrap **`nemohermes` / `openshell` / Docker**. No parallel control AP
|
||||
# Bootstrap (Docker if missing)
|
||||
./scripts/bootstrap.sh
|
||||
|
||||
# Full install (S0b–S2)
|
||||
# Full install (S0b–S5)
|
||||
./scripts/install.sh
|
||||
|
||||
# Individual stages
|
||||
./scripts/install.sh --stage s1 # env only
|
||||
./scripts/install.sh --stage s2 # models only
|
||||
./scripts/install.sh --stage s4 # sandbox verify/onboard
|
||||
./scripts/install.sh --stage s5 # policy + skills
|
||||
./scripts/install.sh --stage s3-s5 # S3 through S5
|
||||
|
||||
# Or via Make
|
||||
make bootstrap
|
||||
make install
|
||||
make install-s1
|
||||
make install-s2
|
||||
make install-s3-s5
|
||||
make install-s5
|
||||
```
|
||||
|
||||
## Design reference
|
||||
|
||||
+45
-17
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/install.sh — Lumina staged installer
|
||||
#
|
||||
# Runs install stages S0b–S2 (S3+ not yet implemented).
|
||||
# Runs install stages S0b–S5.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/install.sh # run all implemented stages (S0b–S2)
|
||||
# ./scripts/install.sh # run all implemented stages (S0b–S5)
|
||||
# ./scripts/install.sh --stage s1 # run only S1
|
||||
# ./scripts/install.sh --stage s2 # run only S2
|
||||
# ./scripts/install.sh --stage s3-s5 # run S3 through S5
|
||||
# ./scripts/install.sh --help
|
||||
#
|
||||
# All stages are idempotent. Re-running is safe.
|
||||
@@ -25,23 +25,27 @@ usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
Run Lumina install stages (S0b–S2 implemented).
|
||||
Run Lumina install stages (S0b–S5 implemented).
|
||||
|
||||
Options:
|
||||
--stage <s1|s2> Run only the specified stage
|
||||
--help Show this help
|
||||
--stage <s0b|s1|s2|s3|s4|s5|s3-s5> Run only the specified stage or range
|
||||
--help Show this help
|
||||
|
||||
Stages:
|
||||
S0b Docker install-if-missing (bootstrap)
|
||||
S1 Repository environment (.env)
|
||||
S2 Model + vision configuration + smoke test
|
||||
S3 Stack alignment (compose documentation; no-op on attach)
|
||||
S4 Sandbox verification or onboard (attach mode default)
|
||||
S5 Policy overlays + skills sync
|
||||
|
||||
All stages are idempotent.
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # run S0b → S1 → S2
|
||||
$(basename "$0") # run S0b → S1 → S2 → S3 → S4 → S5
|
||||
$(basename "$0") --stage s1 # run only S1 (env)
|
||||
$(basename "$0") --stage s2 # run only S2 (models)
|
||||
$(basename "$0") --stage s5 # run only S5 (policy + skills)
|
||||
$(basename "$0") --stage s3-s5 # run S3 → S4 → S5
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -54,7 +58,7 @@ while [[ $# -gt 0 ]]; do
|
||||
shift
|
||||
SINGLE_STAGE="${1:-}"
|
||||
if [[ -z "$SINGLE_STAGE" ]]; then
|
||||
log_error "--stage requires a value (s1 or s2)"
|
||||
log_error "--stage requires a value"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
@@ -67,7 +71,7 @@ while [[ $# -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Run stages ─────────────────────────────────────────────────────────────
|
||||
# ── Stage runners ──────────────────────────────────────────────────────────
|
||||
run_s0b() {
|
||||
log_section "S0b: Docker bootstrap"
|
||||
bash "$SCRIPT_DIR/bootstrap.sh"
|
||||
@@ -81,17 +85,37 @@ run_s2() {
|
||||
bash "$SCRIPT_DIR/install/s2-models.sh"
|
||||
}
|
||||
|
||||
log_section "Lumina installer (stages S0b–S2)"
|
||||
run_s3() {
|
||||
log_section "S3: Stack alignment"
|
||||
log_info "OpenShell manages the Hermes sandbox container."
|
||||
log_info "Product compose (deploy/compose/) is optional for local MCP/webhooks."
|
||||
log_info "No action needed for UAT attach path."
|
||||
}
|
||||
|
||||
run_s4() {
|
||||
bash "$SCRIPT_DIR/install/s4-sandbox.sh"
|
||||
}
|
||||
|
||||
run_s5() {
|
||||
bash "$SCRIPT_DIR/install/s5-policy-skills.sh"
|
||||
}
|
||||
|
||||
# ── Main ───────────────────────────────────────────────────────────────────
|
||||
log_section "Lumina installer (stages S0b–S5)"
|
||||
warn_if_root
|
||||
|
||||
if [[ -n "$SINGLE_STAGE" ]]; then
|
||||
case "$SINGLE_STAGE" in
|
||||
s0b) run_s0b ;;
|
||||
s1) run_s1 ;;
|
||||
s2) run_s2 ;;
|
||||
s0b) run_s0b ;;
|
||||
s1) run_s1 ;;
|
||||
s2) run_s2 ;;
|
||||
s3) run_s3 ;;
|
||||
s4) run_s4 ;;
|
||||
s5) run_s5 ;;
|
||||
s3-s5) run_s3; run_s4; run_s5 ;;
|
||||
*)
|
||||
log_error "Unknown stage: $SINGLE_STAGE"
|
||||
log_error "Valid stages: s0b, s1, s2"
|
||||
log_error "Valid stages: s0b, s1, s2, s3, s4, s5, s3-s5"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -100,10 +124,14 @@ else
|
||||
run_s0b
|
||||
run_s1
|
||||
run_s2
|
||||
run_s3
|
||||
run_s4
|
||||
run_s5
|
||||
fi
|
||||
|
||||
log_section "Install complete (S0b–S2)"
|
||||
log_section "Install complete (S0b–S5)"
|
||||
log_info "Next steps:"
|
||||
log_info " - Review .env for correctness"
|
||||
log_info " - Continue with S3+ when implemented (compose, sandbox, policy)"
|
||||
log_info " - Verify policy: nemohermes $(get_sandbox_name) policy-list"
|
||||
log_info " - Continue with S6 (doctor) when implemented"
|
||||
log_info " - See docs/INSTALL.md for full procedure"
|
||||
|
||||
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/install/s4-sandbox.sh — S4: sandbox verification / onboard
|
||||
#
|
||||
# Two modes:
|
||||
# attach (default) — verify existing sandbox is healthy; no destructive ops
|
||||
# onboard — create new sandbox from agent package (clean host only)
|
||||
#
|
||||
# Platform-first: all mutations via nemohermes. Never hand-edit in-sandbox config.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/install/s4-sandbox.sh # attach mode (default)
|
||||
# ./scripts/install/s4-sandbox.sh --mode attach
|
||||
# ./scripts/install/s4-sandbox.sh --mode onboard
|
||||
# ./scripts/install/s4-sandbox.sh --mode onboard --dry-run
|
||||
# ./scripts/install/s4-sandbox.sh --help
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source shared helpers
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/env.sh
|
||||
source "$SCRIPT_DIR/../lib/env.sh"
|
||||
|
||||
# ── Defaults ───────────────────────────────────────────────────────────────
|
||||
MODE="${LUMINA_INSTALL_MODE:-attach}"
|
||||
DRY_RUN=0
|
||||
|
||||
# ── Parse args ─────────────────────────────────────────────────────────────
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
S4: Sandbox verification or onboard.
|
||||
|
||||
Options:
|
||||
--mode <attach|onboard> Install mode (default: attach)
|
||||
--dry-run Preview onboard without executing
|
||||
--help Show this help
|
||||
|
||||
Modes:
|
||||
attach Verify existing sandbox is healthy (UAT default)
|
||||
onboard Create new sandbox from agent package (clean host)
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # attach mode
|
||||
$(basename "$0") --mode onboard # onboard mode
|
||||
$(basename "$0") --mode onboard --dry-run # onboard dry-run
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
--mode)
|
||||
shift
|
||||
MODE="${1:-}"
|
||||
if [[ -z "$MODE" ]]; then
|
||||
log_error "--mode requires a value (attach or onboard)"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Validate mode ──────────────────────────────────────────────────────────
|
||||
if [[ "$MODE" != "attach" && "$MODE" != "onboard" ]]; then
|
||||
log_error "Invalid mode: $MODE (must be 'attach' or 'onboard')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_section "S4: Sandbox ($MODE mode)"
|
||||
|
||||
# ── Load .env ──────────────────────────────────────────────────────────────
|
||||
load_env
|
||||
|
||||
# ── Validate required keys ─────────────────────────────────────────────────
|
||||
validate_env || exit 1
|
||||
|
||||
# ── Check CLI prerequisites ────────────────────────────────────────────────
|
||||
require_cmd nemohermes "Install nemohermes CLI (part of NemoClaw platform)"
|
||||
|
||||
SANDBOX_NAME="$(get_sandbox_name)"
|
||||
AGENT_PKG_DIR="$REPO_ROOT/agents/hermes"
|
||||
|
||||
# ── Attach mode ────────────────────────────────────────────────────────────
|
||||
do_attach() {
|
||||
log_info "Attach mode: verifying sandbox '$SANDBOX_NAME'"
|
||||
|
||||
# Check sandbox exists and report status
|
||||
if ! nemohermes "$SANDBOX_NAME" status &>/dev/null; then
|
||||
log_error "Sandbox '$SANDBOX_NAME' not found or not reachable."
|
||||
log_error "If this is a clean host, re-run with --mode onboard"
|
||||
log_error "Or create the sandbox manually: nemohermes onboard"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Sandbox '$SANDBOX_NAME' is healthy."
|
||||
|
||||
# Verify agent package exists (reference only in attach mode)
|
||||
if [[ -d "$AGENT_PKG_DIR" ]]; then
|
||||
log_info "Agent package found at $AGENT_PKG_DIR"
|
||||
if [[ -f "$AGENT_PKG_DIR/skills-manifest/manifest.yaml" ]]; then
|
||||
log_info "Skills manifest present — S5 will sync skills"
|
||||
else
|
||||
log_warn "Skills manifest not found — skills sync (S5) may be incomplete"
|
||||
fi
|
||||
else
|
||||
log_warn "Agent package directory not found at $AGENT_PKG_DIR"
|
||||
fi
|
||||
|
||||
log_info "S4 attach complete."
|
||||
}
|
||||
|
||||
# ── Onboard mode ───────────────────────────────────────────────────────────
|
||||
do_onboard() {
|
||||
log_info "Onboard mode: preparing sandbox '$SANDBOX_NAME' from agent package"
|
||||
|
||||
# Verify agent package exists
|
||||
if [[ ! -d "$AGENT_PKG_DIR" ]]; then
|
||||
log_error "Agent package not found at $AGENT_PKG_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if sandbox already exists — do not destroy it
|
||||
if nemohermes "$SANDBOX_NAME" status &>/dev/null; then
|
||||
log_warn "Sandbox '$SANDBOX_NAME' already exists."
|
||||
log_warn "Onboard mode does not destroy existing sandboxes."
|
||||
log_warn "Switching to attach behavior for safety."
|
||||
do_attach
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
log_info "DRY-RUN: Would execute:"
|
||||
log_info " nemohermes onboard --from-dir $AGENT_PKG_DIR"
|
||||
log_info " (with inference from .env: $LUMINA_INFERENCE_BASE_URL)"
|
||||
log_info "S4 onboard dry-run complete."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Onboard with agent package
|
||||
log_info "Running nemohermes onboard with agent package…"
|
||||
if nemohermes onboard --from-dir "$AGENT_PKG_DIR"; then
|
||||
log_info "Sandbox '$SANDBOX_NAME' onboarded successfully."
|
||||
else
|
||||
log_error "Onboard failed. Check nemohermes logs for details."
|
||||
log_error "You may need to run nemohermes onboard manually first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "S4 onboard complete."
|
||||
}
|
||||
|
||||
# ── Execute ────────────────────────────────────────────────────────────────
|
||||
case "$MODE" in
|
||||
attach) do_attach ;;
|
||||
onboard) do_onboard ;;
|
||||
esac
|
||||
Executable
+197
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/install/s5-policy-skills.sh — S5: policy apply + skills sync
|
||||
#
|
||||
# Applies Lumina policy overlays and syncs skills into the sandbox.
|
||||
# Additive only: never removes existing presets.
|
||||
#
|
||||
# Platform-first: all mutations via nemohermes.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/install/s5-policy-skills.sh
|
||||
# ./scripts/install/s5-policy-skills.sh --policy-only
|
||||
# ./scripts/install/s5-policy-skills.sh --skills-only
|
||||
# ./scripts/install/s5-policy-skills.sh --help
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source shared helpers
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/env.sh
|
||||
source "$SCRIPT_DIR/../lib/env.sh"
|
||||
|
||||
# ── Defaults ───────────────────────────────────────────────────────────────
|
||||
DO_POLICY=1
|
||||
DO_SKILLS=1
|
||||
POLICY_ONLY_SET=0
|
||||
SKILLS_ONLY_SET=0
|
||||
|
||||
# ── Parse args ─────────────────────────────────────────────────────────────
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
S5: Apply policy overlays and sync skills into the sandbox.
|
||||
|
||||
Options:
|
||||
--policy-only Apply policy overlays only (skip skills sync)
|
||||
--skills-only Sync skills only (skip policy apply)
|
||||
--help Show this help
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # policy + skills
|
||||
$(basename "$0") --policy-only # policy only
|
||||
$(basename "$0") --skills-only # skills only
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
--policy-only)
|
||||
DO_POLICY=1
|
||||
DO_SKILLS=0
|
||||
POLICY_ONLY_SET=1
|
||||
shift
|
||||
;;
|
||||
--skills-only)
|
||||
DO_POLICY=0
|
||||
DO_SKILLS=1
|
||||
SKILLS_ONLY_SET=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Validate mutually exclusive flags ──────────────────────────────────────
|
||||
if [[ $POLICY_ONLY_SET -eq 1 && $SKILLS_ONLY_SET -eq 1 ]]; then
|
||||
log_error "--policy-only and --skills-only are mutually exclusive"
|
||||
exit 1
|
||||
fi
|
||||
if [[ $DO_POLICY -eq 0 && $DO_SKILLS -eq 0 ]]; then
|
||||
log_error "Internal error: both DO_POLICY and DO_SKILLS are disabled"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_section "S5: Policy + Skills sync"
|
||||
|
||||
# ── Load .env ──────────────────────────────────────────────────────────────
|
||||
load_env
|
||||
|
||||
# ── Validate required keys ─────────────────────────────────────────────────
|
||||
validate_env || exit 1
|
||||
|
||||
# ── Check CLI prerequisites ────────────────────────────────────────────────
|
||||
require_cmd nemohermes "Install nemohermes CLI (part of NemoClaw platform)"
|
||||
|
||||
SANDBOX_NAME="$(get_sandbox_name)"
|
||||
POLICY_DIR="$REPO_ROOT/policy/openshell/overlays"
|
||||
SKILLS_DIR="$REPO_ROOT/skills"
|
||||
|
||||
# ── Verify sandbox exists ──────────────────────────────────────────────────
|
||||
if ! nemohermes "$SANDBOX_NAME" status &>/dev/null 2>&1; then
|
||||
log_error "Sandbox '$SANDBOX_NAME' not found. Run S4 first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Policy apply ───────────────────────────────────────────────────────────
|
||||
apply_policy() {
|
||||
log_section "S5a: Apply policy overlays"
|
||||
|
||||
if [[ ! -d "$POLICY_DIR" ]]; then
|
||||
log_warn "Policy overlays directory not found: $POLICY_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Apply inference overlay (always apply at S5)
|
||||
local inference_policy="$POLICY_DIR/inference.yaml"
|
||||
if [[ -f "$inference_policy" ]]; then
|
||||
log_info "Applying inference policy overlay…"
|
||||
if nemohermes "$SANDBOX_NAME" policy-add --from-file "$inference_policy" --yes 2>&1; then
|
||||
log_info "Inference policy overlay applied."
|
||||
else
|
||||
log_warn "Inference policy overlay may already be applied (idempotent)."
|
||||
fi
|
||||
else
|
||||
log_warn "Inference policy overlay not found: $inference_policy"
|
||||
fi
|
||||
|
||||
# List current policy for verification
|
||||
log_info "Current policy presets:"
|
||||
nemohermes "$SANDBOX_NAME" policy-list 2>&1 || log_warn "Could not list policy presets"
|
||||
|
||||
log_info "S5a policy apply complete."
|
||||
}
|
||||
|
||||
# ── Skills sync ────────────────────────────────────────────────────────────
|
||||
sync_skills() {
|
||||
log_section "S5b: Sync skills"
|
||||
|
||||
if [[ ! -d "$SKILLS_DIR" ]]; then
|
||||
log_warn "Skills directory not found: $SKILLS_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local skill_count=0
|
||||
local skill_ok=0
|
||||
local skill_skip=0
|
||||
local skill_fail=0
|
||||
|
||||
# Iterate skill directories (skip _lib and hidden dirs)
|
||||
for skill_dir in "$SKILLS_DIR"/*/; do
|
||||
# Skip if not a directory
|
||||
[[ -d "$skill_dir" ]] || continue
|
||||
|
||||
local skill_name
|
||||
skill_name="$(basename "$skill_dir")"
|
||||
|
||||
# Skip _lib (shared library, not a skill)
|
||||
if [[ "$skill_name" == "_lib" ]]; then
|
||||
log_info "Skipping _lib (shared library)"
|
||||
continue
|
||||
fi
|
||||
|
||||
skill_count=$((skill_count + 1))
|
||||
|
||||
# Check for SKILL.md (required by nemohermes skill install)
|
||||
local skill_md="$skill_dir/SKILL.md"
|
||||
if [[ ! -f "$skill_md" ]]; then
|
||||
log_warn "Skipping '$skill_name': no SKILL.md found"
|
||||
skill_skip=$((skill_skip + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
log_info "Installing skill: $skill_name"
|
||||
if nemohermes "$SANDBOX_NAME" skill install "$skill_dir" 2>&1; then
|
||||
log_info " ✓ $skill_name installed"
|
||||
skill_ok=$((skill_ok + 1))
|
||||
else
|
||||
log_warn " ✗ $skill_name failed (may already be installed)"
|
||||
skill_fail=$((skill_fail + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "Skills sync summary: $skill_count found, $skill_ok installed, $skill_skip skipped, $skill_fail failed"
|
||||
|
||||
if [[ $skill_count -eq 0 ]]; then
|
||||
log_warn "No skill directories found in $SKILLS_DIR"
|
||||
fi
|
||||
|
||||
log_info "S5b skills sync complete."
|
||||
}
|
||||
|
||||
# ── Execute ────────────────────────────────────────────────────────────────
|
||||
if [[ $DO_POLICY -eq 1 ]]; then
|
||||
apply_policy
|
||||
fi
|
||||
|
||||
if [[ $DO_SKILLS -eq 1 ]]; then
|
||||
sync_skills
|
||||
fi
|
||||
|
||||
log_info "S5 complete."
|
||||
Reference in New Issue
Block a user