diff --git a/.forgejo/workflows/docker-build-push.yaml b/.forgejo/workflows/docker-build-push.yaml new file mode 100644 index 0000000..340cceb --- /dev/null +++ b/.forgejo/workflows/docker-build-push.yaml @@ -0,0 +1,42 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + paths: + - '**/*' # Триггер при любом изменении репозитория + +jobs: + build-and-push: + runs-on: docker + + container: + image: docker:24.0.1 + + steps: + # Установка Docker CLI (если не в базовом образе) + - name: Setup Docker CLI + run: | + apk add --no-cache docker-cli + + # Авторизация в Docker Hub - токен необходимо добавить в Secrets + - name: Login to Docker Hub + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin + + # Клонирование исходников — встроено в Forgejo Actions + + # Сборка Docker образа + - name: Build Docker Image + run: | + docker build -t ${DOCKER_USERNAME}/playwright-domain-scanner:latest . + + # Push образ на Docker Hub + - name: Push Docker Image + run: | + docker push ${DOCKER_USERNAME}/playwright-domain-scanner:latest + diff --git a/.github/workflows/docker-build-push.yaml b/.github/workflows/docker-build-push.yaml deleted file mode 100644 index be6ac82..0000000 --- a/.github/workflows/docker-build-push.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: Build and Push Docker Image - -on: - push: - branches: - - main - -jobs: - build-and-push: - runs-on: ubuntu-latest - environment: dockerhub - - steps: - - name: Checkout the repository - uses: actions/checkout@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - push: true - tags: ${{ secrets.DOCKER_USERNAME }}/gekata:latest - diff --git a/Dockerfile b/Dockerfile index adf380a..f95e58b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,48 +1,46 @@ -# Base minimal Debian -FROM debian:bookworm-slim +# Use official Node.js LTS base image +FROM node:20-slim -# Prevent tzdata prompts -ENV DEBIAN_FRONTEND=noninteractive +# Install dependencies for running Chromium +RUN apt-get update && apt-get install -y \ + ca-certificates \ + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libcups2 \ + libdbus-1-3 \ + libdrm2 \ + libgbm1 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libx11-xcb1 \ + libxcomposite1 \ + libxdamage1 \ + libxrandr2 \ + xdg-utils \ + wget \ + --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* -# Install Node.js, Chromium and minimal runtime libs -# Note: chromium package on Debian provides /usr/bin/chromium -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl gnupg \ - nodejs npm \ - chromium \ - # Minimal GUI/Chromium runtime libs often needed by Playwright Chromium - libx11-6 libxcomposite1 libxdamage1 libxrandr2 libxkbcommon0 \ - libgtk-3-0 libnss3 libdrm2 libgbm1 libasound2 fonts-liberation \ - # Useful for font rendering - fonts-dejavu-core \ - && rm -rf /var/lib/apt/lists/* +# Set working directory +WORKDIR /usr/src/app -# App directory -WORKDIR /app - -# Install only production deps +# Copy package files and install dependencies COPY package*.json ./ -ENV CI=true -RUN npm ci --omit=dev +COPY ignore-domains.txt ./ +RUN npm ci -# Copy source +# Install Playwright browsers (Chromium) +RUN npx playwright install chromium + +# Copy app sources COPY . . -# Security: run as non-root -RUN useradd -ms /bin/bash nodeuser && chown -R nodeuser:nodeuser /app -USER nodeuser - -# Environment for service -ENV PORT=3000 \ - # Ensure Playwright uses system Chromium and does not download browsers - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ - PLAYWRIGHT_BROWSERS_PATH=0 \ - # Explicit executable if needed in code; here server uses default, so optional - CHROMIUM_PATH=/usr/bin/chromium - -# Expose service port +# Expose port EXPOSE 3000 -# Start the service -CMD ["node", "server.js"] +# Run the service +CMD ["npm", "start"] diff --git a/server.js b/server.js index dde4a67..f6ef7fe 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,9 @@ -// server.js const express = require('express'); const { chromium } = require('playwright'); const app = express(); const port = process.env.PORT || 3000; -// Использовать системный Chromium, если задан путь (например, /usr/bin/chromium в Debian) -const executablePath = process.env.CHROMIUM_PATH || undefined; // можно оставить undefined, если Chromium в PATH [1][2] - -// Базовый набор флагов для контейнера без systemd/dbus и без install-deps -const chromiumArgs = [ - '--no-sandbox', // запуск без setuid sandbox в контейнере [14] - '--disable-setuid-sandbox', // отключение setuid sandbox [14] - '--disable-dev-shm-usage', // использовать /tmp вместо /dev/shm (если нет --ipc=host) [15][16] - '--disable-gpu', // headless окружение [14] - '--no-zygote', // упрощение процессов в контейнере [14] -]; - app.use(express.json()); function extractDomain(url) { @@ -36,17 +23,12 @@ app.get('/domains', async (req, res) => { const url = `https://${domain}`; const seenDomains = new Set(); - let browser; - let context; try { - browser = await chromium.launch({ - executablePath, // берётся из CHROMIUM_PATH при наличии [1][2] - headless: true, // явный headless режим для контейнера [14] - args: chromiumArgs, // флаги для стабильности в Docker [15][14] + const browser = await chromium.launch({ + args: ['--no-sandbox', '--disable-setuid-sandbox'] }); - - context = await browser.newContext(); + const context = await browser.newContext(); const page = await context.newPage(); page.on('request', request => { @@ -55,20 +37,15 @@ app.get('/domains', async (req, res) => { }); await page.goto(url, { waitUntil: 'load', timeout: 30000 }); - - // Фильтрация доменов после закрытия страницы - await context.close(); await browser.close(); - const filteredDomains = Array.from(seenDomains) - .filter(d => !d.includes('doubleclick') && !d.includes('google')) - .sort(); + // Фильтрация доменов + const filteredDomains = Array.from(seenDomains).filter(d => + !d.includes('doubleclick') && !d.includes('google') + ).sort(); res.json({ domains: filteredDomains }); } catch (e) { - // Безопасно закрыть ресурсы при ошибке - try { if (context) await context.close(); } catch {} - try { if (browser) await browser.close(); } catch {} res.status(500).json({ error: e.message || 'Internal server error' }); } });