From 34ff3a2693c5ace701f42fd043c5c537b778c581 Mon Sep 17 00:00:00 2001 From: infra-bot Date: Thu, 27 Aug 2026 16:14:23 +0300 Subject: [PATCH] fix .gitignore dir negation so config/ and scripts/ are tracked --- .gitattributes | 5 ++ .gitignore | 2 + config/bitrix-app/dbconn.php.example | 23 +++++++++ config/bitrix-app/settings_extra.php.example | 40 +++++++++++++++ config/cron/bitrix | 13 +++++ config/crowdsec/acquis.yaml | 11 ++++ config/fail2ban/jail.local | 24 +++++++++ config/logrotate/bitrix | 35 +++++++++++++ config/memcached/memcached-cache.service | 13 +++++ config/memcached/memcached-sessions.service | 13 +++++ config/mysql/99-bitrix.cnf | 26 ++++++++++ config/nginx/conf.d/maps.conf | 26 ++++++++++ config/nginx/nginx.conf | 54 ++++++++++++++++++++ config/nginx/sites-available/bitrix.conf | 49 ++++++++++++++++++ config/nginx/snippets/fastcgi-bitrix.conf | 10 ++++ config/php/95-bitrix.ini | 26 ++++++++++ config/php/fpm-pool-bitrix.conf | 27 ++++++++++ scripts/backup.sh | 34 ++++++++++++ scripts/fix-rights.sh | 29 +++++++++++ 19 files changed, 460 insertions(+) create mode 100644 .gitattributes create mode 100644 config/bitrix-app/dbconn.php.example create mode 100644 config/bitrix-app/settings_extra.php.example create mode 100644 config/cron/bitrix create mode 100644 config/crowdsec/acquis.yaml create mode 100644 config/fail2ban/jail.local create mode 100644 config/logrotate/bitrix create mode 100644 config/memcached/memcached-cache.service create mode 100644 config/memcached/memcached-sessions.service create mode 100644 config/mysql/99-bitrix.cnf create mode 100644 config/nginx/conf.d/maps.conf create mode 100644 config/nginx/nginx.conf create mode 100644 config/nginx/sites-available/bitrix.conf create mode 100644 config/nginx/snippets/fastcgi-bitrix.conf create mode 100644 config/php/95-bitrix.ini create mode 100644 config/php/fpm-pool-bitrix.conf create mode 100644 scripts/backup.sh create mode 100644 scripts/fix-rights.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d4c9123 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto eol=lf +*.sh binary +*.png binary +*.jpg binary +*.gz binary diff --git a/.gitignore b/.gitignore index bc90d36..d75f0f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ * +!*/ !.gitignore +!.gitattributes !README.md !install.sh !scripts/** diff --git a/config/bitrix-app/dbconn.php.example b/config/bitrix-app/dbconn.php.example new file mode 100644 index 0000000..3796b79 --- /dev/null +++ b/config/bitrix-app/dbconn.php.example @@ -0,0 +1,23 @@ + memcached instance on :11211 with igbinary serializer. +// Sessions are switched in .settings.php 'session' section (host memcached-sessions-svc note below). + +return array( + 'cache' => array( + 'value' => array( + 'type' => 'memcached', + 'memcached' => array( + 'host' => '127.0.0.1', + 'port' => 11211, + ), + // igbinary shrinks cache ~50% and is faster; php8.4-igbinary installed. + 'serializer' => \Memcached::SERIALIZER_IGBINARY, // = 2 + // Under load only one process regenerates cache, others get stale copy: + 'use_lock' => true, // requires Bitrix main >= 24.0.0 + 'sid' => $_SERVER["DOCUMENT_ROOT"] . "#01", + ), + 'readonly' => false, + ), +); + +/* Sessions (put into .settings.php 'session' value section): + 'session' => array( + 'value' => array( + 'mode' => 'separated', + 'lifetime' => 14400, + 'handlers' => array( + 'kernel' => 'encrypted_cookies', + 'general' => array( + 'type' => 'memcache', + 'host' => '127.0.0.1', + 'port' => 11212, + ), + ), + ), + 'readonly' => true, + ), +*/ diff --git a/config/cron/bitrix b/config/cron/bitrix new file mode 100644 index 0000000..e7c65e5 --- /dev/null +++ b/config/cron/bitrix @@ -0,0 +1,13 @@ +SHELL=/bin/bash +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +MAILTO="" + +# Bitrix agents: CLI php once per minute (BX_CRONTAB_SUPPORT must be true in dbconn.php) +* * * * * bitrix /usr/bin/php -d memory_limit=1024M -f __SITE__/bitrix/modules/main/tools/cron_events.php > /dev/null 2>&1 + +# 1C exchange jobs (adjust script paths after site deploy if different) +*/5 * * * * bitrix flock -n /tmp/exchange-orders.lock /usr/bin/php -f __SITE__/ajax/exchange-orders.php > /dev/null 2>&1 +*/5 * * * * bitrix flock -n /tmp/exchange-import-orders.lock /usr/bin/php -f __SITE__/ajax/exchange-import-orders.php > /dev/null 2>&1 + +# Nightly backup at 03:15 +15 3 * * * root __BACKUP_SCRIPT__ >> /var/log/bitrix-backup.log 2>&1 diff --git a/config/crowdsec/acquis.yaml b/config/crowdsec/acquis.yaml new file mode 100644 index 0000000..4b2f690 --- /dev/null +++ b/config/crowdsec/acquis.yaml @@ -0,0 +1,11 @@ +filenames: + - /var/log/nginx/access.log + - /var/log/nginx/error.log +labels: + type: nginx +--- +source: journalctl +journalctl_filter: + - _SYSTEMD_UNIT=sshd.service +labels: + type: syslog diff --git a/config/fail2ban/jail.local b/config/fail2ban/jail.local new file mode 100644 index 0000000..7794a32 --- /dev/null +++ b/config/fail2ban/jail.local @@ -0,0 +1,24 @@ +[DEFAULT] +bantime = 30m +findtime = 10m +maxretry = 5 +backend = systemd + +# --- SSH brute force --- +[sshd] +enabled = true +maxretry = 3 +bantime = 1h + +# --- nginx basic-auth failures (from error.log) --- +[nginx-http-auth] +enabled = true +logpath = /var/log/nginx/error.log +maxretry = 5 + +# --- enable later if bot noise grows (stock filter, matches probe URIs) --- +#[nginx-botsearch] +#enabled = true +#logpath = /var/log/nginx/access.log +#port = http,https +#maxretry = 20 diff --git a/config/logrotate/bitrix b/config/logrotate/bitrix new file mode 100644 index 0000000..83175ac --- /dev/null +++ b/config/logrotate/bitrix @@ -0,0 +1,35 @@ +/var/log/nginx/*.log { + daily + rotate 14 + minsize 100M + missingok + notifempty + compress + delaycompress + dateext + sharedscripts + postrotate + systemctl reload nginx >/dev/null 2>&1 || true + endscript +} + +/var/log/php/*.log { + weekly + rotate 12 + size 50M + missingok + notifempty + compress + delaycompress + create 0644 www-data www-data + postrotate + test ! -f /run/php/php8.4-fpm.pid || kill -USR1 "$(cat /run/php/php8.4-fpm.pid)" 2>/dev/null || true + endscript +} + +/var/log/bitrix-backup.log { + monthly + rotate 6 + missingok + compress +} diff --git a/config/memcached/memcached-cache.service b/config/memcached/memcached-cache.service new file mode 100644 index 0000000..8bf6d24 --- /dev/null +++ b/config/memcached/memcached-cache.service @@ -0,0 +1,13 @@ +[Unit] +Description=memcached - bitrix managed cache instance (port 11211) +After=network.target + +[Service] +Type=simple +User=memcached +ExecStart=/usr/bin/memcached -u memcached -m __CACHE_MB__ -p 11211 -l 127.0.0.1 -U 0 -t 4 -P /run/memcached-cache.pid +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/config/memcached/memcached-sessions.service b/config/memcached/memcached-sessions.service new file mode 100644 index 0000000..79b982d --- /dev/null +++ b/config/memcached/memcached-sessions.service @@ -0,0 +1,13 @@ +[Unit] +Description=memcached - php sessions instance (port 11212) +After=network.target + +[Service] +Type=simple +User=memcached +ExecStart=/usr/bin/memcached -u memcached -m __SESSIONS_MB__ -p 11212 -l 127.0.0.1 -U 0 -t 2 -P /run/memcached-sessions.pid +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/config/mysql/99-bitrix.cnf b/config/mysql/99-bitrix.cnf new file mode 100644 index 0000000..df479cf --- /dev/null +++ b/config/mysql/99-bitrix.cnf @@ -0,0 +1,26 @@ +# Percona MySQL tuned for Bitrix on a dedicated ~12GB VPS. +# TCP bound to loopback only; production access is via unix socket ('localhost'). +[mysqld] +bind-address = 127.0.0.1 +mysqlx-bind-address = 127.0.0.1 + +innodb_buffer_pool_size = 4G +innodb_flush_method = O_DIRECT +innodb_flush_log_at_trx_commit = 1 + +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci +skip_name_resolve = 1 + +max_connections = 200 +thread_cache_size = 64 +tmp_table_size = 64M +max_heap_table_size = 64M +table_open_cache = 4000 +open_files_limit = 65535 + +slow_query_log = 1 +slow_query_log_file = /var/lib/mysql/mysql-slow.log +long_query_time = 1 + +performance_schema = ON diff --git a/config/nginx/conf.d/maps.conf b/config/nginx/conf.d/maps.conf new file mode 100644 index 0000000..f9eed69 --- /dev/null +++ b/config/nginx/conf.d/maps.conf @@ -0,0 +1,26 @@ +# Bot detection maps. Empty deny lists = fully inert. +# Add an entry only for agents that can never be a real visitor. +# Rejected bots ($bad_agent) are a HINT passed to PHP via fastcgi_param +# (add `fastcgi_param BITRIX_BOT $bad_agent;` in the snippet if you use it); +# denied ones never reach PHP. + +map $http_user_agent $bad_agent { + default 0; + #~*(semrush|ahrefs|mj12bot) 1; +} + +map $http_user_agent $denied_agent { + default 0; + ~*YandexRenderResourcesBot 1; # known page-render crawler storm + #~*(other-bot-pattern) 1; +} + +map $remote_addr $denied_ip { + default 0; + #203.0.113.66 1; + #198.51.100.0/24 1; +} + +map $http_referer $bad_referer { + default 0; +} diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf new file mode 100644 index 0000000..be98175 --- /dev/null +++ b/config/nginx/nginx.conf @@ -0,0 +1,54 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +error_log /var/log/nginx/error.log warn; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 2048; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Standard combined-style format: CrowdSec parses it out of the box. + log_format main '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + server_tokens off; + types_hash_max_size 2048; + + client_max_body_size 100m; + client_body_buffer_size 128k; + client_header_timeout 60; + client_body_timeout 60; + send_timeout 60; + + open_file_cache max=10000 inactive=30s; + open_file_cache_valid 60s; + open_file_cache_min_uses 2; + open_file_cache_errors on; + + gzip on; + gzip_comp_level 5; + gzip_min_length 1024; + gzip_vary on; + gzip_types text/plain text/css text/xml application/json application/javascript + application/x-javascript image/svg+xml application/rss+xml text/xml+rss; + + reset_timedout_connection on; + + # Zone defined here, enforced per-location where needed: + limit_req_zone $binary_remote_addr zone=main_limit:10m rate=10r/s; + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/config/nginx/sites-available/bitrix.conf b/config/nginx/sites-available/bitrix.conf new file mode 100644 index 0000000..e9ee367 --- /dev/null +++ b/config/nginx/sites-available/bitrix.conf @@ -0,0 +1,49 @@ +server { + listen 80; + listen [::]:80; + server_name __DOMAIN__ www.__DOMAIN__; + + root /home/bitrix/www; + index index.php index.html; + charset utf-8; + + # ---- hard denies (order matters: regex locations match top-down) ---- + location ~ /\. { deny all; } # dotfiles (.htaccess, .git...) + location ~* ^/(bitrix|local)/modules/.*\.php$ { deny all; } # no direct module execution + location ~* ^/upload/.*\.(php|phar|phtml)$ { deny all; } # uploads must never execute + + # ---- static assets straight from disk, no PHP ---- + location ~* ^.+\.(jpg|jpeg|gif|png|webp|avif|svg|ico|css|js|woff|woff2|ttf|eot|otf|mp3|mp4|m4a|ogg|pdf|zip|csv|xlsx?)$ { + expires 7d; + add_header Cache-Control "public"; + access_log off; + try_files $uri =404; + } + + # ---- optional rate-limit example zone (uncomment when needed) ---- + #location /search { + # limit_req zone=main_limit burst=20 nodelay; + # rewrite ^(.*)$ /search/index.php last; + #} + + # ---- Bitrix SEF routing (official pattern) ---- + location / { + index index.php; + if (!-e $request_filename) { + rewrite ^(.*)$ /bitrix/urlrewrite.php last; + } + } + + # ---- PHP execution ---- + location ~ \.php$ { + include snippets/fastcgi-bitrix.conf; + } + + # security headers (TLS versions added by certbot block later) + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options SAMEORIGIN always; + add_header Referrer-Policy strict-origin-when-cross-origin always; +} + +# certbot --nginx will extend this file with the :443 server block, +# redirect 80->443 and ACME challenge handling automatically. diff --git a/config/nginx/snippets/fastcgi-bitrix.conf b/config/nginx/snippets/fastcgi-bitrix.conf new file mode 100644 index 0000000..30f9d0c --- /dev/null +++ b/config/nginx/snippets/fastcgi-bitrix.conf @@ -0,0 +1,10 @@ +fastcgi_pass unix:/run/php/bitrix-fpm.sock; +fastcgi_index index.php; +include fastcgi_params; +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param HTTPS $https if_not_empty; +fastcgi_read_timeout 300; +fastcgi_buffer_size 64k; +fastcgi_buffers 16 32k; +fastcgi_hide_header X-Powered-By; diff --git a/config/php/95-bitrix.ini b/config/php/95-bitrix.ini new file mode 100644 index 0000000..e426d05 --- /dev/null +++ b/config/php/95-bitrix.ini @@ -0,0 +1,26 @@ +; Bitrix runtime overrides, shared by fpm and cli. +memory_limit = 512M +max_execution_time = 300 +default_socket_timeout = 60 +date.timezone = Asia/Yekaterinburg +expose_php = Off + +realpath_cache_size = 4096K +realpath_cache_ttl = 3600 + +upload_max_filesize = 100M +post_max_size = 120M +max_file_uploads = 50 + +[opcache] +opcache.enable = 1 +opcache.enable_cli = 1 +opcache.memory_consumption = 256 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 24000 +opcache.validate_timestamps = 1 +opcache.revalidate_freq = 60 + +[apcu] +apc.enabled = 1 +apc.shm_size = 64M diff --git a/config/php/fpm-pool-bitrix.conf b/config/php/fpm-pool-bitrix.conf new file mode 100644 index 0000000..02372f0 --- /dev/null +++ b/config/php/fpm-pool-bitrix.conf @@ -0,0 +1,27 @@ +; php-fpm pool for Bitrix (native, no docker) +[bitrix] +user = bitrix +group = bitrix +listen = /run/php/bitrix-fpm.sock +listen.owner = root +listen.group = www-data +listen.mode = 0660 + +pm = dynamic +pm.max_children = __MAX_CHILDREN__ ; ~150MB each: RAM - mysql(~5G) - memcached(1.2G) - crowdsec(0.4G) +pm.start_servers = 5 +pm.min_spare_servers = 5 +pm.max_spare_servers = 15 ; workers die under low load -> memory stays bounded +pm.max_requests = 500 ; recycle worker after 500 requests (leak guard) + +request_terminate_timeout = 300s +slowlog = /var/log/php/bitrix-slow.log +request_slowlog_timeout = 5s +catch_workers_output = yes + +php_admin_value[error_log] = /var/log/php/bitrix-error.log +php_admin_flag[log_errors] = on +php_admin_value[memory_limit] = 512M + +env[HOSTNAME] = $HOSTNAME +env[TMPDIR] = /tmp/php/upload diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..0ed51e6 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Nightly backup: mysqldump (gz) + code tarball (upload excluded). +# Retention: KEEP_DAYS. Restore notes in README. +set -euo pipefail + +KEEP_DAYS=14 +DB_NAME="${DB_NAME:-sitemanager}" +BACKUP_DIR="${BACKUP_DIR:-/var/backups/bitrix}" +SITE_DIR="${SITE_DIR:-/home/bitrix/www}" + +TS="$(date +%F_%H%M)" +mkdir -p "$BACKUP_DIR/db" "$BACKUP_DIR/code" + +echo "[$(date -Is)] dumping database $DB_NAME" +mysqldump --single-transaction --quick --routines --triggers --events \ + --databases "$DB_NAME" \ + | gzip -6 > "$BACKUP_DIR/db/${TS}_${DB_NAME}.sql.gz" + +echo "[$(date -Is)] archiving code tree (without upload/caches)" +tar czf "$BACKUP_DIR/code/${TS}_site.tar.gz" \ + -C "$(dirname "$SITE_DIR")" \ + --exclude='www/upload' \ + --exclude='www/bitrix/cache' \ + --exclude='www/bitrix/managed_cache' \ + --exclude='www/bitrix/stack_cache' \ + --exclude='www/bitrix/tmp' \ + --exclude='www/local/php_interface/include/.backup*' \ + "$(basename "$SITE_DIR")" + +echo "[$(date -Is)] pruning older than $KEEP_DAYS days" +find "$BACKUP_DIR/db" -name '*.sql.gz' -mtime +"$KEEP_DAYS" -delete +find "$BACKUP_DIR/code" -name '*.tar.gz' -mtime +$((KEEP_DAYS * 3)) -delete + +echo "[$(date -Is)] done: $(du -sh "$BACKUP_DIR" | cut -f1) total" diff --git a/scripts/fix-rights.sh b/scripts/fix-rights.sh new file mode 100644 index 0000000..5036dde --- /dev/null +++ b/scripts/fix-rights.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Fix ownership/permissions for the Bitrix webroot after deploy or update. +set -euo pipefail + +SITE_DIR="${1:-/home/bitrix/www}" +WEB_USER="bitrix" +[[ -d "$SITE_DIR" ]] || { echo "no $SITE_DIR"; exit 1; } + +echo "==> chown -R $WEB_USER:$WEB_USER on writable trees" +for d in upload local bitrix/cache bitrix/managed_cache bitrix/stack_cache \ + bitrix/tmp bitrix/backup bitrix/updates .; do + [[ -e "$SITE_DIR/$d" ]] && chown -R "$WEB_USER:$WEB_USER" "$SITE_DIR/$d" +done + +echo "==> chmod core skeleton" +find "$SITE_DIR" -type d \( -path "$SITE_DIR/bitrix/modules" -o -path "$SITE_DIR/bitrix/themes" \ + -o -path "$SITE_DIR/upload" \) -prune -exec chmod {} 755 \; +chmod 755 "$SITE_DIR" + +echo "==> ensure runtime dirs exist and are writable" +mkdir -p "$SITE_DIR"/upload/{,1c_exchange,import_files} \ + "$SITE_DIR"/bitrix/{cache,managed_cache,stack_cache,tmp} \ + /tmp/php_sessions/www /tmp/php/upload +chown -R "$WEB_USER:$WEB_USER" "$SITE_DIR"/upload \ + "$SITE_DIR"/bitrix/{cache,managed_cache,stack_cache,tmp} \ + /tmp/php_sessions/www /tmp/php/upload +chmod -R u=rwX,g=rX,o= "$SITE_DIR"/bitrix/tmp 2>/dev/null || true + +echo "==> done. sessions dir is 700/$WEB_USER."