mirror of
https://github.com/okxlin/appstore.git
synced 2026-09-22 08:00:59 +00:00
fix(microwarp): verify WARP IP rotation
Add the verified MicroWARP packages to the localApps branch, including real SOCKS egress verification, retry and rollback behavior, and upgrade-safe replacement of legacy root rotation scripts while preserving user data.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
CONTAINER_NAME=microwarp
|
||||
PANEL_APP_PORT_SOCKS5=1080
|
||||
PANEL_APP_PORT_HTTP_PROXY=8080
|
||||
BIND_PORT=1080
|
||||
ROTATE_INTERVAL_MINUTES=0
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
TEST_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
IP_CHECK_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
SOCKS_USER=
|
||||
SOCKS_PASS=
|
||||
ENDPOINT_IP=
|
||||
GH_PROXY=
|
||||
TAILSCALE_CIDR=100.64.0.0/10
|
||||
MTU=1280
|
||||
WARP_WGCF_CONF=
|
||||
TZ=Asia/Shanghai
|
||||
APP_DATA_DIR_1=./data
|
||||
@@ -0,0 +1,277 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 1080
|
||||
envKey: PANEL_APP_PORT_SOCKS5
|
||||
labelEn: SOCKS5 Port
|
||||
labelZh: SOCKS5 端口
|
||||
label:
|
||||
en: SOCKS5 Port
|
||||
zh: SOCKS5 端口
|
||||
zh-Hant: SOCKS5 連接埠
|
||||
ja: SOCKS5 ポート
|
||||
ko: SOCKS5 포트
|
||||
ru: Порт SOCKS5
|
||||
ms: Port SOCKS5
|
||||
pt-br: Porta SOCKS5
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 8080
|
||||
envKey: PANEL_APP_PORT_HTTP_PROXY
|
||||
labelEn: HTTP Proxy Port
|
||||
labelZh: HTTP 代理端口
|
||||
label:
|
||||
en: HTTP Proxy Port
|
||||
zh: HTTP 代理端口
|
||||
zh-Hant: HTTP 代理連接埠
|
||||
ja: HTTP プロキシポート
|
||||
ko: HTTP 프록시 포트
|
||||
ru: Порт HTTP-прокси
|
||||
ms: Port proksi HTTP
|
||||
pt-br: Porta do proxy HTTP
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 1080
|
||||
envKey: BIND_PORT
|
||||
labelEn: Internal Bind Port
|
||||
labelZh: 容器内部绑定端口
|
||||
label:
|
||||
en: Internal Bind Port
|
||||
zh: 容器内部绑定端口
|
||||
zh-Hant: 容器內部綁定連接埠
|
||||
ja: コンテナ内部バインドポート
|
||||
ko: 컨테이너 내부 바인드 포트
|
||||
ru: Внутренний порт привязки контейнера
|
||||
ms: Port bind dalaman kontena
|
||||
pt-br: Porta interna de bind do contêiner
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 0
|
||||
envKey: ROTATE_INTERVAL_MINUTES
|
||||
labelEn: Rotate Interval Minutes
|
||||
labelZh: 轮换间隔(分钟)
|
||||
label:
|
||||
en: Rotate Interval Minutes
|
||||
zh: 轮换间隔(分钟)
|
||||
zh-Hant: 輪換間隔(分鐘)
|
||||
ja: ローテーション間隔(分)
|
||||
ko: 로테이션 간격(분)
|
||||
ru: Интервал ротации (минуты)
|
||||
ms: Selang putaran (minit)
|
||||
pt-br: Intervalo de rotação (minutos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_MAX_ATTEMPTS
|
||||
labelEn: Rotation Attempts
|
||||
labelZh: 轮换尝试次数
|
||||
label:
|
||||
en: Rotation Attempts
|
||||
zh: 轮换尝试次数
|
||||
zh-Hant: 輪換嘗試次數
|
||||
ja: ローテーション試行回数
|
||||
ko: 로테이션 시도 횟수
|
||||
ru: Количество попыток ротации
|
||||
ms: Bilangan percubaan putaran
|
||||
pt-br: Número de tentativas de rotação
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_RETRY_DELAY_SECONDS
|
||||
labelEn: Rotation Retry Delay Seconds
|
||||
labelZh: 轮换重试间隔(秒)
|
||||
label:
|
||||
en: Rotation Retry Delay Seconds
|
||||
zh: 轮换重试间隔(秒)
|
||||
zh-Hant: 輪換重試間隔(秒)
|
||||
ja: ローテーション再試行間隔(秒)
|
||||
ko: 로테이션 재시도 간격(초)
|
||||
ru: Задержка между попытками ротации (секунды)
|
||||
ms: Kelewatan percubaan semula putaran (saat)
|
||||
pt-br: Atraso entre tentativas de rotação (segundos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: TEST_URL
|
||||
labelEn: Probe URL
|
||||
labelZh: 探测 URL
|
||||
label:
|
||||
en: Probe URL
|
||||
zh: 探测 URL
|
||||
zh-Hant: 探測 URL
|
||||
ja: プローブ URL
|
||||
ko: 점검 URL
|
||||
ru: URL проверки
|
||||
ms: URL ujian
|
||||
pt-br: URL de verificação
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: IP_CHECK_URL
|
||||
labelEn: Egress IP Check URL
|
||||
labelZh: 出口 IP 检查 URL
|
||||
label:
|
||||
en: Egress IP Check URL
|
||||
zh: 出口 IP 检查 URL
|
||||
zh-Hant: 出口 IP 檢查 URL
|
||||
ja: 出口 IP 確認 URL
|
||||
ko: 출구 IP 확인 URL
|
||||
ru: URL проверки исходного IP
|
||||
ms: URL semakan IP keluar
|
||||
pt-br: URL de verificação do IP de saída
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_USER
|
||||
labelEn: SOCKS Username
|
||||
labelZh: SOCKS 用户名
|
||||
label:
|
||||
en: SOCKS Username
|
||||
zh: SOCKS 用户名
|
||||
zh-Hant: SOCKS 使用者名稱
|
||||
ja: SOCKS ユーザー名
|
||||
ko: SOCKS 사용자명
|
||||
ru: Имя пользователя SOCKS
|
||||
ms: Nama pengguna SOCKS
|
||||
pt-br: Usuário SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_PASS
|
||||
labelEn: SOCKS Password
|
||||
labelZh: SOCKS 密码
|
||||
label:
|
||||
en: SOCKS Password
|
||||
zh: SOCKS 密码
|
||||
zh-Hant: SOCKS 密碼
|
||||
ja: SOCKS パスワード
|
||||
ko: SOCKS 비밀번호
|
||||
ru: Пароль SOCKS
|
||||
ms: Kata laluan SOCKS
|
||||
pt-br: Senha SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: ENDPOINT_IP
|
||||
labelEn: WARP Endpoint
|
||||
labelZh: WARP 终结点
|
||||
label:
|
||||
en: WARP Endpoint
|
||||
zh: WARP 终结点
|
||||
zh-Hant: WARP 終結點
|
||||
ja: WARP エンドポイント
|
||||
ko: WARP 엔드포인트
|
||||
ru: WARP endpoint
|
||||
ms: Titik akhir WARP
|
||||
pt-br: Endpoint WARP
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: GH_PROXY
|
||||
labelEn: wgcf Download Proxy
|
||||
labelZh: wgcf 下载代理
|
||||
label:
|
||||
en: wgcf Download Proxy
|
||||
zh: wgcf 下载代理
|
||||
zh-Hant: wgcf 下載代理
|
||||
ja: wgcf ダウンロードプロキシ
|
||||
ko: wgcf 다운로드 프록시
|
||||
ru: Прокси для загрузки wgcf
|
||||
ms: Proksi muat turun wgcf
|
||||
pt-br: Proxy de download do wgcf
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: 100.64.0.0/10
|
||||
envKey: TAILSCALE_CIDR
|
||||
labelEn: Tailscale Return Route CIDR
|
||||
labelZh: Tailscale 回程路由 CIDR
|
||||
label:
|
||||
en: Tailscale Return Route CIDR
|
||||
zh: Tailscale 回程路由 CIDR
|
||||
zh-Hant: Tailscale 回程路由 CIDR
|
||||
ja: Tailscale 戻りルート CIDR
|
||||
ko: Tailscale 리턴 라우트 CIDR
|
||||
ru: CIDR обратного маршрута Tailscale
|
||||
ms: CIDR laluan balik Tailscale
|
||||
pt-br: CIDR de rota de retorno do Tailscale
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: 1280
|
||||
envKey: MTU
|
||||
labelEn: WireGuard MTU
|
||||
labelZh: WireGuard MTU
|
||||
label:
|
||||
en: WireGuard MTU
|
||||
zh: WireGuard MTU
|
||||
zh-Hant: WireGuard MTU
|
||||
ja: WireGuard MTU
|
||||
ko: WireGuard MTU
|
||||
ru: MTU WireGuard
|
||||
ms: MTU WireGuard
|
||||
pt-br: MTU do WireGuard
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: WARP_WGCF_CONF
|
||||
labelEn: WireGuard Config Content
|
||||
labelZh: WireGuard 配置内容
|
||||
label:
|
||||
en: WireGuard Config Content
|
||||
zh: WireGuard 配置内容
|
||||
zh-Hant: WireGuard 設定內容
|
||||
ja: WireGuard 設定内容
|
||||
ko: WireGuard 구성 내용
|
||||
ru: Содержимое конфигурации WireGuard
|
||||
ms: Kandungan konfigurasi WireGuard
|
||||
pt-br: Conteúdo da configuração WireGuard
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: Asia/Shanghai
|
||||
envKey: TZ
|
||||
labelEn: Timezone
|
||||
labelZh: 时区
|
||||
label:
|
||||
en: Timezone
|
||||
zh: 时区
|
||||
zh-Hant: 時區
|
||||
ja: タイムゾーン
|
||||
ko: 시간대
|
||||
ru: Часовой пояс
|
||||
ms: Zon waktu
|
||||
pt-br: Fuso horário
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ./data
|
||||
envKey: APP_DATA_DIR_1
|
||||
labelEn: WireGuard Data Directory
|
||||
labelZh: WireGuard 数据目录
|
||||
label:
|
||||
en: WireGuard Data Directory
|
||||
zh: WireGuard 数据目录
|
||||
zh-Hant: WireGuard 資料目錄
|
||||
ja: WireGuard データディレクトリ
|
||||
ko: WireGuard 데이터 디렉터리
|
||||
ru: Каталог данных WireGuard
|
||||
ms: Direktori data WireGuard
|
||||
pt-br: Diretório de dados do WireGuard
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
@@ -0,0 +1,58 @@
|
||||
services:
|
||||
microwarp:
|
||||
image: "ghcr.io/ccbkkb/microwarp:0.1.3"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_SOCKS5}:${BIND_PORT}"
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- BIND_ADDR=0.0.0.0
|
||||
- BIND_PORT=${BIND_PORT}
|
||||
- ROTATE_INTERVAL_MINUTES=${ROTATE_INTERVAL_MINUTES:-0}
|
||||
- ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS:-5}
|
||||
- ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS:-5}
|
||||
- TEST_URL=${TEST_URL}
|
||||
- IP_CHECK_URL=${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}
|
||||
- SOCKS_USER=${SOCKS_USER:-}
|
||||
- SOCKS_PASS=${SOCKS_PASS:-}
|
||||
- ENDPOINT_IP=${ENDPOINT_IP:-}
|
||||
- GH_PROXY=${GH_PROXY:-}
|
||||
- TAILSCALE_CIDR=${TAILSCALE_CIDR:-100.64.0.0/10}
|
||||
- MTU=${MTU}
|
||||
- WARP_WGCF_CONF=${WARP_WGCF_CONF:-}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- "${APP_DATA_DIR_1}:/etc/wireguard"
|
||||
- "./rotate.sh:/usr/local/bin/rotate.sh:ro"
|
||||
restart: always
|
||||
command: ["/bin/sh", "/usr/local/bin/rotate.sh"]
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
http-proxy:
|
||||
image: "gogost/gost:latest"
|
||||
container_name: ${CONTAINER_NAME}-http
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
depends_on:
|
||||
- microwarp
|
||||
command:
|
||||
- "-L"
|
||||
- "http://0.0.0.0:8080"
|
||||
- "-F"
|
||||
- "socks5://microwarp:${BIND_PORT}"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_HTTP_PROXY}:8080"
|
||||
restart: always
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
INTERVAL_MINUTES="${ROTATE_INTERVAL_MINUTES:-0}"
|
||||
ROTATE_MAX_ATTEMPTS="${ROTATE_MAX_ATTEMPTS:-5}"
|
||||
ROTATE_RETRY_DELAY_SECONDS="${ROTATE_RETRY_DELAY_SECONDS:-5}"
|
||||
TEST_URL="${TEST_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
IP_CHECK_URL="${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
SOCKS_ADDR="127.0.0.1:${BIND_PORT:-1080}"
|
||||
SOCKS_USER_VALUE="${SOCKS_USER:-}"
|
||||
SOCKS_PASS_VALUE="${SOCKS_PASS:-}"
|
||||
WG_DIR="/etc/wireguard"
|
||||
WG_CONF="${WG_DIR}/wg0.conf"
|
||||
PREVIOUS_CONF="${WG_CONF}.rotation-previous"
|
||||
FIXED_CONFIG="${WARP_WGCF_CONF:-}"
|
||||
LOG_PREFIX="[microwarp]"
|
||||
ENTRY_PID=""
|
||||
CURRENT_EGRESS_IP=""
|
||||
|
||||
log() {
|
||||
echo "${LOG_PREFIX} $*"
|
||||
}
|
||||
|
||||
is_non_negative_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_positive_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*|0)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_ipv4() {
|
||||
printf '%s\n' "$1" | awk -F. '
|
||||
NF == 4 {
|
||||
for (i = 1; i <= 4; i++) {
|
||||
if ($i !~ /^[0-9]+$/ || $i < 0 || $i > 255) exit 1
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
{ exit 1 }
|
||||
'
|
||||
}
|
||||
|
||||
probe_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
curl_via_socks "$TEST_URL" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
curl_via_socks() {
|
||||
url="$1"
|
||||
if [ -n "$SOCKS_USER_VALUE" ] && [ -n "$SOCKS_PASS_VALUE" ]; then
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" \
|
||||
--proxy-user "${SOCKS_USER_VALUE}:${SOCKS_PASS_VALUE}" "$url"
|
||||
return $?
|
||||
fi
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" "$url"
|
||||
}
|
||||
|
||||
get_egress_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
trace_output="$(curl_via_socks "$IP_CHECK_URL" 2>/dev/null)" || return 1
|
||||
current_ip="$(printf '%s\n' "$trace_output" |
|
||||
awk -F= '$1 == "ip" { print $2; exit }' | tr -d '\r')"
|
||||
is_ipv4 "$current_ip" && printf '%s\n' "$current_ip"
|
||||
}
|
||||
|
||||
record_egress_ip() {
|
||||
current_ip="$(get_egress_ip || true)"
|
||||
if [ -n "$current_ip" ]; then
|
||||
CURRENT_EGRESS_IP="$current_ip"
|
||||
log "current WARP egress IPv4: ${CURRENT_EGRESS_IP}"
|
||||
else
|
||||
CURRENT_EGRESS_IP=""
|
||||
log "unable to verify current WARP egress IPv4"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
inject_fixed_config() {
|
||||
if [ -z "$FIXED_CONFIG" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$WG_DIR"
|
||||
printf '%s\n' "$FIXED_CONFIG" > "$WG_CONF"
|
||||
chmod 600 "$WG_CONF" || true
|
||||
log "wg0.conf injected from WARP_WGCF_CONF"
|
||||
}
|
||||
|
||||
wait_for_proxy() {
|
||||
attempts=0
|
||||
while [ "$attempts" -lt 30 ]; do
|
||||
if [ -n "$ENTRY_PID" ] && ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
wait "$ENTRY_PID" || true
|
||||
ENTRY_PID=""
|
||||
return 1
|
||||
fi
|
||||
|
||||
if probe_ip; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
attempts=$((attempts + 1))
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
stop_runtime() {
|
||||
if [ -n "$ENTRY_PID" ] && kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
kill "$ENTRY_PID" 2>/dev/null || true
|
||||
wait "$ENTRY_PID" || true
|
||||
fi
|
||||
ENTRY_PID=""
|
||||
}
|
||||
|
||||
teardown_for_rotation() {
|
||||
stop_runtime
|
||||
if ip link show wg0 >/dev/null 2>&1; then
|
||||
wg-quick down wg0 >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f "$WG_CONF" "$WG_DIR/wgcf-account.toml" "$WG_DIR/wgcf-profile.conf" "$WG_DIR/extra.env"
|
||||
}
|
||||
|
||||
start_runtime() {
|
||||
CURRENT_EGRESS_IP=""
|
||||
inject_fixed_config
|
||||
log "starting MicroWARP runtime"
|
||||
/app/entrypoint.sh &
|
||||
ENTRY_PID=$!
|
||||
if ! wait_for_proxy; then
|
||||
log "runtime did not become ready in time"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
record_egress_ip
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "runtime exited while its egress was being checked"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
save_previous_config() {
|
||||
if [ -s "$WG_CONF" ]; then
|
||||
previous_tmp="${PREVIOUS_CONF}.tmp.$$"
|
||||
rm -f "$previous_tmp"
|
||||
if ! cp -p "$WG_CONF" "$previous_tmp"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
if ! mv -f "$previous_tmp" "$PREVIOUS_CONF"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
restore_previous_runtime() {
|
||||
if [ ! -s "$PREVIOUS_CONF" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "restoring the previous working WARP configuration"
|
||||
teardown_for_rotation
|
||||
if ! cp -p "$PREVIOUS_CONF" "$WG_CONF"; then
|
||||
log "unable to restore the previous WARP configuration"
|
||||
return 1
|
||||
fi
|
||||
start_runtime
|
||||
}
|
||||
|
||||
rotate_once() {
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "WARP runtime exited before the scheduled identity rebuild"
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_egress_ip
|
||||
old_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -z "$old_ip" ]; then
|
||||
log "scheduled identity rebuild skipped because the current WARP egress IPv4 could not be verified"
|
||||
return 0
|
||||
fi
|
||||
attempt=1
|
||||
if ! save_previous_config; then
|
||||
log "scheduled identity rebuild skipped because the working WARP configuration could not be backed up"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while [ "$attempt" -le "$ROTATE_MAX_ATTEMPTS" ]; do
|
||||
log "WARP identity rebuild attempt ${attempt}/${ROTATE_MAX_ATTEMPTS}"
|
||||
teardown_for_rotation
|
||||
|
||||
if start_runtime; then
|
||||
new_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -n "$new_ip" ] &&
|
||||
[ "$new_ip" != "$old_ip" ]; then
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
log "WARP egress IPv4 changed: ${old_ip:-unknown} -> ${new_ip}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$new_ip" ] && [ "$new_ip" = "$old_ip" ]; then
|
||||
log "WARP egress IPv4 stayed ${new_ip}; retrying identity rebuild"
|
||||
else
|
||||
log "WARP egress IPv4 could not be verified; retrying identity rebuild"
|
||||
fi
|
||||
else
|
||||
log "WARP runtime failed during identity rebuild attempt ${attempt}"
|
||||
fi
|
||||
|
||||
if [ "$attempt" -lt "$ROTATE_MAX_ATTEMPTS" ]; then
|
||||
sleep "$ROTATE_RETRY_DELAY_SECONDS"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
if restore_previous_runtime; then
|
||||
log "no different public IPv4 was observed; previous runtime restored"
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "rotation failed and no working runtime is available"
|
||||
return 1
|
||||
}
|
||||
|
||||
on_signal() {
|
||||
log "received stop signal"
|
||||
stop_runtime
|
||||
exit 0
|
||||
}
|
||||
|
||||
main() {
|
||||
interval="$INTERVAL_MINUTES"
|
||||
if ! is_non_negative_int "$interval"; then
|
||||
log "invalid ROTATE_INTERVAL_MINUTES=${interval}, fallback to 0"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
if ! is_positive_int "$ROTATE_MAX_ATTEMPTS"; then
|
||||
log "invalid ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS}, fallback to 5"
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
fi
|
||||
|
||||
if ! is_non_negative_int "$ROTATE_RETRY_DELAY_SECONDS"; then
|
||||
log "invalid ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS}, fallback to 5"
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
fi
|
||||
|
||||
if [ -n "$FIXED_CONFIG" ] && [ "$interval" -gt 0 ]; then
|
||||
log "WARP_WGCF_CONF is set, scheduled rotation disabled because the config is pinned"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
trap on_signal INT TERM
|
||||
|
||||
if ! start_runtime; then
|
||||
exit 1
|
||||
fi
|
||||
if [ "$interval" -eq 0 ]; then
|
||||
log "scheduled rotation disabled"
|
||||
wait "$ENTRY_PID"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
log "scheduled WARP identity rebuild enabled every ${interval} minute(s); egress verification allows ${ROTATE_MAX_ATTEMPTS} attempt(s)"
|
||||
while true; do
|
||||
sleep "$((interval * 60))"
|
||||
log "trigger scheduled WARP identity rebuild"
|
||||
if ! rotate_once; then
|
||||
log "rotation failed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
TARGET_VERSION_DIR="0.1.3-rotator-http"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
resource_dir_for_app() {
|
||||
local panel_root app_path app_key
|
||||
|
||||
case "${APP_ROOT}" in
|
||||
*/apps/local/*)
|
||||
panel_root="${APP_ROOT%%/apps/local/*}"
|
||||
app_path="${APP_ROOT#"${panel_root}/apps/local/"}"
|
||||
app_key="${app_path%%/*}"
|
||||
[[ -n "${panel_root}" && -n "${app_key}" ]] || return 1
|
||||
printf '%s/resource/apps/local/%s\n' "${panel_root}" "${app_key}"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_rotate_source() {
|
||||
local resource_dir candidate_upgrade candidate_rotate
|
||||
|
||||
if [[ -n "${ROTATE_SOURCE:-}" ]]; then
|
||||
printf '%s\n' "${ROTATE_SOURCE}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if resource_dir="$(resource_dir_for_app 2>/dev/null)" && [[ -d "${resource_dir}" ]]; then
|
||||
candidate_upgrade="${resource_dir}/${TARGET_VERSION_DIR}/scripts/upgrade.sh"
|
||||
candidate_rotate="${resource_dir}/${TARGET_VERSION_DIR}/rotate.sh"
|
||||
if [[ -f "${candidate_upgrade}" && -f "${candidate_rotate}" ]] \
|
||||
&& cmp -s "${SCRIPT_DIR}/upgrade.sh" "${candidate_upgrade}" \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${candidate_rotate}"; then
|
||||
printf '%s\n' "${candidate_rotate}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "${APP_ROOT}/rotate.sh" ]] \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${APP_ROOT}/rotate.sh"; then
|
||||
printf '%s\n' "${APP_ROOT}/rotate.sh"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
rotate_target="${APP_ROOT}/rotate.sh"
|
||||
rotate_source="$(find_rotate_source || true)"
|
||||
if [[ -z "${rotate_source}" || ! -f "${rotate_source}" ]]; then
|
||||
echo "Unable to locate the upgraded rotate.sh payload; refusing to keep the old script" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${rotate_source}" != "${rotate_target}" ]] \
|
||||
&& ! cmp -s "${rotate_source}" "${rotate_target}"; then
|
||||
rotate_tmp="$(mktemp "${rotate_target}.upgrade.XXXXXX")"
|
||||
# shellcheck disable=SC2317
|
||||
cleanup_rotate_tmp() {
|
||||
rm -f -- "${rotate_tmp}"
|
||||
}
|
||||
trap cleanup_rotate_tmp EXIT
|
||||
|
||||
cp -- "${rotate_source}" "${rotate_tmp}"
|
||||
if [[ -e "${rotate_target}" ]]; then
|
||||
chmod --reference="${rotate_target}" "${rotate_tmp}" 2>/dev/null || true
|
||||
else
|
||||
chmod 0644 "${rotate_tmp}"
|
||||
fi
|
||||
mv -f -- "${rotate_tmp}" "${rotate_target}"
|
||||
trap - EXIT
|
||||
echo "Updated ${rotate_target} from the target version payload"
|
||||
fi
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
exit 0
|
||||
@@ -0,0 +1,17 @@
|
||||
CONTAINER_NAME=microwarp
|
||||
PANEL_APP_PORT_SOCKS5=1080
|
||||
BIND_PORT=1080
|
||||
ROTATE_INTERVAL_MINUTES=0
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
TEST_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
IP_CHECK_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
SOCKS_USER=
|
||||
SOCKS_PASS=
|
||||
ENDPOINT_IP=
|
||||
GH_PROXY=
|
||||
TAILSCALE_CIDR=100.64.0.0/10
|
||||
MTU=1280
|
||||
WARP_WGCF_CONF=
|
||||
TZ=Asia/Shanghai
|
||||
APP_DATA_DIR_1=./data
|
||||
@@ -0,0 +1,260 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 1080
|
||||
envKey: PANEL_APP_PORT_SOCKS5
|
||||
labelEn: SOCKS5 Port
|
||||
labelZh: SOCKS5 端口
|
||||
label:
|
||||
en: SOCKS5 Port
|
||||
zh: SOCKS5 端口
|
||||
zh-Hant: SOCKS5 連接埠
|
||||
ja: SOCKS5 ポート
|
||||
ko: SOCKS5 포트
|
||||
ru: Порт SOCKS5
|
||||
ms: Port SOCKS5
|
||||
pt-br: Porta SOCKS5
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 1080
|
||||
envKey: BIND_PORT
|
||||
labelEn: Internal Bind Port
|
||||
labelZh: 容器内部绑定端口
|
||||
label:
|
||||
en: Internal Bind Port
|
||||
zh: 容器内部绑定端口
|
||||
zh-Hant: 容器內部綁定連接埠
|
||||
ja: コンテナ内部バインドポート
|
||||
ko: 컨테이너 내부 바인드 포트
|
||||
ru: Внутренний порт привязки контейнера
|
||||
ms: Port bind dalaman kontena
|
||||
pt-br: Porta interna de bind do contêiner
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 0
|
||||
envKey: ROTATE_INTERVAL_MINUTES
|
||||
labelEn: Rotate Interval Minutes
|
||||
labelZh: 轮换间隔(分钟)
|
||||
label:
|
||||
en: Rotate Interval Minutes
|
||||
zh: 轮换间隔(分钟)
|
||||
zh-Hant: 輪換間隔(分鐘)
|
||||
ja: ローテーション間隔(分)
|
||||
ko: 로테이션 간격(분)
|
||||
ru: Интервал ротации (минуты)
|
||||
ms: Selang putaran (minit)
|
||||
pt-br: Intervalo de rotação (minutos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_MAX_ATTEMPTS
|
||||
labelEn: Rotation Attempts
|
||||
labelZh: 轮换尝试次数
|
||||
label:
|
||||
en: Rotation Attempts
|
||||
zh: 轮换尝试次数
|
||||
zh-Hant: 輪換嘗試次數
|
||||
ja: ローテーション試行回数
|
||||
ko: 로테이션 시도 횟수
|
||||
ru: Количество попыток ротации
|
||||
ms: Bilangan percubaan putaran
|
||||
pt-br: Número de tentativas de rotação
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_RETRY_DELAY_SECONDS
|
||||
labelEn: Rotation Retry Delay Seconds
|
||||
labelZh: 轮换重试间隔(秒)
|
||||
label:
|
||||
en: Rotation Retry Delay Seconds
|
||||
zh: 轮换重试间隔(秒)
|
||||
zh-Hant: 輪換重試間隔(秒)
|
||||
ja: ローテーション再試行間隔(秒)
|
||||
ko: 로테이션 재시도 간격(초)
|
||||
ru: Задержка между попытками ротации (секунды)
|
||||
ms: Kelewatan percubaan semula putaran (saat)
|
||||
pt-br: Atraso entre tentativas de rotação (segundos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: TEST_URL
|
||||
labelEn: Probe URL
|
||||
labelZh: 探测 URL
|
||||
label:
|
||||
en: Probe URL
|
||||
zh: 探测 URL
|
||||
zh-Hant: 探測 URL
|
||||
ja: プローブ URL
|
||||
ko: 점검 URL
|
||||
ru: URL проверки
|
||||
ms: URL ujian
|
||||
pt-br: URL de verificação
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: IP_CHECK_URL
|
||||
labelEn: Egress IP Check URL
|
||||
labelZh: 出口 IP 检查 URL
|
||||
label:
|
||||
en: Egress IP Check URL
|
||||
zh: 出口 IP 检查 URL
|
||||
zh-Hant: 出口 IP 檢查 URL
|
||||
ja: 出口 IP 確認 URL
|
||||
ko: 출구 IP 확인 URL
|
||||
ru: URL проверки исходного IP
|
||||
ms: URL semakan IP keluar
|
||||
pt-br: URL de verificação do IP de saída
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_USER
|
||||
labelEn: SOCKS Username
|
||||
labelZh: SOCKS 用户名
|
||||
label:
|
||||
en: SOCKS Username
|
||||
zh: SOCKS 用户名
|
||||
zh-Hant: SOCKS 使用者名稱
|
||||
ja: SOCKS ユーザー名
|
||||
ko: SOCKS 사용자명
|
||||
ru: Имя пользователя SOCKS
|
||||
ms: Nama pengguna SOCKS
|
||||
pt-br: Usuário SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_PASS
|
||||
labelEn: SOCKS Password
|
||||
labelZh: SOCKS 密码
|
||||
label:
|
||||
en: SOCKS Password
|
||||
zh: SOCKS 密码
|
||||
zh-Hant: SOCKS 密碼
|
||||
ja: SOCKS パスワード
|
||||
ko: SOCKS 비밀번호
|
||||
ru: Пароль SOCKS
|
||||
ms: Kata laluan SOCKS
|
||||
pt-br: Senha SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: ENDPOINT_IP
|
||||
labelEn: WARP Endpoint
|
||||
labelZh: WARP 终结点
|
||||
label:
|
||||
en: WARP Endpoint
|
||||
zh: WARP 终结点
|
||||
zh-Hant: WARP 終結點
|
||||
ja: WARP エンドポイント
|
||||
ko: WARP 엔드포인트
|
||||
ru: WARP endpoint
|
||||
ms: Titik akhir WARP
|
||||
pt-br: Endpoint WARP
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: GH_PROXY
|
||||
labelEn: wgcf Download Proxy
|
||||
labelZh: wgcf 下载代理
|
||||
label:
|
||||
en: wgcf Download Proxy
|
||||
zh: wgcf 下载代理
|
||||
zh-Hant: wgcf 下載代理
|
||||
ja: wgcf ダウンロードプロキシ
|
||||
ko: wgcf 다운로드 프록시
|
||||
ru: Прокси для загрузки wgcf
|
||||
ms: Proksi muat turun wgcf
|
||||
pt-br: Proxy de download do wgcf
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: 100.64.0.0/10
|
||||
envKey: TAILSCALE_CIDR
|
||||
labelEn: Tailscale Return Route CIDR
|
||||
labelZh: Tailscale 回程路由 CIDR
|
||||
label:
|
||||
en: Tailscale Return Route CIDR
|
||||
zh: Tailscale 回程路由 CIDR
|
||||
zh-Hant: Tailscale 回程路由 CIDR
|
||||
ja: Tailscale 戻りルート CIDR
|
||||
ko: Tailscale 리턴 라우트 CIDR
|
||||
ru: CIDR обратного маршрута Tailscale
|
||||
ms: CIDR laluan balik Tailscale
|
||||
pt-br: CIDR de rota de retorno do Tailscale
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: 1280
|
||||
envKey: MTU
|
||||
labelEn: WireGuard MTU
|
||||
labelZh: WireGuard MTU
|
||||
label:
|
||||
en: WireGuard MTU
|
||||
zh: WireGuard MTU
|
||||
zh-Hant: WireGuard MTU
|
||||
ja: WireGuard MTU
|
||||
ko: WireGuard MTU
|
||||
ru: MTU WireGuard
|
||||
ms: MTU WireGuard
|
||||
pt-br: MTU do WireGuard
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: WARP_WGCF_CONF
|
||||
labelEn: WireGuard Config Content
|
||||
labelZh: WireGuard 配置内容
|
||||
label:
|
||||
en: WireGuard Config Content
|
||||
zh: WireGuard 配置内容
|
||||
zh-Hant: WireGuard 設定內容
|
||||
ja: WireGuard 設定内容
|
||||
ko: WireGuard 구성 내용
|
||||
ru: Содержимое конфигурации WireGuard
|
||||
ms: Kandungan konfigurasi WireGuard
|
||||
pt-br: Conteúdo da configuração WireGuard
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: Asia/Shanghai
|
||||
envKey: TZ
|
||||
labelEn: Timezone
|
||||
labelZh: 时区
|
||||
label:
|
||||
en: Timezone
|
||||
zh: 时区
|
||||
zh-Hant: 時區
|
||||
ja: タイムゾーン
|
||||
ko: 시간대
|
||||
ru: Часовой пояс
|
||||
ms: Zon waktu
|
||||
pt-br: Fuso horário
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ./data
|
||||
envKey: APP_DATA_DIR_1
|
||||
labelEn: WireGuard Data Directory
|
||||
labelZh: WireGuard 数据目录
|
||||
label:
|
||||
en: WireGuard Data Directory
|
||||
zh: WireGuard 数据目录
|
||||
zh-Hant: WireGuard 資料目錄
|
||||
ja: WireGuard データディレクトリ
|
||||
ko: WireGuard 데이터 디렉터리
|
||||
ru: Каталог данных WireGuard
|
||||
ms: Direktori data WireGuard
|
||||
pt-br: Diretório de dados do WireGuard
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
@@ -0,0 +1,40 @@
|
||||
services:
|
||||
microwarp:
|
||||
image: "ghcr.io/ccbkkb/microwarp:0.1.3"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_SOCKS5}:${BIND_PORT}"
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- BIND_ADDR=0.0.0.0
|
||||
- BIND_PORT=${BIND_PORT}
|
||||
- ROTATE_INTERVAL_MINUTES=${ROTATE_INTERVAL_MINUTES:-0}
|
||||
- ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS:-5}
|
||||
- ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS:-5}
|
||||
- TEST_URL=${TEST_URL}
|
||||
- IP_CHECK_URL=${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}
|
||||
- SOCKS_USER=${SOCKS_USER:-}
|
||||
- SOCKS_PASS=${SOCKS_PASS:-}
|
||||
- ENDPOINT_IP=${ENDPOINT_IP:-}
|
||||
- GH_PROXY=${GH_PROXY:-}
|
||||
- TAILSCALE_CIDR=${TAILSCALE_CIDR:-100.64.0.0/10}
|
||||
- MTU=${MTU}
|
||||
- WARP_WGCF_CONF=${WARP_WGCF_CONF:-}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- "${APP_DATA_DIR_1}:/etc/wireguard"
|
||||
- "./rotate.sh:/usr/local/bin/rotate.sh:ro"
|
||||
restart: always
|
||||
command: ["/bin/sh", "/usr/local/bin/rotate.sh"]
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
INTERVAL_MINUTES="${ROTATE_INTERVAL_MINUTES:-0}"
|
||||
ROTATE_MAX_ATTEMPTS="${ROTATE_MAX_ATTEMPTS:-5}"
|
||||
ROTATE_RETRY_DELAY_SECONDS="${ROTATE_RETRY_DELAY_SECONDS:-5}"
|
||||
TEST_URL="${TEST_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
IP_CHECK_URL="${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
SOCKS_ADDR="127.0.0.1:${BIND_PORT:-1080}"
|
||||
SOCKS_USER_VALUE="${SOCKS_USER:-}"
|
||||
SOCKS_PASS_VALUE="${SOCKS_PASS:-}"
|
||||
WG_DIR="/etc/wireguard"
|
||||
WG_CONF="${WG_DIR}/wg0.conf"
|
||||
PREVIOUS_CONF="${WG_CONF}.rotation-previous"
|
||||
FIXED_CONFIG="${WARP_WGCF_CONF:-}"
|
||||
LOG_PREFIX="[microwarp]"
|
||||
ENTRY_PID=""
|
||||
CURRENT_EGRESS_IP=""
|
||||
|
||||
log() {
|
||||
echo "${LOG_PREFIX} $*"
|
||||
}
|
||||
|
||||
is_non_negative_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_positive_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*|0)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_ipv4() {
|
||||
printf '%s\n' "$1" | awk -F. '
|
||||
NF == 4 {
|
||||
for (i = 1; i <= 4; i++) {
|
||||
if ($i !~ /^[0-9]+$/ || $i < 0 || $i > 255) exit 1
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
{ exit 1 }
|
||||
'
|
||||
}
|
||||
|
||||
probe_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
curl_via_socks "$TEST_URL" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
curl_via_socks() {
|
||||
url="$1"
|
||||
if [ -n "$SOCKS_USER_VALUE" ] && [ -n "$SOCKS_PASS_VALUE" ]; then
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" \
|
||||
--proxy-user "${SOCKS_USER_VALUE}:${SOCKS_PASS_VALUE}" "$url"
|
||||
return $?
|
||||
fi
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" "$url"
|
||||
}
|
||||
|
||||
get_egress_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
trace_output="$(curl_via_socks "$IP_CHECK_URL" 2>/dev/null)" || return 1
|
||||
current_ip="$(printf '%s\n' "$trace_output" |
|
||||
awk -F= '$1 == "ip" { print $2; exit }' | tr -d '\r')"
|
||||
is_ipv4 "$current_ip" && printf '%s\n' "$current_ip"
|
||||
}
|
||||
|
||||
record_egress_ip() {
|
||||
current_ip="$(get_egress_ip || true)"
|
||||
if [ -n "$current_ip" ]; then
|
||||
CURRENT_EGRESS_IP="$current_ip"
|
||||
log "current WARP egress IPv4: ${CURRENT_EGRESS_IP}"
|
||||
else
|
||||
CURRENT_EGRESS_IP=""
|
||||
log "unable to verify current WARP egress IPv4"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
inject_fixed_config() {
|
||||
if [ -z "$FIXED_CONFIG" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$WG_DIR"
|
||||
printf '%s\n' "$FIXED_CONFIG" > "$WG_CONF"
|
||||
chmod 600 "$WG_CONF" || true
|
||||
log "wg0.conf injected from WARP_WGCF_CONF"
|
||||
}
|
||||
|
||||
wait_for_proxy() {
|
||||
attempts=0
|
||||
while [ "$attempts" -lt 30 ]; do
|
||||
if [ -n "$ENTRY_PID" ] && ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
wait "$ENTRY_PID" || true
|
||||
ENTRY_PID=""
|
||||
return 1
|
||||
fi
|
||||
|
||||
if probe_ip; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
attempts=$((attempts + 1))
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
stop_runtime() {
|
||||
if [ -n "$ENTRY_PID" ] && kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
kill "$ENTRY_PID" 2>/dev/null || true
|
||||
wait "$ENTRY_PID" || true
|
||||
fi
|
||||
ENTRY_PID=""
|
||||
}
|
||||
|
||||
teardown_for_rotation() {
|
||||
stop_runtime
|
||||
if ip link show wg0 >/dev/null 2>&1; then
|
||||
wg-quick down wg0 >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f "$WG_CONF" "$WG_DIR/wgcf-account.toml" "$WG_DIR/wgcf-profile.conf" "$WG_DIR/extra.env"
|
||||
}
|
||||
|
||||
start_runtime() {
|
||||
CURRENT_EGRESS_IP=""
|
||||
inject_fixed_config
|
||||
log "starting MicroWARP runtime"
|
||||
/app/entrypoint.sh &
|
||||
ENTRY_PID=$!
|
||||
if ! wait_for_proxy; then
|
||||
log "runtime did not become ready in time"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
record_egress_ip
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "runtime exited while its egress was being checked"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
save_previous_config() {
|
||||
if [ -s "$WG_CONF" ]; then
|
||||
previous_tmp="${PREVIOUS_CONF}.tmp.$$"
|
||||
rm -f "$previous_tmp"
|
||||
if ! cp -p "$WG_CONF" "$previous_tmp"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
if ! mv -f "$previous_tmp" "$PREVIOUS_CONF"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
restore_previous_runtime() {
|
||||
if [ ! -s "$PREVIOUS_CONF" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "restoring the previous working WARP configuration"
|
||||
teardown_for_rotation
|
||||
if ! cp -p "$PREVIOUS_CONF" "$WG_CONF"; then
|
||||
log "unable to restore the previous WARP configuration"
|
||||
return 1
|
||||
fi
|
||||
start_runtime
|
||||
}
|
||||
|
||||
rotate_once() {
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "WARP runtime exited before the scheduled identity rebuild"
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_egress_ip
|
||||
old_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -z "$old_ip" ]; then
|
||||
log "scheduled identity rebuild skipped because the current WARP egress IPv4 could not be verified"
|
||||
return 0
|
||||
fi
|
||||
attempt=1
|
||||
if ! save_previous_config; then
|
||||
log "scheduled identity rebuild skipped because the working WARP configuration could not be backed up"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while [ "$attempt" -le "$ROTATE_MAX_ATTEMPTS" ]; do
|
||||
log "WARP identity rebuild attempt ${attempt}/${ROTATE_MAX_ATTEMPTS}"
|
||||
teardown_for_rotation
|
||||
|
||||
if start_runtime; then
|
||||
new_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -n "$new_ip" ] &&
|
||||
[ "$new_ip" != "$old_ip" ]; then
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
log "WARP egress IPv4 changed: ${old_ip:-unknown} -> ${new_ip}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$new_ip" ] && [ "$new_ip" = "$old_ip" ]; then
|
||||
log "WARP egress IPv4 stayed ${new_ip}; retrying identity rebuild"
|
||||
else
|
||||
log "WARP egress IPv4 could not be verified; retrying identity rebuild"
|
||||
fi
|
||||
else
|
||||
log "WARP runtime failed during identity rebuild attempt ${attempt}"
|
||||
fi
|
||||
|
||||
if [ "$attempt" -lt "$ROTATE_MAX_ATTEMPTS" ]; then
|
||||
sleep "$ROTATE_RETRY_DELAY_SECONDS"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
if restore_previous_runtime; then
|
||||
log "no different public IPv4 was observed; previous runtime restored"
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "rotation failed and no working runtime is available"
|
||||
return 1
|
||||
}
|
||||
|
||||
on_signal() {
|
||||
log "received stop signal"
|
||||
stop_runtime
|
||||
exit 0
|
||||
}
|
||||
|
||||
main() {
|
||||
interval="$INTERVAL_MINUTES"
|
||||
if ! is_non_negative_int "$interval"; then
|
||||
log "invalid ROTATE_INTERVAL_MINUTES=${interval}, fallback to 0"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
if ! is_positive_int "$ROTATE_MAX_ATTEMPTS"; then
|
||||
log "invalid ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS}, fallback to 5"
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
fi
|
||||
|
||||
if ! is_non_negative_int "$ROTATE_RETRY_DELAY_SECONDS"; then
|
||||
log "invalid ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS}, fallback to 5"
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
fi
|
||||
|
||||
if [ -n "$FIXED_CONFIG" ] && [ "$interval" -gt 0 ]; then
|
||||
log "WARP_WGCF_CONF is set, scheduled rotation disabled because the config is pinned"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
trap on_signal INT TERM
|
||||
|
||||
if ! start_runtime; then
|
||||
exit 1
|
||||
fi
|
||||
if [ "$interval" -eq 0 ]; then
|
||||
log "scheduled rotation disabled"
|
||||
wait "$ENTRY_PID"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
log "scheduled WARP identity rebuild enabled every ${interval} minute(s); egress verification allows ${ROTATE_MAX_ATTEMPTS} attempt(s)"
|
||||
while true; do
|
||||
sleep "$((interval * 60))"
|
||||
log "trigger scheduled WARP identity rebuild"
|
||||
if ! rotate_once; then
|
||||
log "rotation failed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
TARGET_VERSION_DIR="0.1.3-rotator"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
resource_dir_for_app() {
|
||||
local panel_root app_path app_key
|
||||
|
||||
case "${APP_ROOT}" in
|
||||
*/apps/local/*)
|
||||
panel_root="${APP_ROOT%%/apps/local/*}"
|
||||
app_path="${APP_ROOT#"${panel_root}/apps/local/"}"
|
||||
app_key="${app_path%%/*}"
|
||||
[[ -n "${panel_root}" && -n "${app_key}" ]] || return 1
|
||||
printf '%s/resource/apps/local/%s\n' "${panel_root}" "${app_key}"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_rotate_source() {
|
||||
local resource_dir candidate_upgrade candidate_rotate
|
||||
|
||||
if [[ -n "${ROTATE_SOURCE:-}" ]]; then
|
||||
printf '%s\n' "${ROTATE_SOURCE}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if resource_dir="$(resource_dir_for_app 2>/dev/null)" && [[ -d "${resource_dir}" ]]; then
|
||||
candidate_upgrade="${resource_dir}/${TARGET_VERSION_DIR}/scripts/upgrade.sh"
|
||||
candidate_rotate="${resource_dir}/${TARGET_VERSION_DIR}/rotate.sh"
|
||||
if [[ -f "${candidate_upgrade}" && -f "${candidate_rotate}" ]] \
|
||||
&& cmp -s "${SCRIPT_DIR}/upgrade.sh" "${candidate_upgrade}" \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${candidate_rotate}"; then
|
||||
printf '%s\n' "${candidate_rotate}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "${APP_ROOT}/rotate.sh" ]] \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${APP_ROOT}/rotate.sh"; then
|
||||
printf '%s\n' "${APP_ROOT}/rotate.sh"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
rotate_target="${APP_ROOT}/rotate.sh"
|
||||
rotate_source="$(find_rotate_source || true)"
|
||||
if [[ -z "${rotate_source}" || ! -f "${rotate_source}" ]]; then
|
||||
echo "Unable to locate the upgraded rotate.sh payload; refusing to keep the old script" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${rotate_source}" != "${rotate_target}" ]] \
|
||||
&& ! cmp -s "${rotate_source}" "${rotate_target}"; then
|
||||
rotate_tmp="$(mktemp "${rotate_target}.upgrade.XXXXXX")"
|
||||
# shellcheck disable=SC2317
|
||||
cleanup_rotate_tmp() {
|
||||
rm -f -- "${rotate_tmp}"
|
||||
}
|
||||
trap cleanup_rotate_tmp EXIT
|
||||
|
||||
cp -- "${rotate_source}" "${rotate_tmp}"
|
||||
if [[ -e "${rotate_target}" ]]; then
|
||||
chmod --reference="${rotate_target}" "${rotate_tmp}" 2>/dev/null || true
|
||||
else
|
||||
chmod 0644 "${rotate_tmp}"
|
||||
fi
|
||||
mv -f -- "${rotate_tmp}" "${rotate_target}"
|
||||
trap - EXIT
|
||||
echo "Updated ${rotate_target} from the target version payload"
|
||||
fi
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
exit 0
|
||||
@@ -0,0 +1,107 @@
|
||||
# MicroWARP(1Panel v2)
|
||||
|
||||
基于 `ghcr.io/ccbkkb/microwarp` 的轻量级 Cloudflare WARP 代理封装,统一收敛为 `microwarp` 单 key,并提供官方原版与定时轮换变体。
|
||||
|
||||
## 产品介绍
|
||||
|
||||
MicroWARP 将 Cloudflare WARP 封装为可在 1Panel 中直接部署的 SOCKS5 代理;HTTP 变体通过 GOST 额外提供 HTTP 代理端口,rotator 变体负责定时重建 WARP 身份。
|
||||
|
||||
## 主要功能
|
||||
|
||||
- 支持 SOCKS5 代理及可选用户名、密码认证
|
||||
- 支持 SOCKS5-only、SOCKS5 + HTTP、定时轮换三类组合
|
||||
- 轮换前后通过代理检查真实出口 IPv4,并在同 IP 或检查失败时重试
|
||||
- 重试耗尽后恢复上一次可用 WireGuard 配置
|
||||
|
||||
## 访问说明
|
||||
|
||||
- SOCKS5 端口由 `PANEL_APP_PORT_SOCKS5` 配置
|
||||
- HTTP 变体的 HTTP 代理端口由 `PANEL_APP_PORT_HTTP_PROXY` 配置
|
||||
- 代理服务不提供 Web 管理界面;请使用对应端口和 SOCKS5/HTTP 客户端连接
|
||||
- 默认不启用认证,公网部署时建议同时设置 `SOCKS_USER` 与 `SOCKS_PASS`
|
||||
|
||||
## 版本说明
|
||||
|
||||
### latest
|
||||
- 官方原版
|
||||
- 仅提供 SOCKS5 代理
|
||||
- 不做自动 IP 轮换,适合需要稳定出口身份的场景
|
||||
|
||||
### http-latest
|
||||
- 官方原版增强变体
|
||||
- 在 SOCKS5 基础上额外提供 HTTP 代理端口
|
||||
- 通过 `gogost/gost` sidecar 将 HTTP 转发到 SOCKS5
|
||||
|
||||
### 0.1.3-rotator
|
||||
- 基于官方原版增加定时 WARP 身份重建
|
||||
- 固定使用与当前 `latest` 清单一致的官方 MicroWARP `0.1.3` 镜像
|
||||
- 轮换通过删除旧 `wg0.conf` 与账号缓存后重新执行上游初始化完成
|
||||
- 每次轮换都会通过 SOCKS5 代理读取出口 IPv4;出口未变化或无法确认时自动重试
|
||||
- 达到尝试次数仍没有新出口时恢复上一次可用配置,避免留下不可用代理
|
||||
- 轮换时会有秒级短暂中断
|
||||
|
||||
### 0.1.3-rotator-http
|
||||
- 在 `0.1.3-rotator` 基础上额外提供 HTTP 代理端口
|
||||
|
||||
## 安装建议
|
||||
|
||||
- 只需官方 SOCKS5:安装 `latest`
|
||||
- 需官方 SOCKS5 + HTTP:安装 `http-latest`
|
||||
- 需要定时切换 WARP 身份:安装 `0.1.3-rotator`
|
||||
- 需要定时切换并同时提供 HTTP:安装 `0.1.3-rotator-http`
|
||||
|
||||
## 核心参数
|
||||
|
||||
- `PANEL_APP_PORT_SOCKS5`:SOCKS5 对外端口
|
||||
- `PANEL_APP_PORT_HTTP_PROXY`:HTTP 代理端口,仅 HTTP 变体使用
|
||||
- `APP_DATA_DIR_1`:WireGuard 持久化目录,保存 `wg0.conf` 与 `wgcf` 注册信息
|
||||
- `SOCKS_USER` / `SOCKS_PASS`:启用 SOCKS5 认证
|
||||
- `ENDPOINT_IP`:手动覆盖 WARP Endpoint
|
||||
- `GH_PROXY`:辅助 `wgcf` 下载
|
||||
- `TAILSCALE_CIDR`:Tailscale 回程路由 CIDR,默认 `100.64.0.0/10`
|
||||
- `MTU`:WireGuard MTU,默认 `1280`
|
||||
- `WARP_WGCF_CONF`:可选完整 WireGuard 配置内容;也可直接将 `wg0.conf` 放入持久化目录
|
||||
- `ROTATE_INTERVAL_MINUTES`:轮换间隔(分钟),仅 `rotator-*` 版本使用,`0` 表示关闭
|
||||
- `ROTATE_MAX_ATTEMPTS`:单次轮换最多重建身份的次数,默认 `5`
|
||||
- `ROTATE_RETRY_DELAY_SECONDS`:轮换重试间隔(秒),默认 `5`
|
||||
- `TEST_URL`:启动时通过 SOCKS5 探测代理的 URL
|
||||
- `IP_CHECK_URL`:读取出口 IPv4 的 URL,响应需要包含 `ip=<IPv4>`,默认使用 Cloudflare trace
|
||||
|
||||
## 使用说明
|
||||
|
||||
- 默认使用外部网络 `1panel-network`
|
||||
- 首次启动会自动注册 WARP 设备并生成 `/etc/wireguard/wg0.conf`
|
||||
- 建议保留 `APP_DATA_DIR_1` 持久化目录,避免每次重启都重新注册
|
||||
- 官方镜像内部支持通过 GitHub token 缓解 `wgcf` 版本查询的 API 限流,但本应用默认不在 1Panel 表单中暴露该参数,优先保持默认安装简洁稳定
|
||||
- 如果设置了 `WARP_WGCF_CONF`,`rotator-*` 版本会自动关闭定时轮换,因为该配置已固定出口身份
|
||||
- `rotator-*` 版本的轮换逻辑是完整重建 WARP 身份,而不是调用上游并不存在的 rotate API;会比较轮换前后的真实出口 IPv4
|
||||
- 配置了 `SOCKS_USER` 和 `SOCKS_PASS` 时,轮换检查会使用相同的 SOCKS5 认证
|
||||
|
||||
## 升级说明
|
||||
|
||||
- 已有 `rotator-latest` 与 `rotator-http-latest` 安装可分别直接升级到 `0.1.3-rotator` 与 `0.1.3-rotator-http`,不需要中间版本;新版本名用于确保 1Panel 真正提供升级入口
|
||||
- 旧轮换版本仍保留以兼容既有安装;新安装请选择带 `0.1.3` 前缀的版本
|
||||
- 升级会保留 `APP_DATA_DIR_1` 中的 `wg0.conf`、WARP 注册信息和用户自定义环境变量;升级前仍建议使用 1Panel 备份该目录
|
||||
- 1Panel 升级脚本会将当前版本的 `rotate.sh` 原子替换到已安装目录;若安装目录仍是旧脚本且无法定位新版载荷,升级会失败而不会继续运行旧轮换逻辑,重复执行则保持幂等
|
||||
- 新增的 `ROTATE_MAX_ATTEMPTS`、`ROTATE_RETRY_DELAY_SECONDS` 与 `IP_CHECK_URL` 在旧 `.env` 缺失时使用安全默认值,不会覆盖旧的轮换间隔、端口或认证配置
|
||||
- 升级后首次轮换可能产生秒级中断;若出口 IP 无法变化或无法确认,程序会恢复上一份可用配置
|
||||
|
||||
## Introduction
|
||||
|
||||
MicroWARP packages Cloudflare WARP as a 1Panel-ready SOCKS5 proxy. The HTTP variants add an HTTP proxy through GOST, while the rotator variants rebuild the WARP identity on a schedule and verify the real egress IPv4 through the proxy.
|
||||
|
||||
## Features
|
||||
|
||||
- SOCKS5 proxy with optional username/password authentication
|
||||
- SOCKS5-only, SOCKS5 + HTTP, and scheduled-rotation variants
|
||||
- Egress IPv4 verification before and after each rotation
|
||||
- Retries for unchanged or unverifiable egress, with rollback to the last working WireGuard configuration
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
- Existing `rotator-latest` and `rotator-http-latest` installations can upgrade directly to `0.1.3-rotator` and `0.1.3-rotator-http`; no intermediate version is required. The bumped package versions ensure that 1Panel exposes a real upgrade path.
|
||||
- The legacy rotator versions remain for existing-install compatibility. New installations should use the `0.1.3`-prefixed variants.
|
||||
- The upgrade preserves `APP_DATA_DIR_1`, including `wg0.conf`, WARP registration data, and existing user-defined environment values. Back up that directory through 1Panel before upgrading.
|
||||
- The 1Panel upgrade script atomically replaces the installed `rotate.sh` with the target-version payload. If the installed script is still old and that payload cannot be located, the upgrade fails instead of continuing with the old rotation logic; reruns are idempotent.
|
||||
- The new `ROTATE_MAX_ATTEMPTS`, `ROTATE_RETRY_DELAY_SECONDS`, and `IP_CHECK_URL` variables have safe Compose defaults when they are absent from an older `.env`; existing rotation interval, ports, and authentication settings are not overwritten.
|
||||
- The first rotation after an upgrade may briefly interrupt the proxy. If a new or verifiable egress IP cannot be obtained, the last working configuration is restored.
|
||||
@@ -0,0 +1,32 @@
|
||||
name: MicroWARP
|
||||
tags:
|
||||
- 工具
|
||||
title: 轻量级 Cloudflare WARP SOCKS5 代理,附带 HTTP 与定时轮换变体
|
||||
type: 工具
|
||||
description: 轻量级 Cloudflare WARP SOCKS5 代理,附带 HTTP 与定时轮换变体
|
||||
additionalProperties:
|
||||
key: microwarp
|
||||
name: MicroWARP
|
||||
tags:
|
||||
- Tool
|
||||
shortDescZh: 轻量级 Cloudflare WARP SOCKS5 代理,附带 HTTP 与定时轮换变体
|
||||
shortDescEn: Lightweight Cloudflare WARP SOCKS5 proxy with official, HTTP, and rotator variants
|
||||
description:
|
||||
en: Lightweight Cloudflare WARP SOCKS5 proxy with official, HTTP, and rotator variants
|
||||
zh: 轻量级 Cloudflare WARP SOCKS5 代理,附带 HTTP 与定时轮换变体
|
||||
zh-Hant: 輕量級 Cloudflare WARP SOCKS5 代理,附帶 HTTP 與定時輪換變體
|
||||
ja: 公式版、HTTP 変種、定期ローテーション変種を備えた軽量 Cloudflare WARP SOCKS5 プロキシ
|
||||
ko: 공식판, HTTP 변형, 주기적 로테이터 변형을 함께 제공하는 경량 Cloudflare WARP SOCKS5 프록시
|
||||
ru: Легковесный SOCKS5-прокси Cloudflare WARP с официальным, HTTP- и rotator-вариантами
|
||||
ms: Proksi SOCKS5 Cloudflare WARP ringan dengan varian rasmi, HTTP dan rotator
|
||||
pt-br: Proxy SOCKS5 leve do Cloudflare WARP com variantes oficial, HTTP e rotator
|
||||
type: tool
|
||||
crossVersionUpdate: true
|
||||
limit: 0
|
||||
recommend: 0
|
||||
website: https://github.com/ccbkkb/MicroWARP
|
||||
github: https://github.com/ccbkkb/MicroWARP
|
||||
document: https://github.com/ccbkkb/MicroWARP#readme
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
@@ -0,0 +1,13 @@
|
||||
CONTAINER_NAME=microwarp
|
||||
PANEL_APP_PORT_SOCKS5=1080
|
||||
PANEL_APP_PORT_HTTP_PROXY=8080
|
||||
BIND_PORT=1080
|
||||
SOCKS_USER=
|
||||
SOCKS_PASS=
|
||||
ENDPOINT_IP=
|
||||
GH_PROXY=
|
||||
TAILSCALE_CIDR=100.64.0.0/10
|
||||
MTU=1280
|
||||
WARP_WGCF_CONF=
|
||||
TZ=Asia/Shanghai
|
||||
APP_DATA_DIR_1=./data
|
||||
@@ -0,0 +1,197 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 1080
|
||||
envKey: PANEL_APP_PORT_SOCKS5
|
||||
labelEn: SOCKS5 Port
|
||||
labelZh: SOCKS5 端口
|
||||
label:
|
||||
en: SOCKS5 Port
|
||||
zh: SOCKS5 端口
|
||||
zh-Hant: SOCKS5 連接埠
|
||||
ja: SOCKS5 ポート
|
||||
ko: SOCKS5 포트
|
||||
ru: Порт SOCKS5
|
||||
ms: Port SOCKS5
|
||||
pt-br: Porta SOCKS5
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 8080
|
||||
envKey: PANEL_APP_PORT_HTTP_PROXY
|
||||
labelEn: HTTP Proxy Port
|
||||
labelZh: HTTP 代理端口
|
||||
label:
|
||||
en: HTTP Proxy Port
|
||||
zh: HTTP 代理端口
|
||||
zh-Hant: HTTP 代理連接埠
|
||||
ja: HTTP プロキシポート
|
||||
ko: HTTP 프록시 포트
|
||||
ru: Порт HTTP-прокси
|
||||
ms: Port proksi HTTP
|
||||
pt-br: Porta do proxy HTTP
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 1080
|
||||
envKey: BIND_PORT
|
||||
labelEn: Internal Bind Port
|
||||
labelZh: 容器内部绑定端口
|
||||
label:
|
||||
en: Internal Bind Port
|
||||
zh: 容器内部绑定端口
|
||||
zh-Hant: 容器內部綁定連接埠
|
||||
ja: コンテナ内部バインドポート
|
||||
ko: 컨테이너 내부 바인드 포트
|
||||
ru: Внутренний порт привязки контейнера
|
||||
ms: Port bind dalaman kontena
|
||||
pt-br: Porta interna de bind do contêiner
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_USER
|
||||
labelEn: SOCKS Username
|
||||
labelZh: SOCKS 用户名
|
||||
label:
|
||||
en: SOCKS Username
|
||||
zh: SOCKS 用户名
|
||||
zh-Hant: SOCKS 使用者名稱
|
||||
ja: SOCKS ユーザー名
|
||||
ko: SOCKS 사용자명
|
||||
ru: Имя пользователя SOCKS
|
||||
ms: Nama pengguna SOCKS
|
||||
pt-br: Usuário SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_PASS
|
||||
labelEn: SOCKS Password
|
||||
labelZh: SOCKS 密码
|
||||
label:
|
||||
en: SOCKS Password
|
||||
zh: SOCKS 密码
|
||||
zh-Hant: SOCKS 密碼
|
||||
ja: SOCKS パスワード
|
||||
ko: SOCKS 비밀번호
|
||||
ru: Пароль SOCKS
|
||||
ms: Kata laluan SOCKS
|
||||
pt-br: Senha SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: ENDPOINT_IP
|
||||
labelEn: WARP Endpoint
|
||||
labelZh: WARP 终结点
|
||||
label:
|
||||
en: WARP Endpoint
|
||||
zh: WARP 终结点
|
||||
zh-Hant: WARP 終結點
|
||||
ja: WARP エンドポイント
|
||||
ko: WARP 엔드포인트
|
||||
ru: WARP endpoint
|
||||
ms: Titik akhir WARP
|
||||
pt-br: Endpoint WARP
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: GH_PROXY
|
||||
labelEn: wgcf Download Proxy
|
||||
labelZh: wgcf 下载代理
|
||||
label:
|
||||
en: wgcf Download Proxy
|
||||
zh: wgcf 下载代理
|
||||
zh-Hant: wgcf 下載代理
|
||||
ja: wgcf ダウンロードプロキシ
|
||||
ko: wgcf 다운로드 프록시
|
||||
ru: Прокси для загрузки wgcf
|
||||
ms: Proksi muat turun wgcf
|
||||
pt-br: Proxy de download do wgcf
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: 100.64.0.0/10
|
||||
envKey: TAILSCALE_CIDR
|
||||
labelEn: Tailscale Return Route CIDR
|
||||
labelZh: Tailscale 回程路由 CIDR
|
||||
label:
|
||||
en: Tailscale Return Route CIDR
|
||||
zh: Tailscale 回程路由 CIDR
|
||||
zh-Hant: Tailscale 回程路由 CIDR
|
||||
ja: Tailscale 戻りルート CIDR
|
||||
ko: Tailscale 리턴 라우트 CIDR
|
||||
ru: CIDR обратного маршрута Tailscale
|
||||
ms: CIDR laluan balik Tailscale
|
||||
pt-br: CIDR de rota de retorno do Tailscale
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: 1280
|
||||
envKey: MTU
|
||||
labelEn: WireGuard MTU
|
||||
labelZh: WireGuard MTU
|
||||
label:
|
||||
en: WireGuard MTU
|
||||
zh: WireGuard MTU
|
||||
zh-Hant: WireGuard MTU
|
||||
ja: WireGuard MTU
|
||||
ko: WireGuard MTU
|
||||
ru: MTU WireGuard
|
||||
ms: MTU WireGuard
|
||||
pt-br: MTU do WireGuard
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: WARP_WGCF_CONF
|
||||
labelEn: WireGuard Config Content
|
||||
labelZh: WireGuard 配置内容
|
||||
label:
|
||||
en: WireGuard Config Content
|
||||
zh: WireGuard 配置内容
|
||||
zh-Hant: WireGuard 設定內容
|
||||
ja: WireGuard 設定内容
|
||||
ko: WireGuard 구성 내용
|
||||
ru: Содержимое конфигурации WireGuard
|
||||
ms: Kandungan konfigurasi WireGuard
|
||||
pt-br: Conteúdo da configuração WireGuard
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: Asia/Shanghai
|
||||
envKey: TZ
|
||||
labelEn: Timezone
|
||||
labelZh: 时区
|
||||
label:
|
||||
en: Timezone
|
||||
zh: 时区
|
||||
zh-Hant: 時區
|
||||
ja: タイムゾーン
|
||||
ko: 시간대
|
||||
ru: Часовой пояс
|
||||
ms: Zon waktu
|
||||
pt-br: Fuso horário
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ./data
|
||||
envKey: APP_DATA_DIR_1
|
||||
labelEn: WireGuard Data Directory
|
||||
labelZh: WireGuard 数据目录
|
||||
label:
|
||||
en: WireGuard Data Directory
|
||||
zh: WireGuard 数据目录
|
||||
zh-Hant: WireGuard 資料目錄
|
||||
ja: WireGuard データディレクトリ
|
||||
ko: WireGuard 데이터 디렉터리
|
||||
ru: Каталог данных WireGuard
|
||||
ms: Direktori data WireGuard
|
||||
pt-br: Diretório de dados do WireGuard
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
@@ -0,0 +1,51 @@
|
||||
services:
|
||||
microwarp:
|
||||
image: "ghcr.io/ccbkkb/microwarp:latest"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_SOCKS5}:${BIND_PORT}"
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- BIND_ADDR=0.0.0.0
|
||||
- BIND_PORT=${BIND_PORT}
|
||||
- SOCKS_USER=${SOCKS_USER:-}
|
||||
- SOCKS_PASS=${SOCKS_PASS:-}
|
||||
- ENDPOINT_IP=${ENDPOINT_IP:-}
|
||||
- GH_PROXY=${GH_PROXY:-}
|
||||
- TAILSCALE_CIDR=${TAILSCALE_CIDR:-100.64.0.0/10}
|
||||
- MTU=${MTU}
|
||||
- WARP_WGCF_CONF=${WARP_WGCF_CONF:-}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- "${APP_DATA_DIR_1}:/etc/wireguard"
|
||||
restart: always
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
http-proxy:
|
||||
image: "gogost/gost:latest"
|
||||
container_name: ${CONTAINER_NAME}-http
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
depends_on:
|
||||
- microwarp
|
||||
command:
|
||||
- "-L"
|
||||
- "http://0.0.0.0:8080"
|
||||
- "-F"
|
||||
- "socks5://microwarp:${BIND_PORT}"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_HTTP_PROXY}:8080"
|
||||
restart: always
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
exit 0
|
||||
@@ -0,0 +1,12 @@
|
||||
CONTAINER_NAME=microwarp
|
||||
PANEL_APP_PORT_SOCKS5=1080
|
||||
BIND_PORT=1080
|
||||
SOCKS_USER=
|
||||
SOCKS_PASS=
|
||||
ENDPOINT_IP=
|
||||
GH_PROXY=
|
||||
TAILSCALE_CIDR=100.64.0.0/10
|
||||
MTU=1280
|
||||
WARP_WGCF_CONF=
|
||||
TZ=Asia/Shanghai
|
||||
APP_DATA_DIR_1=./data
|
||||
@@ -0,0 +1,180 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 1080
|
||||
envKey: PANEL_APP_PORT_SOCKS5
|
||||
labelEn: SOCKS5 Port
|
||||
labelZh: SOCKS5 端口
|
||||
label:
|
||||
en: SOCKS5 Port
|
||||
zh: SOCKS5 端口
|
||||
zh-Hant: SOCKS5 連接埠
|
||||
ja: SOCKS5 ポート
|
||||
ko: SOCKS5 포트
|
||||
ru: Порт SOCKS5
|
||||
ms: Port SOCKS5
|
||||
pt-br: Porta SOCKS5
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 1080
|
||||
envKey: BIND_PORT
|
||||
labelEn: Internal Bind Port
|
||||
labelZh: 容器内部绑定端口
|
||||
label:
|
||||
en: Internal Bind Port
|
||||
zh: 容器内部绑定端口
|
||||
zh-Hant: 容器內部綁定連接埠
|
||||
ja: コンテナ内部バインドポート
|
||||
ko: 컨테이너 내부 바인드 포트
|
||||
ru: Внутренний порт привязки контейнера
|
||||
ms: Port bind dalaman kontena
|
||||
pt-br: Porta interna de bind do contêiner
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_USER
|
||||
labelEn: SOCKS Username
|
||||
labelZh: SOCKS 用户名
|
||||
label:
|
||||
en: SOCKS Username
|
||||
zh: SOCKS 用户名
|
||||
zh-Hant: SOCKS 使用者名稱
|
||||
ja: SOCKS ユーザー名
|
||||
ko: SOCKS 사용자명
|
||||
ru: Имя пользователя SOCKS
|
||||
ms: Nama pengguna SOCKS
|
||||
pt-br: Usuário SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_PASS
|
||||
labelEn: SOCKS Password
|
||||
labelZh: SOCKS 密码
|
||||
label:
|
||||
en: SOCKS Password
|
||||
zh: SOCKS 密码
|
||||
zh-Hant: SOCKS 密碼
|
||||
ja: SOCKS パスワード
|
||||
ko: SOCKS 비밀번호
|
||||
ru: Пароль SOCKS
|
||||
ms: Kata laluan SOCKS
|
||||
pt-br: Senha SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: ENDPOINT_IP
|
||||
labelEn: WARP Endpoint
|
||||
labelZh: WARP 终结点
|
||||
label:
|
||||
en: WARP Endpoint
|
||||
zh: WARP 终结点
|
||||
zh-Hant: WARP 終結點
|
||||
ja: WARP エンドポイント
|
||||
ko: WARP 엔드포인트
|
||||
ru: WARP endpoint
|
||||
ms: Titik akhir WARP
|
||||
pt-br: Endpoint WARP
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: GH_PROXY
|
||||
labelEn: wgcf Download Proxy
|
||||
labelZh: wgcf 下载代理
|
||||
label:
|
||||
en: wgcf Download Proxy
|
||||
zh: wgcf 下载代理
|
||||
zh-Hant: wgcf 下載代理
|
||||
ja: wgcf ダウンロードプロキシ
|
||||
ko: wgcf 다운로드 프록시
|
||||
ru: Прокси для загрузки wgcf
|
||||
ms: Proksi muat turun wgcf
|
||||
pt-br: Proxy de download do wgcf
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: 100.64.0.0/10
|
||||
envKey: TAILSCALE_CIDR
|
||||
labelEn: Tailscale Return Route CIDR
|
||||
labelZh: Tailscale 回程路由 CIDR
|
||||
label:
|
||||
en: Tailscale Return Route CIDR
|
||||
zh: Tailscale 回程路由 CIDR
|
||||
zh-Hant: Tailscale 回程路由 CIDR
|
||||
ja: Tailscale 戻りルート CIDR
|
||||
ko: Tailscale 리턴 라우트 CIDR
|
||||
ru: CIDR обратного маршрута Tailscale
|
||||
ms: CIDR laluan balik Tailscale
|
||||
pt-br: CIDR de rota de retorno do Tailscale
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: 1280
|
||||
envKey: MTU
|
||||
labelEn: WireGuard MTU
|
||||
labelZh: WireGuard MTU
|
||||
label:
|
||||
en: WireGuard MTU
|
||||
zh: WireGuard MTU
|
||||
zh-Hant: WireGuard MTU
|
||||
ja: WireGuard MTU
|
||||
ko: WireGuard MTU
|
||||
ru: MTU WireGuard
|
||||
ms: MTU WireGuard
|
||||
pt-br: MTU do WireGuard
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: WARP_WGCF_CONF
|
||||
labelEn: WireGuard Config Content
|
||||
labelZh: WireGuard 配置内容
|
||||
label:
|
||||
en: WireGuard Config Content
|
||||
zh: WireGuard 配置内容
|
||||
zh-Hant: WireGuard 設定內容
|
||||
ja: WireGuard 設定内容
|
||||
ko: WireGuard 구성 내용
|
||||
ru: Содержимое конфигурации WireGuard
|
||||
ms: Kandungan konfigurasi WireGuard
|
||||
pt-br: Conteúdo da configuração WireGuard
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: Asia/Shanghai
|
||||
envKey: TZ
|
||||
labelEn: Timezone
|
||||
labelZh: 时区
|
||||
label:
|
||||
en: Timezone
|
||||
zh: 时区
|
||||
zh-Hant: 時區
|
||||
ja: タイムゾーン
|
||||
ko: 시간대
|
||||
ru: Часовой пояс
|
||||
ms: Zon waktu
|
||||
pt-br: Fuso horário
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ./data
|
||||
envKey: APP_DATA_DIR_1
|
||||
labelEn: WireGuard Data Directory
|
||||
labelZh: WireGuard 数据目录
|
||||
label:
|
||||
en: WireGuard Data Directory
|
||||
zh: WireGuard 数据目录
|
||||
zh-Hant: WireGuard 資料目錄
|
||||
ja: WireGuard データディレクトリ
|
||||
ko: WireGuard 데이터 디렉터리
|
||||
ru: Каталог данных WireGuard
|
||||
ms: Direktori data WireGuard
|
||||
pt-br: Diretório de dados do WireGuard
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
microwarp:
|
||||
image: "ghcr.io/ccbkkb/microwarp:latest"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_SOCKS5}:${BIND_PORT}"
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- BIND_ADDR=0.0.0.0
|
||||
- BIND_PORT=${BIND_PORT}
|
||||
- SOCKS_USER=${SOCKS_USER:-}
|
||||
- SOCKS_PASS=${SOCKS_PASS:-}
|
||||
- ENDPOINT_IP=${ENDPOINT_IP:-}
|
||||
- GH_PROXY=${GH_PROXY:-}
|
||||
- TAILSCALE_CIDR=${TAILSCALE_CIDR:-100.64.0.0/10}
|
||||
- MTU=${MTU}
|
||||
- WARP_WGCF_CONF=${WARP_WGCF_CONF:-}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- "${APP_DATA_DIR_1}:/etc/wireguard"
|
||||
restart: always
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
exit 0
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,18 @@
|
||||
CONTAINER_NAME=microwarp
|
||||
PANEL_APP_PORT_SOCKS5=1080
|
||||
PANEL_APP_PORT_HTTP_PROXY=8080
|
||||
BIND_PORT=1080
|
||||
ROTATE_INTERVAL_MINUTES=0
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
TEST_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
IP_CHECK_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
SOCKS_USER=
|
||||
SOCKS_PASS=
|
||||
ENDPOINT_IP=
|
||||
GH_PROXY=
|
||||
TAILSCALE_CIDR=100.64.0.0/10
|
||||
MTU=1280
|
||||
WARP_WGCF_CONF=
|
||||
TZ=Asia/Shanghai
|
||||
APP_DATA_DIR_1=./data
|
||||
@@ -0,0 +1,277 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 1080
|
||||
envKey: PANEL_APP_PORT_SOCKS5
|
||||
labelEn: SOCKS5 Port
|
||||
labelZh: SOCKS5 端口
|
||||
label:
|
||||
en: SOCKS5 Port
|
||||
zh: SOCKS5 端口
|
||||
zh-Hant: SOCKS5 連接埠
|
||||
ja: SOCKS5 ポート
|
||||
ko: SOCKS5 포트
|
||||
ru: Порт SOCKS5
|
||||
ms: Port SOCKS5
|
||||
pt-br: Porta SOCKS5
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 8080
|
||||
envKey: PANEL_APP_PORT_HTTP_PROXY
|
||||
labelEn: HTTP Proxy Port
|
||||
labelZh: HTTP 代理端口
|
||||
label:
|
||||
en: HTTP Proxy Port
|
||||
zh: HTTP 代理端口
|
||||
zh-Hant: HTTP 代理連接埠
|
||||
ja: HTTP プロキシポート
|
||||
ko: HTTP 프록시 포트
|
||||
ru: Порт HTTP-прокси
|
||||
ms: Port proksi HTTP
|
||||
pt-br: Porta do proxy HTTP
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 1080
|
||||
envKey: BIND_PORT
|
||||
labelEn: Internal Bind Port
|
||||
labelZh: 容器内部绑定端口
|
||||
label:
|
||||
en: Internal Bind Port
|
||||
zh: 容器内部绑定端口
|
||||
zh-Hant: 容器內部綁定連接埠
|
||||
ja: コンテナ内部バインドポート
|
||||
ko: 컨테이너 내부 바인드 포트
|
||||
ru: Внутренний порт привязки контейнера
|
||||
ms: Port bind dalaman kontena
|
||||
pt-br: Porta interna de bind do contêiner
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 0
|
||||
envKey: ROTATE_INTERVAL_MINUTES
|
||||
labelEn: Rotate Interval Minutes
|
||||
labelZh: 轮换间隔(分钟)
|
||||
label:
|
||||
en: Rotate Interval Minutes
|
||||
zh: 轮换间隔(分钟)
|
||||
zh-Hant: 輪換間隔(分鐘)
|
||||
ja: ローテーション間隔(分)
|
||||
ko: 로테이션 간격(분)
|
||||
ru: Интервал ротации (минуты)
|
||||
ms: Selang putaran (minit)
|
||||
pt-br: Intervalo de rotação (minutos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_MAX_ATTEMPTS
|
||||
labelEn: Rotation Attempts
|
||||
labelZh: 轮换尝试次数
|
||||
label:
|
||||
en: Rotation Attempts
|
||||
zh: 轮换尝试次数
|
||||
zh-Hant: 輪換嘗試次數
|
||||
ja: ローテーション試行回数
|
||||
ko: 로테이션 시도 횟수
|
||||
ru: Количество попыток ротации
|
||||
ms: Bilangan percubaan putaran
|
||||
pt-br: Número de tentativas de rotação
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_RETRY_DELAY_SECONDS
|
||||
labelEn: Rotation Retry Delay Seconds
|
||||
labelZh: 轮换重试间隔(秒)
|
||||
label:
|
||||
en: Rotation Retry Delay Seconds
|
||||
zh: 轮换重试间隔(秒)
|
||||
zh-Hant: 輪換重試間隔(秒)
|
||||
ja: ローテーション再試行間隔(秒)
|
||||
ko: 로테이션 재시도 간격(초)
|
||||
ru: Задержка между попытками ротации (секунды)
|
||||
ms: Kelewatan percubaan semula putaran (saat)
|
||||
pt-br: Atraso entre tentativas de rotação (segundos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: TEST_URL
|
||||
labelEn: Probe URL
|
||||
labelZh: 探测 URL
|
||||
label:
|
||||
en: Probe URL
|
||||
zh: 探测 URL
|
||||
zh-Hant: 探測 URL
|
||||
ja: プローブ URL
|
||||
ko: 점검 URL
|
||||
ru: URL проверки
|
||||
ms: URL ujian
|
||||
pt-br: URL de verificação
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: IP_CHECK_URL
|
||||
labelEn: Egress IP Check URL
|
||||
labelZh: 出口 IP 检查 URL
|
||||
label:
|
||||
en: Egress IP Check URL
|
||||
zh: 出口 IP 检查 URL
|
||||
zh-Hant: 出口 IP 檢查 URL
|
||||
ja: 出口 IP 確認 URL
|
||||
ko: 출구 IP 확인 URL
|
||||
ru: URL проверки исходного IP
|
||||
ms: URL semakan IP keluar
|
||||
pt-br: URL de verificação do IP de saída
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_USER
|
||||
labelEn: SOCKS Username
|
||||
labelZh: SOCKS 用户名
|
||||
label:
|
||||
en: SOCKS Username
|
||||
zh: SOCKS 用户名
|
||||
zh-Hant: SOCKS 使用者名稱
|
||||
ja: SOCKS ユーザー名
|
||||
ko: SOCKS 사용자명
|
||||
ru: Имя пользователя SOCKS
|
||||
ms: Nama pengguna SOCKS
|
||||
pt-br: Usuário SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_PASS
|
||||
labelEn: SOCKS Password
|
||||
labelZh: SOCKS 密码
|
||||
label:
|
||||
en: SOCKS Password
|
||||
zh: SOCKS 密码
|
||||
zh-Hant: SOCKS 密碼
|
||||
ja: SOCKS パスワード
|
||||
ko: SOCKS 비밀번호
|
||||
ru: Пароль SOCKS
|
||||
ms: Kata laluan SOCKS
|
||||
pt-br: Senha SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: ENDPOINT_IP
|
||||
labelEn: WARP Endpoint
|
||||
labelZh: WARP 终结点
|
||||
label:
|
||||
en: WARP Endpoint
|
||||
zh: WARP 终结点
|
||||
zh-Hant: WARP 終結點
|
||||
ja: WARP エンドポイント
|
||||
ko: WARP 엔드포인트
|
||||
ru: WARP endpoint
|
||||
ms: Titik akhir WARP
|
||||
pt-br: Endpoint WARP
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: GH_PROXY
|
||||
labelEn: wgcf Download Proxy
|
||||
labelZh: wgcf 下载代理
|
||||
label:
|
||||
en: wgcf Download Proxy
|
||||
zh: wgcf 下载代理
|
||||
zh-Hant: wgcf 下載代理
|
||||
ja: wgcf ダウンロードプロキシ
|
||||
ko: wgcf 다운로드 프록시
|
||||
ru: Прокси для загрузки wgcf
|
||||
ms: Proksi muat turun wgcf
|
||||
pt-br: Proxy de download do wgcf
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: 100.64.0.0/10
|
||||
envKey: TAILSCALE_CIDR
|
||||
labelEn: Tailscale Return Route CIDR
|
||||
labelZh: Tailscale 回程路由 CIDR
|
||||
label:
|
||||
en: Tailscale Return Route CIDR
|
||||
zh: Tailscale 回程路由 CIDR
|
||||
zh-Hant: Tailscale 回程路由 CIDR
|
||||
ja: Tailscale 戻りルート CIDR
|
||||
ko: Tailscale 리턴 라우트 CIDR
|
||||
ru: CIDR обратного маршрута Tailscale
|
||||
ms: CIDR laluan balik Tailscale
|
||||
pt-br: CIDR de rota de retorno do Tailscale
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: 1280
|
||||
envKey: MTU
|
||||
labelEn: WireGuard MTU
|
||||
labelZh: WireGuard MTU
|
||||
label:
|
||||
en: WireGuard MTU
|
||||
zh: WireGuard MTU
|
||||
zh-Hant: WireGuard MTU
|
||||
ja: WireGuard MTU
|
||||
ko: WireGuard MTU
|
||||
ru: MTU WireGuard
|
||||
ms: MTU WireGuard
|
||||
pt-br: MTU do WireGuard
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: WARP_WGCF_CONF
|
||||
labelEn: WireGuard Config Content
|
||||
labelZh: WireGuard 配置内容
|
||||
label:
|
||||
en: WireGuard Config Content
|
||||
zh: WireGuard 配置内容
|
||||
zh-Hant: WireGuard 設定內容
|
||||
ja: WireGuard 設定内容
|
||||
ko: WireGuard 구성 내용
|
||||
ru: Содержимое конфигурации WireGuard
|
||||
ms: Kandungan konfigurasi WireGuard
|
||||
pt-br: Conteúdo da configuração WireGuard
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: Asia/Shanghai
|
||||
envKey: TZ
|
||||
labelEn: Timezone
|
||||
labelZh: 时区
|
||||
label:
|
||||
en: Timezone
|
||||
zh: 时区
|
||||
zh-Hant: 時區
|
||||
ja: タイムゾーン
|
||||
ko: 시간대
|
||||
ru: Часовой пояс
|
||||
ms: Zon waktu
|
||||
pt-br: Fuso horário
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ./data
|
||||
envKey: APP_DATA_DIR_1
|
||||
labelEn: WireGuard Data Directory
|
||||
labelZh: WireGuard 数据目录
|
||||
label:
|
||||
en: WireGuard Data Directory
|
||||
zh: WireGuard 数据目录
|
||||
zh-Hant: WireGuard 資料目錄
|
||||
ja: WireGuard データディレクトリ
|
||||
ko: WireGuard 데이터 디렉터리
|
||||
ru: Каталог данных WireGuard
|
||||
ms: Direktori data WireGuard
|
||||
pt-br: Diretório de dados do WireGuard
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
@@ -0,0 +1,58 @@
|
||||
services:
|
||||
microwarp:
|
||||
image: "ghcr.io/ccbkkb/microwarp:0.1.3"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_SOCKS5}:${BIND_PORT}"
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- BIND_ADDR=0.0.0.0
|
||||
- BIND_PORT=${BIND_PORT}
|
||||
- ROTATE_INTERVAL_MINUTES=${ROTATE_INTERVAL_MINUTES:-0}
|
||||
- ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS:-5}
|
||||
- ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS:-5}
|
||||
- TEST_URL=${TEST_URL}
|
||||
- IP_CHECK_URL=${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}
|
||||
- SOCKS_USER=${SOCKS_USER:-}
|
||||
- SOCKS_PASS=${SOCKS_PASS:-}
|
||||
- ENDPOINT_IP=${ENDPOINT_IP:-}
|
||||
- GH_PROXY=${GH_PROXY:-}
|
||||
- TAILSCALE_CIDR=${TAILSCALE_CIDR:-100.64.0.0/10}
|
||||
- MTU=${MTU}
|
||||
- WARP_WGCF_CONF=${WARP_WGCF_CONF:-}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- "${APP_DATA_DIR_1}:/etc/wireguard"
|
||||
- "./rotate.sh:/usr/local/bin/rotate.sh:ro"
|
||||
restart: always
|
||||
command: ["/bin/sh", "/usr/local/bin/rotate.sh"]
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
http-proxy:
|
||||
image: "gogost/gost:latest"
|
||||
container_name: ${CONTAINER_NAME}-http
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
depends_on:
|
||||
- microwarp
|
||||
command:
|
||||
- "-L"
|
||||
- "http://0.0.0.0:8080"
|
||||
- "-F"
|
||||
- "socks5://microwarp:${BIND_PORT}"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_HTTP_PROXY}:8080"
|
||||
restart: always
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
INTERVAL_MINUTES="${ROTATE_INTERVAL_MINUTES:-0}"
|
||||
ROTATE_MAX_ATTEMPTS="${ROTATE_MAX_ATTEMPTS:-5}"
|
||||
ROTATE_RETRY_DELAY_SECONDS="${ROTATE_RETRY_DELAY_SECONDS:-5}"
|
||||
TEST_URL="${TEST_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
IP_CHECK_URL="${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
SOCKS_ADDR="127.0.0.1:${BIND_PORT:-1080}"
|
||||
SOCKS_USER_VALUE="${SOCKS_USER:-}"
|
||||
SOCKS_PASS_VALUE="${SOCKS_PASS:-}"
|
||||
WG_DIR="/etc/wireguard"
|
||||
WG_CONF="${WG_DIR}/wg0.conf"
|
||||
PREVIOUS_CONF="${WG_CONF}.rotation-previous"
|
||||
FIXED_CONFIG="${WARP_WGCF_CONF:-}"
|
||||
LOG_PREFIX="[microwarp]"
|
||||
ENTRY_PID=""
|
||||
CURRENT_EGRESS_IP=""
|
||||
|
||||
log() {
|
||||
echo "${LOG_PREFIX} $*"
|
||||
}
|
||||
|
||||
is_non_negative_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_positive_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*|0)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_ipv4() {
|
||||
printf '%s\n' "$1" | awk -F. '
|
||||
NF == 4 {
|
||||
for (i = 1; i <= 4; i++) {
|
||||
if ($i !~ /^[0-9]+$/ || $i < 0 || $i > 255) exit 1
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
{ exit 1 }
|
||||
'
|
||||
}
|
||||
|
||||
probe_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
curl_via_socks "$TEST_URL" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
curl_via_socks() {
|
||||
url="$1"
|
||||
if [ -n "$SOCKS_USER_VALUE" ] && [ -n "$SOCKS_PASS_VALUE" ]; then
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" \
|
||||
--proxy-user "${SOCKS_USER_VALUE}:${SOCKS_PASS_VALUE}" "$url"
|
||||
return $?
|
||||
fi
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" "$url"
|
||||
}
|
||||
|
||||
get_egress_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
trace_output="$(curl_via_socks "$IP_CHECK_URL" 2>/dev/null)" || return 1
|
||||
current_ip="$(printf '%s\n' "$trace_output" |
|
||||
awk -F= '$1 == "ip" { print $2; exit }' | tr -d '\r')"
|
||||
is_ipv4 "$current_ip" && printf '%s\n' "$current_ip"
|
||||
}
|
||||
|
||||
record_egress_ip() {
|
||||
current_ip="$(get_egress_ip || true)"
|
||||
if [ -n "$current_ip" ]; then
|
||||
CURRENT_EGRESS_IP="$current_ip"
|
||||
log "current WARP egress IPv4: ${CURRENT_EGRESS_IP}"
|
||||
else
|
||||
CURRENT_EGRESS_IP=""
|
||||
log "unable to verify current WARP egress IPv4"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
inject_fixed_config() {
|
||||
if [ -z "$FIXED_CONFIG" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$WG_DIR"
|
||||
printf '%s\n' "$FIXED_CONFIG" > "$WG_CONF"
|
||||
chmod 600 "$WG_CONF" || true
|
||||
log "wg0.conf injected from WARP_WGCF_CONF"
|
||||
}
|
||||
|
||||
wait_for_proxy() {
|
||||
attempts=0
|
||||
while [ "$attempts" -lt 30 ]; do
|
||||
if [ -n "$ENTRY_PID" ] && ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
wait "$ENTRY_PID" || true
|
||||
ENTRY_PID=""
|
||||
return 1
|
||||
fi
|
||||
|
||||
if probe_ip; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
attempts=$((attempts + 1))
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
stop_runtime() {
|
||||
if [ -n "$ENTRY_PID" ] && kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
kill "$ENTRY_PID" 2>/dev/null || true
|
||||
wait "$ENTRY_PID" || true
|
||||
fi
|
||||
ENTRY_PID=""
|
||||
}
|
||||
|
||||
teardown_for_rotation() {
|
||||
stop_runtime
|
||||
if ip link show wg0 >/dev/null 2>&1; then
|
||||
wg-quick down wg0 >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f "$WG_CONF" "$WG_DIR/wgcf-account.toml" "$WG_DIR/wgcf-profile.conf" "$WG_DIR/extra.env"
|
||||
}
|
||||
|
||||
start_runtime() {
|
||||
CURRENT_EGRESS_IP=""
|
||||
inject_fixed_config
|
||||
log "starting MicroWARP runtime"
|
||||
/app/entrypoint.sh &
|
||||
ENTRY_PID=$!
|
||||
if ! wait_for_proxy; then
|
||||
log "runtime did not become ready in time"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
record_egress_ip
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "runtime exited while its egress was being checked"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
save_previous_config() {
|
||||
if [ -s "$WG_CONF" ]; then
|
||||
previous_tmp="${PREVIOUS_CONF}.tmp.$$"
|
||||
rm -f "$previous_tmp"
|
||||
if ! cp -p "$WG_CONF" "$previous_tmp"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
if ! mv -f "$previous_tmp" "$PREVIOUS_CONF"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
restore_previous_runtime() {
|
||||
if [ ! -s "$PREVIOUS_CONF" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "restoring the previous working WARP configuration"
|
||||
teardown_for_rotation
|
||||
if ! cp -p "$PREVIOUS_CONF" "$WG_CONF"; then
|
||||
log "unable to restore the previous WARP configuration"
|
||||
return 1
|
||||
fi
|
||||
start_runtime
|
||||
}
|
||||
|
||||
rotate_once() {
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "WARP runtime exited before the scheduled identity rebuild"
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_egress_ip
|
||||
old_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -z "$old_ip" ]; then
|
||||
log "scheduled identity rebuild skipped because the current WARP egress IPv4 could not be verified"
|
||||
return 0
|
||||
fi
|
||||
attempt=1
|
||||
if ! save_previous_config; then
|
||||
log "scheduled identity rebuild skipped because the working WARP configuration could not be backed up"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while [ "$attempt" -le "$ROTATE_MAX_ATTEMPTS" ]; do
|
||||
log "WARP identity rebuild attempt ${attempt}/${ROTATE_MAX_ATTEMPTS}"
|
||||
teardown_for_rotation
|
||||
|
||||
if start_runtime; then
|
||||
new_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -n "$new_ip" ] &&
|
||||
[ "$new_ip" != "$old_ip" ]; then
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
log "WARP egress IPv4 changed: ${old_ip:-unknown} -> ${new_ip}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$new_ip" ] && [ "$new_ip" = "$old_ip" ]; then
|
||||
log "WARP egress IPv4 stayed ${new_ip}; retrying identity rebuild"
|
||||
else
|
||||
log "WARP egress IPv4 could not be verified; retrying identity rebuild"
|
||||
fi
|
||||
else
|
||||
log "WARP runtime failed during identity rebuild attempt ${attempt}"
|
||||
fi
|
||||
|
||||
if [ "$attempt" -lt "$ROTATE_MAX_ATTEMPTS" ]; then
|
||||
sleep "$ROTATE_RETRY_DELAY_SECONDS"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
if restore_previous_runtime; then
|
||||
log "no different public IPv4 was observed; previous runtime restored"
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "rotation failed and no working runtime is available"
|
||||
return 1
|
||||
}
|
||||
|
||||
on_signal() {
|
||||
log "received stop signal"
|
||||
stop_runtime
|
||||
exit 0
|
||||
}
|
||||
|
||||
main() {
|
||||
interval="$INTERVAL_MINUTES"
|
||||
if ! is_non_negative_int "$interval"; then
|
||||
log "invalid ROTATE_INTERVAL_MINUTES=${interval}, fallback to 0"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
if ! is_positive_int "$ROTATE_MAX_ATTEMPTS"; then
|
||||
log "invalid ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS}, fallback to 5"
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
fi
|
||||
|
||||
if ! is_non_negative_int "$ROTATE_RETRY_DELAY_SECONDS"; then
|
||||
log "invalid ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS}, fallback to 5"
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
fi
|
||||
|
||||
if [ -n "$FIXED_CONFIG" ] && [ "$interval" -gt 0 ]; then
|
||||
log "WARP_WGCF_CONF is set, scheduled rotation disabled because the config is pinned"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
trap on_signal INT TERM
|
||||
|
||||
if ! start_runtime; then
|
||||
exit 1
|
||||
fi
|
||||
if [ "$interval" -eq 0 ]; then
|
||||
log "scheduled rotation disabled"
|
||||
wait "$ENTRY_PID"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
log "scheduled WARP identity rebuild enabled every ${interval} minute(s); egress verification allows ${ROTATE_MAX_ATTEMPTS} attempt(s)"
|
||||
while true; do
|
||||
sleep "$((interval * 60))"
|
||||
log "trigger scheduled WARP identity rebuild"
|
||||
if ! rotate_once; then
|
||||
log "rotation failed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
TARGET_VERSION_DIR="0.1.3-rotator-http"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
resource_dir_for_app() {
|
||||
local panel_root app_path app_key
|
||||
|
||||
case "${APP_ROOT}" in
|
||||
*/apps/local/*)
|
||||
panel_root="${APP_ROOT%%/apps/local/*}"
|
||||
app_path="${APP_ROOT#"${panel_root}/apps/local/"}"
|
||||
app_key="${app_path%%/*}"
|
||||
[[ -n "${panel_root}" && -n "${app_key}" ]] || return 1
|
||||
printf '%s/resource/apps/local/%s\n' "${panel_root}" "${app_key}"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_rotate_source() {
|
||||
local resource_dir candidate_upgrade candidate_rotate
|
||||
|
||||
if [[ -n "${ROTATE_SOURCE:-}" ]]; then
|
||||
printf '%s\n' "${ROTATE_SOURCE}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if resource_dir="$(resource_dir_for_app 2>/dev/null)" && [[ -d "${resource_dir}" ]]; then
|
||||
candidate_upgrade="${resource_dir}/${TARGET_VERSION_DIR}/scripts/upgrade.sh"
|
||||
candidate_rotate="${resource_dir}/${TARGET_VERSION_DIR}/rotate.sh"
|
||||
if [[ -f "${candidate_upgrade}" && -f "${candidate_rotate}" ]] \
|
||||
&& cmp -s "${SCRIPT_DIR}/upgrade.sh" "${candidate_upgrade}" \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${candidate_rotate}"; then
|
||||
printf '%s\n' "${candidate_rotate}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "${APP_ROOT}/rotate.sh" ]] \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${APP_ROOT}/rotate.sh"; then
|
||||
printf '%s\n' "${APP_ROOT}/rotate.sh"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
rotate_target="${APP_ROOT}/rotate.sh"
|
||||
rotate_source="$(find_rotate_source || true)"
|
||||
if [[ -z "${rotate_source}" || ! -f "${rotate_source}" ]]; then
|
||||
echo "Unable to locate the upgraded rotate.sh payload; refusing to keep the old script" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${rotate_source}" != "${rotate_target}" ]] \
|
||||
&& ! cmp -s "${rotate_source}" "${rotate_target}"; then
|
||||
rotate_tmp="$(mktemp "${rotate_target}.upgrade.XXXXXX")"
|
||||
# shellcheck disable=SC2317
|
||||
cleanup_rotate_tmp() {
|
||||
rm -f -- "${rotate_tmp}"
|
||||
}
|
||||
trap cleanup_rotate_tmp EXIT
|
||||
|
||||
cp -- "${rotate_source}" "${rotate_tmp}"
|
||||
if [[ -e "${rotate_target}" ]]; then
|
||||
chmod --reference="${rotate_target}" "${rotate_tmp}" 2>/dev/null || true
|
||||
else
|
||||
chmod 0644 "${rotate_tmp}"
|
||||
fi
|
||||
mv -f -- "${rotate_tmp}" "${rotate_target}"
|
||||
trap - EXIT
|
||||
echo "Updated ${rotate_target} from the target version payload"
|
||||
fi
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
exit 0
|
||||
@@ -0,0 +1,17 @@
|
||||
CONTAINER_NAME=microwarp
|
||||
PANEL_APP_PORT_SOCKS5=1080
|
||||
BIND_PORT=1080
|
||||
ROTATE_INTERVAL_MINUTES=0
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
TEST_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
IP_CHECK_URL=https://cloudflare.com/cdn-cgi/trace
|
||||
SOCKS_USER=
|
||||
SOCKS_PASS=
|
||||
ENDPOINT_IP=
|
||||
GH_PROXY=
|
||||
TAILSCALE_CIDR=100.64.0.0/10
|
||||
MTU=1280
|
||||
WARP_WGCF_CONF=
|
||||
TZ=Asia/Shanghai
|
||||
APP_DATA_DIR_1=./data
|
||||
@@ -0,0 +1,260 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 1080
|
||||
envKey: PANEL_APP_PORT_SOCKS5
|
||||
labelEn: SOCKS5 Port
|
||||
labelZh: SOCKS5 端口
|
||||
label:
|
||||
en: SOCKS5 Port
|
||||
zh: SOCKS5 端口
|
||||
zh-Hant: SOCKS5 連接埠
|
||||
ja: SOCKS5 ポート
|
||||
ko: SOCKS5 포트
|
||||
ru: Порт SOCKS5
|
||||
ms: Port SOCKS5
|
||||
pt-br: Porta SOCKS5
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 1080
|
||||
envKey: BIND_PORT
|
||||
labelEn: Internal Bind Port
|
||||
labelZh: 容器内部绑定端口
|
||||
label:
|
||||
en: Internal Bind Port
|
||||
zh: 容器内部绑定端口
|
||||
zh-Hant: 容器內部綁定連接埠
|
||||
ja: コンテナ内部バインドポート
|
||||
ko: 컨테이너 내부 바인드 포트
|
||||
ru: Внутренний порт привязки контейнера
|
||||
ms: Port bind dalaman kontena
|
||||
pt-br: Porta interna de bind do contêiner
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
edit: true
|
||||
- default: 0
|
||||
envKey: ROTATE_INTERVAL_MINUTES
|
||||
labelEn: Rotate Interval Minutes
|
||||
labelZh: 轮换间隔(分钟)
|
||||
label:
|
||||
en: Rotate Interval Minutes
|
||||
zh: 轮换间隔(分钟)
|
||||
zh-Hant: 輪換間隔(分鐘)
|
||||
ja: ローテーション間隔(分)
|
||||
ko: 로테이션 간격(분)
|
||||
ru: Интервал ротации (минуты)
|
||||
ms: Selang putaran (minit)
|
||||
pt-br: Intervalo de rotação (minutos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_MAX_ATTEMPTS
|
||||
labelEn: Rotation Attempts
|
||||
labelZh: 轮换尝试次数
|
||||
label:
|
||||
en: Rotation Attempts
|
||||
zh: 轮换尝试次数
|
||||
zh-Hant: 輪換嘗試次數
|
||||
ja: ローテーション試行回数
|
||||
ko: 로테이션 시도 횟수
|
||||
ru: Количество попыток ротации
|
||||
ms: Bilangan percubaan putaran
|
||||
pt-br: Número de tentativas de rotação
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: 5
|
||||
envKey: ROTATE_RETRY_DELAY_SECONDS
|
||||
labelEn: Rotation Retry Delay Seconds
|
||||
labelZh: 轮换重试间隔(秒)
|
||||
label:
|
||||
en: Rotation Retry Delay Seconds
|
||||
zh: 轮换重试间隔(秒)
|
||||
zh-Hant: 輪換重試間隔(秒)
|
||||
ja: ローテーション再試行間隔(秒)
|
||||
ko: 로테이션 재시도 간격(초)
|
||||
ru: Задержка между попытками ротации (секунды)
|
||||
ms: Kelewatan percubaan semula putaran (saat)
|
||||
pt-br: Atraso entre tentativas de rotação (segundos)
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: TEST_URL
|
||||
labelEn: Probe URL
|
||||
labelZh: 探测 URL
|
||||
label:
|
||||
en: Probe URL
|
||||
zh: 探测 URL
|
||||
zh-Hant: 探測 URL
|
||||
ja: プローブ URL
|
||||
ko: 점검 URL
|
||||
ru: URL проверки
|
||||
ms: URL ujian
|
||||
pt-br: URL de verificação
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: https://cloudflare.com/cdn-cgi/trace
|
||||
envKey: IP_CHECK_URL
|
||||
labelEn: Egress IP Check URL
|
||||
labelZh: 出口 IP 检查 URL
|
||||
label:
|
||||
en: Egress IP Check URL
|
||||
zh: 出口 IP 检查 URL
|
||||
zh-Hant: 出口 IP 檢查 URL
|
||||
ja: 出口 IP 確認 URL
|
||||
ko: 출구 IP 확인 URL
|
||||
ru: URL проверки исходного IP
|
||||
ms: URL semakan IP keluar
|
||||
pt-br: URL de verificação do IP de saída
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_USER
|
||||
labelEn: SOCKS Username
|
||||
labelZh: SOCKS 用户名
|
||||
label:
|
||||
en: SOCKS Username
|
||||
zh: SOCKS 用户名
|
||||
zh-Hant: SOCKS 使用者名稱
|
||||
ja: SOCKS ユーザー名
|
||||
ko: SOCKS 사용자명
|
||||
ru: Имя пользователя SOCKS
|
||||
ms: Nama pengguna SOCKS
|
||||
pt-br: Usuário SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: SOCKS_PASS
|
||||
labelEn: SOCKS Password
|
||||
labelZh: SOCKS 密码
|
||||
label:
|
||||
en: SOCKS Password
|
||||
zh: SOCKS 密码
|
||||
zh-Hant: SOCKS 密碼
|
||||
ja: SOCKS パスワード
|
||||
ko: SOCKS 비밀번호
|
||||
ru: Пароль SOCKS
|
||||
ms: Kata laluan SOCKS
|
||||
pt-br: Senha SOCKS
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: ENDPOINT_IP
|
||||
labelEn: WARP Endpoint
|
||||
labelZh: WARP 终结点
|
||||
label:
|
||||
en: WARP Endpoint
|
||||
zh: WARP 终结点
|
||||
zh-Hant: WARP 終結點
|
||||
ja: WARP エンドポイント
|
||||
ko: WARP 엔드포인트
|
||||
ru: WARP endpoint
|
||||
ms: Titik akhir WARP
|
||||
pt-br: Endpoint WARP
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: GH_PROXY
|
||||
labelEn: wgcf Download Proxy
|
||||
labelZh: wgcf 下载代理
|
||||
label:
|
||||
en: wgcf Download Proxy
|
||||
zh: wgcf 下载代理
|
||||
zh-Hant: wgcf 下載代理
|
||||
ja: wgcf ダウンロードプロキシ
|
||||
ko: wgcf 다운로드 프록시
|
||||
ru: Прокси для загрузки wgcf
|
||||
ms: Proksi muat turun wgcf
|
||||
pt-br: Proxy de download do wgcf
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: 100.64.0.0/10
|
||||
envKey: TAILSCALE_CIDR
|
||||
labelEn: Tailscale Return Route CIDR
|
||||
labelZh: Tailscale 回程路由 CIDR
|
||||
label:
|
||||
en: Tailscale Return Route CIDR
|
||||
zh: Tailscale 回程路由 CIDR
|
||||
zh-Hant: Tailscale 回程路由 CIDR
|
||||
ja: Tailscale 戻りルート CIDR
|
||||
ko: Tailscale 리턴 라우트 CIDR
|
||||
ru: CIDR обратного маршрута Tailscale
|
||||
ms: CIDR laluan balik Tailscale
|
||||
pt-br: CIDR de rota de retorno do Tailscale
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: 1280
|
||||
envKey: MTU
|
||||
labelEn: WireGuard MTU
|
||||
labelZh: WireGuard MTU
|
||||
label:
|
||||
en: WireGuard MTU
|
||||
zh: WireGuard MTU
|
||||
zh-Hant: WireGuard MTU
|
||||
ja: WireGuard MTU
|
||||
ko: WireGuard MTU
|
||||
ru: MTU WireGuard
|
||||
ms: MTU WireGuard
|
||||
pt-br: MTU do WireGuard
|
||||
required: true
|
||||
type: number
|
||||
edit: true
|
||||
- default: ""
|
||||
envKey: WARP_WGCF_CONF
|
||||
labelEn: WireGuard Config Content
|
||||
labelZh: WireGuard 配置内容
|
||||
label:
|
||||
en: WireGuard Config Content
|
||||
zh: WireGuard 配置内容
|
||||
zh-Hant: WireGuard 設定內容
|
||||
ja: WireGuard 設定内容
|
||||
ko: WireGuard 구성 내용
|
||||
ru: Содержимое конфигурации WireGuard
|
||||
ms: Kandungan konfigurasi WireGuard
|
||||
pt-br: Conteúdo da configuração WireGuard
|
||||
required: false
|
||||
type: text
|
||||
edit: true
|
||||
- default: Asia/Shanghai
|
||||
envKey: TZ
|
||||
labelEn: Timezone
|
||||
labelZh: 时区
|
||||
label:
|
||||
en: Timezone
|
||||
zh: 时区
|
||||
zh-Hant: 時區
|
||||
ja: タイムゾーン
|
||||
ko: 시간대
|
||||
ru: Часовой пояс
|
||||
ms: Zon waktu
|
||||
pt-br: Fuso horário
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
- default: ./data
|
||||
envKey: APP_DATA_DIR_1
|
||||
labelEn: WireGuard Data Directory
|
||||
labelZh: WireGuard 数据目录
|
||||
label:
|
||||
en: WireGuard Data Directory
|
||||
zh: WireGuard 数据目录
|
||||
zh-Hant: WireGuard 資料目錄
|
||||
ja: WireGuard データディレクトリ
|
||||
ko: WireGuard 데이터 디렉터리
|
||||
ru: Каталог данных WireGuard
|
||||
ms: Direktori data WireGuard
|
||||
pt-br: Diretório de dados do WireGuard
|
||||
required: true
|
||||
type: text
|
||||
edit: true
|
||||
@@ -0,0 +1,40 @@
|
||||
services:
|
||||
microwarp:
|
||||
image: "ghcr.io/ccbkkb/microwarp:0.1.3"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_SOCKS5}:${BIND_PORT}"
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- BIND_ADDR=0.0.0.0
|
||||
- BIND_PORT=${BIND_PORT}
|
||||
- ROTATE_INTERVAL_MINUTES=${ROTATE_INTERVAL_MINUTES:-0}
|
||||
- ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS:-5}
|
||||
- ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS:-5}
|
||||
- TEST_URL=${TEST_URL}
|
||||
- IP_CHECK_URL=${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}
|
||||
- SOCKS_USER=${SOCKS_USER:-}
|
||||
- SOCKS_PASS=${SOCKS_PASS:-}
|
||||
- ENDPOINT_IP=${ENDPOINT_IP:-}
|
||||
- GH_PROXY=${GH_PROXY:-}
|
||||
- TAILSCALE_CIDR=${TAILSCALE_CIDR:-100.64.0.0/10}
|
||||
- MTU=${MTU}
|
||||
- WARP_WGCF_CONF=${WARP_WGCF_CONF:-}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- "${APP_DATA_DIR_1}:/etc/wireguard"
|
||||
- "./rotate.sh:/usr/local/bin/rotate.sh:ro"
|
||||
restart: always
|
||||
command: ["/bin/sh", "/usr/local/bin/rotate.sh"]
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
INTERVAL_MINUTES="${ROTATE_INTERVAL_MINUTES:-0}"
|
||||
ROTATE_MAX_ATTEMPTS="${ROTATE_MAX_ATTEMPTS:-5}"
|
||||
ROTATE_RETRY_DELAY_SECONDS="${ROTATE_RETRY_DELAY_SECONDS:-5}"
|
||||
TEST_URL="${TEST_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
IP_CHECK_URL="${IP_CHECK_URL:-https://cloudflare.com/cdn-cgi/trace}"
|
||||
SOCKS_ADDR="127.0.0.1:${BIND_PORT:-1080}"
|
||||
SOCKS_USER_VALUE="${SOCKS_USER:-}"
|
||||
SOCKS_PASS_VALUE="${SOCKS_PASS:-}"
|
||||
WG_DIR="/etc/wireguard"
|
||||
WG_CONF="${WG_DIR}/wg0.conf"
|
||||
PREVIOUS_CONF="${WG_CONF}.rotation-previous"
|
||||
FIXED_CONFIG="${WARP_WGCF_CONF:-}"
|
||||
LOG_PREFIX="[microwarp]"
|
||||
ENTRY_PID=""
|
||||
CURRENT_EGRESS_IP=""
|
||||
|
||||
log() {
|
||||
echo "${LOG_PREFIX} $*"
|
||||
}
|
||||
|
||||
is_non_negative_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_positive_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*|0)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_ipv4() {
|
||||
printf '%s\n' "$1" | awk -F. '
|
||||
NF == 4 {
|
||||
for (i = 1; i <= 4; i++) {
|
||||
if ($i !~ /^[0-9]+$/ || $i < 0 || $i > 255) exit 1
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
{ exit 1 }
|
||||
'
|
||||
}
|
||||
|
||||
probe_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
curl_via_socks "$TEST_URL" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
curl_via_socks() {
|
||||
url="$1"
|
||||
if [ -n "$SOCKS_USER_VALUE" ] && [ -n "$SOCKS_PASS_VALUE" ]; then
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" \
|
||||
--proxy-user "${SOCKS_USER_VALUE}:${SOCKS_PASS_VALUE}" "$url"
|
||||
return $?
|
||||
fi
|
||||
curl --max-time 20 --socks5-hostname "$SOCKS_ADDR" "$url"
|
||||
}
|
||||
|
||||
get_egress_ip() {
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
trace_output="$(curl_via_socks "$IP_CHECK_URL" 2>/dev/null)" || return 1
|
||||
current_ip="$(printf '%s\n' "$trace_output" |
|
||||
awk -F= '$1 == "ip" { print $2; exit }' | tr -d '\r')"
|
||||
is_ipv4 "$current_ip" && printf '%s\n' "$current_ip"
|
||||
}
|
||||
|
||||
record_egress_ip() {
|
||||
current_ip="$(get_egress_ip || true)"
|
||||
if [ -n "$current_ip" ]; then
|
||||
CURRENT_EGRESS_IP="$current_ip"
|
||||
log "current WARP egress IPv4: ${CURRENT_EGRESS_IP}"
|
||||
else
|
||||
CURRENT_EGRESS_IP=""
|
||||
log "unable to verify current WARP egress IPv4"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
inject_fixed_config() {
|
||||
if [ -z "$FIXED_CONFIG" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$WG_DIR"
|
||||
printf '%s\n' "$FIXED_CONFIG" > "$WG_CONF"
|
||||
chmod 600 "$WG_CONF" || true
|
||||
log "wg0.conf injected from WARP_WGCF_CONF"
|
||||
}
|
||||
|
||||
wait_for_proxy() {
|
||||
attempts=0
|
||||
while [ "$attempts" -lt 30 ]; do
|
||||
if [ -n "$ENTRY_PID" ] && ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
wait "$ENTRY_PID" || true
|
||||
ENTRY_PID=""
|
||||
return 1
|
||||
fi
|
||||
|
||||
if probe_ip; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
attempts=$((attempts + 1))
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
stop_runtime() {
|
||||
if [ -n "$ENTRY_PID" ] && kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
kill "$ENTRY_PID" 2>/dev/null || true
|
||||
wait "$ENTRY_PID" || true
|
||||
fi
|
||||
ENTRY_PID=""
|
||||
}
|
||||
|
||||
teardown_for_rotation() {
|
||||
stop_runtime
|
||||
if ip link show wg0 >/dev/null 2>&1; then
|
||||
wg-quick down wg0 >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f "$WG_CONF" "$WG_DIR/wgcf-account.toml" "$WG_DIR/wgcf-profile.conf" "$WG_DIR/extra.env"
|
||||
}
|
||||
|
||||
start_runtime() {
|
||||
CURRENT_EGRESS_IP=""
|
||||
inject_fixed_config
|
||||
log "starting MicroWARP runtime"
|
||||
/app/entrypoint.sh &
|
||||
ENTRY_PID=$!
|
||||
if ! wait_for_proxy; then
|
||||
log "runtime did not become ready in time"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
record_egress_ip
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "runtime exited while its egress was being checked"
|
||||
stop_runtime
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
save_previous_config() {
|
||||
if [ -s "$WG_CONF" ]; then
|
||||
previous_tmp="${PREVIOUS_CONF}.tmp.$$"
|
||||
rm -f "$previous_tmp"
|
||||
if ! cp -p "$WG_CONF" "$previous_tmp"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
if ! mv -f "$previous_tmp" "$PREVIOUS_CONF"; then
|
||||
rm -f "$previous_tmp"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
restore_previous_runtime() {
|
||||
if [ ! -s "$PREVIOUS_CONF" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "restoring the previous working WARP configuration"
|
||||
teardown_for_rotation
|
||||
if ! cp -p "$PREVIOUS_CONF" "$WG_CONF"; then
|
||||
log "unable to restore the previous WARP configuration"
|
||||
return 1
|
||||
fi
|
||||
start_runtime
|
||||
}
|
||||
|
||||
rotate_once() {
|
||||
if [ -z "$ENTRY_PID" ] || ! kill -0 "$ENTRY_PID" 2>/dev/null; then
|
||||
log "WARP runtime exited before the scheduled identity rebuild"
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_egress_ip
|
||||
old_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -z "$old_ip" ]; then
|
||||
log "scheduled identity rebuild skipped because the current WARP egress IPv4 could not be verified"
|
||||
return 0
|
||||
fi
|
||||
attempt=1
|
||||
if ! save_previous_config; then
|
||||
log "scheduled identity rebuild skipped because the working WARP configuration could not be backed up"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while [ "$attempt" -le "$ROTATE_MAX_ATTEMPTS" ]; do
|
||||
log "WARP identity rebuild attempt ${attempt}/${ROTATE_MAX_ATTEMPTS}"
|
||||
teardown_for_rotation
|
||||
|
||||
if start_runtime; then
|
||||
new_ip="$CURRENT_EGRESS_IP"
|
||||
if [ -n "$new_ip" ] &&
|
||||
[ "$new_ip" != "$old_ip" ]; then
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
log "WARP egress IPv4 changed: ${old_ip:-unknown} -> ${new_ip}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$new_ip" ] && [ "$new_ip" = "$old_ip" ]; then
|
||||
log "WARP egress IPv4 stayed ${new_ip}; retrying identity rebuild"
|
||||
else
|
||||
log "WARP egress IPv4 could not be verified; retrying identity rebuild"
|
||||
fi
|
||||
else
|
||||
log "WARP runtime failed during identity rebuild attempt ${attempt}"
|
||||
fi
|
||||
|
||||
if [ "$attempt" -lt "$ROTATE_MAX_ATTEMPTS" ]; then
|
||||
sleep "$ROTATE_RETRY_DELAY_SECONDS"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
if restore_previous_runtime; then
|
||||
log "no different public IPv4 was observed; previous runtime restored"
|
||||
rm -f "$PREVIOUS_CONF"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "rotation failed and no working runtime is available"
|
||||
return 1
|
||||
}
|
||||
|
||||
on_signal() {
|
||||
log "received stop signal"
|
||||
stop_runtime
|
||||
exit 0
|
||||
}
|
||||
|
||||
main() {
|
||||
interval="$INTERVAL_MINUTES"
|
||||
if ! is_non_negative_int "$interval"; then
|
||||
log "invalid ROTATE_INTERVAL_MINUTES=${interval}, fallback to 0"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
if ! is_positive_int "$ROTATE_MAX_ATTEMPTS"; then
|
||||
log "invalid ROTATE_MAX_ATTEMPTS=${ROTATE_MAX_ATTEMPTS}, fallback to 5"
|
||||
ROTATE_MAX_ATTEMPTS=5
|
||||
fi
|
||||
|
||||
if ! is_non_negative_int "$ROTATE_RETRY_DELAY_SECONDS"; then
|
||||
log "invalid ROTATE_RETRY_DELAY_SECONDS=${ROTATE_RETRY_DELAY_SECONDS}, fallback to 5"
|
||||
ROTATE_RETRY_DELAY_SECONDS=5
|
||||
fi
|
||||
|
||||
if [ -n "$FIXED_CONFIG" ] && [ "$interval" -gt 0 ]; then
|
||||
log "WARP_WGCF_CONF is set, scheduled rotation disabled because the config is pinned"
|
||||
interval=0
|
||||
fi
|
||||
|
||||
trap on_signal INT TERM
|
||||
|
||||
if ! start_runtime; then
|
||||
exit 1
|
||||
fi
|
||||
if [ "$interval" -eq 0 ]; then
|
||||
log "scheduled rotation disabled"
|
||||
wait "$ENTRY_PID"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
log "scheduled WARP identity rebuild enabled every ${interval} minute(s); egress verification allows ${ROTATE_MAX_ATTEMPTS} attempt(s)"
|
||||
while true; do
|
||||
sleep "$((interval * 60))"
|
||||
log "trigger scheduled WARP identity rebuild"
|
||||
if ! rotate_once; then
|
||||
log "rotation failed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
APP_ROOT="${APP_ROOT:-$(CDPATH="" cd -- "${SCRIPT_DIR}/.." && pwd -P)}"
|
||||
ENV_FILE="${ENV_FILE:-${APP_ROOT}/.env}"
|
||||
TARGET_VERSION_DIR="0.1.3-rotator"
|
||||
|
||||
if [[ "${ENV_FILE}" != /* ]]; then
|
||||
ENV_FILE="${APP_ROOT}/${ENV_FILE#./}"
|
||||
fi
|
||||
|
||||
resource_dir_for_app() {
|
||||
local panel_root app_path app_key
|
||||
|
||||
case "${APP_ROOT}" in
|
||||
*/apps/local/*)
|
||||
panel_root="${APP_ROOT%%/apps/local/*}"
|
||||
app_path="${APP_ROOT#"${panel_root}/apps/local/"}"
|
||||
app_key="${app_path%%/*}"
|
||||
[[ -n "${panel_root}" && -n "${app_key}" ]] || return 1
|
||||
printf '%s/resource/apps/local/%s\n' "${panel_root}" "${app_key}"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_rotate_source() {
|
||||
local resource_dir candidate_upgrade candidate_rotate
|
||||
|
||||
if [[ -n "${ROTATE_SOURCE:-}" ]]; then
|
||||
printf '%s\n' "${ROTATE_SOURCE}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if resource_dir="$(resource_dir_for_app 2>/dev/null)" && [[ -d "${resource_dir}" ]]; then
|
||||
candidate_upgrade="${resource_dir}/${TARGET_VERSION_DIR}/scripts/upgrade.sh"
|
||||
candidate_rotate="${resource_dir}/${TARGET_VERSION_DIR}/rotate.sh"
|
||||
if [[ -f "${candidate_upgrade}" && -f "${candidate_rotate}" ]] \
|
||||
&& cmp -s "${SCRIPT_DIR}/upgrade.sh" "${candidate_upgrade}" \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${candidate_rotate}"; then
|
||||
printf '%s\n' "${candidate_rotate}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "${APP_ROOT}/rotate.sh" ]] \
|
||||
&& grep -q 'ROTATE_MAX_ATTEMPTS' "${APP_ROOT}/rotate.sh"; then
|
||||
printf '%s\n' "${APP_ROOT}/rotate.sh"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
strip_matching_quotes() {
|
||||
local value="$1"
|
||||
|
||||
case "${value}" in
|
||||
\"*\")
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
;;
|
||||
\'*\')
|
||||
value="${value#\'}"
|
||||
value="${value%\'}"
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
rotate_target="${APP_ROOT}/rotate.sh"
|
||||
rotate_source="$(find_rotate_source || true)"
|
||||
if [[ -z "${rotate_source}" || ! -f "${rotate_source}" ]]; then
|
||||
echo "Unable to locate the upgraded rotate.sh payload; refusing to keep the old script" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${rotate_source}" != "${rotate_target}" ]] \
|
||||
&& ! cmp -s "${rotate_source}" "${rotate_target}"; then
|
||||
rotate_tmp="$(mktemp "${rotate_target}.upgrade.XXXXXX")"
|
||||
# shellcheck disable=SC2317
|
||||
cleanup_rotate_tmp() {
|
||||
rm -f -- "${rotate_tmp}"
|
||||
}
|
||||
trap cleanup_rotate_tmp EXIT
|
||||
|
||||
cp -- "${rotate_source}" "${rotate_tmp}"
|
||||
if [[ -e "${rotate_target}" ]]; then
|
||||
chmod --reference="${rotate_target}" "${rotate_tmp}" 2>/dev/null || true
|
||||
else
|
||||
chmod 0644 "${rotate_tmp}"
|
||||
fi
|
||||
mv -f -- "${rotate_tmp}" "${rotate_target}"
|
||||
trap - EXIT
|
||||
echo "Updated ${rotate_target} from the target version payload"
|
||||
fi
|
||||
|
||||
app_data_dir="${APP_DATA_DIR_1:-}"
|
||||
if [[ -z "${app_data_dir}" && -f "${ENV_FILE}" ]]; then
|
||||
app_data_dir="$(sed -n 's/^APP_DATA_DIR_1=//p' "${ENV_FILE}" | tail -n 1)"
|
||||
fi
|
||||
app_data_dir="$(strip_matching_quotes "${app_data_dir}")"
|
||||
app_data_dir="${app_data_dir:-./data}"
|
||||
if [[ "${app_data_dir}" != /* ]]; then
|
||||
app_data_dir="${APP_ROOT}/${app_data_dir#./}"
|
||||
fi
|
||||
|
||||
mkdir -p "${app_data_dir}"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user