mirror of
https://github.com/manualdousuario/marreta.git
synced 2025-04-25 16:09:10 +00:00
Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f09a861cd1 | ||
|
7d449b5229 | ||
|
5ca8403afc | ||
|
91176050c0 | ||
|
abb1966b33 | ||
|
badd23ba7c | ||
|
602fc277dd | ||
|
8f277a648e | ||
|
4079f568ba | ||
|
30ad1d9113 | ||
|
72a5c6781f | ||
|
d921dcd115 | ||
|
9a257efd46 | ||
|
7df2056c1d | ||
|
884641e58a | ||
|
b7921a2a97 | ||
|
8b3aae2985 | ||
|
6d08a3e017 |
30 changed files with 591 additions and 512 deletions
.github/workflows
.gitignoreDockerfileREADME.en.mdREADME.mdapp
bin
default.confdocker-compose.ymldocker-entrypoint.sh
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
|
@ -9,6 +9,7 @@ on:
|
|||
env:
|
||||
DOCKER_REGISTRY: ghcr.io
|
||||
DOCKER_IMAGE_NAME: ${{ github.repository }}
|
||||
DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_USERNAME }}/marreta
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
|
@ -38,19 +39,27 @@ jobs:
|
|||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
|
||||
images: |
|
||||
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REPOSITORY }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
|
||||
- name: 🔐 Log in to Registry
|
||||
- name: 🔐 Log in to GitHub Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.DOCKER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 🔐 Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: 🏗️ Build and Push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ composer.lock
|
|||
.env
|
||||
app/logs/*.log
|
||||
app/cache/*.gz
|
||||
app/cache/database/.sqlite
|
||||
TODO.md
|
||||
node_modules
|
||||
|
||||
|
|
20
Dockerfile
20
Dockerfile
|
@ -10,11 +10,11 @@ RUN apt-get update && apt-get install -y \
|
|||
zip \
|
||||
git \
|
||||
htop \
|
||||
cron \
|
||||
libzip-dev \
|
||||
libhiredis-dev \
|
||||
&& docker-php-ext-install zip opcache \
|
||||
&& pecl install redis \
|
||||
&& docker-php-ext-enable redis opcache \
|
||||
libsqlite3-dev \
|
||||
&& docker-php-ext-install zip opcache pdo_sqlite \
|
||||
&& docker-php-ext-enable opcache \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Stage 1: Build stage
|
||||
|
@ -46,15 +46,21 @@ COPY default.conf /etc/nginx/sites-available/default
|
|||
|
||||
# Copy and configure initialization script permissions
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
COPY bin/cleanup /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh \
|
||||
&& chmod +x /usr/local/bin/cleanup
|
||||
|
||||
# Create cache and logs folders
|
||||
RUN mkdir -p /app/cache /app/logs
|
||||
# Create cache, database, and logs folders
|
||||
RUN mkdir -p /app/cache /app/cache/database /app/logs
|
||||
|
||||
# Configure base permissions for /app directory
|
||||
RUN chown -R www-data:www-data /app \
|
||||
&& chmod -R 755 /app
|
||||
|
||||
# Configure Cron
|
||||
RUN touch /app/logs/cron.log
|
||||
RUN echo '0 * * * * root php "/app/bin/cleanup" >> /app/logs/cron.log 2>&1' >> /etc/crontab
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
199
README.en.md
199
README.en.md
|
@ -1,7 +1,7 @@
|
|||
# 🛠️ Marreta
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/network/members)
|
||||
[](https://github.com/manualdousuario/marreta/stargazers)
|
||||
|
@ -9,32 +9,32 @@
|
|||
|
||||
Marreta is a tool that breaks access barriers and elements that hinder reading!
|
||||
|
||||

|
||||

|
||||
|
||||
Public instance at [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
|
||||
|
||||
## ✨ What's cool about it?
|
||||
## ✨ What's Cool?
|
||||
|
||||
- Cleans and corrects URLs automatically
|
||||
- Automatically cleans and corrects URLs
|
||||
- Removes annoying tracking parameters
|
||||
- Forces HTTPS to keep everything secure
|
||||
- Changes user agent to avoid blockages
|
||||
- Leaves the HTML clean and optimized
|
||||
- Changes user agent to avoid blocking
|
||||
- Leaves HTML clean and optimized
|
||||
- Fixes relative URLs on its own
|
||||
- Allows you to put your own styles and scripts
|
||||
- Allows you to add your own styles and scripts
|
||||
- Removes unwanted elements
|
||||
- Cache, cache!
|
||||
- Caching, caching!
|
||||
- Blocks domains you don't want
|
||||
- Allows you to configure headers and cookies your way
|
||||
- Allows configuring headers and cookies your way
|
||||
- PHP-FPM and OPcache
|
||||
|
||||
## 🐳 Installing with Docker
|
||||
|
||||
Install Docker and Docker Compose
|
||||
Install [Docker and Docker Compose](https://docs.docker.com/engine/install/)
|
||||
|
||||
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml`
|
||||
|
||||
Now modify it with your settings:
|
||||
Now modify with your preferences:
|
||||
|
||||
`nano docker-compose.yml`
|
||||
|
||||
|
@ -49,181 +49,36 @@ services:
|
|||
- SITE_NAME=
|
||||
- SITE_DESCRIPTION=
|
||||
- SITE_URL=
|
||||
- LANGUAGE=
|
||||
```
|
||||
|
||||
- `SITE_NAME`: Your Marreta's name
|
||||
- `SITE_DESCRIPTION`: What it's for
|
||||
- `SITE_URL`: Where it will run, complete address with `https://`. If you change the port in docker-compose (e.g. 8080:80), you must also include the port in SITE_URL (e.g. https://yoursite:8080)
|
||||
- `DNS_SERVERS`: Which DNS servers to use `1.1.1.1, 8.8.8.8`
|
||||
- `SELENIUM_HOST`: Selenium host server:PORT (e.g. selenium-hub:4444)
|
||||
- `SITE_NAME`: Name of your Marreta
|
||||
- `SITE_DESCRIPTION`: Explain what it's for
|
||||
- `SITE_URL`: Where it will run, full address with `https://`. If you change the port in docker-compose (e.g., 8080:80), you must also include the port in SITE_URL (e.g., https://yoursite:8080)
|
||||
- `SELENIUM_HOST`: Server:PORT of Selenium host (e.g., selenium-hub:4444)
|
||||
- `LANGUAGE`: pt-br (Brazilian Portuguese), en (English), es (Spanish), de-de (German), ru-ru (Russian)
|
||||
|
||||
Now just run `docker compose up -d`
|
||||
|
||||
Now you can run `docker compose up -d`
|
||||
|
||||
### S3 Cache
|
||||
|
||||
Support for cache storage in S3. Configure the following variables in your `.env`:
|
||||
|
||||
```env
|
||||
S3_CACHE_ENABLED=true
|
||||
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=bucket_name
|
||||
S3_REGION=us-east-1
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
S3_ENDPOINT=
|
||||
```
|
||||
|
||||
Possible configurations:
|
||||
|
||||
```
|
||||
## R2
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=bucket_name
|
||||
S3_ENDPOINT=https://{TOKEN}.r2.cloudflarestorage.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
|
||||
## DigitalOcean
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=bucket_name
|
||||
S3_ENDPOINT=https://{REGION}.digitaloceanspaces.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
```
|
||||
|
||||
### Selenium Integration
|
||||
|
||||
Integration with Selenium allows processing sites that require JavaScript or have some more advanced protection barriers. To use this feature, you need to set up a Selenium environment with Firefox. Add the following configuration to your `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
selenium-firefox:
|
||||
container_name: selenium-firefox
|
||||
image: selenium/node-firefox:4.27.0-20241204
|
||||
shm_size: 2gb
|
||||
environment:
|
||||
- SE_EVENT_BUS_HOST=selenium-hub
|
||||
- SE_EVENT_BUS_PUBLISH_PORT=4442
|
||||
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
|
||||
- SE_ENABLE_TRACING=false
|
||||
- SE_NODE_MAX_SESSIONS=10
|
||||
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
|
||||
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
|
||||
depends_on:
|
||||
- selenium-hub
|
||||
|
||||
selenium-hub:
|
||||
image: selenium/hub:4.27.0-20241204
|
||||
container_name: selenium-hub
|
||||
environment:
|
||||
- SE_ENABLE_TRACING=false
|
||||
- GRID_MAX_SESSION=10
|
||||
- GRID_BROWSER_TIMEOUT=10
|
||||
- GRID_TIMEOUT=10
|
||||
ports:
|
||||
- 4442:4442
|
||||
- 4443:4443
|
||||
- 4444:4444
|
||||
```
|
||||
|
||||
Important settings:
|
||||
- `shm_size`: Sets the shared memory size for Firefox (2GB recommended)
|
||||
- `SE_NODE_MAX_SESSIONS`: Maximum number of concurrent sessions per node
|
||||
- `GRID_MAX_SESSION`: Maximum number of concurrent sessions on the hub
|
||||
- `GRID_BROWSER_TIMEOUT` and `GRID_TIMEOUT`: Timeouts in seconds
|
||||
|
||||
After configuring Selenium, make sure to set the `SELENIUM_HOST` variable in your environment to point to the Selenium hub (usually `selenium-hub:4444`).
|
||||
|
||||
## Development
|
||||
|
||||
1. First, clone the project:
|
||||
```bash
|
||||
git clone https://github.com/manualdousuario/marreta/
|
||||
cd marreta/app
|
||||
```
|
||||
|
||||
2. Install the project dependencies:
|
||||
```bash
|
||||
composer install
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Create the configuration file:
|
||||
```bash
|
||||
cp .env.sample .env
|
||||
```
|
||||
|
||||
4. Configure the environment variables in `.env`
|
||||
|
||||
5. Use the `default.conf` as a base for NGINX or point your webservice to `app/`
|
||||
|
||||
Gulp is used to compile Sass to CSS, minify JavaScript, use: `gulp`
|
||||
|
||||
### ⚙️ Customizing
|
||||
|
||||
The settings are organized in `data/`:
|
||||
|
||||
- `domain_rules.php`: Specific rules for each site
|
||||
- `global_rules.php`: Rules that apply to all sites
|
||||
- `blocked_domains.php`: List of blocked sites
|
||||
|
||||
### Translations
|
||||
|
||||
- `/languages/`: Each language is in its ISO id (`pt-br, en, es or de-de`) and can be defined in the environment `LANGUAGE`
|
||||
|
||||
## 🛠️ Maintenance
|
||||
|
||||
### Logging System
|
||||
|
||||
Logs are stored in `app/logs/*.log` with automatic rotation every 7 days.
|
||||
|
||||
Log settings available in `.env` or docker:
|
||||
|
||||
```env
|
||||
LOG_LEVEL=WARNING
|
||||
```
|
||||
|
||||
Available log levels:
|
||||
- DEBUG: Detailed information for debugging
|
||||
- INFO: General information about operations
|
||||
- WARNING: Warnings that deserve attention (default)
|
||||
- ERROR: Errors that do not interrupt operation
|
||||
- CRITICAL: Critical errors that need immediate attention
|
||||
|
||||
View the application logs:
|
||||
```bash
|
||||
docker-compose logs app
|
||||
# or directly from the log file
|
||||
cat app/logs/*.log
|
||||
```
|
||||
|
||||
### Clearing the cache
|
||||
|
||||
When you need to clear:
|
||||
```bash
|
||||
docker-compose exec app rm -rf /app/cache/*
|
||||
```
|
||||
### More configurations:
|
||||
- Selenium: https://github.com/manualdousuario/marreta/wiki/%F0%9F%92%BB-Selenium-Hub-(Chrome-and-Firefox)
|
||||
- S3 Cache: https://github.com/manualdousuario/marreta/wiki/%F0%9F%97%83%EF%B8%8F-Cache-S3
|
||||
- Maintenance: https://github.com/manualdousuario/marreta/wiki/%F0%9F%9B%A0%EF%B8%8F-Maintenance
|
||||
|
||||
## 🚀 Integrations
|
||||
|
||||
- 🤖 **Telegram**: [Official Bot](https://t.me/leissoai_bot)
|
||||
- 🦊 **Firefox**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://addons.mozilla.org/pt-BR/firefox/addon/marreta/) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🦊 **Firefox**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://addons.mozilla.org/en-US/firefox/addon/marreta/) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🌀 **Chrome**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://chromewebstore.google.com/detail/marreta/ipelapagohjgjcgpncpbmaaacemafppe) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🦋 **Bluesky**: Bot by [Joselito](https://bsky.app/profile/joseli.to) - [Profile](https://bsky.app/profile/marreta.pcdomanual.com) | [Source Code](https://github.com/manualdousuario/marreta-bot)
|
||||
- 🍎 **Apple**: Integration with [Shortcuts](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f)
|
||||
|
||||
---
|
||||
|
||||
Made with ❤️! If you have any questions or suggestions, open an issue and we'll help! 😉
|
||||
Made with ❤️! If you have questions or suggestions, open an issue and we'll help! 😉
|
||||
|
||||
Thanks to the [https://github.com/burlesco/burlesco](Burlesco) and [https://github.com/nang-dev/hover-paywalls-browser-extension/](Hover) projects that served as the basis for several rules!
|
||||
Special thanks to the projects [Burlesco](https://github.com/burlesco/burlesco) and [Hover](https://github.com/nang-dev/hover-paywalls-browser-extension/) which served as the basis for many rules!
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#manualdousuario/marreta&Date)
|
||||
[](https://star-history.com/#manualdousuario/marreta&Date)
|
167
README.md
167
README.md
|
@ -1,7 +1,7 @@
|
|||
# 🛠️ Marreta
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.md)
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/network/members)
|
||||
[](https://github.com/manualdousuario/marreta/stargazers)
|
||||
|
@ -30,11 +30,11 @@ Instancia publica em [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
|
|||
|
||||
## 🐳 Instalando em Docker
|
||||
|
||||
Instale Docker e Docker Compose
|
||||
Instale [Docker e Docker Compose](https://docs.docker.com/engine/install/)
|
||||
|
||||
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml`
|
||||
|
||||
Agora modifique com suas configurações:
|
||||
Agora modifique com suas preferencias:
|
||||
|
||||
`nano docker-compose.yml`
|
||||
|
||||
|
@ -49,166 +49,21 @@ services:
|
|||
- SITE_NAME=
|
||||
- SITE_DESCRIPTION=
|
||||
- SITE_URL=
|
||||
- LANGUAGE=
|
||||
```
|
||||
|
||||
- `SITE_NAME`: Nome do seu Marreta
|
||||
- `SITE_DESCRIPTION`: Conta pra que serve
|
||||
- `SITE_URL`: Onde vai rodar, endereço completo com `https://`. Se você alterar a porta no docker-compose (ex: 8080:80), você também deve incluir a porta no SITE_URL (ex: https://seusite:8080)
|
||||
- `DNS_SERVERS`: Quais servidores DNS usar `1.1.1.1, 8.8.8.8`
|
||||
- `SELENIUM_HOST`: Servidor:PORTA do host do Selenium (ex: selenium-hub:4444)
|
||||
-
|
||||
Agora pode rodar `docker compose up -d`
|
||||
- `LANGUAGE`: pt-br (Português Brasil), en (Inglês), es (Espanhol) ou de-de (Alemão), ru-ru (Russo)
|
||||
|
||||
Agora só rodar `docker compose up -d`
|
||||
|
||||
### Cache S3
|
||||
|
||||
Suporte de armazenamento do cache em S3. Configure as seguintes variáveis no seu `.env`:
|
||||
|
||||
```env
|
||||
S3_CACHE_ENABLED=true
|
||||
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=nome_do_bucket
|
||||
S3_REGION=us-east-1
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
S3_ENDPOINT=
|
||||
```
|
||||
|
||||
Configurações possiveis:
|
||||
|
||||
```
|
||||
## R2
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=nome_do_bucket
|
||||
S3_ENDPOINT=https://{TOKEN}.r2.cloudflarestorage.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
|
||||
## DigitalOcean
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=nome_do_bucket
|
||||
S3_ENDPOINT=https://{REGIAO}.digitaloceanspaces.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
```
|
||||
|
||||
### Integração com Selenium
|
||||
|
||||
Integração com Selenium permite processar sites que requerem javascript ou têm algumas barreiras de proteção mais avançadas. Para usar esta funcionalidade, você precisa configurar um ambiente Selenium com Firefox. Adicione a seguinte configuração ao seu `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
selenium-firefox:
|
||||
container_name: selenium-firefox
|
||||
image: selenium/node-firefox:4.27.0-20241204
|
||||
shm_size: 2gb
|
||||
environment:
|
||||
- SE_EVENT_BUS_HOST=selenium-hub
|
||||
- SE_EVENT_BUS_PUBLISH_PORT=4442
|
||||
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
|
||||
- SE_ENABLE_TRACING=false
|
||||
- SE_NODE_MAX_SESSIONS=10
|
||||
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
|
||||
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
|
||||
depends_on:
|
||||
- selenium-hub
|
||||
|
||||
selenium-hub:
|
||||
image: selenium/hub:4.27.0-20241204
|
||||
container_name: selenium-hub
|
||||
environment:
|
||||
- SE_ENABLE_TRACING=false
|
||||
- GRID_MAX_SESSION=10
|
||||
- GRID_BROWSER_TIMEOUT=10
|
||||
- GRID_TIMEOUT=10
|
||||
ports:
|
||||
- 4442:4442
|
||||
- 4443:4443
|
||||
- 4444:4444
|
||||
```
|
||||
|
||||
Configurações importantes:
|
||||
- `shm_size`: Define o tamanho da memória compartilhada para o Firefox (2GB recomendado)
|
||||
- `SE_NODE_MAX_SESSIONS`: Número máximo de sessões simultâneas por nó
|
||||
- `GRID_MAX_SESSION`: Número máximo de sessões simultâneas no hub
|
||||
- `GRID_BROWSER_TIMEOUT` e `GRID_TIMEOUT`: Timeouts em segundos
|
||||
|
||||
Após configurar o Selenium, certifique-se de definir a variável `SELENIUM_HOST` no seu ambiente para apontar para o hub do Selenium (geralmente `selenium-hub:4444`).
|
||||
|
||||
## Desenvolvimento
|
||||
|
||||
1. Primeiro, clone o projeto:
|
||||
```bash
|
||||
git clone https://github.com/manualdousuario/marreta/
|
||||
cd marreta/app
|
||||
```
|
||||
|
||||
2. Instale as dependências do projeto:
|
||||
```bash
|
||||
composer install
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Cria o arquivo de configuração:
|
||||
```bash
|
||||
cp .env.sample .env
|
||||
```
|
||||
|
||||
4. Configure as variáveis de ambiente no `.env`
|
||||
|
||||
5. Utilize o `default.conf` como base do NGINX ou aponte seu webservice para `app/`
|
||||
|
||||
O Gulp é usado para compilar Sass para CSS, minificar JavaScript, utilize: `gulp`
|
||||
|
||||
### ⚙️ Personalizando
|
||||
|
||||
As configurações estão organizadas em `data/`:
|
||||
|
||||
- `domain_rules.php`: Regras específicas para cada site
|
||||
- `global_rules.php`: Regras que se aplicam a todos os sites
|
||||
- `blocked_domains.php`: Lista de sites bloqueados
|
||||
|
||||
### Traduções
|
||||
|
||||
- `/languages/`: Cada lingua está em seu ISO id (`pt-br, en, es ou de-de`) e pode ser definida no environment `LANGUAGE`
|
||||
|
||||
## 🛠️ Manutenção
|
||||
|
||||
### Sistema de Logs
|
||||
|
||||
Os logs são armazenados em `app/logs/*.log` com rotação automática a cada 7 dias.
|
||||
|
||||
Configurações de log disponíveis no `.env` ou docker:
|
||||
|
||||
```env
|
||||
LOG_LEVEL=WARNING
|
||||
```
|
||||
|
||||
Níveis de log disponíveis:
|
||||
- DEBUG: Informações detalhadas para debug
|
||||
- INFO: Informações gerais sobre operações
|
||||
- WARNING: Avisos que merecem atenção (padrão)
|
||||
- ERROR: Erros que não interrompem a operação
|
||||
- CRITICAL: Erros críticos que precisam de atenção imediata
|
||||
|
||||
Ver os logs da aplicação:
|
||||
```bash
|
||||
docker-compose logs app
|
||||
# ou diretamente do arquivo de log
|
||||
cat app/logs/*.log
|
||||
```
|
||||
|
||||
### Limpando o cache
|
||||
|
||||
Quando precisar limpar:
|
||||
```bash
|
||||
docker-compose exec app rm -rf /app/cache/*
|
||||
```
|
||||
### Mais configurações:
|
||||
- Selenium: https://github.com/manualdousuario/marreta/wiki/%F0%9F%92%BB-Selenium-Hub-(Chrome-and-Firefox)
|
||||
- Cache S3: https://github.com/manualdousuario/marreta/wiki/%F0%9F%97%83%EF%B8%8F-Cache-S3
|
||||
- Manutenção: https://github.com/manualdousuario/marreta/wiki/%F0%9F%9B%A0%EF%B8%8F-Maintenance
|
||||
|
||||
## 🚀 Integrações
|
||||
|
||||
|
|
|
@ -40,5 +40,10 @@ S3_ENDPOINT=
|
|||
# Selenium Configuration
|
||||
SELENIUM_HOST=localhost:4444
|
||||
|
||||
# Debug Settings
|
||||
DEBUG=false
|
||||
# Debug Settings
|
||||
DEBUG=false
|
||||
|
||||
# Cache Cleanup Settings
|
||||
# Number of days to keep cache files (*.gz)
|
||||
# If not set, no files will be cleaned
|
||||
CLEANUP_DAYS=7
|
||||
|
|
|
@ -85,4 +85,20 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Paste button functionality
|
||||
const pasteButton = document.getElementById('paste');
|
||||
const urlInput = document.getElementById('url');
|
||||
|
||||
if (pasteButton && urlInput) {
|
||||
pasteButton.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const clipboardText = await navigator.clipboard.readText();
|
||||
urlInput.value = clipboardText.trim();
|
||||
} catch (err) {
|
||||
console.error('Failed to read clipboard contents', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -38,4 +38,6 @@
|
|||
@include mixin.icon('warning', 'invert(89%) sepia(25%) saturate(5861%) hue-rotate(353deg) brightness(101%) contrast(101%)');
|
||||
|
||||
@include mixin.icon('hamburguer', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
|
||||
@include mixin.icon('close', 'invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%)');
|
||||
@include mixin.icon('close', 'invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%)');
|
||||
|
||||
@include mixin.icon('paste', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
|
|
@ -496,6 +496,23 @@ main {
|
|||
}
|
||||
}
|
||||
|
||||
.paste {
|
||||
background: rgb(244,244,245);
|
||||
background: linear-gradient(90deg, rgba(244,244,245,0) 0%, rgba(244,244,245,1) 30%, rgba(244,244,245,1) 100%);
|
||||
align-items: center;
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
padding: 0 18px 0 22px;
|
||||
right: 50px;
|
||||
cursor: pointer;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
.icon {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
position: relative;
|
||||
background-color: var(--marreta);
|
||||
|
|
0
app/cache/database/.gitkeep
vendored
Normal file
0
app/cache/database/.gitkeep
vendored
Normal file
|
@ -5,7 +5,8 @@
|
|||
"php-curl-class/php-curl-class": "^11.0",
|
||||
"php-webdriver/webdriver": "^1.15",
|
||||
"monolog/monolog": "^3.8.1",
|
||||
"nikic/fast-route": "^1.3"
|
||||
"nikic/fast-route": "^1.3",
|
||||
"league/climate": "^3.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
@ -30,6 +30,7 @@ try {
|
|||
define('SITE_NAME', $_ENV['SITE_NAME']);
|
||||
define('SITE_DESCRIPTION', $_ENV['SITE_DESCRIPTION']);
|
||||
define('SITE_URL', $_ENV['SITE_URL']);
|
||||
define('CLEANUP_DAYS', $_ENV['CLEANUP_DAYS'] ?? 0);
|
||||
|
||||
// Optional settings with defaults
|
||||
define('DNS_SERVERS', $_ENV['DNS_SERVERS'] ?? '1.1.1.1, 8.8.8.8');
|
||||
|
@ -38,11 +39,6 @@ try {
|
|||
define('CACHE_DIR', __DIR__ . '/cache');
|
||||
define('LANGUAGE', $_ENV['LANGUAGE'] ?? 'pt-br');
|
||||
|
||||
// Redis connection settings
|
||||
define('REDIS_HOST', $_ENV['REDIS_HOST'] ?? 'localhost');
|
||||
define('REDIS_PORT', $_ENV['REDIS_PORT'] ?? 6379);
|
||||
define('REDIS_PREFIX', $_ENV['REDIS_PREFIX'] ?? 'marreta:');
|
||||
|
||||
// Logging configuration
|
||||
define('LOG_LEVEL', $_ENV['LOG_LEVEL'] ?? 'WARNING'); // DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
define('LOG_DAYS_TO_KEEP', 7);
|
||||
|
|
|
@ -27,6 +27,7 @@ return [
|
|||
'mittelbayerische.de',
|
||||
'josimarfootball.com',
|
||||
'nordsee-zeitung.de',
|
||||
'zorgvisie.nl',
|
||||
// List of common blocked sites to avoid unnecessary requests
|
||||
//-- Technical access blocking
|
||||
'bloomberg.com',
|
||||
|
|
|
@ -40,6 +40,12 @@ return [
|
|||
'removeElementsByTag' => ['style'],
|
||||
'removeCustomAttr' => ['hidden','data-*']
|
||||
],
|
||||
'wired.com' => [
|
||||
'scriptTagRemove' => ['.js'],
|
||||
],
|
||||
'newyorker.com' => [
|
||||
'scriptTagRemove' => ['.js'],
|
||||
],
|
||||
'globo.com' => [
|
||||
'idElementRemove' => ['cookie-banner-lgpd', 'paywall-cpt', 'mc-read-more-wrapper', 'paywall-cookie-content', 'paywall-cpt'],
|
||||
'classElementRemove' => ['banner-lgpd', 'article-related-link__title', 'article-related-link__picture', 'paywall-denied', 'banner-subscription'],
|
||||
|
@ -64,6 +70,18 @@ return [
|
|||
'classElementRemove' => ['leaderboard__container'],
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
],
|
||||
'lepoint.fr' => [
|
||||
'classElementRemove' => ['paywall'],
|
||||
],
|
||||
'gamestar.de' => [
|
||||
'classElementRemove' => ['plus-teaser'],
|
||||
'classAttrRemove' => ['plus-'],
|
||||
'idElementRemove' => ['commentReload']
|
||||
],
|
||||
'heise.de' => [
|
||||
'classAttrRemove' => ['curtain__purchase-container'],
|
||||
'removeElementsByTag' => ['a-gift']
|
||||
],
|
||||
'fortune.com' => [
|
||||
'classElementRemove' => ['latest-popular-module','own','drawer-menu'],
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
|
@ -130,6 +148,21 @@ return [
|
|||
'paywall_access' => 'true'
|
||||
]
|
||||
],
|
||||
'ftm.nl' => [
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
'removeCustomAttr' => ['dialog','iframe'],
|
||||
'classElementRemove' => ['modal'],
|
||||
'scriptTagRemove' => ['footer.min','diffuser.js','insight.ftm.nl'],
|
||||
'classAttrRemove' => ['hasBlockingOverlay', 'localstorage']
|
||||
],
|
||||
'denikn.cz' => [
|
||||
'idElementRemove' => ['e_lock__hard']
|
||||
],
|
||||
'dtest.cz' => [
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
'classAttrRemove' => ['is-hidden-compare'],
|
||||
'classElementRemove' => ['cc-window']
|
||||
],
|
||||
'uol.com.br' => [
|
||||
'scriptTagRemove' => ['me.jsuol.com.br', 'c.jsuol.com.br'],
|
||||
'classElementRemove' => ['header-top-wrapper'],
|
||||
|
@ -152,7 +185,8 @@ return [
|
|||
]
|
||||
],
|
||||
'theverge.com' => [
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
'scriptTagRemove' => 'zephr',
|
||||
'classElementRemove' => 'zephr'
|
||||
],
|
||||
'economist.com' => [
|
||||
'cookies' => [
|
||||
|
@ -179,7 +213,7 @@ return [
|
|||
'fromGoogleBot' => true
|
||||
],
|
||||
'nytimes.com' => [
|
||||
'idElementRemove' => ['gateway-content','site-index'],
|
||||
'idElementRemove' => ['gateway-content','site-index','complianceOverlay'],
|
||||
'customCode' => '
|
||||
setTimeout(function() {
|
||||
const walk = document.createTreeWalker(
|
||||
|
|
|
@ -68,6 +68,8 @@ return [
|
|||
'getblue.io',
|
||||
'smartocto.com',
|
||||
'cdn.pn.vg',
|
||||
'static.vocstatic.com'
|
||||
'static.vocstatic.com',
|
||||
'recaptcha',
|
||||
'intercom'
|
||||
]
|
||||
];
|
||||
|
|
2
app/dist/css/style.css
vendored
2
app/dist/css/style.css
vendored
File diff suppressed because one or more lines are too long
2
app/dist/css/style.css.map
vendored
2
app/dist/css/style.css.map
vendored
File diff suppressed because one or more lines are too long
3
app/dist/icons/paste.svg
vendored
Normal file
3
app/dist/icons/paste.svg
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33333 2.66667H3.45333C3.72667 3.44 4.46 4 5.33333 4H6.66667C7.53333 4 8.26667 3.44 8.54667 2.66667H8.66667C9.58 2.66667 10.3733 3.28 10.6067 4.16667C10.7 4.52667 11.06 4.74 11.42 4.64667C11.7733 4.55333 11.9933 4.19333 11.9 3.83333C11.52 2.36 10.1933 1.33333 8.67333 1.33333H8.56C8.28667 0.56 7.54667 0 6.67333 0H5.34C4.47333 0 3.72667 0.56 3.45333 1.33333H3.34C1.49333 1.33333 0 2.82667 0 4.66667V12.6667C0 14.5067 1.49333 16 3.33333 16H5.33333C5.7 16 6 15.7 6 15.3333C6 14.9667 5.7 14.6667 5.33333 14.6667H3.33333C2.23333 14.6667 1.33333 13.7667 1.33333 12.6667V4.66667C1.33333 3.56667 2.23333 2.66667 3.33333 2.66667ZM5.33333 1.33333H6.66667C7.03333 1.33333 7.33333 1.63333 7.33333 2C7.33333 2.36667 7.03333 2.66667 6.66667 2.66667H5.33333C4.96667 2.66667 4.66667 2.36667 4.66667 2C4.66667 1.63333 4.96667 1.33333 5.33333 1.33333ZM13.3333 6H10C8.52667 6 7.33333 7.19333 7.33333 8.66667V13.3333C7.33333 14.8067 8.52667 16 10 16H13.3333C14.8067 16 16 14.8067 16 13.3333V8.66667C16 7.19333 14.8067 6 13.3333 6ZM14.6667 13.3333C14.6667 14.0667 14.0667 14.6667 13.3333 14.6667H10C9.26667 14.6667 8.66667 14.0667 8.66667 13.3333V8.66667C8.66667 7.93333 9.26667 7.33333 10 7.33333H13.3333C14.0667 7.33333 14.6667 7.93333 14.6667 8.66667V13.3333ZM13.3333 9.33333C13.3333 9.7 13.0333 10 12.6667 10H10.6667C10.3 10 10 9.7 10 9.33333C10 8.96667 10.3 8.66667 10.6667 8.66667H12.6667C13.0333 8.66667 13.3333 8.96667 13.3333 9.33333ZM13.3333 12C13.3333 12.3667 13.0333 12.6667 12.6667 12.6667H10.6667C10.3 12.6667 10 12.3667 10 12C10 11.6333 10.3 11.3333 10.6667 11.3333H12.6667C13.0333 11.3333 13.3333 11.6333 13.3333 12Z" fill="black"/>
|
||||
</svg>
|
After (image error) Size: 1.7 KiB |
2
app/dist/js/scripts.js
vendored
2
app/dist/js/scripts.js
vendored
|
@ -1,2 +1,2 @@
|
|||
"serviceWorker"in navigator&&window.addEventListener("load",()=>{navigator.serviceWorker.register("/service-worker.js").then(()=>{}).catch(()=>{})}),document.addEventListener("DOMContentLoaded",function(){let t=document.querySelector(".integration");var e=document.querySelector(".integration__toggle");let o=document.querySelector(".extension");var n=document.querySelector(".extension__toggle");let s=e=>{e!==t&&t.classList.remove("open"),e!==o&&o.classList.remove("open")};e.addEventListener("click",e=>{e.stopPropagation(),s(t),t.classList.toggle("open")}),n.addEventListener("click",e=>{e.stopPropagation(),s(o),o.classList.toggle("open")}),t.addEventListener("click",e=>{e.stopPropagation()}),o.addEventListener("click",e=>{e.stopPropagation()}),document.addEventListener("click",()=>{t.classList.remove("open"),o.classList.remove("open")}),document.addEventListener("click",e=>{e=e.target.closest(".toasty");e&&e.remove()}),document.addEventListener("click",e=>{e.target.closest(".open-nav")&&((e=document.querySelector("header")).classList.contains("open")?e.classList.remove("open"):e.classList.add("open"))})});
|
||||
"serviceWorker"in navigator&&window.addEventListener("load",()=>{navigator.serviceWorker.register("/service-worker.js").then(()=>{}).catch(()=>{})}),document.addEventListener("DOMContentLoaded",function(){let t=document.querySelector(".integration");var e=document.querySelector(".integration__toggle");let o=document.querySelector(".extension");var n=document.querySelector(".extension__toggle");let r=e=>{e!==t&&t.classList.remove("open"),e!==o&&o.classList.remove("open")};e.addEventListener("click",e=>{e.stopPropagation(),r(t),t.classList.toggle("open")}),n.addEventListener("click",e=>{e.stopPropagation(),r(o),o.classList.toggle("open")}),t.addEventListener("click",e=>{e.stopPropagation()}),o.addEventListener("click",e=>{e.stopPropagation()}),document.addEventListener("click",()=>{t.classList.remove("open"),o.classList.remove("open")}),document.addEventListener("click",e=>{e=e.target.closest(".toasty");e&&e.remove()}),document.addEventListener("click",e=>{e.target.closest(".open-nav")&&((e=document.querySelector("header")).classList.contains("open")?e.classList.remove("open"):e.classList.add("open"))});e=document.getElementById("paste");let a=document.getElementById("url");e&&a&&e.addEventListener("click",async e=>{e.preventDefault();try{var t=await navigator.clipboard.readText();a.value=t.trim()}catch(e){console.error("Failed to read clipboard contents",e)}})});
|
||||
//# sourceMappingURL=scripts.js.map
|
||||
|
|
2
app/dist/js/scripts.js.map
vendored
2
app/dist/js/scripts.js.map
vendored
File diff suppressed because one or more lines are too long
|
@ -5,7 +5,7 @@ namespace Inc;
|
|||
use Inc\Cache\CacheStorageInterface;
|
||||
use Inc\Cache\DiskStorage;
|
||||
use Inc\Cache\S3Storage;
|
||||
use Inc\Cache\RedisStorage;
|
||||
use Inc\Cache\SQLiteStorage;
|
||||
|
||||
/**
|
||||
* System cache management with multiple storage backends (disk/S3)
|
||||
|
@ -17,18 +17,18 @@ class Cache
|
|||
/** @var CacheStorageInterface Cache storage implementation */
|
||||
private $storage;
|
||||
|
||||
/** @var RedisStorage Redis instance for file counting */
|
||||
private $redisStorage;
|
||||
/** @var SQLiteStorage SQLite instance for file counting */
|
||||
private $sqliteStorage;
|
||||
|
||||
/**
|
||||
* Initializes storage based on configuration
|
||||
* Uses S3Storage if configured and enabled
|
||||
* Defaults to DiskStorage otherwise
|
||||
* Defaults to SQLiteStorage otherwise (which delegates to DiskStorage)
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->redisStorage = new RedisStorage(CACHE_DIR);
|
||||
|
||||
$this->sqliteStorage = new SQLiteStorage(CACHE_DIR);
|
||||
|
||||
if (defined('S3_CACHE_ENABLED') && S3_CACHE_ENABLED === true) {
|
||||
$this->storage = new S3Storage([
|
||||
'key' => S3_ACCESS_KEY,
|
||||
|
@ -40,14 +40,14 @@ class Cache
|
|||
'endpoint' => defined('S3_ENDPOINT') ? S3_ENDPOINT : null
|
||||
]);
|
||||
} else {
|
||||
$this->storage = new DiskStorage(CACHE_DIR);
|
||||
$this->storage = $this->sqliteStorage;
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets total number of cached files */
|
||||
public function getCacheFileCount(): int
|
||||
{
|
||||
return $this->redisStorage->countCacheFiles();
|
||||
return $this->sqliteStorage->countCacheFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Inc\Cache;
|
||||
|
||||
use Redis;
|
||||
|
||||
/**
|
||||
* Redis-based cache storage implementation
|
||||
* Provides cache storage and file counting functionality using Redis
|
||||
*/
|
||||
class RedisStorage implements CacheStorageInterface
|
||||
{
|
||||
/**
|
||||
* @var \Redis|null Redis client instance
|
||||
*/
|
||||
private $redis;
|
||||
|
||||
/**
|
||||
* @var string Cache directory for file counting
|
||||
*/
|
||||
private $cacheDir;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param string $cacheDir Base directory for cache storage
|
||||
*/
|
||||
public function __construct(string $cacheDir)
|
||||
{
|
||||
$this->cacheDir = $cacheDir;
|
||||
|
||||
// Try to initialize Redis connection
|
||||
try {
|
||||
$this->redis = new \Redis();
|
||||
$this->redis->connect(REDIS_HOST, REDIS_PORT, 2.5);
|
||||
$this->redis->setOption(\Redis::OPT_PREFIX, REDIS_PREFIX);
|
||||
} catch (\Exception $e) {
|
||||
$this->redis = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of files in the cache directory
|
||||
* @return int Number of files in the cache directory
|
||||
*/
|
||||
public function countCacheFiles(): int
|
||||
{
|
||||
$cacheCountKey = 'cache_file_count';
|
||||
|
||||
if ($this->redis !== null) {
|
||||
$cachedCount = $this->redis->get($cacheCountKey);
|
||||
if ($cachedCount !== false) {
|
||||
return (int)$cachedCount;
|
||||
}
|
||||
}
|
||||
|
||||
$fileCount = 0;
|
||||
$iterator = new \FilesystemIterator($this->cacheDir);
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'gz') {
|
||||
$fileCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->redis !== null) {
|
||||
$this->redis->set($cacheCountKey, $fileCount);
|
||||
}
|
||||
|
||||
return $fileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file count in Redis
|
||||
* @param int $count Number of files
|
||||
*/
|
||||
public function updateCacheFileCount(int $count): void
|
||||
{
|
||||
if ($this->redis !== null) {
|
||||
$this->redis->set('cache_file_count', $count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cache exists for a given ID
|
||||
* @param string $id Cache ID
|
||||
* @return bool True if cache exists, false otherwise
|
||||
*/
|
||||
public function exists(string $id): bool
|
||||
{
|
||||
return $this->redis !== null ? $this->redis->exists($id) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cached content
|
||||
* @param string $id Cache ID
|
||||
* @return string|null Cached content or null if not found
|
||||
*/
|
||||
public function get(string $id): ?string
|
||||
{
|
||||
if ($this->redis === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = $this->redis->get($id);
|
||||
return $content === false ? null : $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores content in cache
|
||||
* @param string $id Cache ID
|
||||
* @param string $content Content to be stored
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
public function set(string $id, string $content): bool
|
||||
{
|
||||
if ($this->redis === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->redis->set($id, $content);
|
||||
|
||||
if ($result) {
|
||||
$currentCount = $this->redis->get('cache_file_count') ?: 0;
|
||||
$this->redis->set('cache_file_count', $currentCount + 1);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
177
app/inc/Cache/SQLiteStorage.php
Normal file
177
app/inc/Cache/SQLiteStorage.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Inc\Cache;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* SQLite-based cache storage implementation
|
||||
* Provides file counting functionality using SQLite
|
||||
* Delegates actual cache storage to DiskStorage
|
||||
*/
|
||||
class SQLiteStorage implements CacheStorageInterface
|
||||
{
|
||||
/**
|
||||
* @var PDO|null SQLite connection
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* @var string Cache directory for file counting
|
||||
*/
|
||||
private $cacheDir;
|
||||
|
||||
/**
|
||||
* @var string Path to SQLite database file
|
||||
*/
|
||||
private $dbPath;
|
||||
|
||||
/**
|
||||
* @var DiskStorage Disk storage for cache entries
|
||||
*/
|
||||
private $diskStorage;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param string $cacheDir Base directory for cache storage
|
||||
*/
|
||||
public function __construct(string $cacheDir)
|
||||
{
|
||||
$this->cacheDir = $cacheDir;
|
||||
$this->diskStorage = new DiskStorage($cacheDir);
|
||||
|
||||
// Ensure database directory exists
|
||||
$dbDir = $cacheDir . '/database';
|
||||
if (!is_dir($dbDir)) {
|
||||
mkdir($dbDir, 0755, true);
|
||||
}
|
||||
|
||||
$this->dbPath = $dbDir . '/.sqlite';
|
||||
|
||||
// Try to initialize SQLite connection
|
||||
try {
|
||||
$this->db = new PDO('sqlite:' . $this->dbPath);
|
||||
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// Create tables if they don't exist
|
||||
$this->initDatabase();
|
||||
|
||||
// If database file was just created, count cache files
|
||||
if (!file_exists($this->dbPath) || filesize($this->dbPath) < 1024) {
|
||||
$this->countCacheFiles();
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$this->db = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database tables
|
||||
*/
|
||||
private function initDatabase(): void
|
||||
{
|
||||
$this->db->exec("
|
||||
CREATE TABLE IF NOT EXISTS stats (
|
||||
key TEXT PRIMARY KEY,
|
||||
value INTEGER NOT NULL
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of files in the cache directory
|
||||
* @return int Number of files in the cache directory
|
||||
*/
|
||||
public function countCacheFiles(): int
|
||||
{
|
||||
if ($this->db !== null) {
|
||||
try {
|
||||
$stmt = $this->db->query("SELECT value FROM stats WHERE key = 'count'");
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result) {
|
||||
return (int)$result['value'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// Continue to count files if query fails
|
||||
}
|
||||
}
|
||||
|
||||
$fileCount = 0;
|
||||
$iterator = new \FilesystemIterator($this->cacheDir);
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'gz') {
|
||||
$fileCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->db !== null) {
|
||||
$this->updateCacheFileCount($fileCount);
|
||||
}
|
||||
|
||||
return $fileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file count in SQLite
|
||||
* @param int $count Number of files
|
||||
*/
|
||||
public function updateCacheFileCount(int $count): void
|
||||
{
|
||||
if ($this->db !== null) {
|
||||
try {
|
||||
$stmt = $this->db->prepare("
|
||||
INSERT OR REPLACE INTO stats (key, value)
|
||||
VALUES ('count', :count)
|
||||
");
|
||||
$stmt->bindParam(':count', $count, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
// Silently fail if update fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cache exists for a given ID
|
||||
* Delegates to DiskStorage
|
||||
* @param string $id Cache ID
|
||||
* @return bool True if cache exists, false otherwise
|
||||
*/
|
||||
public function exists(string $id): bool
|
||||
{
|
||||
return $this->diskStorage->exists($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cached content
|
||||
* Delegates to DiskStorage
|
||||
* @param string $id Cache ID
|
||||
* @return string|null Cached content or null if not found
|
||||
*/
|
||||
public function get(string $id): ?string
|
||||
{
|
||||
return $this->diskStorage->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores content in cache
|
||||
* Delegates to DiskStorage and updates file count
|
||||
* @param string $id Cache ID
|
||||
* @param string $content Content to be stored
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
public function set(string $id, string $content): bool
|
||||
{
|
||||
$result = $this->diskStorage->set($id, $content);
|
||||
|
||||
if ($result) {
|
||||
// Increment cache file count
|
||||
$currentCount = $this->countCacheFiles();
|
||||
$this->updateCacheFileCount($currentCount + 1);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -237,11 +237,11 @@ class URLAnalyzerProcess extends URLAnalyzerBase
|
|||
$body = $xpath->query('//body')->item(0);
|
||||
if ($body) {
|
||||
$brandDiv = $dom->createElement('div');
|
||||
$brandDiv->setAttribute('style', 'z-index: 99999; position: fixed; top: 0; right: 1rem; display: flex; gap: 8px;');
|
||||
$brandDiv->setAttribute('style', 'z-index: 9999; position: fixed; top: 0; right: 1rem; display: flex; gap: 8px;');
|
||||
$brandHtml = $dom->createDocumentFragment();
|
||||
$brandHtml->appendXML('<a href="' . htmlspecialchars($url) . '" style="color: #fff; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 16 16" width="20" height="20"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg></a>');
|
||||
$brandHtml->appendXML('<a href="' . htmlspecialchars($url) . '" style="color: #fff; line-height: 1em; z-index: 9999; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 16 16" width="20" height="20"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg></a>');
|
||||
$brandDiv->appendChild($brandHtml);
|
||||
$brandHtml->appendXML('<a href="' . htmlspecialchars(SITE_URL) . '" style="color: #fff; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 640 512" width="20" height="20"><path d="m283.9 378.6 18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9-85.7-42.9-104.3-52.2c-15.8-7.9-22.2-27.1-14.3-42.9l40-80C48.8 22.8 59.9 16 72 16h120c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zm-204.6-39.5 85.9 42.9L90.9 485.5C79 509.2 50.2 518.8 26.5 507s-33.3-40.8-21.4-64.5l102.5-205.1z"/></svg></a>');
|
||||
$brandHtml->appendXML('<a href="' . htmlspecialchars(SITE_URL) . '" style="color: #fff; line-height: 1em; z-index: 9999; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 640 512" width="20" height="20"><path d="m283.9 378.6 18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9-85.7-42.9-104.3-52.2c-15.8-7.9-22.2-27.1-14.3-42.9l40-80C48.8 22.8 59.9 16 72 16h120c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zm-204.6-39.5 85.9 42.9L90.9 485.5C79 509.2 50.2 518.8 26.5 507s-33.3-40.8-21.4-64.5l102.5-205.1z"/></svg></a>');
|
||||
$brandDiv->appendChild($brandHtml);
|
||||
$body->appendChild($brandDiv);
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ class URLAnalyzerProcess extends URLAnalyzerBase
|
|||
$body = $xpath->query('//body')->item(0);
|
||||
if ($body) {
|
||||
$debugDiv = $dom->createElement('div');
|
||||
$debugDiv->setAttribute('style', 'position: fixed; bottom: 1rem; right: 1rem; max-width: 400px; padding: 1rem; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); border: 1px solid #e5e7eb; border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: auto; max-height: 80vh; z-index: 9999; font-family: monospace; font-size: 13px; line-height: 1.4;');
|
||||
$debugDiv->setAttribute('style', 'position: fixed; bottom: 1rem; right: 1rem; max-width: 400px; padding: 1rem; color: #000; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); border: 1px solid #e5e7eb; border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: auto; max-height: 80vh; z-index: 9999; font-family: monospace; font-size: 13px; line-height: 1.4;');
|
||||
|
||||
if (empty($this->activatedRules)) {
|
||||
$ruleElement = $dom->createElement('div');
|
||||
|
|
|
@ -4,8 +4,8 @@ return [
|
|||
'walls_destroyed' => 'стены разрушены!',
|
||||
'url_placeholder' => 'Введите URL (например, https://example.com)',
|
||||
'analyze_button' => 'Анализировать',
|
||||
'nav_integration' => 'Integrations',
|
||||
'nav_extension' => 'Install',
|
||||
'nav_integration' => 'Интеграции',
|
||||
'nav_extension' => 'Установить',
|
||||
'bookmarklet_title' => 'Добавить в закладки',
|
||||
'bookmarklet_description' => 'Перетащите кнопку ниже на панель закладок, чтобы быстро получить доступ к на любой странице:',
|
||||
'open_in' => 'Открыть в {site_name}',
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
required
|
||||
pattern="https?://.+"
|
||||
title="<?php echo \Inc\Language::getMessage('INVALID_URL')['message']; ?>">
|
||||
<span class="paste" id="paste"><span class="icon icon--paste"></span></span>
|
||||
</div>
|
||||
<button type="submit" alt="<?php echo \Inc\Language::get('analyze_button'); ?>">
|
||||
<span class="icon icon--marreta"></span>
|
||||
|
|
211
bin/cleanup
Normal file
211
bin/cleanup
Normal file
|
@ -0,0 +1,211 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Cache Cleanup Script
|
||||
*
|
||||
* Removes *.gz files from the cache directory that are older than the number
|
||||
* of days specified in the CLEANUP_DAYS environment variable.
|
||||
* If CLEANUP_DAYS is not set, no files will be cleaned.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../app/vendor/autoload.php';
|
||||
|
||||
use League\CLImate\CLImate;
|
||||
use Dotenv\Dotenv;
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
$climate = new CLImate();
|
||||
$climate->bold()->out('Cache Cleanup Tool');
|
||||
$climate->br();
|
||||
|
||||
$cleanupDays = 0;
|
||||
|
||||
try {
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/../app');
|
||||
$dotenv->load();
|
||||
$climate->out('Environment variables loaded');
|
||||
$cleanupDays = $_ENV['CLEANUP_DAYS'];
|
||||
} catch (\Exception $e) {
|
||||
$climate->yellow()->out('Warning: ' . $e->getMessage());
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (!defined('CACHE_DIR')) {
|
||||
define('CACHE_DIR', __DIR__ . '/../app/cache');
|
||||
}
|
||||
|
||||
if ($cleanupDays == 0) {
|
||||
$climate->yellow()->out('CLEANUP_DAYS variable not set or 0. No files will be cleaned.');
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$cleanupDays = (int)$cleanupDays;
|
||||
if ($cleanupDays <= 0) {
|
||||
$climate->red()->out('CLEANUP_DAYS must be a positive integer. No files will be cleaned.');
|
||||
exit(1);
|
||||
};
|
||||
|
||||
// Calculate the cutoff timestamp
|
||||
$cutoffTime = time() - ($cleanupDays * 86400);
|
||||
|
||||
// Check if S3 cache is enabled
|
||||
$s3CacheEnabled = isset($_ENV['S3_CACHE_ENABLED']) && filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if ($s3CacheEnabled) {
|
||||
// Clean S3 cache
|
||||
cleanS3Cache($climate, $cutoffTime, $cleanupDays);
|
||||
} else {
|
||||
// Clean local disk cache
|
||||
cleanDiskCache($climate, $cutoffTime, $cleanupDays);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache files from S3 bucket
|
||||
*
|
||||
* @param CLImate $climate CLImate instance for output
|
||||
* @param int $cutoffTime Timestamp to use as cutoff for file age
|
||||
* @param int $cleanupDays Number of days to keep files
|
||||
*/
|
||||
function cleanS3Cache($climate, $cutoffTime, $cleanupDays) {
|
||||
$requiredVars = ['S3_ACCESS_KEY', 'S3_SECRET_KEY', 'S3_BUCKET'];
|
||||
foreach ($requiredVars as $var) {
|
||||
if (!isset($_ENV[$var]) || empty($_ENV[$var])) {
|
||||
$climate->red()->out("$var environment variable is required for S3 cache cleaning.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$climate->out("S3 cache enabled. Cleaning S3 cache files older than {$cleanupDays} days...");
|
||||
|
||||
$clientConfig = [
|
||||
'version' => 'latest',
|
||||
'region' => $_ENV['S3_REGION'] ?? 'us-east-1',
|
||||
'credentials' => [
|
||||
'key' => $_ENV['S3_ACCESS_KEY'],
|
||||
'secret' => $_ENV['S3_SECRET_KEY'],
|
||||
]
|
||||
];
|
||||
|
||||
if (!empty($_ENV['S3_ENDPOINT'])) {
|
||||
$clientConfig['endpoint'] = $_ENV['S3_ENDPOINT'];
|
||||
$clientConfig['use_path_style_endpoint'] = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$s3Client = new S3Client($clientConfig);
|
||||
$bucket = $_ENV['S3_BUCKET'];
|
||||
$prefix = $_ENV['S3_FOLDER'] ?? 'cache/';
|
||||
|
||||
$climate->out("Listing objects in bucket: {$bucket} with prefix: {$prefix}");
|
||||
|
||||
$objects = [];
|
||||
$marker = null;
|
||||
|
||||
do {
|
||||
$params = [
|
||||
'Bucket' => $bucket,
|
||||
'Prefix' => $prefix,
|
||||
'MaxKeys' => 1000
|
||||
];
|
||||
|
||||
if ($marker) {
|
||||
$params['Marker'] = $marker;
|
||||
}
|
||||
|
||||
$result = $s3Client->listObjects($params);
|
||||
|
||||
if (isset($result['Contents'])) {
|
||||
foreach ($result['Contents'] as $object) {
|
||||
if (substr($object['Key'], -3) === '.gz') {
|
||||
$objects[] = $object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$marker = $result['NextMarker'] ?? ($result['IsTruncated'] ? end($result['Contents'])['Key'] : null);
|
||||
} while ($marker);
|
||||
|
||||
$totalObjects = count($objects);
|
||||
$climate->out("Found {$totalObjects} .gz objects in S3 bucket.");
|
||||
|
||||
if ($totalObjects === 0) {
|
||||
$climate->out('No .gz objects found in S3 bucket.');
|
||||
return;
|
||||
}
|
||||
|
||||
$progress = $climate->progress()->total($totalObjects);
|
||||
$deletedObjects = 0;
|
||||
|
||||
foreach ($objects as $index => $object) {
|
||||
$progress->current($index + 1);
|
||||
$lastModified = strtotime($object['LastModified']);
|
||||
|
||||
if ($lastModified < $cutoffTime) {
|
||||
try {
|
||||
$s3Client->deleteObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $object['Key']
|
||||
]);
|
||||
$deletedObjects++;
|
||||
} catch (AwsException $e) {
|
||||
$climate->red()->out("Failed to delete: " . $object['Key'] . " - " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$climate->br();
|
||||
$climate->green()->out("S3 cleanup complete: {$deletedObjects} objects deleted.");
|
||||
} catch (AwsException $e) {
|
||||
$climate->red()->out("AWS Error: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean cache files from local disk
|
||||
*
|
||||
* @param CLImate $climate CLImate instance for output
|
||||
* @param int $cutoffTime Timestamp to use as cutoff for file age
|
||||
* @param int $cleanupDays Number of days to keep files
|
||||
*/
|
||||
function cleanDiskCache($climate, $cutoffTime, $cleanupDays) {
|
||||
$cacheDir = CACHE_DIR;
|
||||
|
||||
$climate->out("Cleaning cache files older than {$cleanupDays} days from: {$cacheDir}");
|
||||
|
||||
if (!is_dir($cacheDir)) {
|
||||
$climate->red()->out("Cache directory not found: {$cacheDir}");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$gzFiles = glob($cacheDir . '/*.gz');
|
||||
$totalFiles = count($gzFiles);
|
||||
$deletedFiles = 0;
|
||||
|
||||
if ($totalFiles === 0) {
|
||||
$climate->out('No .gz files found in cache directory.');
|
||||
return;
|
||||
}
|
||||
|
||||
$climate->out("Found {$totalFiles} .gz files in cache directory.");
|
||||
|
||||
$progress = $climate->progress()->total($totalFiles);
|
||||
|
||||
foreach ($gzFiles as $index => $file) {
|
||||
$progress->current($index + 1);
|
||||
$fileTime = filemtime($file);
|
||||
|
||||
if ($fileTime < $cutoffTime) {
|
||||
if (unlink($file)) {
|
||||
$deletedFiles++;
|
||||
} else {
|
||||
$climate->red()->out("Failed to delete: " . basename($file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$climate->br();
|
||||
$climate->green()->out("Disk cleanup complete: {$deletedFiles} files deleted.");
|
||||
}
|
|
@ -31,6 +31,12 @@ server {
|
|||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
if ($uri ~ ^/p/) {
|
||||
rewrite ^/p/(.*)$ /index.php?url=$1 last;
|
||||
}
|
||||
if ($uri ~ ^/api/) {
|
||||
rewrite ^/api/(.*)$ /index.php?url=$1 last;
|
||||
}
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
services:
|
||||
# Marreta
|
||||
marreta:
|
||||
container_name: marreta
|
||||
image: ghcr.io/manualdousuario/marreta:latest
|
||||
|
@ -11,11 +12,27 @@ services:
|
|||
- SITE_NAME=${SITE_NAME:-}
|
||||
- SITE_DESCRIPTION=${SITE_DESCRIPTION:-}
|
||||
- SITE_URL=${SITE_URL:-https://marreta.localhost}
|
||||
- DNS_SERVERS=${DNS_SERVER:-}
|
||||
- LANGUAGE=${LANGUAGE:-}
|
||||
- DNS_SERVERS=${DNS_SERVER:-1.1.1.1,8.8.8.8}
|
||||
- LANGUAGE=${LANGUAGE:-pt-br}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-WARNING}
|
||||
- SELENIUM_HOST=${SELENIUM_HOST:-selenium-hub:4444}
|
||||
- CLEANUP_DAYS=7 # Optional
|
||||
restart: unless-stopped
|
||||
# Selenium
|
||||
selenium-hub:
|
||||
image: selenium/hub:4.27.0-20241204
|
||||
container_name: selenium-hub
|
||||
environment:
|
||||
- SE_ENABLE_TRACING=false
|
||||
- GRID_MAX_SESSION=10
|
||||
- GRID_BROWSER_TIMEOUT=10
|
||||
- GRID_TIMEOUT=10
|
||||
depends_on:
|
||||
- marreta
|
||||
ports:
|
||||
- 4442:4442
|
||||
- 4443:4443
|
||||
- 4444:4444
|
||||
selenium-chromium:
|
||||
container_name: selenium-chromium
|
||||
image: selenium/node-chromium:4.27.0-20241204
|
||||
|
@ -43,18 +60,4 @@ services:
|
|||
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
|
||||
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
|
||||
depends_on:
|
||||
- selenium-hub
|
||||
selenium-hub:
|
||||
image: selenium/hub:4.27.0-20241204
|
||||
container_name: selenium-hub
|
||||
environment:
|
||||
- SE_ENABLE_TRACING=false
|
||||
- GRID_MAX_SESSION=10
|
||||
- GRID_BROWSER_TIMEOUT=10
|
||||
- GRID_TIMEOUT=10
|
||||
depends_on:
|
||||
- marreta
|
||||
ports:
|
||||
- 4442:4442
|
||||
- 4443:4443
|
||||
- 4444:4444
|
||||
- selenium-hub
|
|
@ -54,6 +54,7 @@ log_success "Environment variables configured"
|
|||
log_info "Adjusting directory permissions..."
|
||||
|
||||
mkdir -p /app/cache /app/logs # Ensures directories exist
|
||||
mkdir -p /app/cache/database
|
||||
chown -R www-data:www-data /app/cache /app/logs
|
||||
chmod -R 775 /app/cache /app/logs
|
||||
|
||||
|
@ -108,6 +109,11 @@ nginx -g "daemon off;" &
|
|||
sleep 3
|
||||
check_nginx
|
||||
|
||||
# Starting Cron
|
||||
log_info "Starting Cron..."
|
||||
service cron restart
|
||||
log_success "Cron started"
|
||||
|
||||
echo -e "\n${GREEN}=== Marreta initialized ===${NC}\n"
|
||||
|
||||
# Wait for any process to exit
|
||||
|
|
Loading…
Add table
Reference in a new issue