diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..1a3df35 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,239 @@ +name: CI + +on: + push: + branches: + - main + - "*" + pull_request: + branches: + - main + +jobs: + ci: + name: Lint, Unit Tests & Build + runs-on: docker-host + 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 + + - 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 + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=${TELEGRAM_CHAT_ID}" \ + -d "text=❌ Momento CI Failed +Step: Lint +Commit: ${{ github.sha }} +Branch: ${{ github.ref_name }}" \ + -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 + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=${TELEGRAM_CHAT_ID}" \ + -d "text=❌ Momento CI Failed +Step: Unit Tests +Commit: ${{ github.sha }} +Branch: ${{ github.ref_name }}" \ + -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 + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=${TELEGRAM_CHAT_ID}" \ + -d "text=❌ Momento CI Failed +Step: Build +Commit: ${{ github.sha }} +Branch: ${{ github.ref_name }}" \ + -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@v3 + 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@v3 + 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: ${{ vars.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 }} + 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_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" + + - 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