name: CI on: push: branches: - main - "*" pull_request: branches: - main workflow_dispatch: jobs: ci: name: Lint, Unit Tests & Build runs-on: ubuntu-24.04 defaults: run: working-directory: memento-note steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node 22 uses: actions/setup-node@v4 with: node-version: "22" - name: Cache node_modules uses: actions/cache@v3 with: path: memento-note/node_modules key: ${{ runner.os }}-node-${{ hashFiles('memento-note/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install dependencies run: npm ci --legacy-peer-deps - name: Generate Prisma client run: npx prisma generate - name: Lint run: npm run lint - name: Notify lint failure if: failure() env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then MSG=$(printf "❌ Momento CI Failed\nStep: Lint\nCommit: %s\nBranch: %s" "${{ github.sha }}" "${{ github.ref_name }}") curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d "chat_id=${TELEGRAM_CHAT_ID}" \ --data-urlencode "text=${MSG}" \ -d "parse_mode=Markdown" || true fi - name: Unit tests (Vitest - fast logic tests) run: npm run test:unit - name: Notify test failure if: failure() env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then MSG=$(printf "❌ Momento CI Failed\nStep: Unit Tests\nCommit: %s\nBranch: %s" "${{ github.sha }}" "${{ github.ref_name }}") curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d "chat_id=${TELEGRAM_CHAT_ID}" \ --data-urlencode "text=${MSG}" \ -d "parse_mode=Markdown" || true fi - name: Build run: npm run build - name: Notify build failure if: failure() env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then MSG=$(printf "❌ Momento CI Failed\nStep: Build\nCommit: %s\nBranch: %s" "${{ github.sha }}" "${{ github.ref_name }}") curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d "chat_id=${TELEGRAM_CHAT_ID}" \ --data-urlencode "text=${MSG}" \ -d "parse_mode=Markdown" || true fi - name: Pack web artifact for deploy if: github.ref == 'refs/heads/main' && github.event_name == 'push' working-directory: memento-note run: | tar czf ../web-artifact.tgz \ .next/standalone .next/static public prisma \ node_modules/.prisma node_modules/@prisma node_modules/prisma \ docker-entrypoint.sh socket-server.ts tsconfig.json ls -lh ../web-artifact.tgz - name: Upload web artifact if: github.ref == 'refs/heads/main' && github.event_name == 'push' uses: actions/upload-artifact@v4 continue-on-error: true with: name: web-artifact path: web-artifact.tgz retention-days: 2 deploy: name: Deploy production (on server) needs: ci if: github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: docker-host steps: - name: Sync deploy scripts on server run: | git config --global --add safe.directory /opt/memento || true cd /opt/memento git fetch origin main git reset --hard origin/main - name: Download web artifact uses: actions/download-artifact@v4 continue-on-error: true with: name: web-artifact - name: Update .env.docker env: APP_URL: ${{ vars.APP_URL }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} ADMIN_EMAIL: ${{ vars.ADMIN_EMAIL }} ALLOW_REGISTRATION: ${{ vars.ALLOW_REGISTRATION }} POSTGRES_USER: ${{ vars.POSTGRES_USER }} POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} POSTGRES_DB: ${{ vars.POSTGRES_DB }} POSTGRES_PORT: ${{ vars.POSTGRES_PORT }} AI_PROVIDER_TAGS: ${{ vars.AI_PROVIDER_TAGS }} AI_MODEL_TAGS: ${{ vars.AI_MODEL_TAGS }} AI_PROVIDER_EMBEDDING: ${{ vars.AI_PROVIDER_EMBEDDING }} AI_MODEL_EMBEDDING: ${{ vars.AI_MODEL_EMBEDDING }} AI_PROVIDER_CHAT: ${{ vars.AI_PROVIDER_CHAT }} AI_MODEL_CHAT: ${{ vars.AI_MODEL_CHAT }} CUSTOM_OPENAI_BASE_URL: ${{ vars.CUSTOM_OPENAI_BASE_URL }} CUSTOM_OPENAI_API_KEY: ${{ secrets.CUSTOM_OPENAI_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OLLAMA_BASE_URL: ${{ vars.OLLAMA_BASE_URL }} EMAIL_PROVIDER: ${{ vars.EMAIL_PROVIDER }} SMTP_FROM: ${{ vars.SMTP_FROM }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} SMTP_HOST: ${{ vars.SMTP_HOST }} SMTP_PORT: ${{ vars.SMTP_PORT }} SMTP_USER: ${{ vars.SMTP_USER }} SMTP_PASS: ${{ secrets.SMTP_PASS }} SMTP_SECURE: ${{ vars.SMTP_SECURE }} SMTP_IGNORE_CERT: ${{ vars.SMTP_IGNORE_CERT }} MCP_MODE: ${{ vars.MCP_MODE }} MCP_PORT: ${{ vars.MCP_PORT }} WEB_SEARCH_PROVIDER: ${{ vars.WEB_SEARCH_PROVIDER }} SEARXNG_URL: ${{ vars.SEARXNG_URL }} BRAVE_SEARCH_API_KEY: ${{ secrets.BRAVE_SEARCH_API_KEY }} JINA_API_KEY: ${{ secrets.JINA_API_KEY }} AUTH_GOOGLE_ID: ${{ vars.AUTH_GOOGLE_ID }} AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }} SOCKET_INTERNAL_KEY: ${{ secrets.SOCKET_INTERNAL_KEY }} SOCKET_PORT: ${{ vars.SOCKET_PORT }} SOCKET_HTTP_PORT: ${{ vars.SOCKET_HTTP_PORT }} SOCKET_INTERNAL_URL: ${{ vars.SOCKET_INTERNAL_URL }} NEXT_PUBLIC_SOCKET_URL: ${{ vars.NEXT_PUBLIC_SOCKET_URL }} TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} METRICS_TOKEN: ${{ secrets.METRICS_TOKEN }} GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} MCP_API_KEY: ${{ secrets.MCP_API_KEY }} run: | ENV_FILE="/opt/memento/.env.docker" touch "$ENV_FILE" upsert() { local key="$1" val="$2" [ -z "$val" ] && return sed -i "/^[[:space:]]*${key}=/d" "$ENV_FILE" echo "${key}=\"${val}\"" >> "$ENV_FILE" } upsert NEXTAUTH_URL "$APP_URL" upsert NEXTAUTH_SECRET "$NEXTAUTH_SECRET" upsert ADMIN_EMAIL "$ADMIN_EMAIL" upsert ALLOW_REGISTRATION "$ALLOW_REGISTRATION" upsert POSTGRES_USER "$POSTGRES_USER" upsert POSTGRES_PASSWORD "$POSTGRES_PASSWORD" upsert POSTGRES_DB "$POSTGRES_DB" upsert POSTGRES_PORT "$POSTGRES_PORT" upsert AI_PROVIDER_TAGS "$AI_PROVIDER_TAGS" upsert AI_MODEL_TAGS "$AI_MODEL_TAGS" upsert AI_PROVIDER_EMBEDDING "$AI_PROVIDER_EMBEDDING" upsert AI_MODEL_EMBEDDING "$AI_MODEL_EMBEDDING" upsert AI_PROVIDER_CHAT "$AI_PROVIDER_CHAT" upsert AI_MODEL_CHAT "$AI_MODEL_CHAT" upsert CUSTOM_OPENAI_BASE_URL "$CUSTOM_OPENAI_BASE_URL" upsert CUSTOM_OPENAI_API_KEY "$CUSTOM_OPENAI_API_KEY" upsert OPENAI_API_KEY "$OPENAI_API_KEY" upsert OLLAMA_BASE_URL "$OLLAMA_BASE_URL" upsert REDIS_HOST "redis" upsert EMAIL_PROVIDER "$EMAIL_PROVIDER" upsert SMTP_FROM "$SMTP_FROM" upsert RESEND_API_KEY "$RESEND_API_KEY" upsert SMTP_HOST "$SMTP_HOST" upsert SMTP_PORT "$SMTP_PORT" upsert SMTP_USER "$SMTP_USER" upsert SMTP_PASS "$SMTP_PASS" upsert SMTP_SECURE "$SMTP_SECURE" upsert SMTP_IGNORE_CERT "$SMTP_IGNORE_CERT" upsert MCP_MODE "$MCP_MODE" upsert MCP_PORT "$MCP_PORT" upsert WEB_SEARCH_PROVIDER "$WEB_SEARCH_PROVIDER" upsert SEARXNG_URL "$SEARXNG_URL" upsert BRAVE_SEARCH_API_KEY "$BRAVE_SEARCH_API_KEY" upsert JINA_API_KEY "$JINA_API_KEY" upsert AUTH_GOOGLE_ID "$AUTH_GOOGLE_ID" upsert AUTH_GOOGLE_SECRET "$AUTH_GOOGLE_SECRET" upsert SOCKET_INTERNAL_KEY "$SOCKET_INTERNAL_KEY" upsert SOCKET_PORT "$SOCKET_PORT" upsert SOCKET_HTTP_PORT "$SOCKET_HTTP_PORT" upsert SOCKET_INTERNAL_URL "$SOCKET_INTERNAL_URL" upsert NEXT_PUBLIC_SOCKET_URL "$NEXT_PUBLIC_SOCKET_URL" upsert TELEGRAM_BOT_TOKEN "$TELEGRAM_BOT_TOKEN" upsert TELEGRAM_CHAT_ID "$TELEGRAM_CHAT_ID" upsert METRICS_TOKEN "$METRICS_TOKEN" upsert GRAFANA_ADMIN_PASSWORD "$GRAFANA_ADMIN_PASSWORD" upsert MCP_API_KEY "$MCP_API_KEY" # Write metrics token file for Prometheus (same secret) [ -n "$METRICS_TOKEN" ] && echo "$METRICS_TOKEN" > /opt/memento/monitoring/metrics-token && chmod 600 /opt/memento/monitoring/metrics-token || true - name: Deploy on 192.168.1.190 env: ARTIFACT_TGZ: ${{ github.workspace }}/web-artifact.tgz EXPECTED_COMMIT: ${{ github.sha }} run: bash /opt/memento/scripts/deploy-prod.sh