2026-03-31 12:08:42 +07:00
|
|
|
#!/bin/bash
|
|
|
|
|
|
2026-03-31 14:31:15 +07:00
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
2026-04-01 09:37:09 +07:00
|
|
|
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
|
|
|
EXAMPLES_DIR="$SCRIPT_DIR/examples"
|
2026-04-01 09:48:32 +07:00
|
|
|
PROJECT_NAME="$(basename "$PARENT_DIR")"
|
2026-03-31 14:31:15 +07:00
|
|
|
|
2026-07-20 07:46:21 +07:00
|
|
|
# Siapkan variabel waktu eksekusi (dicetak di akhir)
|
|
|
|
|
LAST_RUN_FILE="$SCRIPT_DIR/.last_run"
|
|
|
|
|
RUN_TIME="$(date '+%Y-%m-%d %H:%M:%S %Z')"
|
|
|
|
|
RUN_CMD="${*:-<kosong>}"
|
|
|
|
|
|
2026-04-22 21:04:38 +07:00
|
|
|
# Check for --verbose flag
|
|
|
|
|
VERBOSE=0
|
|
|
|
|
for arg in "$@"; do
|
|
|
|
|
if [ "$arg" == "--verbose" ]; then
|
|
|
|
|
VERBOSE=1
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# Function to run podman-compose quietly unless verbose
|
|
|
|
|
run_compose() {
|
|
|
|
|
# Ensure we are in the script directory so podman-compose finds the yaml file
|
|
|
|
|
cd "$SCRIPT_DIR" || exit
|
|
|
|
|
if [ "$VERBOSE" -eq 1 ]; then
|
|
|
|
|
podman-compose -p "$PROJECT_NAME" --env-file "$PARENT_DIR/.env" "$@"
|
|
|
|
|
else
|
|
|
|
|
podman-compose -p "$PROJECT_NAME" --env-file "$PARENT_DIR/.env" "$@" >/dev/null 2>&1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 12:08:42 +07:00
|
|
|
case "$1" in
|
2026-04-01 09:37:09 +07:00
|
|
|
init)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "✨ === Elemes Quick Start === ✨"
|
2026-04-01 09:37:09 +07:00
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# .env
|
|
|
|
|
if [ -f "$PARENT_DIR/.env" ]; then
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "✅ [Skip] .env sudah ada"
|
2026-04-01 09:37:09 +07:00
|
|
|
else
|
|
|
|
|
cp "$EXAMPLES_DIR/../.env.example" "$PARENT_DIR/.env"
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "📝 [Buat] .env (pastikan untuk edit sesuai kebutuhanmu)"
|
2026-04-01 09:37:09 +07:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# content/
|
|
|
|
|
if [ -d "$PARENT_DIR/content" ] && [ "$(ls -A "$PARENT_DIR/content" 2>/dev/null)" ]; then
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "✅ [Skip] Folder content/ sudah ada"
|
2026-04-01 09:37:09 +07:00
|
|
|
else
|
|
|
|
|
mkdir -p "$PARENT_DIR/content"
|
2026-05-14 19:45:10 +07:00
|
|
|
cp -rn "$EXAMPLES_DIR/content/"* "$PARENT_DIR/content/"
|
|
|
|
|
echo "📁 [Buat] Folder content/ ($(find "$PARENT_DIR/content/" -name "*.md" 2>/dev/null | wc -l) materi contoh ditambahkan)"
|
2026-04-01 09:37:09 +07:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# assets/
|
2026-04-11 09:48:33 +07:00
|
|
|
if [ -d "$PARENT_DIR/assets" ]; then
|
|
|
|
|
echo "✅ [Skip] Folder assets/ sudah ada"
|
|
|
|
|
else
|
|
|
|
|
mkdir -p "$PARENT_DIR/assets"
|
|
|
|
|
echo "📁 [Buat] Folder assets/ (untuk menyimpan file gambar/media)"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# state/ (untuk Tailscale tun/state)
|
|
|
|
|
if [ -d "$PARENT_DIR/state" ]; then
|
|
|
|
|
echo "✅ [Skip] Folder state/ sudah ada"
|
|
|
|
|
else
|
|
|
|
|
mkdir -p "$PARENT_DIR/state"
|
|
|
|
|
echo "🔐 [Buat] Folder state/ (untuk Tailscale credentials & state)"
|
|
|
|
|
fi
|
2026-04-01 09:37:09 +07:00
|
|
|
|
|
|
|
|
# tokens
|
|
|
|
|
if [ -f "$PARENT_DIR/tokens_siswa.csv" ]; then
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "✅ [Skip] tokens_siswa.csv sudah ada"
|
2026-04-01 09:37:09 +07:00
|
|
|
else
|
|
|
|
|
cp "$EXAMPLES_DIR/tokens_siswa.csv" "$PARENT_DIR/tokens_siswa.csv"
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "👥 [Buat] tokens_siswa.csv (edit untuk menambah data siswa asli)"
|
2026-04-01 09:37:09 +07:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo ""
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🎯 Selesai! Langkah selanjutnya yang direkomendasikan:"
|
|
|
|
|
echo " 👉 1. Edit file ../.env sesuai dengan kebutuhan environment-mu"
|
|
|
|
|
echo " 👉 2. Edit ../content/home.md untuk menyusun daftar materi"
|
|
|
|
|
echo " 👉 3. Edit ../tokens_siswa.csv untuk mendaftarkan akun siswa"
|
|
|
|
|
echo " 🚀 4. Jalankan: ./elemes.sh runbuild"
|
|
|
|
|
echo ""
|
2026-04-01 09:37:09 +07:00
|
|
|
;;
|
2026-04-09 10:51:36 +07:00
|
|
|
stop | run | runbuild | runclearbuild)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🛑 Menghentikan container yang sedang berjalan..."
|
2026-04-22 21:04:38 +07:00
|
|
|
run_compose down
|
2026-03-31 14:31:15 +07:00
|
|
|
;;&
|
2026-04-22 12:57:54 +07:00
|
|
|
stop)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "✅ Container berhasil dihentikan."
|
|
|
|
|
;;
|
2026-04-09 10:51:36 +07:00
|
|
|
runclearbuild)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🧹 Membersihkan container dan image (prune)..."
|
2026-04-09 10:51:36 +07:00
|
|
|
podman image prune -f
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🏗️ Membangun ulang container dari awal (no-cache)..."
|
2026-04-22 21:04:38 +07:00
|
|
|
run_compose build --no-cache
|
2026-04-09 10:51:36 +07:00
|
|
|
;;&
|
2026-03-31 12:08:42 +07:00
|
|
|
runbuild)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🏗️ Membangun container..."
|
2026-04-22 21:04:38 +07:00
|
|
|
run_compose build
|
2026-03-31 12:08:42 +07:00
|
|
|
;;&
|
2026-04-09 10:51:36 +07:00
|
|
|
runbuild | runclearbuild)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🚀 Menjalankan container di background..."
|
2026-04-22 21:04:38 +07:00
|
|
|
run_compose up --force-recreate -d
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "✅ Elemes berhasil dijalankan!"
|
2026-04-09 10:51:36 +07:00
|
|
|
;;
|
2026-03-31 12:08:42 +07:00
|
|
|
run)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🚀 Menjalankan container..."
|
2026-04-22 21:04:38 +07:00
|
|
|
run_compose up -d
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "✅ Elemes berhasil dijalankan!"
|
2026-04-09 10:51:36 +07:00
|
|
|
;;
|
2026-03-31 14:31:15 +07:00
|
|
|
generatetoken)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "🔄 Melakukan sinkronisasi tokens_siswa.csv dari materi yang ada..."
|
2026-03-31 14:31:15 +07:00
|
|
|
python3 "$SCRIPT_DIR/generate_tokens.py"
|
2026-04-09 10:51:36 +07:00
|
|
|
;;
|
2026-04-13 07:34:44 +07:00
|
|
|
exportall)
|
|
|
|
|
echo "📦 === Mengekspor Semua Image (Pre-Compiled Bundle) ==="
|
|
|
|
|
TAR_FILE="lms-c-precompiled.tar"
|
2026-04-22 21:04:38 +07:00
|
|
|
cd "$SCRIPT_DIR" || exit
|
2026-04-13 12:33:26 +07:00
|
|
|
|
2026-04-13 07:34:44 +07:00
|
|
|
echo "🏗️ 1. Mem-build Backend Elemes..."
|
2026-04-13 12:33:26 +07:00
|
|
|
podman build -t lms-c-backend:latest -f Dockerfile .
|
|
|
|
|
|
2026-04-13 07:34:44 +07:00
|
|
|
echo "🏗️ 2. Mem-build Frontend Elemes (SvelteKit)..."
|
2026-04-13 12:33:26 +07:00
|
|
|
podman build -t lms-c-frontend:latest -f frontend/Dockerfile frontend/
|
|
|
|
|
|
2026-04-13 07:34:44 +07:00
|
|
|
echo "🏗️ 3. Mem-build Velxio Simulator..."
|
2026-04-13 12:33:26 +07:00
|
|
|
podman build -t lms-c-velxio:latest -f velxio/Dockerfile.standalone --build-arg ENABLE_ESP32=${ENABLE_ESP32:-0} velxio/
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "🔍 Memverifikasi image yang berhasil di-build..."
|
|
|
|
|
podman images | grep -E "lms-c-(backend|frontend|velxio)"
|
|
|
|
|
echo ""
|
|
|
|
|
|
2026-04-13 07:34:44 +07:00
|
|
|
echo "💾 Menyatukan semua image menjadi 1 file tar: $TAR_FILE..."
|
2026-04-22 12:57:54 +07:00
|
|
|
podman save lms-c-backend:latest lms-c-frontend:latest lms-c-velxio:latest >"$TAR_FILE"
|
2026-04-13 12:33:26 +07:00
|
|
|
|
2026-04-13 07:34:44 +07:00
|
|
|
if [ $? -eq 0 ]; then
|
2026-04-13 12:33:26 +07:00
|
|
|
FILESIZE=$(du -h "$TAR_FILE" | cut -f1)
|
2026-04-13 07:34:44 +07:00
|
|
|
echo "✅ Selesai! File '$TAR_FILE' siap di-upload ke VPS."
|
2026-04-13 12:33:26 +07:00
|
|
|
echo " 📦 Ukuran file: $FILESIZE"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " 📋 Cara deploy di VPS:"
|
|
|
|
|
echo " 1. Upload file: scp $TAR_FILE user@vps:/path/lms-dev/elemes/"
|
|
|
|
|
echo " 2. Load image: podman load -i $TAR_FILE"
|
|
|
|
|
echo " 3. Jalankan: podman-compose up -d"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " ⚠️ Pastikan podman-compose.yml menggunakan image:"
|
|
|
|
|
echo " - lms-c-backend:latest (untuk service elemes)"
|
|
|
|
|
echo " - lms-c-frontend:latest (untuk service elemes-frontend)"
|
|
|
|
|
echo " - lms-c-velxio:latest (untuk service velxio)"
|
2026-04-13 07:34:44 +07:00
|
|
|
else
|
|
|
|
|
echo "❌ Export gagal."
|
|
|
|
|
fi
|
|
|
|
|
;;
|
|
|
|
|
importall)
|
|
|
|
|
echo "📦 === Mengimpor Semua Image (Pre-Compiled Bundle) ==="
|
|
|
|
|
TAR_FILE="lms-c-precompiled.tar"
|
2026-04-13 12:33:26 +07:00
|
|
|
|
|
|
|
|
if [ ! -f "$TAR_FILE" ]; then
|
|
|
|
|
echo "❌ File $TAR_FILE tidak ditemukan!"
|
|
|
|
|
echo " Pastikan file sudah ada di direktori ini."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-04-13 07:34:44 +07:00
|
|
|
echo "💾 Mengimpor image dari file $TAR_FILE..."
|
2026-04-13 12:33:26 +07:00
|
|
|
podman load -i "$TAR_FILE"
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "🔍 Memverifikasi image yang berhasil di-load..."
|
|
|
|
|
podman images | grep -E "lms-c-(backend|frontend|velxio)"
|
|
|
|
|
echo ""
|
|
|
|
|
|
2026-04-13 07:34:44 +07:00
|
|
|
echo "✅ Selesai! Image berhasil diimpor."
|
2026-04-13 12:33:26 +07:00
|
|
|
echo " Sekarang jalankan: ./elemes.sh run"
|
|
|
|
|
;;
|
|
|
|
|
verify)
|
|
|
|
|
echo "🔍 === Memverifikasi Image Container ==="
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
echo "📋 Image yang tersedia:"
|
|
|
|
|
podman images | grep -E "lms-c-(backend|frontend|velxio)" || echo " ⚠️ Tidak ada image lms-c yang ditemukan"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
echo "📋 Container yang sedang berjalan:"
|
|
|
|
|
podman ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" | grep -E "lms-dev" || echo " ⚠️ Tidak ada container yang berjalan"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
echo "🔍 Memeriksa CMD tiap container..."
|
2026-08-08 11:57:08 +07:00
|
|
|
for container in "lms-dev_elemes_1" "lms-dev_elemes-frontend_1" "lms-dev_velxio_1" "lms-dev_postgres_1"; do
|
2026-04-13 12:33:26 +07:00
|
|
|
if podman ps --format "{{.Names}}" | grep -q "$container"; then
|
|
|
|
|
CMD=$(podman inspect "$container" --format '{{.Config.Cmd}}' 2>/dev/null)
|
|
|
|
|
ENTRYPOINT=$(podman inspect "$container" --format '{{.Config.Entrypoint}}' 2>/dev/null)
|
|
|
|
|
IMAGE=$(podman inspect "$container" --format '{{.Image}}' 2>/dev/null)
|
|
|
|
|
echo " ✅ $container"
|
|
|
|
|
echo " Image: $IMAGE"
|
|
|
|
|
echo " Entrypoint: $ENTRYPOINT"
|
|
|
|
|
echo " Cmd: $CMD"
|
|
|
|
|
echo ""
|
|
|
|
|
else
|
|
|
|
|
echo " ⚠️ $container tidak ditemukan"
|
|
|
|
|
echo ""
|
|
|
|
|
fi
|
|
|
|
|
done
|
2026-04-13 07:34:44 +07:00
|
|
|
;;
|
2026-08-08 11:57:08 +07:00
|
|
|
dbupgrade)
|
|
|
|
|
echo "🗄️ Menjalankan migrasi database (alembic upgrade head)..."
|
|
|
|
|
podman exec -w /app -e PYTHONPATH=services lms-dev_elemes_1 python -m alembic upgrade head
|
|
|
|
|
;;
|
|
|
|
|
dbstatus)
|
|
|
|
|
echo "🗄️ Versi schema database (alembic current & heads):"
|
|
|
|
|
podman exec -w /app -e PYTHONPATH=services lms-dev_elemes_1 python -m alembic current
|
|
|
|
|
podman exec -w /app -e PYTHONPATH=services lms-dev_elemes_1 python -m alembic heads
|
|
|
|
|
;;
|
|
|
|
|
dbimport)
|
|
|
|
|
DRY_RUN=""
|
|
|
|
|
if [ "$2" == "--dry-run" ]; then
|
|
|
|
|
DRY_RUN="--dry-run"
|
|
|
|
|
echo "🧪 Dry-run impor CSV → PostgreSQL (tanpa menulis)..."
|
|
|
|
|
else
|
|
|
|
|
echo "📥 Mengimpor tokens.csv → PostgreSQL (idempotent)..."
|
|
|
|
|
fi
|
|
|
|
|
podman exec -w /app -e PYTHONPATH=services lms-dev_elemes_1 \
|
|
|
|
|
python scripts/migrate_csv_to_postgres.py --csv tokens.csv $DRY_RUN
|
|
|
|
|
;;
|
|
|
|
|
synclessons)
|
|
|
|
|
echo "🔄 Sinkronisasi registry lesson (Markdown → tabel lessons)..."
|
|
|
|
|
podman exec -w /app -e PYTHONPATH=services lms-dev_elemes_1 python -c "
|
|
|
|
|
from services.database import SessionLocal
|
|
|
|
|
from services.lesson_registry import sync_lesson_registry
|
|
|
|
|
db = SessionLocal()
|
|
|
|
|
try:
|
|
|
|
|
print(sync_lesson_registry(db))
|
|
|
|
|
finally:
|
|
|
|
|
db.close()
|
|
|
|
|
"
|
|
|
|
|
;;
|
|
|
|
|
dbverify)
|
|
|
|
|
echo "🔎 Verifikasi parity CSV ↔ PostgreSQL (wajib lulus sebelum cutover)..."
|
|
|
|
|
podman exec -w /app -e PYTHONPATH=/app lms-dev_elemes_1 \
|
|
|
|
|
python scripts/verify_database_migration.py --csv tokens.csv
|
|
|
|
|
;;
|
|
|
|
|
dbbackup)
|
|
|
|
|
echo "💾 Membackup database PostgreSQL → backups/"
|
|
|
|
|
set -a; source "$PARENT_DIR/.env" 2>/dev/null; set +a
|
|
|
|
|
mkdir -p "$PARENT_DIR/backups"
|
|
|
|
|
OUT="$PARENT_DIR/backups/elemes_$(date +%Y%m%d_%H%M%S).sql"
|
|
|
|
|
podman exec lms-dev_postgres_1 pg_dump \
|
|
|
|
|
-U "${POSTGRES_USER:-elemes}" -d "${POSTGRES_DB:-elemes}" > "$OUT"
|
|
|
|
|
echo "✅ Backup selesai: $OUT"
|
|
|
|
|
;;
|
|
|
|
|
dbrestore)
|
|
|
|
|
set -a; source "$PARENT_DIR/.env" 2>/dev/null; set +a
|
|
|
|
|
LATEST=$(ls -t "$PARENT_DIR"/backups/elemes_*.sql 2>/dev/null | head -1)
|
|
|
|
|
if [ -z "$LATEST" ]; then
|
|
|
|
|
echo "❌ Tidak ada backup di backups/elemes_*.sql"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "♻️ Restore backup: $LATEST"
|
|
|
|
|
podman exec -i lms-dev_postgres_1 psql \
|
|
|
|
|
-U "${POSTGRES_USER:-elemes}" -d "${POSTGRES_DB:-elemes}" < "$LATEST"
|
|
|
|
|
echo "✅ Restore selesai. Jalankan ./elemes.sh synclessons bila perlu."
|
|
|
|
|
;;
|
|
|
|
|
dbexport)
|
|
|
|
|
echo "📤 Ekspor snapshot PostgreSQL → pg_snapshot.csv (tanpa token mentah)"
|
|
|
|
|
podman exec -w /app -e PYTHONPATH=/app lms-dev_elemes_1 \
|
|
|
|
|
python scripts/export_postgres_to_csv.py --out /tmp/pg_snapshot.csv
|
|
|
|
|
podman cp lms-dev_elemes_1:/tmp/pg_snapshot.csv "$PARENT_DIR/pg_snapshot.csv"
|
|
|
|
|
echo "✅ Snapshot: $PARENT_DIR/pg_snapshot.csv"
|
|
|
|
|
;;
|
2026-03-31 14:31:15 +07:00
|
|
|
*)
|
2026-04-11 08:39:57 +07:00
|
|
|
echo "💡 Cara Penggunaan elemes.sh:"
|
|
|
|
|
echo " ./elemes.sh init # Inisialisasi konfigurasi, folder, & data tokens"
|
|
|
|
|
echo " ./elemes.sh run # Menjalankan container LMS yang sudah ada"
|
|
|
|
|
echo " ./elemes.sh runbuild # Build image lalu jalankan container"
|
|
|
|
|
echo " ./elemes.sh runclearbuild # Bersihkan cache, Re-build total, lalu jalankan"
|
|
|
|
|
echo " ./elemes.sh stop # Menghentikan container yang sedang berjalan"
|
|
|
|
|
echo " ./elemes.sh generatetoken # Sinkronisasi kolom tokens CSV sesuai file markdown"
|
2026-04-13 07:34:44 +07:00
|
|
|
echo " ./elemes.sh exportall # Build & Export semua image LMS (Backend, Frontend, Velxio) jadi satu file tar"
|
2026-04-13 12:33:26 +07:00
|
|
|
echo " ./elemes.sh importall # Import image dari file tar (untuk deployment di VPS)"
|
|
|
|
|
echo " ./elemes.sh verify # Verifikasi image dan konfigurasi container"
|
2026-08-08 11:57:08 +07:00
|
|
|
echo " ./elemes.sh dbupgrade # Jalankan migrasi schema (alembic upgrade head)"
|
|
|
|
|
echo " ./elemes.sh dbstatus # Lihat versi schema database"
|
|
|
|
|
echo " ./elemes.sh dbimport # Impor tokens.csv → PostgreSQL (idempotent)"
|
|
|
|
|
echo " ./elemes.sh dbimport --dry-run # Simulasi impor tanpa menulis"
|
|
|
|
|
echo " ./elemes.sh synclessons # Sinkronisasi daftar lesson Markdown → DB"
|
|
|
|
|
echo " ./elemes.sh dbverify # Verifikasi parity CSV ↔ PostgreSQL"
|
|
|
|
|
echo " ./elemes.sh dbbackup # Backup database → backups/elemes_<ts>.sql"
|
|
|
|
|
echo " ./elemes.sh dbrestore # Restore backup terbaru dari backups/"
|
|
|
|
|
echo " ./elemes.sh dbexport # Ekspor snapshot PG → pg_snapshot.csv"
|
2026-04-11 08:39:57 +07:00
|
|
|
echo " ./elemes.sh loadtest # Menjalankan utilitas simulasi Load Test (Locust)"
|
2026-03-31 12:08:42 +07:00
|
|
|
;;
|
|
|
|
|
esac
|
2026-07-20 07:46:21 +07:00
|
|
|
|
|
|
|
|
# Catat & tampilkan waktu eksekusi terakhir (setelah semua selesai)
|
|
|
|
|
{
|
|
|
|
|
echo "command: $RUN_CMD"
|
|
|
|
|
echo "time: $RUN_TIME"
|
|
|
|
|
} > "$LAST_RUN_FILE"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "🕒 Selesai dijalankan: $RUN_TIME (perintah: $RUN_CMD)"
|