@@ -80,7 +80,7 @@ ssh_remote() {
80
80
local ssh_addr=" $1 "
81
81
local target port
82
82
# Split out the port component, if exists
83
- if [[ " $ssh_addr " =~ ^([^:]+)(:([0-9]+))? $ ]]; then
83
+ if [[ " ${ ssh_addr} " =~ ^([^:]+)(:([0-9]+))? $ ]]; then
84
84
target=" ${BASH_REMATCH[1]} "
85
85
port=" ${BASH_REMATCH[3]:- } "
86
86
else
@@ -96,17 +96,17 @@ ssh_remote() {
96
96
-o " ConnectTimeout=15"
97
97
)
98
98
# Add port if specified
99
- if [[ -n " $port " ]]; then
100
- ssh_opts+=(-p " $port " )
99
+ if [[ -n " ${ port} " ]]; then
100
+ ssh_opts+=(-p " ${ port} " )
101
101
fi
102
102
# Add SSH key option if provided.
103
- if [[ -n " $SSH_KEY " ]]; then
104
- ssh_opts+=(-i " $SSH_KEY " )
103
+ if [[ -n " ${ SSH_KEY} " ]]; then
104
+ ssh_opts+=(-i " ${ SSH_KEY} " )
105
105
fi
106
106
107
107
# Establish ControlMaster connection in the background.
108
108
if ! ssh " ${ssh_opts[@]} " -f -N " ${target} " ; then
109
- error " Failed to connect to remote host via SSH: $ssh_addr "
109
+ error " Failed to connect to remote host via SSH: ${ ssh_addr} "
110
110
fi
111
111
112
112
# Populate SSH_ARGS array for reuse in all subsequent commands.
@@ -172,9 +172,9 @@ find_containerd_socket() {
172
172
for socket_path in " ${socket_paths[@]} " ; do
173
173
# Try without sudo first, then with sudo if REMOTE_SUDO is set
174
174
# shellcheck disable=SC2029
175
- if ssh " ${SSH_ARGS[@]} " " test -S '$socket_path '" 2> /dev/null ||
176
- ssh " ${SSH_ARGS[@]} " " sudo -n test -S '$socket_path '" 2> /dev/null; then
177
- CONTAINERD_SOCKET=" $socket_path "
175
+ if ssh " ${SSH_ARGS[@]} " " test -S '${ socket_path} '" 2> /dev/null ||
176
+ ssh " ${SSH_ARGS[@]} " " sudo -n test -S '${ socket_path} '" 2> /dev/null; then
177
+ CONTAINERD_SOCKET=" ${ socket_path} "
178
178
return 0
179
179
fi
180
180
done
@@ -194,42 +194,42 @@ run_unregistry() {
194
194
# Pull unregistry image if it doesn't exist on the remote host. This is done separately to not capture the output
195
195
# and print the pull progress to the terminal.
196
196
# shellcheck disable=SC2029
197
- if ! ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker image inspect $UNREGISTRY_IMAGE " > /dev/null 2>&1 ; then
198
- ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker pull $UNREGISTRY_IMAGE "
197
+ if ! ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker image inspect ${ UNREGISTRY_IMAGE} " > /dev/null 2>&1 ; then
198
+ ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker pull ${ UNREGISTRY_IMAGE} "
199
199
fi
200
200
201
201
for _ in {1..10}; do
202
202
UNREGISTRY_PORT=$( random_port)
203
- UNREGISTRY_CONTAINER=" unregistry-pussh-$$ -$UNREGISTRY_PORT "
203
+ UNREGISTRY_CONTAINER=" unregistry-pussh-$$ -${ UNREGISTRY_PORT} "
204
204
205
205
# shellcheck disable=SC2029
206
- if output=$( ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker run -d \
207
- --name $UNREGISTRY_CONTAINER \
208
- -p 127.0.0.1:$UNREGISTRY_PORT :5000 \
209
- -v $CONTAINERD_SOCKET :/run/containerd/containerd.sock \
206
+ if output=$( ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker run -d \
207
+ --name ${ UNREGISTRY_CONTAINER} \
208
+ -p 127.0.0.1:${ UNREGISTRY_PORT} :5000 \
209
+ -v ${ CONTAINERD_SOCKET} :/run/containerd/containerd.sock \
210
210
--userns=host \
211
211
--user root:root \
212
- $UNREGISTRY_IMAGE " 2>&1 ) ;
212
+ ${ UNREGISTRY_IMAGE} " 2>&1 ) ;
213
213
then
214
214
# Wait a moment for the container to start
215
215
sleep 1
216
216
217
217
# Verify the container is actually running and healthy
218
- if ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker ps --filter name=$UNREGISTRY_CONTAINER --filter status=running --quiet" | grep -q . ; then
218
+ if ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker ps --filter name=${ UNREGISTRY_CONTAINER} --filter status=running --quiet" | grep -q . ; then
219
219
return 0
220
220
fi
221
221
fi
222
222
223
223
# Remove the container that failed to start if it was created.
224
224
# shellcheck disable=SC2029
225
- ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker rm -f $UNREGISTRY_CONTAINER " > /dev/null 2>&1 || true
225
+ ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker rm -f ${ UNREGISTRY_CONTAINER} " > /dev/null 2>&1 || true
226
226
# Check if the error is due to port binding.
227
- if ! echo " $output " | grep -q --ignore-case " bind.*$UNREGISTRY_PORT " ; then
228
- error " Failed to start unregistry container:\n$output "
227
+ if ! echo " ${ output} " | grep -q --ignore-case " bind.*${ UNREGISTRY_PORT} " ; then
228
+ error " Failed to start unregistry container:\n${ output} "
229
229
fi
230
230
done
231
231
232
- error " Failed to start unregistry container:\n$output "
232
+ error " Failed to start unregistry container:\n${ output} "
233
233
}
234
234
235
235
# Forward a local port to a remote port over the established SSH connection.
@@ -244,16 +244,16 @@ forward_port() {
244
244
245
245
# Check if port is already in use locally.
246
246
# TODO: handle the case when nc is not available.
247
- if command -v nc > /dev/null && nc -z 127.0.0.1 " $local_port " 2> /dev/null; then
247
+ if command -v nc > /dev/null && nc -z 127.0.0.1 " ${ local_port} " 2> /dev/null; then
248
248
continue
249
249
fi
250
250
251
- if output=$( ssh " ${SSH_ARGS[@]} " -O forward -L " $local_port :127.0.0.1:$remote_port " 2>&1 ) ; then
252
- echo " $local_port "
251
+ if output=$( ssh " ${SSH_ARGS[@]} " -O forward -L " ${ local_port} :127.0.0.1:${ remote_port} " 2>&1 ) ; then
252
+ echo " ${ local_port} "
253
253
return 0
254
254
fi
255
255
256
- error " Failed to forward local port $local_port to remote unregistry port 127.0.0.1:$remote_port : $output "
256
+ error " Failed to forward local port ${ local_port} to remote unregistry port 127.0.0.1:${ remote_port} : ${ output} "
257
257
done
258
258
259
259
error " Failed to find an available local port to forward to remote unregistry port. Please try again."
@@ -264,7 +264,7 @@ is_additional_tunneling_needed() {
264
264
# Read all output to a variable to avoid issues with pipefail when 'grep -q' exits early.
265
265
local output
266
266
output=$( docker version 2> /dev/null)
267
- echo " $output " | grep -E -q " Docker Desktop|colima" && return 0
267
+ echo " ${ output} " | grep -E -q " Docker Desktop|colima" && return 0
268
268
return 1
269
269
}
270
270
@@ -283,24 +283,24 @@ run_docker_desktop_tunnel() {
283
283
DOCKER_DESKTOP_TUNNEL_PORT=$( random_port)
284
284
285
285
if output=$( docker run -d --rm \
286
- --name " $DOCKER_DESKTOP_TUNNEL_CONTAINER " \
287
- -p " 127.0.0.1:$DOCKER_DESKTOP_TUNNEL_PORT :5000" \
286
+ --name " ${ DOCKER_DESKTOP_TUNNEL_CONTAINER} " \
287
+ -p " 127.0.0.1:${ DOCKER_DESKTOP_TUNNEL_PORT} :5000" \
288
288
alpine/socat \
289
289
TCP-LISTEN:5000,fork,reuseaddr \
290
- " TCP-CONNECT:host.docker.internal:$host_port " 2>&1 ) ;
290
+ " TCP-CONNECT:host.docker.internal:${ host_port} " 2>&1 ) ;
291
291
then
292
292
return 0
293
293
fi
294
294
295
295
# Remove the container that failed to start if it was created.
296
- docker rm -f " $DOCKER_DESKTOP_TUNNEL_CONTAINER " > /dev/null 2>&1 || true
296
+ docker rm -f " ${ DOCKER_DESKTOP_TUNNEL_CONTAINER} " > /dev/null 2>&1 || true
297
297
# Check if error is due to port binding.
298
- if ! echo " $output " | grep -q --ignore-case " bind.*$DOCKER_DESKTOP_TUNNEL_PORT " ; then
299
- error " Failed to create a tunnel from Docker Desktop VM to localhost:$host_port :\n$output "
298
+ if ! echo " ${ output} " | grep -q --ignore-case " bind.*${ DOCKER_DESKTOP_TUNNEL_PORT} " ; then
299
+ error " Failed to create a tunnel from Docker Desktop VM to localhost:${ host_port} :\n${ output} "
300
300
fi
301
301
done
302
302
303
- error " Failed to create a tunnel from Docker Desktop VM to localhost:$host_port :\n$output "
303
+ error " Failed to create a tunnel from Docker Desktop VM to localhost:${ host_port} :\n${ output} "
304
304
}
305
305
306
306
DOCKER_PLATFORM=" "
@@ -319,14 +319,14 @@ while [[ $# -gt 0 ]]; do
319
319
case " $1 " in
320
320
-i|--ssh-key)
321
321
if [[ -z " ${2:- } " ]]; then
322
- error " -i/--ssh-key option requires an argument.\n$help_command "
322
+ error " -i/--ssh-key option requires an argument.\n${ help_command} "
323
323
fi
324
324
SSH_KEY=" $2 "
325
325
shift 2
326
326
;;
327
327
--platform)
328
328
if [[ -z " ${2:- } " ]]; then
329
- error " --platform option requires an argument.\n$help_command "
329
+ error " --platform option requires an argument.\n${ help_command} "
330
330
fi
331
331
DOCKER_PLATFORM=" $2 "
332
332
shift 2
@@ -341,38 +341,38 @@ while [[ $# -gt 0 ]]; do
341
341
exit 0
342
342
;;
343
343
-* )
344
- error " Unknown option: $1 \n$help_command "
344
+ error " Unknown option: $1 \n${ help_command} "
345
345
;;
346
346
* )
347
347
# First non-option argument is the image.
348
- if [[ -z " $IMAGE " ]]; then
348
+ if [[ -z " ${ IMAGE} " ]]; then
349
349
IMAGE=" $1 "
350
350
# Second non-option argument is the SSH address.
351
- elif [[ -z " $SSH_ADDRESS " ]]; then
351
+ elif [[ -z " ${ SSH_ADDRESS} " ]]; then
352
352
SSH_ADDRESS=" $1 "
353
353
else
354
- error " Too many arguments.\n$help_command "
354
+ error " Too many arguments.\n${ help_command} "
355
355
fi
356
356
shift
357
357
;;
358
358
esac
359
359
done
360
360
361
361
# Validate required arguments.
362
- if [[ -z " $IMAGE " ]] || [[ -z " $SSH_ADDRESS " ]]; then
363
- error " IMAGE and HOST are required.\n$help_command "
362
+ if [[ -z " ${ IMAGE} " ]] || [[ -z " ${ SSH_ADDRESS} " ]]; then
363
+ error " IMAGE and HOST are required.\n${ help_command} "
364
364
fi
365
365
# Validate SSH key file exists if provided.
366
- if [[ -n " $SSH_KEY " ]] && [[ ! -f " $SSH_KEY " ]]; then
367
- error " SSH key file not found: $SSH_KEY "
366
+ if [[ -n " ${ SSH_KEY} " ]] && [[ ! -f " ${ SSH_KEY} " ]]; then
367
+ error " SSH key file not found: ${ SSH_KEY} "
368
368
fi
369
369
370
370
get_temp_image_name () {
371
371
local image=$1
372
372
local regex=" ^(.*:.*?\/)?(.*\/)?(.+)$"
373
373
local repo_with_tag
374
374
375
- if [[ $image =~ $regex ]]; then
375
+ if [[ ${ image} =~ ${ regex} ]]; then
376
376
repo_with_tag=${BASH_REMATCH[3]}
377
377
echo " ${RANDOM} -${repo_with_tag} "
378
378
return 0
@@ -391,19 +391,19 @@ cleanup() {
391
391
fi
392
392
393
393
# Remove Docker Desktop tunnel container if exists.
394
- if [[ -n " $DOCKER_DESKTOP_TUNNEL_CONTAINER " ]]; then
395
- docker rm -f " $DOCKER_DESKTOP_TUNNEL_CONTAINER " > /dev/null 2>&1 || true
394
+ if [[ -n " ${ DOCKER_DESKTOP_TUNNEL_CONTAINER} " ]]; then
395
+ docker rm -f " ${ DOCKER_DESKTOP_TUNNEL_CONTAINER} " > /dev/null 2>&1 || true
396
396
fi
397
397
398
398
# Clean up the temporary registry image tag.
399
399
if [[ -n " ${REGISTRY_IMAGE:- } " ]]; then
400
- docker rmi " $REGISTRY_IMAGE " > /dev/null 2>&1 || true
400
+ docker rmi " ${ REGISTRY_IMAGE} " > /dev/null 2>&1 || true
401
401
fi
402
402
403
403
# Stop and remove unregistry container on remote host.
404
- if [[ -n " $UNREGISTRY_CONTAINER " ]]; then
404
+ if [[ -n " ${ UNREGISTRY_CONTAINER} " ]]; then
405
405
# shellcheck disable=SC2029
406
- ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker rm -f $UNREGISTRY_CONTAINER " > /dev/null 2>&1 || true
406
+ ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker rm -f ${ UNREGISTRY_CONTAINER} " > /dev/null 2>&1 || true
407
407
fi
408
408
409
409
# Terminate the shared SSH connection if it was established.
@@ -413,37 +413,37 @@ cleanup() {
413
413
}
414
414
trap cleanup EXIT
415
415
416
- info " Connecting to $SSH_ADDRESS ..."
417
- ssh_remote " $SSH_ADDRESS "
416
+ info " Connecting to ${ SSH_ADDRESS} ..."
417
+ ssh_remote " ${ SSH_ADDRESS} "
418
418
check_remote_docker
419
419
420
420
info " Starting unregistry container on remote host..."
421
421
run_unregistry
422
- success " Unregistry is listening localhost:$UNREGISTRY_PORT on remote host."
422
+ success " Unregistry is listening localhost:${ UNREGISTRY_PORT} on remote host."
423
423
424
424
# Forward random local port to remote unregistry port through established SSH connection.
425
- LOCAL_PORT=$( forward_port " $UNREGISTRY_PORT " )
426
- success " Forwarded localhost:$LOCAL_PORT to unregistry over SSH connection."
425
+ LOCAL_PORT=$( forward_port " ${ UNREGISTRY_PORT} " )
426
+ success " Forwarded localhost:${ LOCAL_PORT} to unregistry over SSH connection."
427
427
428
- PUSH_PORT=$LOCAL_PORT
428
+ PUSH_PORT=${ LOCAL_PORT}
429
429
# Handle virtualized Docker on macOS (e.g., Docker Desktop, Colima, etc.)
430
430
# shellcheck disable=SC2310
431
431
if is_additional_tunneling_needed; then
432
- info " Detected virtualized Docker, creating additional tunnel to localhost:$LOCAL_PORT ..."
433
- run_docker_desktop_tunnel " $LOCAL_PORT "
434
- PUSH_PORT=$DOCKER_DESKTOP_TUNNEL_PORT
435
- success " Additional tunnel created: localhost:$PUSH_PORT → localhost:$LOCAL_PORT "
432
+ info " Detected virtualized Docker, creating additional tunnel to localhost:${ LOCAL_PORT} ..."
433
+ run_docker_desktop_tunnel " ${ LOCAL_PORT} "
434
+ PUSH_PORT=${ DOCKER_DESKTOP_TUNNEL_PORT}
435
+ success " Additional tunnel created: localhost:${ PUSH_PORT} → localhost:${ LOCAL_PORT} "
436
436
fi
437
437
438
438
IMAGE_NAME_TAG=$( get_temp_image_name " ${IMAGE} " )
439
439
# Tag and push the image to unregistry through the forwarded port.
440
- REGISTRY_IMAGE=" localhost:$PUSH_PORT / $ IMAGE_NAME_TAG"
441
- docker tag " $IMAGE " " $REGISTRY_IMAGE "
442
- info " Pushing '$REGISTRY_IMAGE ' to unregistry..."
440
+ REGISTRY_IMAGE=" localhost:${ PUSH_PORT} / ${ IMAGE_NAME_TAG} "
441
+ docker tag " ${ IMAGE} " " ${ REGISTRY_IMAGE} "
442
+ info " Pushing '${ REGISTRY_IMAGE} ' to unregistry..."
443
443
444
444
DOCKER_PUSH_OPTS=()
445
- if [[ -n " $DOCKER_PLATFORM " ]]; then
446
- DOCKER_PUSH_OPTS+=(" --platform" " $DOCKER_PLATFORM " )
445
+ if [[ -n " ${ DOCKER_PLATFORM} " ]]; then
446
+ DOCKER_PUSH_OPTS+=(" --platform" " ${ DOCKER_PLATFORM} " )
447
447
fi
448
448
449
449
# Try push with retry logic for connection issues
@@ -453,44 +453,44 @@ PUSH_SLEEP_INTERVAL=3
453
453
454
454
for attempt in $( seq 1 " ${PUSH_RETRY_COUNT} " ) ; do
455
455
# That DOCKER_PUSH_OPTS expansion is needed to avoid issues with empty array expansion in older bash versions.
456
- if docker push ${DOCKER_PUSH_OPTS[@]+" ${DOCKER_PUSH_OPTS[@]} " } " $REGISTRY_IMAGE " ; then
456
+ if docker push ${DOCKER_PUSH_OPTS[@]+" ${DOCKER_PUSH_OPTS[@]} " } " ${ REGISTRY_IMAGE} " ; then
457
457
PUSH_SUCCESS=true
458
458
break
459
459
else
460
460
if [[ " ${attempt} " -lt " ${PUSH_RETRY_COUNT} " ]]; then
461
- warning " Push attempt $attempt failed, retrying in ${PUSH_SLEEP_INTERVAL} seconds..."
461
+ warning " Push attempt ${ attempt} failed, retrying in ${PUSH_SLEEP_INTERVAL} seconds..."
462
462
sleep " ${PUSH_SLEEP_INTERVAL} "
463
463
fi
464
464
fi
465
465
done
466
466
467
- if [[ " $PUSH_SUCCESS " = false ]]; then
468
- error " Failed to push image after $PUSH_RETRY_COUNT attempts."
467
+ if [[ " ${ PUSH_SUCCESS} " = false ]]; then
468
+ error " Failed to push image after ${ PUSH_RETRY_COUNT} attempts."
469
469
fi
470
470
471
471
REMOTE_REGISTRY_IMAGE=" "
472
472
# Pull image from unregistry if remote Docker doesn't uses containerd image store.
473
473
# shellcheck disable=SC2029
474
- if ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker info -f '{{ .DriverStatus }}' | grep -q 'containerd.snapshotter'" ; then
474
+ if ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker info -f '{{ .DriverStatus }}' | grep -q 'containerd.snapshotter'" ; then
475
475
# Remote image store uses containerd, so we can use the image directly.
476
- REMOTE_REGISTRY_IMAGE=" $IMAGE_NAME_TAG "
476
+ REMOTE_REGISTRY_IMAGE=" ${ IMAGE_NAME_TAG} "
477
477
else
478
478
info " Remote Docker doesn't use containerd image store. Pulling image from unregistry..."
479
- REMOTE_REGISTRY_IMAGE=" localhost:$UNREGISTRY_PORT / $ IMAGE_NAME_TAG"
480
- if ! ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker pull $REMOTE_REGISTRY_IMAGE " ; then
479
+ REMOTE_REGISTRY_IMAGE=" localhost:${ UNREGISTRY_PORT} / ${ IMAGE_NAME_TAG} "
480
+ if ! ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker pull ${ REMOTE_REGISTRY_IMAGE} " ; then
481
481
error " Failed to pull image from unregistry on remote host."
482
482
fi
483
483
fi
484
484
485
485
# shellcheck disable=SC2029
486
- if ! ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker tag $REMOTE_REGISTRY_IMAGE $ IMAGE" ; then
487
- error " Failed to retag image on remote host $REMOTE_REGISTRY_IMAGE → $IMAGE "
486
+ if ! ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker tag ${ REMOTE_REGISTRY_IMAGE} ${ IMAGE} " ; then
487
+ error " Failed to retag image on remote host ${ REMOTE_REGISTRY_IMAGE} → ${ IMAGE} "
488
488
fi
489
489
# shellcheck disable=SC2029
490
- ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker rmi $REMOTE_REGISTRY_IMAGE " > /dev/null || true
490
+ ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker rmi ${ REMOTE_REGISTRY_IMAGE} " > /dev/null || true
491
491
492
492
info " Removing unregistry container on remote host..."
493
493
# shellcheck disable=SC2029
494
- ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker rm -f $UNREGISTRY_CONTAINER " > /dev/null || true
494
+ ssh " ${SSH_ARGS[@]} " " ${ REMOTE_SUDO} docker rm -f ${ UNREGISTRY_CONTAINER} " > /dev/null || true
495
495
496
- success " Successfully pushed '$IMAGE ' to $SSH_ADDRESS "
496
+ success " Successfully pushed '${ IMAGE} ' to ${ SSH_ADDRESS} "
0 commit comments