cnxqchen commited on
Commit
fa7fe4b
·
verified ·
1 Parent(s): 97b4a55

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +11 -3
  2. entrypoint.sh +71 -43
  3. scripts/release-common.sh +240 -0
  4. scripts/update-loop.sh +15 -0
  5. supervisord.conf +41 -0
Dockerfile CHANGED
@@ -1,12 +1,20 @@
1
  FROM alpine:3.21
2
 
3
  ENV TZ=Asia/Shanghai
 
 
 
 
 
4
 
5
- RUN apk add --no-cache ca-certificates curl tzdata \
6
  && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
7
  && echo $TZ > /etc/timezone
8
 
9
  COPY entrypoint.sh /usr/local/bin/entrypoint.sh
10
- RUN chmod +x /usr/local/bin/entrypoint.sh
 
 
 
11
 
12
- ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
 
1
  FROM alpine:3.21
2
 
3
  ENV TZ=Asia/Shanghai
4
+ ENV RUNTIME_DIR=/tmp/runtime
5
+ ENV SUPERVISOR_CONF=/etc/supervisord.conf
6
+ ENV SUPERVISOR_CONF_TEMPLATE=/etc/supervisord.conf.template
7
+ ENV UPDATE_INTERVAL_SECONDS=14400
8
+ ENV STOP_WAIT_SECS=60
9
 
10
+ RUN apk add --no-cache ca-certificates curl supervisor tzdata \
11
  && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
12
  && echo $TZ > /etc/timezone
13
 
14
  COPY entrypoint.sh /usr/local/bin/entrypoint.sh
15
+ COPY scripts/release-common.sh /usr/local/bin/release-common.sh
16
+ COPY scripts/update-loop.sh /usr/local/bin/update-loop.sh
17
+ COPY supervisord.conf /etc/supervisord.conf.template
18
+ RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/release-common.sh /usr/local/bin/update-loop.sh
19
 
20
+ ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
entrypoint.sh CHANGED
@@ -1,46 +1,74 @@
1
  #!/bin/sh
2
  set -eu
3
 
4
- REPO_API_URL="https://api.github.com/repos/caidaoli/ccload/releases/latest"
5
- EXTRACT_DIR="/tmp/cliproxyapi"
6
-
7
- asset_url="$({
8
- curl -fsSL "$REPO_API_URL" \
9
- | grep -Eo '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*"' \
10
- | cut -d '"' -f 4 \
11
- | grep -E '/[^/]*linux[-_]amd64(\.tar\.gz)?$' \
12
- | head -n 1
13
- } || true)"
14
-
15
- if [ -z "${asset_url}" ]; then
16
- echo "错误: 未找到 linux amd64 发布包 URL" >&2
17
- exit 1
18
- fi
19
-
20
- echo "下载: ${asset_url}"
21
- download_path="/tmp/$(basename "$asset_url")"
22
- echo "保存到: ${download_path}"
23
- curl -fsSL "$asset_url" -o "$download_path"
24
-
25
- case "$download_path" in
26
- *.tar.gz)
27
- rm -rf "$EXTRACT_DIR"
28
- mkdir -p "$EXTRACT_DIR"
29
- tar -xzf "$download_path" -C "$EXTRACT_DIR"
30
- binary_path="$({
31
- find "$EXTRACT_DIR" -maxdepth 2 -type f \( -name 'cli-proxy-api' -o -name 'CLIProxyAPI' \) | head -n 1
32
- } || true)"
33
- ;;
34
- *)
35
- binary_path="$download_path"
36
- ;;
37
- esac
38
-
39
- if [ -z "${binary_path}" ]; then
40
- echo "错误: 未找到 cli-proxy-api 可执行文件" >&2
41
- exit 1
42
- fi
43
-
44
- chmod +x "$binary_path"
45
- echo "启动: ${binary_path} $*"
46
- exec "$binary_path" "$@"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  #!/bin/sh
2
  set -eu
3
 
4
+ : "${RELEASE_COMMON_SH:=/usr/local/bin/release-common.sh}"
5
+ : "${SUPERVISORD_BIN:=/usr/bin/supervisord}"
6
+ : "${SUPERVISOR_CONF_TEMPLATE:=/etc/supervisord.conf.template}"
7
+ : "${OPEN_FILE_LIMIT:=65535}"
8
+
9
+ . "$RELEASE_COMMON_SH"
10
+
11
+ export RUNTIME_DIR
12
+ export SUPERVISOR_CONF
13
+
14
+ shell_quote() {
15
+ printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\"'\"'/g")"
16
+ }
17
+
18
+ escape_sed_replacement() {
19
+ printf '%s' "$1" | sed 's/[\/&]/\\&/g'
20
+ }
21
+
22
+ write_supervisord_conf() {
23
+ mkdir -p "$(dirname "$SUPERVISOR_CONF")"
24
+ app_program_name=$(escape_sed_replacement "$SUPERVISOR_PROGRAM_NAME")
25
+ updater_program_name=$(escape_sed_replacement "$SUPERVISOR_UPDATER_PROGRAM_NAME")
26
+
27
+ sed \
28
+ -e "s/__APP_PROGRAM__/$app_program_name/g" \
29
+ -e "s/__UPDATER_PROGRAM__/$updater_program_name/g" \
30
+ "$SUPERVISOR_CONF_TEMPLATE" > "$SUPERVISOR_CONF"
31
+ }
32
+
33
+ write_run_app_script() {
34
+ run_app_script="$RUNTIME_DIR/run-app.sh"
35
+
36
+ {
37
+ printf '#!/bin/sh\n'
38
+ printf 'set -eu\n'
39
+ printf 'exec "%s"' "$(current_binary_path)"
40
+ for arg in "$@"; do
41
+ printf ' %s' "$(shell_quote "$arg")"
42
+ done
43
+ printf '\n'
44
+ } > "$run_app_script"
45
+
46
+ chmod +x "$run_app_script"
47
+ }
48
+
49
+ prepare_runtime_environment() {
50
+ ulimit -n "$OPEN_FILE_LIMIT"
51
+ ensure_runtime_dirs
52
+ rm -f "$RUNTIME_DIR/supervisor.sock" "$RUNTIME_DIR/supervisord.pid"
53
+ }
54
+
55
+ bootstrap_release_or_exit() {
56
+ if ! ensure_initial_release; then
57
+ echo "错误: 初次启动时无法准备可运行版本" >&2
58
+ exit 1
59
+ fi
60
+ }
61
+
62
+ start_supervisord() {
63
+ exec "$SUPERVISORD_BIN" -c "$SUPERVISOR_CONF"
64
+ }
65
+
66
+ main() {
67
+ prepare_runtime_environment
68
+ write_supervisord_conf
69
+ bootstrap_release_or_exit
70
+ write_run_app_script "$@"
71
+ start_supervisord
72
+ }
73
+
74
+ main "$@"
scripts/release-common.sh ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ : "${REPO_API_URL:=https://api.github.com/repos/caidaoli/ccload/releases/latest}"
4
+ : "${RUNTIME_DIR:=/tmp/runtime}"
5
+ : "${RELEASES_DIR:=$RUNTIME_DIR/releases}"
6
+ : "${CURRENT_LINK:=$RUNTIME_DIR/current}"
7
+ : "${CURRENT_TAG_FILE:=$RUNTIME_DIR/current.tag}"
8
+ : "${DOWNLOADS_DIR:=$RUNTIME_DIR/downloads}"
9
+ : "${SUPERVISOR_CONF:=/etc/supervisord.conf}"
10
+ : "${SUPERVISOR_PROGRAM_NAME:=app}"
11
+ : "${SUPERVISOR_UPDATER_PROGRAM_NAME:=${SUPERVISOR_PROGRAM_NAME}-updater}"
12
+ : "${CURRENT_BINARY_NAME:=app}"
13
+ : "${APP_BINARY_NAMES:=}"
14
+
15
+ log() {
16
+ timestamp=$(date '+%Y-%m-%d %H:%M:%S %z')
17
+ printf '[%s] %s\n' "$timestamp" "$*" >&2
18
+ }
19
+
20
+ tag_to_dir_name() {
21
+ printf '%s\n' "$1" | tr '/:' '__'
22
+ }
23
+
24
+ release_dir_for_tag() {
25
+ printf '%s/%s\n' "$RELEASES_DIR" "$(tag_to_dir_name "$1")"
26
+ }
27
+
28
+ ensure_runtime_dirs() {
29
+ mkdir -p "$RUNTIME_DIR" "$RELEASES_DIR" "$DOWNLOADS_DIR"
30
+ }
31
+
32
+ current_tag() {
33
+ if [ -f "$CURRENT_TAG_FILE" ]; then
34
+ tr -d '\n' < "$CURRENT_TAG_FILE"
35
+ fi
36
+ }
37
+
38
+ current_binary_path() {
39
+ printf '%s/%s\n' "$CURRENT_LINK" "$CURRENT_BINARY_NAME"
40
+ }
41
+
42
+ fetch_latest_release_info() {
43
+ response=$(curl -fsSL "$REPO_API_URL") || return 1
44
+
45
+ tag_name=$(
46
+ printf '%s\n' "$response" \
47
+ | grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"[^"]+"' \
48
+ | head -n 1 \
49
+ | cut -d '"' -f 4 \
50
+ || true
51
+ )
52
+ asset_url=$(
53
+ printf '%s\n' "$response" \
54
+ | grep -Eo '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*"' \
55
+ | cut -d '"' -f 4 \
56
+ | grep -E '/[^/]*linux[-_]amd64(\.tar\.gz)?$' \
57
+ | head -n 1 \
58
+ || true
59
+ )
60
+
61
+ if [ -z "$tag_name" ] || [ -z "$asset_url" ]; then
62
+ return 1
63
+ fi
64
+
65
+ printf '%s|%s\n' "$tag_name" "$asset_url"
66
+ }
67
+
68
+ load_latest_release() {
69
+ release_info=$(fetch_latest_release_info) || return 1
70
+ LATEST_TAG=${release_info%%|*}
71
+ LATEST_ASSET_URL=${release_info#*|}
72
+ }
73
+
74
+ find_release_binary() {
75
+ release_dir=$1
76
+
77
+ for name in $APP_BINARY_NAMES; do
78
+ if [ -f "$release_dir/$name" ]; then
79
+ printf '%s\n' "$release_dir/$name"
80
+ return 0
81
+ fi
82
+ done
83
+
84
+ for name in $APP_BINARY_NAMES; do
85
+ binary_path=$(find "$release_dir" -maxdepth 3 -type f -name "$name" | head -n 1)
86
+ if [ -n "$binary_path" ]; then
87
+ printf '%s\n' "$binary_path"
88
+ return 0
89
+ fi
90
+ done
91
+
92
+ file_list=$(find "$release_dir" -maxdepth 3 -type f ! -name '._*' ! -path '*/__MACOSX/*' | sort)
93
+ first_file=$(printf '%s\n' "$file_list" | sed -n '1p')
94
+ second_file=$(printf '%s\n' "$file_list" | sed -n '2p')
95
+ if [ -n "$first_file" ] && [ -z "$second_file" ]; then
96
+ printf '%s\n' "$first_file"
97
+ return 0
98
+ fi
99
+
100
+ return 1
101
+ }
102
+
103
+ activate_release() {
104
+ tag_name=$1
105
+ release_dir=$2
106
+
107
+ rm -f "$CURRENT_LINK"
108
+ ln -s "$release_dir" "$CURRENT_LINK"
109
+ printf '%s\n' "$tag_name" > "$CURRENT_TAG_FILE"
110
+ }
111
+
112
+ extract_release_binary() {
113
+ source_dir=$1
114
+ target_path=$2
115
+ binary_path=$(find_release_binary "$source_dir" || true)
116
+
117
+ if [ -z "$binary_path" ]; then
118
+ return 1
119
+ fi
120
+
121
+ if [ "$binary_path" != "$target_path" ]; then
122
+ cp "$binary_path" "$target_path"
123
+ fi
124
+ chmod +x "$target_path"
125
+ }
126
+
127
+ cleanup_workspace() {
128
+ workspace=$1
129
+ if [ -n "$workspace" ] && [ -d "$workspace" ]; then
130
+ rm -rf "$workspace"
131
+ fi
132
+ }
133
+
134
+ install_release() {
135
+ tag_name=$1
136
+ asset_url=$2
137
+
138
+ ensure_runtime_dirs
139
+ release_dir=$(release_dir_for_tag "$tag_name")
140
+ release_binary="$release_dir/$CURRENT_BINARY_NAME"
141
+
142
+ if [ -x "$release_binary" ]; then
143
+ activate_release "$tag_name" "$release_dir"
144
+ return 0
145
+ fi
146
+
147
+ workspace=$(mktemp -d "$RUNTIME_DIR/install.XXXXXX") || return 1
148
+ download_name=$(basename "$asset_url")
149
+ if [ -z "$download_name" ] || [ "$download_name" = "/" ] || [ "$download_name" = "." ]; then
150
+ download_name="release.asset"
151
+ fi
152
+ download_path="$DOWNLOADS_DIR/$download_name"
153
+ stage_dir="$workspace/stage"
154
+
155
+ rm -f "$download_path" || {
156
+ cleanup_workspace "$workspace"
157
+ return 1
158
+ }
159
+ mkdir -p "$stage_dir" || {
160
+ cleanup_workspace "$workspace"
161
+ return 1
162
+ }
163
+ curl -fsSL "$asset_url" -o "$download_path" || {
164
+ cleanup_workspace "$workspace"
165
+ return 1
166
+ }
167
+
168
+ case "$download_path" in
169
+ *.tar.gz)
170
+ tar -xzf "$download_path" -C "$stage_dir" || {
171
+ cleanup_workspace "$workspace"
172
+ return 1
173
+ }
174
+ extract_release_binary "$stage_dir" "$stage_dir/$CURRENT_BINARY_NAME" || {
175
+ cleanup_workspace "$workspace"
176
+ return 1
177
+ }
178
+ ;;
179
+ *)
180
+ cp "$download_path" "$stage_dir/$CURRENT_BINARY_NAME" || {
181
+ cleanup_workspace "$workspace"
182
+ return 1
183
+ }
184
+ chmod +x "$stage_dir/$CURRENT_BINARY_NAME" || {
185
+ cleanup_workspace "$workspace"
186
+ return 1
187
+ }
188
+ ;;
189
+ esac
190
+
191
+ rm -rf "$release_dir" || {
192
+ cleanup_workspace "$workspace"
193
+ return 1
194
+ }
195
+ mv "$stage_dir" "$release_dir" || {
196
+ cleanup_workspace "$workspace"
197
+ return 1
198
+ }
199
+ cleanup_workspace "$workspace"
200
+
201
+ activate_release "$tag_name" "$release_dir"
202
+ }
203
+
204
+ ensure_initial_release() {
205
+ if load_latest_release; then
206
+ if install_release "$LATEST_TAG" "$LATEST_ASSET_URL"; then
207
+ return 0
208
+ fi
209
+ log "警告: 安装最新版本失败,尝试使用已有版本继续启动"
210
+ fi
211
+
212
+ [ -x "$(current_binary_path)" ]
213
+ }
214
+
215
+ restart_managed_service() {
216
+ supervisorctl -c "$SUPERVISOR_CONF" restart "$SUPERVISOR_PROGRAM_NAME"
217
+ }
218
+
219
+ update_if_needed() {
220
+ load_latest_release || {
221
+ log "检查最新版本失败"
222
+ return 1
223
+ }
224
+
225
+ installed_tag=$(current_tag)
226
+ if [ "$LATEST_TAG" = "$installed_tag" ]; then
227
+ log "当前已是最新版本: $LATEST_TAG"
228
+ return 0
229
+ fi
230
+
231
+ log "发现新版本: ${installed_tag:-<none>} -> $LATEST_TAG"
232
+ install_release "$LATEST_TAG" "$LATEST_ASSET_URL" || {
233
+ log "安装新版本失败: $LATEST_TAG"
234
+ return 1
235
+ }
236
+
237
+ if [ -n "$installed_tag" ]; then
238
+ restart_managed_service
239
+ fi
240
+ }
scripts/update-loop.sh ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ : "${UPDATE_INTERVAL_SECONDS:=3600}"
5
+ : "${RELEASE_COMMON_SH:=/usr/local/bin/release-common.sh}"
6
+
7
+ . "$RELEASE_COMMON_SH"
8
+
9
+ while true; do
10
+ sleep "$UPDATE_INTERVAL_SECONDS"
11
+ log "开始定时版本检查"
12
+ if ! update_if_needed; then
13
+ log "警告: 自动更新检查失败,继续运行当前版本"
14
+ fi
15
+ done
supervisord.conf ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [unix_http_server]
2
+ file=%(ENV_RUNTIME_DIR)s/supervisor.sock
3
+ chmod=0700
4
+
5
+ [supervisord]
6
+ nodaemon=true
7
+ pidfile=%(ENV_RUNTIME_DIR)s/supervisord.pid
8
+ logfile=%(ENV_RUNTIME_DIR)s/supervisord.log
9
+ logfile_maxbytes=0
10
+ minfds=65535
11
+
12
+ [rpcinterface:supervisor]
13
+ supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
14
+
15
+ [supervisorctl]
16
+ serverurl=unix://%(ENV_RUNTIME_DIR)s/supervisor.sock
17
+
18
+ [program:__APP_PROGRAM__]
19
+ command=/bin/sh %(ENV_RUNTIME_DIR)s/run-app.sh
20
+ autostart=true
21
+ autorestart=true
22
+ startsecs=0
23
+ stopasgroup=true
24
+ killasgroup=true
25
+ stopwaitsecs=%(ENV_STOP_WAIT_SECS)s
26
+ stdout_logfile=/dev/fd/1
27
+ stdout_logfile_maxbytes=0
28
+ stderr_logfile=/dev/fd/2
29
+ stderr_logfile_maxbytes=0
30
+
31
+ [program:__UPDATER_PROGRAM__]
32
+ command=/usr/local/bin/update-loop.sh
33
+ autostart=true
34
+ autorestart=true
35
+ startsecs=0
36
+ stopasgroup=true
37
+ killasgroup=true
38
+ stdout_logfile=/dev/fd/1
39
+ stdout_logfile_maxbytes=0
40
+ stderr_logfile=/dev/fd/2
41
+ stderr_logfile_maxbytes=0