pipeline { agent any options { timeout(time: 20, unit: 'MINUTES') timestamps() buildDiscarder(logRotator(numToKeepStr: '10')) disableConcurrentBuilds() } triggers { cron('H/30 * * * *') } environment { VENV_DIR = 'venv' PIP_CACHE_DIR = "${WORKSPACE}/.pip-cache" PYTHONUNBUFFERED = '1' } stages { // ───────────────────────────────────────────── // 1. Checkout // ───────────────────────────────────────────── stage('Checkout Code') { steps { checkout scm } } // ───────────────────────────────────────────── // 2. Python env + dependencies // ───────────────────────────────────────────── stage('Setup Python & Install Dependencies') { steps { sh ''' set -euxo pipefail # Create venv python3 -m venv "${VENV_DIR}" # Upgrade pip toolchain "${VENV_DIR}/bin/python" -m pip install --upgrade pip wheel setuptools # Install all required packages "${VENV_DIR}/bin/pip" install \ --cache-dir "${PIP_CACHE_DIR}" \ -U \ atproto \ playwright \ yt-dlp \ httpx \ arrow \ python-dotenv \ moviepy \ beautifulsoup4 \ charset-normalizer \ Pillow \ grapheme # ── Install playwright-stealth and detect version ── "${VENV_DIR}/bin/pip" install --cache-dir "${PIP_CACHE_DIR}" -U playwright-stealth # Print which version was installed for traceability "${VENV_DIR}/bin/pip" show playwright-stealth | grep -E "^(Name|Version)" # Verify the stealth import works (handles both v1 and v2) "${VENV_DIR}/bin/python" -c " try: from playwright_stealth import stealth_sync print('playwright_stealth OK (v1.x - stealth_sync)') except ImportError: from playwright_stealth import Stealth print('playwright_stealth OK (v2.x - Stealth class)') " # ── Sanity checks ────────────────────────────────── "${VENV_DIR}/bin/python" -c "import atproto; print('atproto OK')" "${VENV_DIR}/bin/python" -c "import playwright; print('playwright OK')" "${VENV_DIR}/bin/python" -c "import yt_dlp; print('yt_dlp OK')" "${VENV_DIR}/bin/python" -c "import httpx; print('httpx OK')" "${VENV_DIR}/bin/python" -c "import arrow; print('arrow OK')" "${VENV_DIR}/bin/python" -c "import moviepy; print('moviepy OK')" # ── System tools ─────────────────────────────────── ffmpeg -version | head -1 ffprobe -version | head -1 # ── Playwright browser binaries (no sudo needed) ─── "${VENV_DIR}/bin/python" -m playwright install chromium ''' } } // ───────────────────────────────────────────── // 3. Run the bot // ───────────────────────────────────────────── stage('Run Script') { steps { withCredentials([ string( credentialsId: 'TIKTOK_JIJANTESFC_HANDLE', variable: 'TIKTOK_JIJANTESFC_HANDLE' ), string( credentialsId: 'BSKY_JIJANTESFC_HANDLE', variable: 'BSKY_JIJANTESFC_HANDLE' ), string( credentialsId: 'BSKY_JIJANTESFC_APP_PASSWORD', variable: 'BSKY_JIJANTESFC_APP_PASSWORD' ), file( credentialsId: 'TIKTOK_COOKIES_JSON', variable: 'TIKTOK_COOKIES_FILE' ) ]) { sh ''' set -euxo pipefail # Inject the secret cookies file into the workspace cp "$TIKTOK_COOKIES_FILE" tiktok_cookies.json "${VENV_DIR}/bin/python" tiktok2bsky.py \ --tiktok-handle "$TIKTOK_JIJANTESFC_HANDLE" \ --bsky-handle "$BSKY_JIJANTESFC_HANDLE" \ --bsky-app-password "$BSKY_JIJANTESFC_APP_PASSWORD" \ --bsky-base-url https://eurosky.social \ --bsky-langs es \ --cookies-path tiktok_cookies.json ''' } } } } // ───────────────────────────────────────────── // Post actions // ───────────────────────────────────────────── post { always { // Archive logs, state, and any CAPTCHA/debug screenshots archiveArtifacts( artifacts: '*.log, *.json, screenshot_*.png', allowEmptyArchive: true ) // Remove injected cookies file — never leave on disk between runs sh 'rm -f tiktok_cookies.json || true' } success { echo '✅ Sync cycle completed successfully.' } failure { echo '❌ Sync cycle failed — check archived logs and screenshots.' } unstable { echo '⚠️ Sync cycle finished with warnings.' } } }