From 0f563dc0308801c31cd37389f99d8f4788e956f7 Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Wed, 19 Nov 2025 18:14:14 +0300 Subject: [PATCH 01/10] Add .dockerignore --- .dockerignore | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..096960c3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +db +cert +*.log +Dockerfile +docker-compose.yml +.tmp +.idea +.vscode \ No newline at end of file From 4b9e34aea6e7ae7410b7e0ebe51c3b1a972bf0c7 Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Wed, 19 Nov 2025 18:52:53 +0300 Subject: [PATCH 02/10] Fixed errors in x-ui.sh --- x-ui.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-ui.sh b/x-ui.sh index 4b0989eb..e1648e51 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -44,7 +44,7 @@ iplimit_log_path="${log_folder}/3xipl.log" iplimit_banned_log_path="${log_folder}/3xipl-banned.log" confirm() { - if [[ $# > 1 ]]; then + if [[ $# -gt 1 ]]; then echo && read -rp "$1 [Default $2]: " temp if [[ "${temp}" == "" ]]; then temp=$2 @@ -2022,7 +2022,7 @@ show_menu() { esac } -if [[ $# > 0 ]]; then +if [[ $# -gt 0 ]]; then case $1 in "start") check_install 0 && start 0 From 3a2988c06812605fda074f240a284ee6c7202c8d Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Sun, 23 Nov 2025 16:43:17 +0300 Subject: [PATCH 03/10] DRY: unified sh script for geodata update --- x-ui.sh | 42 +++++++++--------- xray-tools.sh | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 xray-tools.sh diff --git a/x-ui.sh b/x-ui.sh index e1648e51..d9112b50 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -6,6 +6,8 @@ blue='\033[0;34m' yellow='\033[0;33m' plain='\033[0m' +source xray-tools.sh + #Add some basic function here function LOGD() { echo -e "${yellow}[DEG] $* ${plain}" @@ -863,26 +865,26 @@ delete_ports() { fi } -update_all_geofiles() { - update_main_geofiles - update_ir_geofiles - update_ru_geofiles -} - -update_main_geofiles() { - wget -O geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat - wget -O geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat -} - -update_ir_geofiles() { - wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat - wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat -} - -update_ru_geofiles() { - wget -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat - wget -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat -} +#update_all_geofiles() { +# update_main_geofiles +# update_ir_geofiles +# update_ru_geofiles +#} +# +#update_main_geofiles() { +# wget -O geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat +# wget -O geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat +#} +# +#update_ir_geofiles() { +# wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat +# wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat +#} +# +#update_ru_geofiles() { +# wget -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat +# wget -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat +#} update_geo() { echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)" diff --git a/xray-tools.sh b/xray-tools.sh new file mode 100644 index 00000000..c28c8537 --- /dev/null +++ b/xray-tools.sh @@ -0,0 +1,117 @@ +#!/bin/sh + +update_all_geofiles() { + update_main_geofiles + update_ir_geofiles + update_ru_geofiles +} + +update_main_geofiles() { + wget -O geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat + wget -O geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat +} + +update_ir_geofiles() { + wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat + wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat +} + +update_ru_geofiles() { + wget -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat + wget -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat +} + +update_geodata_in_docker() { + WORKDIR="$1" + OLD_DIR=$(pwd) + trap 'cd "$OLD_DIR"' EXIT + + echo "[$(date)] Running update_geodata" + + if [ ! -d "$WORKDIR" ]; then + mkdir -p "$WORKDIR" + fi + cd "$WORKDIR" + + update_all_geofiles + echo "[$(date)] All geo files have been updated successfully!" +} + + +update_xray_core() { + TARGETARCH="$1" + WORKDIR="$2" + XRAY_VERSION="$3" + + OLD_DIR=$(pwd) + trap 'cd "$OLD_DIR"' EXIT + + echo "[$(date)] Running update_xray_core" + + case $1 in + amd64) + ARCH="64" + FNAME="amd64" + ;; + i386) + ARCH="32" + FNAME="i386" + ;; + armv8 | arm64 | aarch64) + ARCH="arm64-v8a" + FNAME="arm64" + ;; + armv7 | arm | arm32) + ARCH="arm32-v7a" + FNAME="arm32" + ;; + armv6) + ARCH="arm32-v6" + FNAME="armv6" + ;; + *) + ARCH="64" + FNAME="amd64" + ;; + esac + + if [ ! -d "$WORKDIR" ]; then + mkdir -p "$WORKDIR" + fi + cd "$WORKDIR" + + wget -q "https://github.com/XTLS/Xray-core/releases/download/${XRAY_VERSION}/Xray-linux-${ARCH}.zip" + unzip "Xray-linux-${ARCH}.zip" -d ./xray-unzip + cp ./xray-unzip/xray ./"xray-linux-${FNAME}" + rm -r xray-unzip + rm "Xray-linux-${ARCH}.zip" + } + +# --- dispatcher: вызываем функции по имени ТОЛЬКО если скрипт запущен как файл --- +# Предполагаем, что файл называется xray-updates.sh +if [ "${0##*/}" = "xray-tools.sh" ]; then + cmd="$1" + shift || true + + case "$cmd" in + update_xray_core) + # args: TARGETARCH WORKDIR XRAY_VERSION + update_xray_core "$@" + ;; + update_geodata_in_docker) + # args: WORKDIR + update_geodata_in_docker "$@" + ;; + ""|help|-h|--help) + echo "Usage:" + echo " $0 update_xray_core TARGETARCH WORKDIR XRAY_VERSION" + echo " $0 update_geodata_in_docker WORKDIR" + exit 1 + ;; + *) + echo "Unknown command: $cmd" >&2 + echo "Try: $0 help" >&2 + exit 1 + ;; + esac +fi \ No newline at end of file From e1058b1eafc413b4a953bd6a5c34e0a39c8051d5 Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Sun, 23 Nov 2025 17:16:16 +0300 Subject: [PATCH 04/10] Integrated unified sh scripts into docker build system + some cache updates --- .dockerignore | 4 +++- DockerEntrypoint.sh | 2 +- Dockerfile | 35 +++++++++++++++++++++++++++-------- docker-compose.yml | 8 +++++--- xray-tools.sh | 10 +++++----- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/.dockerignore b/.dockerignore index 096960c3..99bb78cc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,6 @@ Dockerfile docker-compose.yml .tmp .idea -.vscode \ No newline at end of file +.vscode +LICENSE +README.* \ No newline at end of file diff --git a/DockerEntrypoint.sh b/DockerEntrypoint.sh index 7511d2ea..277026b8 100644 --- a/DockerEntrypoint.sh +++ b/DockerEntrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh # Start fail2ban -[ $XUI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start +[ "$XUI_ENABLE_FAIL2BAN" = "true" ] && fail2ban-client -x start # Run x-ui exec /app/x-ui diff --git a/Dockerfile b/Dockerfile index cddc945c..e3d04a17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,27 +2,44 @@ # Stage: Builder # ======================================================== FROM golang:1.25-alpine AS builder + WORKDIR /app -ARG TARGETARCH RUN apk --no-cache --update add \ build-base \ - gcc \ - wget \ - unzip + gcc + +# docker CACHE +COPY go.mod go.sum ./ +RUN go mod download COPY . . ENV CGO_ENABLED=1 ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" RUN go build -ldflags "-w -s" -o build/x-ui main.go -RUN ./DockerInit.sh "$TARGETARCH" + +# ======================================================== +# Stage: Xray downloader +# ======================================================== +FROM alpine AS xray-downloader + +ARG TARGETARCH +ARG XRAY_VERSION + +WORKDIR /app +RUN apk add --no-cache wget unzip + +COPY xray-tools.sh . +RUN chmod +x /app/xray-tools.sh +RUN ./xray-tools.sh install_xray_core "$TARGETARCH" "/app/bin" "$XRAY_VERSION" +RUN ./xray-tools.sh update_geodata_in_docker "/app/bin" # ======================================================== # Stage: Final Image of 3x-ui # ======================================================== FROM alpine -ENV TZ=Asia/Tehran + WORKDIR /app RUN apk add --no-cache --update \ @@ -31,9 +48,11 @@ RUN apk add --no-cache --update \ fail2ban \ bash +COPY DockerEntrypoint.sh /app/ COPY --from=builder /app/build/ /app/ -COPY --from=builder /app/DockerEntrypoint.sh /app/ +#COPY --from=builder /app/DockerEntrypoint.sh /app/ COPY --from=builder /app/x-ui.sh /usr/bin/x-ui +COPY --from=xray-downloader /app/bin /app/bin # Configure fail2ban @@ -51,5 +70,5 @@ RUN chmod +x \ ENV XUI_ENABLE_FAIL2BAN="true" EXPOSE 2053 VOLUME [ "/etc/x-ui" ] -CMD [ "./x-ui" ] +#CMD [ "./x-ui" ] ENTRYPOINT [ "/app/DockerEntrypoint.sh" ] diff --git a/docker-compose.yml b/docker-compose.yml index 198df198..53f96a97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,15 +2,17 @@ services: 3xui: build: context: . - dockerfile: ./Dockerfile + args: + XRAY_VERSION: "v25.10.15" + GEOUPDATE_CRON_SCHEDULE: "0 */6 * * *" container_name: 3xui_app - # hostname: yourhostname <- optional volumes: - $PWD/db/:/etc/x-ui/ - $PWD/cert/:/root/cert/ environment: + TZ: "Asia/Tehran" XRAY_VMESS_AEAD_FORCED: "false" XUI_ENABLE_FAIL2BAN: "true" tty: true network_mode: host - restart: unless-stopped + restart: unless-stopped \ No newline at end of file diff --git a/xray-tools.sh b/xray-tools.sh index c28c8537..a2ddd682 100644 --- a/xray-tools.sh +++ b/xray-tools.sh @@ -38,7 +38,7 @@ update_geodata_in_docker() { } -update_xray_core() { +install_xray_core() { TARGETARCH="$1" WORKDIR="$2" XRAY_VERSION="$3" @@ -46,7 +46,7 @@ update_xray_core() { OLD_DIR=$(pwd) trap 'cd "$OLD_DIR"' EXIT - echo "[$(date)] Running update_xray_core" + echo "[$(date)] Running install_xray_core" case $1 in amd64) @@ -94,9 +94,9 @@ if [ "${0##*/}" = "xray-tools.sh" ]; then shift || true case "$cmd" in - update_xray_core) + install_xray_core) # args: TARGETARCH WORKDIR XRAY_VERSION - update_xray_core "$@" + install_xray_core "$@" ;; update_geodata_in_docker) # args: WORKDIR @@ -104,7 +104,7 @@ if [ "${0##*/}" = "xray-tools.sh" ]; then ;; ""|help|-h|--help) echo "Usage:" - echo " $0 update_xray_core TARGETARCH WORKDIR XRAY_VERSION" + echo " $0 install_xray_core TARGETARCH WORKDIR XRAY_VERSION" echo " $0 update_geodata_in_docker WORKDIR" exit 1 ;; From 1012636d35f193732d0bb2fa38574cd24dfc0973 Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Sun, 23 Nov 2025 17:30:52 +0300 Subject: [PATCH 05/10] Quick docker refactoring --- Dockerfile | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index e3d04a17..56e6a3d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM golang:1.25-alpine AS builder WORKDIR /app -RUN apk --no-cache --update add \ +RUN apk add --no-cache \ build-base \ gcc @@ -28,12 +28,12 @@ ARG TARGETARCH ARG XRAY_VERSION WORKDIR /app -RUN apk add --no-cache wget unzip +RUN apk add --no-cache wget unzip && mkdir -p /app/bin COPY xray-tools.sh . -RUN chmod +x /app/xray-tools.sh -RUN ./xray-tools.sh install_xray_core "$TARGETARCH" "/app/bin" "$XRAY_VERSION" -RUN ./xray-tools.sh update_geodata_in_docker "/app/bin" +RUN chmod +x /app/xray-tools.sh && \ + ./xray-tools.sh install_xray_core "$TARGETARCH" "/app/bin" "$XRAY_VERSION" && \ + ./xray-tools.sh update_geodata_in_docker "/app/bin" # ======================================================== # Stage: Final Image of 3x-ui @@ -42,7 +42,7 @@ FROM alpine WORKDIR /app -RUN apk add --no-cache --update \ +RUN apk add --no-cache \ ca-certificates \ tzdata \ fail2ban \ @@ -50,7 +50,6 @@ RUN apk add --no-cache --update \ COPY DockerEntrypoint.sh /app/ COPY --from=builder /app/build/ /app/ -#COPY --from=builder /app/DockerEntrypoint.sh /app/ COPY --from=builder /app/x-ui.sh /usr/bin/x-ui COPY --from=xray-downloader /app/bin /app/bin @@ -70,5 +69,5 @@ RUN chmod +x \ ENV XUI_ENABLE_FAIL2BAN="true" EXPOSE 2053 VOLUME [ "/etc/x-ui" ] -#CMD [ "./x-ui" ] + ENTRYPOINT [ "/app/DockerEntrypoint.sh" ] From 4b8275f15f8c689c8ec8667a7fa268b57d3a61cc Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Tue, 25 Nov 2025 00:05:40 +0300 Subject: [PATCH 06/10] Docker crom runner: cron works and restarts the main container, but shared volume still not in use --- DockerInit.sh | 40 ---------------- Dockerfile | 2 +- docker-compose.yml | 46 ++++++++++++++++-- docker-cron-runner/Dockerfile | 15 ++++++ docker-cron-runner/entrypoint.sh | 48 +++++++++++++++++++ .../xray-tools.sh | 0 x-ui.sh | 2 +- 7 files changed, 108 insertions(+), 45 deletions(-) delete mode 100755 DockerInit.sh create mode 100644 docker-cron-runner/Dockerfile create mode 100644 docker-cron-runner/entrypoint.sh rename xray-tools.sh => docker-cron-runner/xray-tools.sh (100%) diff --git a/DockerInit.sh b/DockerInit.sh deleted file mode 100755 index fb603fb8..00000000 --- a/DockerInit.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh -case $1 in - amd64) - ARCH="64" - FNAME="amd64" - ;; - i386) - ARCH="32" - FNAME="i386" - ;; - armv8 | arm64 | aarch64) - ARCH="arm64-v8a" - FNAME="arm64" - ;; - armv7 | arm | arm32) - ARCH="arm32-v7a" - FNAME="arm32" - ;; - armv6) - ARCH="arm32-v6" - FNAME="armv6" - ;; - *) - ARCH="64" - FNAME="amd64" - ;; -esac -mkdir -p build/bin -cd build/bin -wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.10.15/Xray-linux-${ARCH}.zip" -unzip "Xray-linux-${ARCH}.zip" -rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat -mv xray "xray-linux-${FNAME}" -wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat -wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat -wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat -wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat -wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat -wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat -cd ../../ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 56e6a3d2..20c092bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ ARG XRAY_VERSION WORKDIR /app RUN apk add --no-cache wget unzip && mkdir -p /app/bin -COPY xray-tools.sh . +COPY docker-cron-runner/xray-tools.sh . RUN chmod +x /app/xray-tools.sh && \ ./xray-tools.sh install_xray_core "$TARGETARCH" "/app/bin" "$XRAY_VERSION" && \ ./xray-tools.sh update_geodata_in_docker "/app/bin" diff --git a/docker-compose.yml b/docker-compose.yml index 53f96a97..b9227d73 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,58 @@ services: - 3xui: + 3x-ui: build: context: . args: XRAY_VERSION: "v25.10.15" - GEOUPDATE_CRON_SCHEDULE: "0 */6 * * *" container_name: 3xui_app volumes: - $PWD/db/:/etc/x-ui/ - $PWD/cert/:/root/cert/ + - $PWD/geodata/:/opt/geodata/ environment: TZ: "Asia/Tehran" XRAY_VMESS_AEAD_FORCED: "false" XUI_ENABLE_FAIL2BAN: "true" tty: true network_mode: host - restart: unless-stopped \ No newline at end of file + restart: unless-stopped + depends_on: + - geodata-cron + + docker-proxy: + image: tecnativa/docker-socket-proxy + container_name: docker_proxy + restart: unless-stopped + environment: + - CONTAINERS=1 + - POST=1 + - ALLOW_RESTARTS=1 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - docker-internal + + geodata-cron: + build: + context: docker-cron-runner + container_name: geodata_cron + restart: unless-stopped + depends_on: + - docker-proxy + environment: + DOCKER_PROXY_URL: "http://docker-proxy:2375" + TARGET_CONTAINER_NAME: "3xui_app" + # Расписание в формате crond (пример: каждые 6 часов) + # CRON_SCHEDULE: "0 */6 * * *" +# CRON_SCHEDULE: "*/1 * * * *" + CRON_SCHEDULE: "0 */6 * * *" + SHARED_VOLUME_PATH: "/opt/geodata" + volumes: + - $PWD/geodata/:/opt/geodata/ + - ./xray-tools.sh:/usr/local/bin/xray-tools.sh:ro + networks: + - docker-internal + +networks: + docker-internal: + driver: bridge \ No newline at end of file diff --git a/docker-cron-runner/Dockerfile b/docker-cron-runner/Dockerfile new file mode 100644 index 00000000..77f069e0 --- /dev/null +++ b/docker-cron-runner/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine:3.20 + +WORKDIR app +COPY xray-tools.sh . +# Нужные утилиты: curl + bash (если xray-tools.sh написан под bash) +RUN apk add --no-cache curl bash ca-certificates tzdata + +# Копируем entrypoint +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +RUN chmod +x xray-tools.sh + + +# cron внутри alpine использует crond (busybox) +CMD ["/entrypoint.sh"] \ No newline at end of file diff --git a/docker-cron-runner/entrypoint.sh b/docker-cron-runner/entrypoint.sh new file mode 100644 index 00000000..f580f647 --- /dev/null +++ b/docker-cron-runner/entrypoint.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env sh +set -e + +: "${CRON_SCHEDULE:=0 */6 * * *}" +: "${DOCKER_PROXY_URL:?DOCKER_PROXY_URL is required}" +: "${TARGET_CONTAINER_NAME:?TARGET_CONTAINER_NAME is required}" +: "${SHARED_VOLUME_PATH:?SHARED_VOLUME_PATH is required}" + +# Скрипт, который будет исполняться по крону +CRON_JOB_SCRIPT="/usr/local/bin/run_update_and_restart.sh" + +cat > "$CRON_JOB_SCRIPT" << 'EOF' +#!/usr/bin/env sh +set -e + +echo "[$(date)] Starting geodata update..." + +# Обновление геоданных +/app/xray-tools.sh update_geodata_in_docker "${SHARED_VOLUME_PATH}" + +echo "[$(date)] Geodata update finished, restarting container..." + +# Рестарт контейнера через Docker Socket Proxy +curl -s -X POST \ + "${DOCKER_PROXY_URL}/containers/${TARGET_CONTAINER_NAME}/restart" \ + -o /dev/null -w "%{http_code}\n" + +echo "[$(date)] Restart request sent." +EOF + +chmod +x "$CRON_JOB_SCRIPT" + +# Создаём кронтаб +# Важный момент: переменные окружения надо прокинуть в cron. +CRON_ENV_FILE="/env.sh" +env | grep -v '^CRON_SCHEDULE=' | sed 's/^/export /' > "$CRON_ENV_FILE" + +# crond не тянет env напрямую, поэтому в крон-строке source env-файла +echo "${CRON_SCHEDULE} . ${CRON_ENV_FILE} && ${CRON_JOB_SCRIPT} >> /var/log/cron.log 2>&1" > /etc/crontabs/root + +echo "Starting crond with schedule: ${CRON_SCHEDULE}" +mkdir -p /var/log +touch /var/log/cron.log + +bash $CRON_JOB_SCRIPT + +# Запускаем crond в foreground +exec crond -f -l 2 \ No newline at end of file diff --git a/xray-tools.sh b/docker-cron-runner/xray-tools.sh similarity index 100% rename from xray-tools.sh rename to docker-cron-runner/xray-tools.sh diff --git a/x-ui.sh b/x-ui.sh index d9112b50..f56eca35 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -6,7 +6,7 @@ blue='\033[0;34m' yellow='\033[0;33m' plain='\033[0m' -source xray-tools.sh +source docker-cron-runner/xray-tools.sh #Add some basic function here function LOGD() { From 67d4142c3ad9ec3912efa1b60745c96d70c4480a Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Tue, 25 Nov 2025 02:10:02 +0300 Subject: [PATCH 07/10] Used shared volume + system optimization --- DockerEntrypoint.sh | 7 +++++ Dockerfile | 18 ----------- docker-compose.yml | 18 +++++------ docker-cron-runner/Dockerfile | 34 ++++++++++++++------- docker-cron-runner/cron-job-script.sh | 23 ++++++++++++++ docker-cron-runner/entrypoint.sh | 43 +++++++-------------------- 6 files changed, 73 insertions(+), 70 deletions(-) create mode 100644 docker-cron-runner/cron-job-script.sh diff --git a/DockerEntrypoint.sh b/DockerEntrypoint.sh index 277026b8..95c86869 100644 --- a/DockerEntrypoint.sh +++ b/DockerEntrypoint.sh @@ -1,5 +1,12 @@ #!/bin/sh +FINISH_FILE="$GEODATA_DIR/cron-job-finished.txt" + +while [ ! -f "$FINISH_FILE" ]; do + echo "Still waiting... (looking for $FINISH_FILE)" + sleep 10 +done + # Start fail2ban [ "$XUI_ENABLE_FAIL2BAN" = "true" ] && fail2ban-client -x start diff --git a/Dockerfile b/Dockerfile index 20c092bf..ab861c8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,22 +19,6 @@ ENV CGO_ENABLED=1 ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" RUN go build -ldflags "-w -s" -o build/x-ui main.go -# ======================================================== -# Stage: Xray downloader -# ======================================================== -FROM alpine AS xray-downloader - -ARG TARGETARCH -ARG XRAY_VERSION - -WORKDIR /app -RUN apk add --no-cache wget unzip && mkdir -p /app/bin - -COPY docker-cron-runner/xray-tools.sh . -RUN chmod +x /app/xray-tools.sh && \ - ./xray-tools.sh install_xray_core "$TARGETARCH" "/app/bin" "$XRAY_VERSION" && \ - ./xray-tools.sh update_geodata_in_docker "/app/bin" - # ======================================================== # Stage: Final Image of 3x-ui # ======================================================== @@ -51,8 +35,6 @@ RUN apk add --no-cache \ COPY DockerEntrypoint.sh /app/ COPY --from=builder /app/build/ /app/ COPY --from=builder /app/x-ui.sh /usr/bin/x-ui -COPY --from=xray-downloader /app/bin /app/bin - # Configure fail2ban RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \ diff --git a/docker-compose.yml b/docker-compose.yml index b9227d73..a9d69373 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,17 +2,16 @@ services: 3x-ui: build: context: . - args: - XRAY_VERSION: "v25.10.15" container_name: 3xui_app volumes: - $PWD/db/:/etc/x-ui/ - $PWD/cert/:/root/cert/ - - $PWD/geodata/:/opt/geodata/ + - $PWD/geodata/:/app/bin environment: TZ: "Asia/Tehran" XRAY_VMESS_AEAD_FORCED: "false" XUI_ENABLE_FAIL2BAN: "true" + GEODATA_DIR: "/app/bin" tty: true network_mode: host restart: unless-stopped @@ -35,21 +34,22 @@ services: geodata-cron: build: context: docker-cron-runner + args: + XRAY_VERSION: "v25.10.15" + XRAY_BUILD_DIR: "/app/xray" container_name: geodata_cron restart: unless-stopped depends_on: - docker-proxy environment: + TZ: "UTC" DOCKER_PROXY_URL: "http://docker-proxy:2375" TARGET_CONTAINER_NAME: "3xui_app" - # Расписание в формате crond (пример: каждые 6 часов) - # CRON_SCHEDULE: "0 */6 * * *" -# CRON_SCHEDULE: "*/1 * * * *" +# CRON_SCHEDULE: "*/5 * * * *" CRON_SCHEDULE: "0 */6 * * *" - SHARED_VOLUME_PATH: "/opt/geodata" + SHARED_VOLUME_PATH: "/app/bin" volumes: - - $PWD/geodata/:/opt/geodata/ - - ./xray-tools.sh:/usr/local/bin/xray-tools.sh:ro + - $PWD/geodata/:/app/bin/ networks: - docker-internal diff --git a/docker-cron-runner/Dockerfile b/docker-cron-runner/Dockerfile index 77f069e0..b3c1fcc8 100644 --- a/docker-cron-runner/Dockerfile +++ b/docker-cron-runner/Dockerfile @@ -1,15 +1,29 @@ FROM alpine:3.20 -WORKDIR app -COPY xray-tools.sh . -# Нужные утилиты: curl + bash (если xray-tools.sh написан под bash) -RUN apk add --no-cache curl bash ca-certificates tzdata +ARG TARGETARCH +ARG XRAY_VERSION +ARG XRAY_BUILD_DIR -# Копируем entrypoint -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh -RUN chmod +x xray-tools.sh +WORKDIR /app + +RUN apk add --no-cache \ + wget \ + unzip \ + curl \ + bash \ + ca-certificates \ + tzdata -# cron внутри alpine использует crond (busybox) -CMD ["/entrypoint.sh"] \ No newline at end of file +COPY xray-tools.sh entrypoint.sh cron-job-script.sh ./ + +#RUN mkdir -p "$XRAY_BUILD_DIR" +RUN chmod +x /app/xray-tools.sh /app/entrypoint.sh /app/cron-job-script.sh \ + && mkdir -p "$XRAY_BUILD_DIR" \ + && ./xray-tools.sh install_xray_core "$TARGETARCH" "$XRAY_BUILD_DIR" "$XRAY_VERSION" \ + && ./xray-tools.sh update_geodata_in_docker "$XRAY_BUILD_DIR" + +ENV XRAY_BUILD_DIR=${XRAY_BUILD_DIR} + +#CMD ["/app/entrypoint.sh"] \ +ENTRYPOINT ["/app/entrypoint.sh"] \ No newline at end of file diff --git a/docker-cron-runner/cron-job-script.sh b/docker-cron-runner/cron-job-script.sh new file mode 100644 index 00000000..ec2eff8b --- /dev/null +++ b/docker-cron-runner/cron-job-script.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env sh +set -eu + +echo "[$(date)] Starting geodata update..." + +FINISHED_FLAG="${SHARED_VOLUME_PATH}/cron-job-finished.txt" + +if [ -f "$FINISHED_FLAG" ]; then + rm -f "$FINISHED_FLAG" +fi + +/app/xray-tools.sh update_geodata_in_docker "${SHARED_VOLUME_PATH}" +touch "$FINISHED_FLAG" + +echo "[$(date)] Geodata update finished, restarting container..." + +HTTP_CODE=$( + curl -s -X POST \ + "${DOCKER_PROXY_URL}/containers/${TARGET_CONTAINER_NAME}/restart" \ + -o /dev/null -w "%{http_code}" +) + +echo "[$(date)] Restart request sent, HTTP status: ${HTTP_CODE}" \ No newline at end of file diff --git a/docker-cron-runner/entrypoint.sh b/docker-cron-runner/entrypoint.sh index f580f647..9d4cc001 100644 --- a/docker-cron-runner/entrypoint.sh +++ b/docker-cron-runner/entrypoint.sh @@ -1,48 +1,25 @@ #!/usr/bin/env sh -set -e + +set -eu : "${CRON_SCHEDULE:=0 */6 * * *}" : "${DOCKER_PROXY_URL:?DOCKER_PROXY_URL is required}" -: "${TARGET_CONTAINER_NAME:?TARGET_CONTAINER_NAME is required}" +: "${TARGET_CONTAINER_NAME:?TARGET_CONTAINER_NAME is required}" # required for cron-job-script.sh for container restart : "${SHARED_VOLUME_PATH:?SHARED_VOLUME_PATH is required}" -# Скрипт, который будет исполняться по крону -CRON_JOB_SCRIPT="/usr/local/bin/run_update_and_restart.sh" - -cat > "$CRON_JOB_SCRIPT" << 'EOF' -#!/usr/bin/env sh -set -e - -echo "[$(date)] Starting geodata update..." - -# Обновление геоданных -/app/xray-tools.sh update_geodata_in_docker "${SHARED_VOLUME_PATH}" - -echo "[$(date)] Geodata update finished, restarting container..." - -# Рестарт контейнера через Docker Socket Proxy -curl -s -X POST \ - "${DOCKER_PROXY_URL}/containers/${TARGET_CONTAINER_NAME}/restart" \ - -o /dev/null -w "%{http_code}\n" - -echo "[$(date)] Restart request sent." -EOF - -chmod +x "$CRON_JOB_SCRIPT" - -# Создаём кронтаб -# Важный момент: переменные окружения надо прокинуть в cron. CRON_ENV_FILE="/env.sh" -env | grep -v '^CRON_SCHEDULE=' | sed 's/^/export /' > "$CRON_ENV_FILE" -# crond не тянет env напрямую, поэтому в крон-строке source env-файла -echo "${CRON_SCHEDULE} . ${CRON_ENV_FILE} && ${CRON_JOB_SCRIPT} >> /var/log/cron.log 2>&1" > /etc/crontabs/root +env | grep -v '^CRON_SCHEDULE=' | sed 's/^/export /' > "$CRON_ENV_FILE" +echo "${CRON_SCHEDULE} . ${CRON_ENV_FILE} && /app/cron-job-script.sh >> /var/log/cron.log 2>&1" > /etc/crontabs/root echo "Starting crond with schedule: ${CRON_SCHEDULE}" + mkdir -p /var/log touch /var/log/cron.log -bash $CRON_JOB_SCRIPT +mkdir -p "$SHARED_VOLUME_PATH" +cp -r "$XRAY_BUILD_DIR"/* "$SHARED_VOLUME_PATH"/ + +touch "$SHARED_VOLUME_PATH/cron-job-finished.txt" # cron job execution imitation -# Запускаем crond в foreground exec crond -f -l 2 \ No newline at end of file From c4c5aee9acea0c7dec44c8653e5badd374de307a Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Thu, 27 Nov 2025 13:47:41 +0300 Subject: [PATCH 08/10] docker update --- .gitignore | 3 +++ docker-compose.yml | 5 +++-- docker-cron-runner/xray-tools.sh | 28 ++++++++++++++-------------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 8fa4eeb0..69b9c69d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# shared volume +geodata/ + # Ignore editor and IDE settings .idea/ .vscode/ diff --git a/docker-compose.yml b/docker-compose.yml index a9d69373..2c70c4f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,11 +42,12 @@ services: depends_on: - docker-proxy environment: + ENABLE_CRON_UPDATE: "true" TZ: "UTC" DOCKER_PROXY_URL: "http://docker-proxy:2375" TARGET_CONTAINER_NAME: "3xui_app" -# CRON_SCHEDULE: "*/5 * * * *" - CRON_SCHEDULE: "0 */6 * * *" + CRON_SCHEDULE: "*/1 * * * *" +# CRON_SCHEDULE: "0 */6 * * *" SHARED_VOLUME_PATH: "/app/bin" volumes: - $PWD/geodata/:/app/bin/ diff --git a/docker-cron-runner/xray-tools.sh b/docker-cron-runner/xray-tools.sh index a2ddd682..caf1dc92 100644 --- a/docker-cron-runner/xray-tools.sh +++ b/docker-cron-runner/xray-tools.sh @@ -2,8 +2,8 @@ update_all_geofiles() { update_main_geofiles - update_ir_geofiles - update_ru_geofiles +# update_ir_geofiles +# update_ru_geofiles } update_main_geofiles() { @@ -22,16 +22,16 @@ update_ru_geofiles() { } update_geodata_in_docker() { - WORKDIR="$1" + XRAYDIR="$1" OLD_DIR=$(pwd) trap 'cd "$OLD_DIR"' EXIT echo "[$(date)] Running update_geodata" - if [ ! -d "$WORKDIR" ]; then - mkdir -p "$WORKDIR" + if [ ! -d "$XRAYDIR" ]; then + mkdir -p "$XRAYDIR" fi - cd "$WORKDIR" + cd "$XRAYDIR" update_all_geofiles echo "[$(date)] All geo files have been updated successfully!" @@ -40,7 +40,7 @@ update_geodata_in_docker() { install_xray_core() { TARGETARCH="$1" - WORKDIR="$2" + XRAYDIR="$2" XRAY_VERSION="$3" OLD_DIR=$(pwd) @@ -75,10 +75,10 @@ install_xray_core() { ;; esac - if [ ! -d "$WORKDIR" ]; then - mkdir -p "$WORKDIR" + if [ ! -d "$XRAYDIR" ]; then + mkdir -p "$XRAYDIR" fi - cd "$WORKDIR" + cd "$XRAYDIR" wget -q "https://github.com/XTLS/Xray-core/releases/download/${XRAY_VERSION}/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" -d ./xray-unzip @@ -95,17 +95,17 @@ if [ "${0##*/}" = "xray-tools.sh" ]; then case "$cmd" in install_xray_core) - # args: TARGETARCH WORKDIR XRAY_VERSION + # args: TARGETARCH XRAYDIR XRAY_VERSION install_xray_core "$@" ;; update_geodata_in_docker) - # args: WORKDIR + # args: XRAYDIR update_geodata_in_docker "$@" ;; ""|help|-h|--help) echo "Usage:" - echo " $0 install_xray_core TARGETARCH WORKDIR XRAY_VERSION" - echo " $0 update_geodata_in_docker WORKDIR" + echo " $0 install_xray_core TARGETARCH XRAYDIR XRAY_VERSION" + echo " $0 update_geodata_in_docker XRAYDIR" exit 1 ;; *) From 11031dad004a835eacc7cd0587336c7e8874354e Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Thu, 27 Nov 2025 14:20:54 +0300 Subject: [PATCH 09/10] Code refactoring --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2c70c4f8..99c7231b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,8 +46,7 @@ services: TZ: "UTC" DOCKER_PROXY_URL: "http://docker-proxy:2375" TARGET_CONTAINER_NAME: "3xui_app" - CRON_SCHEDULE: "*/1 * * * *" -# CRON_SCHEDULE: "0 */6 * * *" + CRON_SCHEDULE: "0 */6 * * *" SHARED_VOLUME_PATH: "/app/bin" volumes: - $PWD/geodata/:/app/bin/ From c7e9018e716e624212b338d38b4e24b2ebb12505 Mon Sep 17 00:00:00 2001 From: Michael S2pac Date: Thu, 27 Nov 2025 14:22:01 +0300 Subject: [PATCH 10/10] Updated bash script: safe file replace system now saves it from rewrite if the downloaded file is empty --- docker-cron-runner/xray-tools.sh | 60 +++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/docker-cron-runner/xray-tools.sh b/docker-cron-runner/xray-tools.sh index caf1dc92..8eafd937 100644 --- a/docker-cron-runner/xray-tools.sh +++ b/docker-cron-runner/xray-tools.sh @@ -1,24 +1,65 @@ #!/bin/sh +safe_download_and_update() { + url="$1" + dest="$2" + + # Create a temporary file + tmp=$(mktemp "${dest}.XXXXXX") || return 1 + + # Download file into a temporary location + if wget -q -O "$tmp" "$url"; then + # Check that the downloaded file is not empty + if [ -s "$tmp" ]; then + # Atomically replace the destination file + mv "$tmp" "$dest" + echo "[OK] Downloaded: $dest" + else + echo "[ERR] Downloaded file is empty: $url" + rm -f "$tmp" + return 1 + fi + else + echo "[ERR] Failed to download: $url" + rm -f "$tmp" + return 1 + fi +} + update_all_geofiles() { update_main_geofiles -# update_ir_geofiles -# update_ru_geofiles + update_ir_geofiles + update_ru_geofiles } update_main_geofiles() { - wget -O geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat - wget -O geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat + safe_download_and_update \ + "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" \ + "geoip.dat" + + safe_download_and_update \ + "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" \ + "geosite.dat" } update_ir_geofiles() { - wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat - wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat + safe_download_and_update \ + "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" \ + "geoip_IR.dat" + + safe_download_and_update \ + "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" \ + "geosite_IR.dat" } update_ru_geofiles() { - wget -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat - wget -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat + safe_download_and_update \ + "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" \ + "geoip_RU.dat" + + safe_download_and_update \ + "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" \ + "geosite_RU.dat" } update_geodata_in_docker() { @@ -102,6 +143,9 @@ if [ "${0##*/}" = "xray-tools.sh" ]; then # args: XRAYDIR update_geodata_in_docker "$@" ;; + update_all_geofiles) + update_all_geofiles + ;; ""|help|-h|--help) echo "Usage:" echo " $0 install_xray_core TARGETARCH XRAYDIR XRAY_VERSION"