caidao78 commited on
Commit
a4317bf
·
verified ·
1 Parent(s): 9660ccb

Upload 7 files

Browse files
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 "$@"
release-common.sh ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ : "${REPO_API_URL:=https://api.github.com/repos/caidaoli/CLIProxyAPI/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/release-common.sh CHANGED
@@ -1,18 +1,28 @@
1
  #!/bin/sh
2
 
3
  : "${REPO_API_URL:=https://api.github.com/repos/caidaoli/CLIProxyAPI/releases/latest}"
4
- : "${RUNTIME_DIR:=/tmp/cliproxyapi}"
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:=cliproxyapi}"
11
- : "${CURRENT_BINARY_NAME:=cli-proxy-api}"
12
- : "${APP_BINARY_NAMES:=cli-proxy-api CLIProxyAPI}"
 
13
 
14
  log() {
15
- printf '%s\n' "$*" >&2
 
 
 
 
 
 
 
 
 
16
  }
17
 
18
  ensure_runtime_dirs() {
@@ -30,19 +40,22 @@ current_binary_path() {
30
  }
31
 
32
  fetch_latest_release_info() {
33
- response=$(curl -fsSL "$REPO_API_URL")
 
34
  tag_name=$(
35
  printf '%s\n' "$response" \
36
  | grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"[^"]+"' \
37
  | head -n 1 \
38
- | cut -d '"' -f 4
 
39
  )
40
  asset_url=$(
41
  printf '%s\n' "$response" \
42
  | grep -Eo '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*"' \
43
  | cut -d '"' -f 4 \
44
  | grep -E '/[^/]*linux[-_]amd64(\.tar\.gz)?$' \
45
- | head -n 1
 
46
  )
47
 
48
  if [ -z "$tag_name" ] || [ -z "$asset_url" ]; then
@@ -52,6 +65,12 @@ fetch_latest_release_info() {
52
  printf '%s|%s\n' "$tag_name" "$asset_url"
53
  }
54
 
 
 
 
 
 
 
55
  find_release_binary() {
56
  release_dir=$1
57
 
@@ -62,7 +81,23 @@ find_release_binary() {
62
  fi
63
  done
64
 
65
- find "$release_dir" -maxdepth 3 -type f \( -name 'cli-proxy-api' -o -name 'CLIProxyAPI' \) | head -n 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
68
  activate_release() {
@@ -89,12 +124,19 @@ extract_release_binary() {
89
  chmod +x "$target_path"
90
  }
91
 
 
 
 
 
 
 
 
92
  install_release() {
93
  tag_name=$1
94
  asset_url=$2
95
 
96
  ensure_runtime_dirs
97
- release_dir="$RELEASES_DIR/$tag_name"
98
  release_binary="$release_dir/$CURRENT_BINARY_NAME"
99
 
100
  if [ -x "$release_binary" ]; then
@@ -102,42 +144,68 @@ install_release() {
102
  return 0
103
  fi
104
 
105
- temp_dir=$(mktemp -d "$RUNTIME_DIR/install.XXXXXX")
106
- download_path="$DOWNLOADS_DIR/$(basename "$asset_url")"
107
- extracted_dir="$temp_dir/release"
 
 
 
 
108
 
109
- rm -f "$download_path"
110
- mkdir -p "$extracted_dir"
111
- curl -fsSL "$asset_url" -o "$download_path"
 
 
 
 
 
 
 
 
 
112
 
113
  case "$download_path" in
114
  *.tar.gz)
115
- tar -xzf "$download_path" -C "$extracted_dir"
116
- extract_release_binary "$extracted_dir" "$extracted_dir/$CURRENT_BINARY_NAME" || {
117
- rm -rf "$temp_dir"
 
 
 
118
  return 1
119
  }
120
  ;;
121
  *)
122
- cp "$download_path" "$extracted_dir/$CURRENT_BINARY_NAME"
123
- chmod +x "$extracted_dir/$CURRENT_BINARY_NAME"
 
 
 
 
 
 
124
  ;;
125
  esac
126
 
127
- mkdir -p "$(dirname "$release_dir")"
128
- rm -rf "$release_dir"
129
- mv "$extracted_dir" "$release_dir"
130
- rm -rf "$temp_dir"
 
 
 
 
 
 
131
  activate_release "$tag_name" "$release_dir"
132
  }
133
 
134
  ensure_initial_release() {
135
- release_info=$(fetch_latest_release_info) || release_info=""
136
-
137
- if [ -n "$release_info" ]; then
138
- latest_tag=${release_info%%|*}
139
- asset_url=${release_info#*|}
140
- install_release "$latest_tag" "$asset_url" && return 0
141
  log "警告: 安装最新版本失败,尝试使用已有版本继续启动"
142
  fi
143
 
@@ -149,21 +217,20 @@ restart_managed_service() {
149
  }
150
 
151
  update_if_needed() {
152
- release_info=$(fetch_latest_release_info) || {
153
  log "检查最新版本失败"
154
  return 1
155
  }
156
- latest_tag=${release_info%%|*}
157
- asset_url=${release_info#*|}
158
- installed_tag=$(current_tag)
159
 
160
- if [ "$latest_tag" = "$installed_tag" ]; then
161
- log "当前已是最新版本: $latest_tag"
 
162
  return 0
163
  fi
164
 
165
- install_release "$latest_tag" "$asset_url" || {
166
- log "安装新版本失败: $latest_tag"
 
167
  return 1
168
  }
169
 
 
1
  #!/bin/sh
2
 
3
  : "${REPO_API_URL:=https://api.github.com/repos/caidaoli/CLIProxyAPI/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() {
 
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
 
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
 
 
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() {
 
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
 
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
 
 
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
 
scripts/update-loop.sh CHANGED
@@ -1,12 +1,14 @@
1
  #!/bin/sh
2
  set -eu
3
 
4
- : "${UPDATE_INTERVAL_SECONDS:=14400}"
 
5
 
6
- . /usr/local/bin/release-common.sh
7
 
8
  while true; do
9
  sleep "$UPDATE_INTERVAL_SECONDS"
 
10
  if ! update_if_needed; then
11
  log "警告: 自动更新检查失败,继续运行当前版本"
12
  fi
 
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
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
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