diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..183fd3b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,66 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Verify tag matches package.json version + run: | + tag="${GITHUB_REF#refs/tags/v}" + pkg="$(node -p 'require("./package.json").version')" + if [ "$tag" != "$pkg" ]; then + echo "Tag v$tag does not match package.json version $pkg" >&2 + exit 1 + fi + + - name: Lint and type-check + run: npx tsc --noEmit + + - name: Test + run: npm test + + - name: Build + run: npm run build + + - name: Package release tarball + id: package + run: | + tag="${GITHUB_REF#refs/tags/}" + staging="maestro-discord-${tag}" + mkdir -p "$staging" + cp -R dist "$staging/" + cp -R bin templates "$staging/" + cp package.json package-lock.json .env.example README.md "$staging/" + cp LICENSE "$staging/" 2>/dev/null || true + tar -czf "${staging}.tar.gz" "$staging" + sha256sum "${staging}.tar.gz" > "${staging}.tar.gz.sha256" + echo "tarball=${staging}.tar.gz" >> "$GITHUB_OUTPUT" + echo "checksum=${staging}.tar.gz.sha256" >> "$GITHUB_OUTPUT" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: | + ${{ steps.package.outputs.tarball }} + ${{ steps.package.outputs.checksum }} diff --git a/README.md b/README.md index a0fa81f..5305591 100644 --- a/README.md +++ b/README.md @@ -13,51 +13,47 @@ A Discord bot that connects your server to [Maestro](https://runmaestro.ai) AI a ## Prerequisites -- Node.js 18+ +- Linux or macOS +- Node.js 22+ - A Discord application + bot token -- [Maestro CLI](https://docs.runmaestro.ai/cli) available on your `PATH` (no authentication required) +- [Maestro CLI](https://docs.runmaestro.ai/cli) on your `PATH` -### Install maestro-discord CLI +## Install (production) -The `maestro-discord` CLI lets your Maestro agents reach out to you on Discord — for example, to ping you when a long-running task finishes. See [docs/api.md](docs/api.md) for usage. - -After building the project (`npm run build`), create a shell wrapper. - -macOS / Linux: +One command — downloads the latest tagged release, installs dependencies, prompts for Discord credentials, and registers a user-level service. ```bash -printf '#!/bin/bash\nnode "%s/dist/cli/maestro-discord.js" "$@"\n' "$(pwd)" | sudo tee /usr/local/bin/maestro-discord && sudo chmod +x /usr/local/bin/maestro-discord +curl -fsSL https://raw.githubusercontent.com/RunMaestro/Maestro-Discord/main/install.sh | bash ``` -Windows (PowerShell) — writes the wrapper to `%USERPROFILE%\bin` and adds it to your user `PATH`: +After install: -```powershell -$repoPath = (Get-Location).Path -$binDir = "$env:USERPROFILE\bin" -New-Item -ItemType Directory -Force -Path $binDir | Out-Null -@" -@echo off -node "$repoPath\dist\cli\maestro-discord.js" %* -"@ | Out-File -FilePath "$binDir\maestro-discord.cmd" -Encoding ASCII - -# Add $binDir to user PATH if it isn't already (restart your shell afterwards) -$userPath = [Environment]::GetEnvironmentVariable('PATH', 'User') -if (-not ($userPath -split ';' -contains $binDir)) { - [Environment]::SetEnvironmentVariable('PATH', "$binDir;$userPath", 'User') -} +```bash +maestro-discord-ctl start # boot the bot +maestro-discord-ctl logs # tail logs +maestro-discord-ctl status # service status +maestro-discord-ctl update # upgrade to latest release (preserves config) +maestro-discord-ctl uninstall # remove install + service files ``` -Or use `npm link`: +Defaults: -```bash -npm link -``` +| Path | Purpose | +| ----------------------------- | ---------------------------------------- | +| `~/.local/share/maestro-discord/` | Installed bot (built JS + dependencies) | +| `~/.config/maestro-discord/.env` | Configuration (preserved across updates) | +| `~/.local/bin/maestro-discord-ctl` | Service control wrapper | +| systemd user / launchd agent | Auto-start unit | + +Override any of these with `MAESTRO_DISCORD_HOME`, `XDG_CONFIG_HOME`, or `MAESTRO_DISCORD_BIN_DIR`. Pin a specific version with `MAESTRO_DISCORD_VERSION=v1.0.0`. -## Quick start +## Install (development from source) -1. Install dependencies: +1. Clone and install: ```bash +git clone https://github.com/RunMaestro/Maestro-Discord.git +cd Maestro-Discord npm install ``` @@ -93,6 +89,42 @@ npm run deploy-commands npm run dev ``` +### Install maestro-discord CLI (dev) + +The `maestro-discord` CLI lets your Maestro agents reach out to you on Discord — for example, to ping you when a long-running task finishes. See [docs/api.md](docs/api.md) for usage. + +After building the project (`npm run build`), create a shell wrapper. + +macOS / Linux: + +```bash +printf '#!/bin/bash\nnode "%s/dist/cli/maestro-discord.js" "$@"\n' "$(pwd)" | sudo tee /usr/local/bin/maestro-discord && sudo chmod +x /usr/local/bin/maestro-discord +``` + +Windows (PowerShell) — writes the wrapper to `%USERPROFILE%\bin` and adds it to your user `PATH`: + +```powershell +$repoPath = (Get-Location).Path +$binDir = "$env:USERPROFILE\bin" +New-Item -ItemType Directory -Force -Path $binDir | Out-Null +@" +@echo off +node "$repoPath\dist\cli\maestro-discord.js" %* +"@ | Out-File -FilePath "$binDir\maestro-discord.cmd" -Encoding ASCII + +# Add $binDir to user PATH if it isn't already (restart your shell afterwards) +$userPath = [Environment]::GetEnvironmentVariable('PATH', 'User') +if (-not ($userPath -split ';' -contains $binDir)) { + [Environment]::SetEnvironmentVariable('PATH', "$binDir;$userPath", 'User') +} +``` + +Or use `npm link`: + +```bash +npm link +``` + ## Voice Transcription (optional) When a user posts a Discord **voice message** (the mic-button recording, not an arbitrary `.ogg` upload) in a session thread, the bot transcribes the audio with `whisper.cpp` and forwards the transcript to the agent. The original `.ogg` is **not** sent to the agent — only the transcribed text — and a `🎧` reaction marks the message while transcription runs. @@ -132,13 +164,6 @@ WHISPER_MODEL_PATH=models/ggml-base.en.bin The bot probes these at startup; any missing piece is logged as `⚠️ Transcription disabled: …` and transcription is skipped at runtime. -## Production run - -```bash -npm run build -npm start -``` - ## Tests ```bash diff --git a/bin/maestro-discord-ctl.sh b/bin/maestro-discord-ctl.sh new file mode 100755 index 0000000..09e00ca --- /dev/null +++ b/bin/maestro-discord-ctl.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +# Service wrapper for the Maestro Discord bot. +# Subcommands: start | stop | restart | status | logs | deploy | update | uninstall | version + +set -euo pipefail + +INSTALL_DIR="${MAESTRO_DISCORD_HOME:-$HOME/.local/share/maestro-discord}" +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/maestro-discord" +BIN_DIR="${MAESTRO_DISCORD_BIN_DIR:-$HOME/.local/bin}" +REPO="${MAESTRO_DISCORD_REPO:-RunMaestro/Maestro-Discord}" +SERVICE_NAME="maestro-discord" +LAUNCHD_LABEL="sh.maestro.discord" +LAUNCHD_PLIST="$HOME/Library/LaunchAgents/${LAUNCHD_LABEL}.plist" + +die() { printf '✗ %s\n' "$*" >&2; exit 1; } +info() { printf '==> %s\n' "$*"; } + +detect_os() { + case "$(uname -s)" in + Linux) echo linux ;; + Darwin) echo macos ;; + *) echo unsupported ;; + esac +} + +usage() { + cat <<'EOF' +maestro-discord-ctl — control the Maestro Discord bot service. + +Usage: + maestro-discord-ctl + +Commands: + start Start the bot service + stop Stop the bot service + restart Restart the bot service + status Show service status + logs Tail service logs (Ctrl+C to stop) + deploy Deploy slash commands to Discord + update Reinstall the latest release (preserves config) + uninstall Remove the bot, service files, and CLI symlink + version Print installed version + +Environment: + MAESTRO_DISCORD_HOME Override install dir (default: ~/.local/share/maestro-discord) + XDG_CONFIG_HOME Config dir parent (default: ~/.config) +EOF +} + +require_install() { + [ -d "$INSTALL_DIR" ] || die "Not installed at $INSTALL_DIR. Run install.sh first." +} + +cmd_start() { + require_install + case "$(detect_os)" in + linux) + systemctl --user start "$SERVICE_NAME" + info "Started $SERVICE_NAME (systemd user)" + ;; + macos) + [ -f "$LAUNCHD_PLIST" ] || die "Plist not installed: $LAUNCHD_PLIST" + launchctl load -w "$LAUNCHD_PLIST" 2>/dev/null || launchctl start "$LAUNCHD_LABEL" + info "Started $LAUNCHD_LABEL (launchd)" + ;; + *) die "Unsupported OS for service management" ;; + esac +} + +cmd_stop() { + case "$(detect_os)" in + linux) + systemctl --user stop "$SERVICE_NAME" || true + info "Stopped $SERVICE_NAME" + ;; + macos) + launchctl unload -w "$LAUNCHD_PLIST" 2>/dev/null || launchctl stop "$LAUNCHD_LABEL" || true + info "Stopped $LAUNCHD_LABEL" + ;; + *) die "Unsupported OS for service management" ;; + esac +} + +cmd_restart() { + cmd_stop || true + cmd_start +} + +cmd_status() { + case "$(detect_os)" in + linux) systemctl --user status "$SERVICE_NAME" --no-pager || true ;; + macos) launchctl list | grep -F "$LAUNCHD_LABEL" || echo "(not loaded)" ;; + *) die "Unsupported OS for service management" ;; + esac +} + +cmd_logs() { + case "$(detect_os)" in + linux) journalctl --user -u "$SERVICE_NAME" -f --no-pager ;; + macos) + local log_file="$INSTALL_DIR/logs/maestro-discord.log" + mkdir -p "$INSTALL_DIR/logs" + [ -f "$log_file" ] || touch "$log_file" + tail -f "$log_file" + ;; + *) die "Unsupported OS for log tailing" ;; + esac +} + +cmd_deploy() { + require_install + [ -f "$INSTALL_DIR/.env" ] || die "Config missing: $INSTALL_DIR/.env" + (cd "$INSTALL_DIR" && node dist/deploy-commands.js) +} + +cmd_update() { + info "Re-running installer to pull the latest release" + local tag config_parent + tag="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | sed -nE 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' | head -n1)" + [ -n "$tag" ] || die "Could not resolve latest release tag" + config_parent="${CONFIG_DIR%/maestro-discord}" + curl -fsSL "https://raw.githubusercontent.com/${REPO}/${tag}/install.sh" \ + | env \ + MAESTRO_DISCORD_HOME="$INSTALL_DIR" \ + MAESTRO_DISCORD_BIN_DIR="$BIN_DIR" \ + MAESTRO_DISCORD_REPO="$REPO" \ + XDG_CONFIG_HOME="$config_parent" \ + bash +} + +cmd_uninstall() { + read -r -p "Remove $INSTALL_DIR, service files, and CLI symlink? [y/N] " ans + case "${ans:-n}" in + y|Y|yes|YES) ;; + *) info "Aborted"; exit 0 ;; + esac + cmd_stop || true + case "$(detect_os)" in + linux) + systemctl --user disable --now "$SERVICE_NAME" 2>/dev/null || true + rm -f "${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/${SERVICE_NAME}.service" + systemctl --user daemon-reload || true + systemctl --user reset-failed "$SERVICE_NAME" 2>/dev/null || true + ;; + macos) rm -f "$LAUNCHD_PLIST" ;; + esac + rm -rf "$INSTALL_DIR" + rm -f "$BIN_DIR/maestro-discord-ctl" + info "Uninstalled. Config preserved at $CONFIG_DIR (delete manually if desired)." +} + +cmd_version() { + if [ -f "$INSTALL_DIR/.version" ]; then + cat "$INSTALL_DIR/.version" + else + die "No version file at $INSTALL_DIR/.version" + fi +} + +main() { + local sub="${1:-}" + case "$sub" in + start) cmd_start ;; + stop) cmd_stop ;; + restart) cmd_restart ;; + status) cmd_status ;; + logs) cmd_logs ;; + deploy) cmd_deploy ;; + update) cmd_update ;; + uninstall) cmd_uninstall ;; + version) cmd_version ;; + -h|--help|help|"") usage ;; + *) usage; exit 2 ;; + esac +} + +main "$@" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9a0a9e1 --- /dev/null +++ b/install.sh @@ -0,0 +1,357 @@ +#!/usr/bin/env bash +# Maestro Discord Bot installer. +# Usage: +# curl -fsSL https://raw.githubusercontent.com/RunMaestro/Maestro-Discord/main/install.sh | bash +# Re-run to upgrade to the latest release. Existing config is preserved. + +set -Eeuo pipefail + +REPO="${MAESTRO_DISCORD_REPO:-RunMaestro/Maestro-Discord}" +INSTALL_DIR="${MAESTRO_DISCORD_HOME:-$HOME/.local/share/maestro-discord}" +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/maestro-discord" +BIN_DIR="${MAESTRO_DISCORD_BIN_DIR:-$HOME/.local/bin}" +VERSION="${MAESTRO_DISCORD_VERSION:-latest}" +NODE_MIN_MAJOR=22 +RELEASE_BACKUP="" + +rollback_install() { + if [ -n "$RELEASE_BACKUP" ] && [ -d "$RELEASE_BACKUP" ]; then + rm -rf "$INSTALL_DIR" + mv "$RELEASE_BACKUP" "$INSTALL_DIR" + warn "Restored previous install from $RELEASE_BACKUP" + fi +} + +c_red() { printf '\033[31m%s\033[0m' "$*"; } +c_green() { printf '\033[32m%s\033[0m' "$*"; } +c_yellow() { printf '\033[33m%s\033[0m' "$*"; } +c_blue() { printf '\033[34m%s\033[0m' "$*"; } +c_bold() { printf '\033[1m%s\033[0m' "$*"; } + +info() { printf '%s %s\n' "$(c_blue '==>')" "$*"; } +ok() { printf '%s %s\n' "$(c_green '✓')" "$*"; } +warn() { printf '%s %s\n' "$(c_yellow '!')" "$*" >&2; } +die() { printf '%s %s\n' "$(c_red '✗')" "$*" >&2; exit 1; } + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1${2:+ — $2}" +} + +detect_os() { + case "$(uname -s)" in + Linux) echo linux ;; + Darwin) echo macos ;; + *) die "Unsupported OS: $(uname -s). Linux and macOS only." ;; + esac +} + +check_node() { + require_cmd node "install Node.js ${NODE_MIN_MAJOR}+ from https://nodejs.org/" + require_cmd npm "install Node.js ${NODE_MIN_MAJOR}+ from https://nodejs.org/" + local major + major="$(node -p 'process.versions.node.split(".")[0]')" + if [ "$major" -lt "$NODE_MIN_MAJOR" ]; then + die "Node.js ${NODE_MIN_MAJOR}+ required (found $(node --version))." + fi + ok "Node.js $(node --version)" +} + +check_maestro_cli() { + if command -v maestro-cli >/dev/null 2>&1; then + ok "maestro-cli found ($(maestro-cli --version 2>/dev/null | head -n1 || echo 'version unknown'))" + else + warn "maestro-cli not found on PATH. The bot will fail to relay messages until it is installed." + warn "See https://docs.runmaestro.ai/cli for instructions." + fi +} + +resolve_release() { + local api_url tag + if [ "$VERSION" = "latest" ]; then + api_url="https://api.github.com/repos/${REPO}/releases/latest" + else + api_url="https://api.github.com/repos/${REPO}/releases/tags/${VERSION}" + fi + tag="$(curl -fsSL "$api_url" | sed -nE 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' | head -n1)" + [ -n "$tag" ] || die "Could not resolve release tag from ${api_url}" + echo "$tag" +} + +sha256_of() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + else + return 1 + fi +} + +download_release() { + local tag="$1" dest="$2" + local url="https://github.com/${REPO}/releases/download/${tag}/maestro-discord-${tag}.tar.gz" + local sha_url="${url}.sha256" + info "Downloading ${tag} from ${url}" + curl -fsSL "$url" -o "$dest" || die "Download failed: $url" + + local sha_file expected actual + sha_file="$(mktemp)" + if curl -fsSL "$sha_url" -o "$sha_file" 2>/dev/null; then + expected="$(awk '{print $1}' "$sha_file")" + rm -f "$sha_file" + if [ -z "$expected" ]; then + die "Empty checksum at $sha_url" + fi + if ! actual="$(sha256_of "$dest")"; then + warn "No sha256sum/shasum on PATH — skipping checksum verification" + return + fi + if [ "$expected" != "$actual" ]; then + die "Checksum mismatch for $url (expected $expected, got $actual)" + fi + ok "Verified SHA-256 checksum" + else + rm -f "$sha_file" + warn "Checksum file not published at $sha_url — skipping verification" + fi +} + +install_release() { + local tag="$1" tarball="$2" + local staging + staging="$(mktemp -d)" + trap 'rm -rf "$staging"' RETURN + tar -xzf "$tarball" -C "$staging" + local extracted + extracted="$(find "$staging" -mindepth 1 -maxdepth 1 -type d | head -n1)" + [ -n "$extracted" ] || extracted="$staging" + + mkdir -p "$INSTALL_DIR" + if [ -d "$INSTALL_DIR/dist" ]; then + RELEASE_BACKUP="${INSTALL_DIR}.backup.$(date +%s)" + mv "$INSTALL_DIR" "$RELEASE_BACKUP" + mkdir -p "$INSTALL_DIR" + info "Backed up previous install to $RELEASE_BACKUP" + fi + + cp -R "$extracted"/. "$INSTALL_DIR"/ + printf '%s\n' "$tag" > "$INSTALL_DIR/.version" + + if [ -n "$RELEASE_BACKUP" ] && [ -f "$RELEASE_BACKUP/maestro-bot.db" ] && [ ! -f "$INSTALL_DIR/maestro-bot.db" ]; then + cp "$RELEASE_BACKUP/maestro-bot.db" "$INSTALL_DIR/maestro-bot.db" + info "Preserved SQLite database" + fi + + # Migrate .env from a legacy install (e.g. manual git clone) into the XDG + # config dir so write_config will preserve it instead of writing a template. + if [ -n "$RELEASE_BACKUP" ] \ + && [ -f "$RELEASE_BACKUP/.env" ] \ + && [ ! -L "$RELEASE_BACKUP/.env" ] \ + && [ ! -f "$CONFIG_DIR/.env" ]; then + mkdir -p "$CONFIG_DIR" + cp "$RELEASE_BACKUP/.env" "$CONFIG_DIR/.env" + chmod 600 "$CONFIG_DIR/.env" + info "Migrated existing .env → $CONFIG_DIR/.env" + fi + + ok "Extracted release to $INSTALL_DIR" +} + +install_deps() { + info "Installing production dependencies (npm ci --omit=dev)…" + (cd "$INSTALL_DIR" && npm ci --omit=dev --no-audit --no-fund --silent) + ok "Dependencies installed" +} + +prompt_var() { + local desc="$2" default="${3:-}" current="${!1:-}" + if [ -n "$current" ]; then + echo "$current" + return + fi + local prompt=" ${desc}" + [ -n "$default" ] && prompt="${prompt} [${default}]" + prompt="${prompt}: " + local value="" + if [ -r /dev/tty ]; then + read -r -p "$prompt" value "$tmp_env" + mv "$tmp_env" "$env_file" + ln -sf "$env_file" "$INSTALL_DIR/.env" + ok "Wrote $env_file" +} + +config_complete() { + local file="$1" key value + [ -f "$file" ] || return 1 + for key in DISCORD_BOT_TOKEN DISCORD_CLIENT_ID DISCORD_GUILD_ID; do + value="$(sed -nE "s/^${key}=([^#[:space:]]+).*/\1/p" "$file" | head -n1)" + [ -n "$value" ] || return 1 + case "$value" in + your_*) return 1 ;; + esac + done + return 0 +} + +deploy_commands() { + local env_file="$CONFIG_DIR/.env" + if ! config_complete "$env_file"; then + warn "Skipping slash command deployment — config at $env_file is incomplete or contains template values." + warn "Edit it and run 'maestro-discord-ctl deploy' when ready." + return + fi + info "Deploying slash commands to Discord" + if (cd "$INSTALL_DIR" && node dist/deploy-commands.js); then + ok "Slash commands deployed" + else + warn "Slash command deployment failed. Edit $env_file and re-run 'maestro-discord-ctl deploy'." + fi +} + +install_ctl() { + mkdir -p "$BIN_DIR" + local ctl="$INSTALL_DIR/bin/maestro-discord-ctl.sh" + [ -f "$ctl" ] || die "Control script missing at $ctl" + chmod +x "$ctl" + ln -sf "$ctl" "$BIN_DIR/maestro-discord-ctl" + ok "Installed maestro-discord-ctl → $BIN_DIR/maestro-discord-ctl" + case ":$PATH:" in + *":$BIN_DIR:"*) : ;; + *) warn "$BIN_DIR is not on your PATH. Add it to your shell profile." ;; + esac +} + +install_service_linux() { + command -v systemctl >/dev/null 2>&1 || { warn "systemctl not found — skipping service install."; return; } + local unit_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user" + mkdir -p "$unit_dir" + local template="$INSTALL_DIR/templates/maestro-discord.service" + [ -f "$template" ] || { warn "Service template missing at $template"; return; } + sed \ + -e "s|@INSTALL_DIR@|$INSTALL_DIR|g" \ + -e "s|@CONFIG_DIR@|$CONFIG_DIR|g" \ + -e "s|@NODE_BIN@|$(command -v node)|g" \ + "$template" > "$unit_dir/maestro-discord.service" + systemctl --user daemon-reload || true + ok "Installed systemd unit → $unit_dir/maestro-discord.service" + echo " Enable on login: systemctl --user enable --now maestro-discord" + echo " (and optionally: loginctl enable-linger \$USER)" +} + +install_service_macos() { + local plist_dir="$HOME/Library/LaunchAgents" + mkdir -p "$plist_dir" + mkdir -p "$INSTALL_DIR/logs" + local template="$INSTALL_DIR/templates/sh.maestro.discord.plist" + [ -f "$template" ] || { warn "Plist template missing at $template"; return; } + sed \ + -e "s|@INSTALL_DIR@|$INSTALL_DIR|g" \ + -e "s|@CONFIG_DIR@|$CONFIG_DIR|g" \ + -e "s|@NODE_BIN@|$(command -v node)|g" \ + "$template" > "$plist_dir/sh.maestro.discord.plist" + ok "Installed launchd plist → $plist_dir/sh.maestro.discord.plist" + echo " Load at login: launchctl load -w $plist_dir/sh.maestro.discord.plist" +} + +install_service() { + case "$(detect_os)" in + linux) install_service_linux ;; + macos) install_service_macos ;; + esac +} + +main() { + c_bold 'Maestro Discord Bot installer' + echo + echo + + require_cmd curl + require_cmd tar + require_cmd sed + check_node + check_maestro_cli + + local tag tarball + tag="$(resolve_release)" + info "Target release: ${tag}" + + tarball="$(mktemp)" + trap 'rm -f "$tarball"' EXIT + download_release "$tag" "$tarball" + trap 'rollback_install' ERR + install_release "$tag" "$tarball" + install_deps + trap - ERR + install_ctl + write_config + deploy_commands + install_service + + echo + ok "$(c_bold 'Install complete') — version $(c_green "$tag")" + echo + echo " Start: $(c_bold 'maestro-discord-ctl start')" + echo " Logs: $(c_bold 'maestro-discord-ctl logs')" + echo " Config: $CONFIG_DIR/.env" + echo +} + +main "$@" diff --git a/package.json b/package.json index 815a696..7db4e2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-maestro", - "version": "0.1.0", + "version": "1.0.0", "description": "Discord Maestro Bot", "main": "dist/index.js", "bin": { diff --git a/templates/maestro-discord.service b/templates/maestro-discord.service new file mode 100644 index 0000000..971916c --- /dev/null +++ b/templates/maestro-discord.service @@ -0,0 +1,19 @@ +[Unit] +Description=Maestro Discord Bot +After=network-online.target +Wants=network-online.target +StartLimitIntervalSec=300 +StartLimitBurst=5 + +[Service] +Type=simple +WorkingDirectory=@INSTALL_DIR@ +EnvironmentFile=@CONFIG_DIR@/.env +ExecStart=@NODE_BIN@ @INSTALL_DIR@/dist/index.js +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=default.target diff --git a/templates/sh.maestro.discord.plist b/templates/sh.maestro.discord.plist new file mode 100644 index 0000000..cd71deb --- /dev/null +++ b/templates/sh.maestro.discord.plist @@ -0,0 +1,27 @@ + + + + + Label + sh.maestro.discord + WorkingDirectory + @INSTALL_DIR@ + ProgramArguments + + /bin/bash + -c + set -a; . "@CONFIG_DIR@/.env"; set +a; exec "@NODE_BIN@" "@INSTALL_DIR@/dist/index.js" + + RunAtLoad + + KeepAlive + + SuccessfulExit + + + StandardOutPath + @INSTALL_DIR@/logs/maestro-discord.log + StandardErrorPath + @INSTALL_DIR@/logs/maestro-discord.log + +