diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..99bb78cc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +db +cert +*.log +Dockerfile +docker-compose.yml +.tmp +.idea +.vscode +LICENSE +README.* \ No newline at end of file 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/DockerEntrypoint.sh b/DockerEntrypoint.sh index 7511d2ea..95c86869 100644 --- a/DockerEntrypoint.sh +++ b/DockerEntrypoint.sh @@ -1,7 +1,14 @@ #!/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 +[ "$XUI_ENABLE_FAIL2BAN" = "true" ] && fail2ban-client -x start # Run x-ui exec /app/x-ui 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 cddc945c..ab861c8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,40 +2,40 @@ # Stage: Builder # ======================================================== FROM golang:1.25-alpine AS builder -WORKDIR /app -ARG TARGETARCH -RUN apk --no-cache --update add \ +WORKDIR /app + +RUN apk add --no-cache \ 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: Final Image of 3x-ui # ======================================================== FROM alpine -ENV TZ=Asia/Tehran + WORKDIR /app -RUN apk add --no-cache --update \ +RUN apk add --no-cache \ ca-certificates \ tzdata \ fail2ban \ bash +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 - # Configure fail2ban RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \ && cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \ @@ -51,5 +51,5 @@ RUN chmod +x \ ENV XUI_ENABLE_FAIL2BAN="true" EXPOSE 2053 VOLUME [ "/etc/x-ui" ] -CMD [ "./x-ui" ] + ENTRYPOINT [ "/app/DockerEntrypoint.sh" ] diff --git a/docker-compose.yml b/docker-compose.yml index 198df198..dc0b966c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,57 @@ services: - 3xui: + 3x-ui: build: context: . - dockerfile: ./Dockerfile container_name: 3xui_app - # hostname: yourhostname <- optional volumes: - $PWD/db/:/etc/x-ui/ - $PWD/cert/:/root/cert/ + - $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 + 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 + 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" + CRON_SCHEDULE: "0 */6 * * *" + SHARED_VOLUME_PATH: "/app/bin" + volumes: + - $PWD/geodata/:/app/bin/ + 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..b3c1fcc8 --- /dev/null +++ b/docker-cron-runner/Dockerfile @@ -0,0 +1,29 @@ +FROM alpine:3.20 + +ARG TARGETARCH +ARG XRAY_VERSION +ARG XRAY_BUILD_DIR + +WORKDIR /app + +RUN apk add --no-cache \ + wget \ + unzip \ + curl \ + bash \ + ca-certificates \ + tzdata + + +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 new file mode 100644 index 00000000..9d4cc001 --- /dev/null +++ b/docker-cron-runner/entrypoint.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +set -eu + +: "${CRON_SCHEDULE:=0 */6 * * *}" +: "${DOCKER_PROXY_URL:?DOCKER_PROXY_URL 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_ENV_FILE="/env.sh" + +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 + +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 + +exec crond -f -l 2 \ No newline at end of file diff --git a/docker-cron-runner/xray-tools.sh b/docker-cron-runner/xray-tools.sh new file mode 100644 index 00000000..5765f330 --- /dev/null +++ b/docker-cron-runner/xray-tools.sh @@ -0,0 +1,159 @@ +#!/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_main_geofiles() { + 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() { + 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() { + 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() { + XRAYDIR="$1" + OLD_DIR=$(pwd) + trap 'cd "$OLD_DIR"' EXIT + + echo "[$(date)] Running update_geodata" + + if [ ! -d "$XRAYDIR" ]; then + mkdir -p "$XRAYDIR" + fi + cd "$XRAYDIR" + + update_all_geofiles + echo "[$(date)] All geo files have been updated successfully!" +} + + +install_xray_core() { + TARGETARCH="$1" + XRAYDIR="$2" + XRAY_VERSION="$3" + + OLD_DIR=$(pwd) + trap 'cd "$OLD_DIR"' EXIT + + echo "[$(date)] Running install_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 "$XRAYDIR" ]; then + mkdir -p "$XRAYDIR" + fi + 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 + cp ./xray-unzip/xray ./"xray-linux-${FNAME}" + rm -r xray-unzip + rm "Xray-linux-${ARCH}.zip" + } + +if [ "${0##*/}" = "xray-tools.sh" ]; then + cmd="$1" + shift || true + + case "$cmd" in + install_xray_core) + # args: TARGETARCH XRAYDIR XRAY_VERSION + install_xray_core "$@" + ;; + update_geodata_in_docker) + # 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" + echo " $0 update_geodata_in_docker XRAYDIR" + exit 1 + ;; + *) + echo "Unknown command: $cmd" >&2 + echo "Try: $0 help" >&2 + exit 1 + ;; + esac +fi \ No newline at end of file diff --git a/x-ui.sh b/x-ui.sh index 4b0989eb..d9ad9621 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 docker-cron-runner/xray-tools.sh + #Add some basic function here function LOGD() { echo -e "${yellow}[DEG] $* ${plain}" @@ -44,7 +46,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 @@ -863,27 +865,6 @@ 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_geo() { echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)" echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)" @@ -2022,7 +2003,7 @@ show_menu() { esac } -if [[ $# > 0 ]]; then +if [[ $# -gt 0 ]]; then case $1 in "start") check_install 0 && start 0