-
Notifications
You must be signed in to change notification settings - Fork 14
/
action.yml
397 lines (315 loc) · 15.1 KB
/
action.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
name: "FTP/SFTP file deployer"
description: "Fast and customizable deployment with parallel connections and proxy support. Deploy only changed files or do full sync."
branding:
icon: "upload"
color: "black"
inputs:
remote-protocol:
description: "Remote file transfer protocol (ftp, sftp)"
required: true
default: "sftp"
remote-host:
description: "Remote host"
required: true
remote-port:
description: "Remote port"
required: true
default: 22
remote-user:
description: "FTP/SSH username"
required: true
remote-password:
description: "FTP/SSH password"
required: false
default: ""
ssh-private-key:
description: "SSH private key of user"
required: false
proxy:
description: "Enable proxy for FTP connection (true, false)"
required: true
default: false
proxy-host:
description: "Proxy host"
required: false
proxy-port:
description: "Proxy port"
required: false
default: 22
proxy-forwarding-port:
description: "Proxy forwarding port"
required: false
default: 1080
proxy-user:
description: "Proxy username"
required: false
proxy-private-key:
description: "Proxy SSH private key of user"
required: false
local-path:
description: "Local path to repository"
required: true
default: .
remote-path:
description: "Remote path on host"
required: true
default: .
sync:
description: "File synchronization (delta, full)"
required: true
default: "delta"
sync-delta-excludes:
description: "Files to exclude from delta sync"
required: false
ssh-options:
description: "Additional command arguments for SSH client"
required: false
ftp-options:
description: "Additional command arguments for FTP client (lftp)"
required: false
ftp-mirror-options:
description: "Additional command arguments for mirroring (lftp)"
required: false
ftp-post-sync-commands:
description: "Additional ftp commands to run after synchronization (lftp)"
required: false
webhook:
description: "Send webhook event notifications"
required: false
artifacts:
description: "Upload logs to artifacts"
required: false
debug:
description: "Enable debug information (true, false)"
required: false
runs:
using: "composite"
steps:
- name: "Deploy"
shell: bash
run: |
echo "::group::Initialization"
function show_hr() {
printf '%.s_' {1..100} && echo ""
}
function send_webhook() {
local status="$1"
local post_data=$(jq --null-input \
--arg timestamp `date +%s` \
--arg status "${status}" \
--arg repository "${{ github.repository }}" \
--arg workflow "${{ github.workflow }}" \
--arg job "${{ github.job }}" \
--arg run_id "${{ github.run_id }}" \
--arg ref "${{ github.ref }}" \
--arg event_name "${{ github.event_name }}" \
--arg actor "${{ github.actor }}" \
--arg message "${{ github.event.head_commit.message }}" \
--arg revision "${{ github.sha }}" \
'{"timestamp": $timestamp, "status": $status, "repository": $repository, "workflow": $workflow, "job": $job, "run_id": $run_id, "ref": $ref, "event_name": $event_name, "actor": $actor, "message": $message, "revision": $revision}')
curl --data "${post_data}" --header "Content-Type: application/json" --max-time 30 --show-error --silent --user-agent "GitHub Workflow" "${{inputs.webhook}}"
}
[ "${{inputs.webhook}}" != "" ] && echo "Webhook notification (start): $(send_webhook "start")"
echo "Check repository"
if [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" != "true" ]; then
echo "::error::Git repository not found. Please ensure you have a checkout step before this step."
exit 1
fi
echo "Initialize inputs"
local_path_unslash=$(echo "${{inputs.local-path}}" | sed 's:/*$::')
local_path_slash="${local_path_unslash}/"
remote_path_unslash=$(realpath --canonicalize-missing '${{inputs.remote-path}}')
remote_path_slash="${remote_path_unslash}/"
input_remote_password="${{inputs.remote-password}}"
if [ "${{inputs.remote-password}}" == "" ]; then
input_remote_password="dummypassword"
fi
input_proxy="${{inputs.proxy}}"
proxy_cmd=""
if [ "${input_proxy}" == "true" ]; then
proxy_cmd="proxychains"
fi
input_sync=${{inputs.sync}}
if [ "${{github.event_name}}" == "workflow_dispatch" ] && [ -n "${{github.event.inputs.sync}}" ]; then
input_sync=${{github.event.inputs.sync}}
fi
echo "Validate inputs"
if [ "${{inputs.remote-protocol}}" != "sftp" ] && [ "${{inputs.remote-protocol}}" != "ftp" ]; then
echo "::error::Invalid protocol: ${{inputs.remote-protocol}}. Valid protocols are 'ftp' and 'sftp'."
exit 1
fi
if [ "${input_sync}" != "delta" ] && [ "${input_sync}" != "full" ]; then
echo "::error::Invalid synchronization: ${input_sync}. Valid types are 'delta' and 'full'."
exit 1
fi
echo "::endgroup::"
if [ "${{inputs.debug}}" == "true" ]; then
echo "::group::Debug"
echo "Context: github.event" && cat ${{github.event_path}} && show_hr
echo "Context: env" && echo "${{toJSON(env)}}" && show_hr
echo "Inputs:" && echo "${{toJSON(inputs)}}"
echo "::endgroup::"
fi
echo "::group::Install packages"
apt_install=""
apt_quiet="--quiet --quiet"
if [ "${{inputs.debug}}" == "true" ]; then
apt_quiet=""
fi
sudo apt-get ${apt_quiet} update && sudo apt-get ${apt_quiet} --no-install-recommends --yes install lftp ${proxy_cmd}
echo "::endgroup::"
echo "::group::Configurations"
config_ssh=~/.ssh/config
mkdir ~/.ssh && echo -e "ExitOnForwardFailure=yes\nStrictHostKeyChecking=no" > ${config_ssh} && chmod 600 ${config_ssh} && echo "File created: ${config_ssh}"
[ "${{inputs.debug}}" == "true" ] && cat ${config_ssh}
show_hr
netrc=~/.netrc
echo "machine ${{inputs.remote-host}} login ${{inputs.remote-user}} password ${input_remote_password}" > ${netrc} && chmod 600 ${netrc} && echo "File created: ${netrc}"
[ "${{inputs.debug}}" == "true" ] && cat ${netrc}
show_hr
if [ "${{inputs.remote-protocol}}" == "sftp" ] && [ "${{inputs.ssh-private-key}}" != "" ]; then
key_ssh=~/ssh_private_key
echo "${{inputs.ssh-private-key}}" > ${key_ssh} && chmod 600 ${key_ssh} && echo "File created: ${key_ssh}" && show_hr
fi
if [ "${input_proxy}" == "true" ]; then
if [ "${{inputs.proxy-private-key}}" != "" ]; then
key_proxy=~/proxy_private_key
echo "${{inputs.proxy-private-key}}" > ${key_proxy} && chmod 600 ${key_proxy} && echo "File created: ${key_proxy}" && show_hr
config_proxychains=~/.proxychains/proxychains.conf
mkdir ~/.proxychains && echo "strict_chain
quiet_mode
tcp_read_time_out 15000
tcp_connect_time_out 10000
[ProxyList]
socks5 127.0.0.1 ${{inputs.proxy-forwarding-port}}" > ${config_proxychains} && echo "File created: ${config_proxychains}"
[ "${{inputs.debug}}" == "true" ] && cat ${config_proxychains}
show_hr
else
input_proxy="false"
echo "::warning::Invalid input 'proxy-private-key'. Skipping proxy connection."
fi
fi
echo "debug $([ "${{inputs.debug}}" == "true" ] && echo "9" || echo "false")
set cmd:trace $([ "${{inputs.debug}}" == "true" ] && echo "true" || echo "false")
set ftp:ssl-protect-data true
set ftp:sync-mode false
set log:enabled/xfer true
set log:file/xfer ~/transfer_log.txt
set log:show-time/xfer false
set mirror:overwrite true
set mirror:parallel-transfer-count 3
set mirror:set-permissions false
set net:max-retries 1
set net:persist-retries 0
set net:timeout 10
set sftp:auto-confirm true
set ssl:check-hostname false
set ssl:verify-certificate false
set xfer:parallel 3
${{inputs.ftp-options}}" > ~/.lftprc
if [ "${{inputs.remote-protocol}}" == "sftp" ] && [ "${{inputs.ssh-private-key}}" != "" ]; then
echo "set sftp:connect-program /usr/bin/ssh -a -x -i ~/ssh_private_key ${{inputs.ssh-options}}" >> ~/.lftprc
else
echo "set sftp:connect-program /usr/bin/ssh -a -x ${{inputs.ssh-options}}" >> ~/.lftprc
fi
echo "open ${{inputs.remote-protocol}}://${{inputs.remote-user}}@${{inputs.remote-host}}:${{inputs.remote-port}}" >> ~/.lftprc
echo "File created: ~/.lftprc"
[ "${{inputs.debug}}" == "true" ] && cat ~/.lftprc
echo "::endgroup::"
if [ "${input_proxy}" == "true" ]; then
echo "::group::Setup proxy"
if [ "${{inputs.proxy-user}}" != "" ] && [ "${{inputs.proxy-host}}" != "" ]; then
if ssh -A -D ${{inputs.proxy-forwarding-port}} -f -N -p ${{inputs.proxy-port}} -i ~/proxy_private_key ${{inputs.proxy-user}}@${{inputs.proxy-host}}; then
echo "Proxy connected" && show_hr && echo "Proxy IP address: $(${proxy_cmd} curl --max-time 10 --show-error --silent "http://checkip.amazonaws.com/")"
else
echo "::error::Proxy connection failed."
exit 1
fi
else
input_proxy="false"
echo "::warning::Invalid input 'proxy-user', 'proxy-host'. Skipping proxy connection."
fi
echo "::endgroup::"
fi
echo "::group::Prepare files"
echo "Event: ${{github.event_name}}
Revision: https://github.com/${{github.repository}}/commit/${{github.sha}}
Committer: ${{github.actor}}
Message: ${{github.event.head_commit.message}}" && show_hr
echo "${{github.sha}}" > "${local_path_slash}.deploy-revision" && echo "File created: ${local_path_slash}.deploy-revision" && cat "${local_path_slash}.deploy-revision" && show_hr
if [ "${input_sync}" == "delta" ]; then
touch ~/files_to_upload ~/files_to_delete
git_depth=$(git rev-list --count --all)
git_previous_commit=""
if [ "${git_depth}" -gt 1 ]; then
if [ "${{github.event_name}}" == "push" ]; then
git_previous_commit=${{github.event.before}}
elif [ "${{github.event_name}}" == "pull_request" ]; then
git_previous_commit=${{github.event.pull_request.base.sha}}
elif [ "${{github.event_name}}" == "workflow_dispatch" ]; then
git_previous_commit=$(git rev-parse ${{github.sha}}^)
else
echo "::error::Event not supported for delta synchronization: ${{github.event_name}}. Supported events are 'push', 'pull_request' and 'workflow_dispatch'."
exit 1
fi
else
echo "::error::Commit history not found for delta synchronization. Please ensure you have 'fetch-depth: 0' option in checkout action. Please ignore if this is an initial commit or newly created branch."
exit 1
fi
echo "Previous Revision: https://github.com/${{github.repository}}/commit/${git_previous_commit}" && show_hr
# ${proxy_cmd} lftp -c "set log:enabled/xfer false; get -O ~ \"${remote_path_slash}.deploy-revision\"; exit 0"
# echo -n "Remote Revision: " && [ -f ~/.deploy-revision ] && cat ~/.deploy-revision || echo ""
# show_hr
if git cat-file -t ${git_previous_commit} &>/dev/null; then
git diff --diff-filter=ACMRT --name-only ${git_previous_commit}..${{github.sha}} -- ${local_path_unslash} ':!/.git*' ${{inputs.sync-delta-excludes}} > ~/files_to_upload
git diff-tree --diff-filter=D --name-only -t ${git_previous_commit}..${{github.sha}} -- ${local_path_unslash} ':!/.git*' ${{inputs.sync-delta-excludes}} > ~/files_to_delete
sed --in-place --regexp-extended "s#(.*)#realpath --canonicalize-missing --relative-to=$local_path_unslash \1#e" ~/files_to_upload
sed --in-place --regexp-extended "s#(.*)#realpath --canonicalize-missing --relative-to=$local_path_unslash \1#e" ~/files_to_delete
echo "File created: ~/files_to_upload" && cat ~/files_to_upload && show_hr
echo "File created: ~/files_to_delete" && cat ~/files_to_delete && show_hr
if [ "${{inputs.artifacts}}" == "true" ]; then
echo "Copy transfer artifacts" && mkdir ~/transfer_files && rsync --verbose --files-from=$HOME/files_to_upload ${local_path_slash} ~/transfer_files/
fi
else
echo "::warning::Invalid base commit for delta synchronization: ${git_previous_commit}. Please ignore if this is an initial commit or newly created branch."
fi
fi
echo "::endgroup::"
echo "::group::Transfer files"
echo "Protocol: ${{inputs.remote-protocol}}
Synchronization: ${input_sync}
Local path: ${local_path_unslash}
Remote path: ${remote_path_unslash}"
[ "${input_sync}" == "delta" ] && echo -e "Upload files: $(wc --lines < ~/files_to_upload)\nDelete files: $(wc --lines < ~/files_to_delete)"
show_hr
touch "${local_path_slash}.deploy-running"
if [ "${input_sync}" == "full" ]; then
${proxy_cmd} lftp -c "put -O \"${remote_path_unslash}\" \"${local_path_slash}.deploy-running\";
mirror --exclude-glob=.git*/ --max-errors=10 --reverse ${{inputs.ftp-mirror-options}} ${local_path_unslash} ${remote_path_unslash};
rm -f \"${remote_path_slash}.deploy-running\";
${{inputs.ftp-post-sync-commands}}"
else
${proxy_cmd} lftp -c "lcd \"${local_path_unslash}\";
put -O \"${remote_path_unslash}\" \"${local_path_slash}.deploy-running\";
mput -d -O \"${remote_path_unslash}\" .deploy-revision $(awk '{ printf "\"%s\" ", $0 }' ~/files_to_upload);
rm -f \"${remote_path_slash}.deploy-check\" $(awk -v REMOTEPATH=\"${remote_path_slash}\" '{ printf "\"%s%s\" ", REMOTEPATH, $0 }' ~/files_to_delete);
rm -f \"${remote_path_slash}.deploy-running\";
${{inputs.ftp-post-sync-commands}}"
fi
[ -f ~/transfer_log.txt ] && cat ~/transfer_log.txt
echo "::endgroup::"
echo "::group::Cleanup"
[ "${input_proxy}" == "true" ] && sudo pkill ssh
rm --force --verbose ~/.netrc ~/proxy_private_key ~/ssh_private_key
[ "${{inputs.artifacts}}" != "true" ] && rm --force --verbose ~/transfer_log.txt
[ "${{inputs.webhook}}" != "" ] && echo "Webhook notification (finish): $(send_webhook "finish")"
echo "::endgroup::"
exit 0
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: "transfer_artifacts"
path: |
~/transfer_log.txt
~/transfer_files
if-no-files-found: ignore