Add start.sh
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
# ── OTA update ────────────────────────────────────────────────────────────────
|
||||||
|
# Uses curl + Python stdlib tarfile — no git or unzip required.
|
||||||
|
#
|
||||||
|
# Flow:
|
||||||
|
# 1. Fetch latest GitHub Release tag via API (tiny JSON, 5 s timeout).
|
||||||
|
# 2. Compare against .poke_version (last installed version tag). Skip if
|
||||||
|
# already up to date or if the remote is unreachable.
|
||||||
|
# 3. Download the release tarball only when an update exists (30 s timeout).
|
||||||
|
# 4. Extract with Python tarfile, stripping the GitHub top-level prefix and
|
||||||
|
# skipping protected local files (.env, config.yml, .venv).
|
||||||
|
# 5. Persist new version tag to .poke_version and reinstall deps if
|
||||||
|
# requirements.txt changed.
|
||||||
|
if command -v curl &>/dev/null && command -v python3 &>/dev/null; then
|
||||||
|
_OTA_REPO="kacperkwapisz/poke-mail"
|
||||||
|
_VERSION_FILE=".poke_version"
|
||||||
|
echo "Checking for updates..."
|
||||||
|
# Step 1: fetch latest release tag and tarball URL (fail silently if offline)
|
||||||
|
_RELEASE_JSON=$(curl -sf --max-time 5 \ "https://api.github.com/repos/${_OTA_REPO}/releases/latest" \ 2>/dev/null || echo "")
|
||||||
|
_REMOTE_TAG=""
|
||||||
|
_TARBALL_URL=""
|
||||||
|
if [ -n "$_RELEASE_JSON" ]; then
|
||||||
|
_REMOTE_TAG=$(echo "$_RELEASE_JSON" | python3 -c \ "import json,sys; print(json.load(sys.stdin).get('tag_name',''))" \ 2>/dev/null || echo "")
|
||||||
|
_TARBALL_URL=$(echo "$_RELEASE_JSON" | python3 -c \ "import json,sys; print(json.load(sys.stdin).get('tarball_url',''))" \ 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
_LOCAL_TAG=$(cat "$_VERSION_FILE" 2>/dev/null || echo "")
|
||||||
|
if [ -z "$_REMOTE_TAG" ]; then
|
||||||
|
echo " ℹ Could not reach remote — continuing with local version."
|
||||||
|
echo ""
|
||||||
|
elif [ "$_REMOTE_TAG" = "$_LOCAL_TAG" ]; then
|
||||||
|
echo " ✓ Already up to date (${_LOCAL_TAG})"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
if [ -n "$_LOCAL_TAG" ]; then
|
||||||
|
echo " ↳ Update found (${_LOCAL_TAG} → ${_REMOTE_TAG}), downloading..."
|
||||||
|
else
|
||||||
|
echo " ↳ Update found (${_REMOTE_TAG}), downloading..."
|
||||||
|
fi
|
||||||
|
# Hash requirements.txt before extraction so we can detect changes
|
||||||
|
_REQS_BEFORE=$(python3 -c \ "import hashlib; print(hashlib.md5(open('requirements.txt','rb').read()).hexdigest())" \ 2>/dev/null || echo "")
|
||||||
|
_TMP_TAR=$(mktemp /tmp/poke-mail-update.XXXXXX.tar.gz)
|
||||||
|
if curl -sfL --max-time 30 \ "$_TARBALL_URL" \ -o "$_TMP_TAR" 2>/dev/null; then
|
||||||
|
# Extract with Python: strip GitHub's top-level dir, skip protected paths
|
||||||
|
python3 - <<'PYEOF'
|
||||||
|
import tarfile, sys
|
||||||
|
TMP_TAR = sys.argv[1]
|
||||||
|
PROTECTED = {'.env', 'config.yml', '.venv'}
|
||||||
|
with tarfile.open(TMP_TAR, 'r:gz') as tf:
|
||||||
|
members = tf.getmembers()
|
||||||
|
prefix = members[0].name.split('/')[0] + '/'
|
||||||
|
for m in members:
|
||||||
|
if not m.name.startswith(prefix):
|
||||||
|
continue
|
||||||
|
rel = m.name[len(prefix):]
|
||||||
|
if not rel:
|
||||||
|
continue
|
||||||
|
top = rel.split('/')[0]
|
||||||
|
if top in PROTECTED:
|
||||||
|
continue
|
||||||
|
m.name = rel
|
||||||
|
try:
|
||||||
|
tf.extract(m, path='.', set_attrs=False)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
PYEOF "$_TMP_TAR"
|
||||||
|
# Persist new version tag
|
||||||
|
echo "$_REMOTE_TAG" > "$_VERSION_FILE"
|
||||||
|
echo " ✓ Updated to ${_REMOTE_TAG}"
|
||||||
|
# Reinstall deps if requirements.txt changed
|
||||||
|
_REQS_AFTER=$(python3 -c \ "import hashlib; print(hashlib.md5(open('requirements.txt','rb').read()).hexdigest())" \ 2>/dev/null || echo "")
|
||||||
|
if [ -n "$_REQS_BEFORE" ] && [ "$_REQS_BEFORE" != "$_REQS_AFTER" ]; then
|
||||||
|
echo " ↳ requirements.txt changed — reinstalling dependencies..."
|
||||||
|
[ -d .venv ] && source .venv/bin/activate
|
||||||
|
pip install -q -r requirements.txt
|
||||||
|
echo " ✓ Dependencies updated"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ℹ Download failed — continuing with local version."
|
||||||
|
fi
|
||||||
|
rm -f "$_TMP_TAR"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# ── One-time setup (skipped on subsequent runs) ───────────────────────────────
|
||||||
|
# 1. Python virtualenv + dependencies
|
||||||
|
if [ ! -d .venv ]; then
|
||||||
|
echo "First run — setting up poke-mail..."
|
||||||
|
echo ""
|
||||||
|
echo "Creating Python virtualenv (.venv)..."
|
||||||
|
python3 -m venv .venv
|
||||||
|
fi
|
||||||
|
source .venv/bin/activate
|
||||||
|
if ! python3 -c "import fastmcp" &>/dev/null 2>&1; then
|
||||||
|
echo "Installing Python dependencies..."
|
||||||
|
pip install -q -r requirements.txt
|
||||||
|
echo " ✓ Dependencies installed"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
# 2. config.yml — copy example if missing
|
||||||
|
if [ ! -f config.yml ]; then
|
||||||
|
echo "Copying config.example.yml → config.yml..."
|
||||||
|
cp config.example.yml config.yml
|
||||||
|
echo " ✓ config.yml created"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
# 3. Poke API key — read from 'poke login' credentials, inject into config.yml
|
||||||
|
if grep -q 'your-api-key-here' config.yml 2>/dev/null; then
|
||||||
|
POKE_CREDENTIALS_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/poke/credentials.json"
|
||||||
|
POKE_TOKEN=""
|
||||||
|
if [ -f "$POKE_CREDENTIALS_FILE" ]; then
|
||||||
|
POKE_TOKEN=$(python3 -c "import json,sys; print(json.load(open('$POKE_CREDENTIALS_FILE')).get('token',''))" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
if [ -n "$POKE_TOKEN" ]; then
|
||||||
|
echo " ✓ Poke API key detected from 'poke login'"
|
||||||
|
# Generate MCP_API_KEY if not set
|
||||||
|
if ! grep -Eq '^[[:space:]]*MCP_API_KEY=' .env 2>/dev/null; then
|
||||||
|
RANDOM_KEY=$(python3 -c "import secrets, string; print(''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(48)))")
|
||||||
|
echo "MCP_API_KEY=$RANDOM_KEY" >> .env
|
||||||
|
echo " ✓ MCP_API_KEY generated and saved to .env"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
export POKE_TUNNEL=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# ── Tunnel-mode enforcement ───────────────────────────────────────────────────
|
||||||
|
if [ "${POKE_TUNNEL}" != "1" ]; then
|
||||||
|
: "${MCP_API_KEY:?MCP_API_KEY is not set — add it to .env or export it}"
|
||||||
|
else
|
||||||
|
if [ -z "${MCP_API_KEY:-}" ]; then
|
||||||
|
echo " ℹ MCP_API_KEY not set — server runs unauthenticated (safe: poke tunnel handles auth)."
|
||||||
|
fi
|
||||||
|
export POKE_TUNNEL
|
||||||
|
fi
|
||||||
|
# ── Start server + tunnel ─────────────────────────────────────────────────────
|
||||||
|
echo "Starting poke-mail server..."
|
||||||
|
python3 src/server.py & SERVER_PID=$!
|
||||||
|
trap "kill $SERVER_PID 2>/dev/null" EXIT
|
||||||
|
sleep 2
|
||||||
|
echo "Starting tunnel to Poke..."
|
||||||
|
if command -v poke &>/dev/null; then
|
||||||
|
poke tunnel http://localhost:3000/mcp --name "poke-mail"
|
||||||
|
else
|
||||||
|
echo " ℹ 'poke' binary not found in PATH — using npx poke (requires Node.js)."
|
||||||
|
if ! command -v npx &>/dev/null; then
|
||||||
|
echo " ✗ Neither 'poke' nor 'npx' found. Install Node.js (nodejs.org) and run:"
|
||||||
|
echo " npm install -g poke OR npx poke tunnel ..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
npx --yes poke tunnel http://localhost:3000/mcp --name "poke-mail"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user