Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ name: docker-install CI
on: [push, pull_request]

jobs:
# Test mirror-option handling, not mirror reachability. Live Tencent Cloud
# validation must be run explicitly from a Tencent Cloud VPC private network.
test-mirror-options:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
- name: Check mirror test syntax
run: |
sh -n scripts/test-mirrors.sh
sh -n scripts/test-tencent-cloud-mirror.sh
- name: Check mirror options (offline only)
run: |
sh scripts/test-mirrors.sh
TEST_SHELL=bash sh scripts/test-mirrors.sh

test:
runs-on: ubuntu-latest
timeout-minutes: 5
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/setup-repo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: setup-repo option regression

on: [push, pull_request]

permissions:
contents: read

jobs:
test-setup-repo-options:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
- name: Check shell syntax
run: |
sh -n install.sh
sh -n scripts/test-setup-repo.sh
- name: Check setup-repo options (offline only)
run: |
sh scripts/test-setup-repo.sh
TEST_SHELL=bash sh scripts/test-setup-repo.sh
118 changes: 118 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,55 @@ From the source repo (This will install latest from the `stable` channel):
sh install.sh
```

### Package mirrors

Use `--mirror` to select a Docker package mirror:

| Mirror | Download URL |
| --- | --- |
| `Aliyun` | `https://mirrors.aliyun.com/docker-ce` |
| `AzureChinaCloud` | `https://mirror.azure.cn/docker-ce` |
| `TencentCloud` | `https://mirrors.cloud.tencent.com/docker-ce` |

**Tencent Cloud environment:** use the `TencentCloud` preset on a Tencent Cloud
VM through its VPC private-network route. Keep the default Tencent Cloud VPC DNS
configuration and avoid external proxies for private-network validation.
According to [Tencent Cloud's software mirror documentation](https://cloud.tencent.com/document/product/213/8623),
`mirrors.cloud.tencent.com` is a unified public/private domain that preferentially
resolves to the internal route with the default VPC DNS. This is not a claim that
the domain can never be reached publicly. Public CI results are not evidence of
Tencent Cloud intranet connectivity; live validation must run in that environment.

On a fresh Tencent Cloud VM running a supported Linux distribution, review the
script and preview the installation before running it as root:

```shell
sh install.sh --mirror TencentCloud --dry-run
sudo sh install.sh --mirror TencentCloud
```

`--dry-run` only prints commands; it does not verify DNS, mirror reachability,
package availability, or installation success, even when run on a Tencent Cloud VM.

To configure the package repository without installing Docker packages:

```shell
sudo sh install.sh --mirror TencentCloud --setup-repo
```

The `DOWNLOAD_URL` environment variable remains available for custom mirrors.
For example, the following previews the same mirror selection:

```shell
sudo env DOWNLOAD_URL=https://mirrors.cloud.tencent.com/docker-ce \
sh install.sh --dry-run
```

An explicit `--mirror` takes precedence over `DOWNLOAD_URL`. These options select
Docker package repositories, not Docker Hub registry mirrors. Mirror selection
does not change the supported distributions, and package availability depends on
the selected mirror.

## Testing:

To verify that the install script works amongst the supported operating systems run:
Expand All @@ -39,6 +88,75 @@ To verify that the install script works amongst the supported operating systems
make shellcheck
```

### Offline mirror-option regression checks

Run on a supported Linux distribution, including ordinary GitHub-hosted runners:

```shell
sh scripts/test-mirrors.sh
```

This suite checks argument parsing and dry-run output for all mirror presets,
including `TencentCloud`. It does not contact any mirror or install packages.
Downloader, package-manager and service commands are guarded against accidental
execution. Output is labelled `PASS (offline)` and must not be reported as live
mirror validation. The separate CI job runs only these offline checks and syntax
checks; it does not add Tencent Cloud network access to the installation matrix.

### Opt-in Tencent Cloud intranet smoke check

**Run live checks only from a Tencent Cloud VM using the VPC private network.**
Check the VM's DNS and routing first; do not run this on ordinary GitHub-hosted
runners or enable it automatically for untrusted pull requests on private runners.
For example, inspect resolution from the VM:

```shell
getent ahostsv4 mirrors.cloud.tencent.com
```

The network script skips by default, before DNS lookup or any network request:

```shell
sh scripts/test-tencent-cloud-mirror.sh ubuntu resolute
# SKIP: Tencent Cloud network smoke check is disabled; no network requests made.
```

After confirming the Tencent Cloud private-network environment, explicitly enable
it. The following checks the Ubuntu 26.04 (`resolute`) repository:

```shell
TENCENT_CLOUD_INTRANET=1 \
sh scripts/test-tencent-cloud-mirror.sh ubuntu resolute
```

The switch is an operator confirmation, not automatic cloud or route detection.
The script covers Ubuntu and Debian APT repositories; pass the target distribution
and codename, for example `debian trixie`. It downloads only the GPG key and Release
metadata to temporary files, with HTTPS verification, proxies disabled and bounded
timeouts. It neither follows redirects nor silently falls back to another endpoint.
It needs `curl`, not root privileges, and makes no package or service changes.

A disabled check reports `SKIP` and exits 0. Invalid arguments or opt-in values
exit 2. After opting in, DNS/TLS/HTTP errors, timeouts and unexpected content
fail with exit 1; they are never converted to a successful skip.

`PASS (network smoke only)` means the two endpoints returned expected content
markers. It does **not** verify cryptographic signatures, architecture/channel
package availability, package downloads, installation, or Docker service startup.
For installation acceptance, use a fresh disposable Tencent Cloud VM in the same
private-network environment, review the script, and run:

```shell
sudo sh install.sh --mirror TencentCloud
sudo docker version
sudo docker info
sudo docker compose version
```

Record the OS, architecture, commit, network environment and results separately
from the offline and smoke checks. Docker Hub access is a separate service and is
not part of this package-mirror test.

## Legal
*Brought to you courtesy of our legal counsel. For more context,
please see the [NOTICE](NOTICE) document in this repo.*
Expand Down
16 changes: 10 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ set -e
# Alternatively, use the script at https://test.docker.com, which uses the test
# channel as default.
#
# --mirror <Aliyun|AzureChinaCloud>
# --mirror <Aliyun|AzureChinaCloud|TencentCloud>
#
# Use the --mirror option to install from a mirror supported by this script.
# Available mirrors are "Aliyun" (https://mirrors.aliyun.com/docker-ce), and
# "AzureChinaCloud" (https://mirror.azure.cn/docker-ce), for example:
# Available mirrors are "Aliyun" (https://mirrors.aliyun.com/docker-ce),
# "AzureChinaCloud" (https://mirror.azure.cn/docker-ce), and
# "TencentCloud" (https://mirrors.cloud.tencent.com/docker-ce), for example:
#
# $ sudo sh install-docker.sh --mirror AzureChinaCloud
# $ sudo sh install-docker.sh --mirror TencentCloud
#
# --setup-repo
#
Expand Down Expand Up @@ -146,7 +148,7 @@ SBX=${SBX:-0}
usage() {
echo
echo "USAGE: "
echo " ${0} [--channel <stable|test>] [--mirror <Aliyun|AzureChinaCloud>] [--version <VERSION>] [--setup-repo] [--no-autostart] [--dry-run] [--help]"
echo " ${0} [--channel <stable|test>] [--mirror <Aliyun|AzureChinaCloud|TencentCloud>] [--version <VERSION>] [--setup-repo] [--no-autostart] [--dry-run] [--help]"
echo
}

Expand All @@ -169,7 +171,6 @@ while [ $# -gt 0 ]; do
;;
--setup-repo)
REPO_ONLY=1
shift
;;
--no-autostart)
NO_AUTOSTART=1
Expand All @@ -194,10 +195,13 @@ case "$mirror" in
AzureChinaCloud)
DOWNLOAD_URL="https://mirror.azure.cn/docker-ce"
;;
TencentCloud)
DOWNLOAD_URL="https://mirrors.cloud.tencent.com/docker-ce"
;;
"")
;;
*)
>&2 echo "unknown mirror '$mirror': use either 'Aliyun', or 'AzureChinaCloud'."
>&2 echo "unknown mirror '$mirror': use 'Aliyun', 'AzureChinaCloud', or 'TencentCloud'."
exit 1
;;
esac
Expand Down
165 changes: 165 additions & 0 deletions scripts/test-mirrors.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/bin/sh
# Offline mirror-option/dry-run regression checks, not mirror availability tests.
# TencentCloud must stay covered here even on non-Tencent CI runners: these
# assertions never contact a mirror. See test-tencent-cloud-mirror.sh for the
# separate, opt-in smoke check that requires Tencent Cloud's private network.
set -eu

cd "$(dirname "$0")/.."
test_shell=${TEST_SHELL:-sh}
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' 0
trap 'exit 1' HUP INT TERM

# Avoid the existing-installation warning delay on CI hosts with Docker installed.
mkdir "$tmp_dir/bin"
printf '#!/bin/sh\nexit 0\n' > "$tmp_dir/bin/sleep"
chmod +x "$tmp_dir/bin/sleep"

# Fail if dry-run accidentally invokes a downloader, package manager or service
# command. Preserve command-existence detection by only shadowing installed tools.
for tool in curl wget apt-get apt-cache dnf dnf5 yum yum-config-manager systemctl sudo su docker; do
command -v "$tool" > /dev/null 2>&1 || continue
cat > "$tmp_dir/bin/$tool" <<-'GUARD'
#!/bin/sh
printf '%s\n' "${0##*/}" >> "$FORBIDDEN_COMMAND_LOG"
echo "ERROR: ${0##*/} must not execute in an offline test." >&2
exit 99
GUARD
chmod +x "$tmp_dir/bin/$tool"
done
output="$tmp_dir/output"
download_url=''
network_test=''

fail() {
printf 'FAIL: %s\n' "$1" >&2
cat "$output" >&2
exit 1
}

run_script() {
status=0
: > "$tmp_dir/forbidden"
# Discard inherited installer options and any network-test opt-in.
env -i PATH="$tmp_dir/bin:$PATH" HOME="${HOME:-/tmp}" \
DOWNLOAD_URL="$download_url" TENCENT_CLOUD_INTRANET="$network_test" \
FORBIDDEN_COMMAND_LOG="$tmp_dir/forbidden" \
"$test_shell" "$@" > "$output" 2>&1 || status=$?
[ ! -s "$tmp_dir/forbidden" ] || fail 'offline test executed a forbidden command'
return "$status"
}

run() {
run_script ./install.sh --dry-run "$@"
}

contains() {
grep -F -- "$1" "$output" > /dev/null || fail "missing: $1"
}

excludes() {
if grep -F -- "$1" "$output" > /dev/null; then
fail "unexpected: $1"
fi
}

run || fail 'default download URL'
contains 'https://download.docker.com/linux/'
echo 'PASS (offline): default download URL'

for mirror in Aliyun AzureChinaCloud TencentCloud; do
case "$mirror" in
Aliyun) url='https://mirrors.aliyun.com/docker-ce' ;;
AzureChinaCloud) url='https://mirror.azure.cn/docker-ce' ;;
TencentCloud) url='https://mirrors.cloud.tencent.com/docker-ce' ;;
esac
run --mirror "$mirror" || fail "$mirror"
contains "$url/linux/"
excludes 'https://download.docker.com/linux/'
echo "PASS (offline): $mirror option selects expected URL"
done
cp "$output" "$tmp_dir/tencent-cloud"

download_url='https://mirrors.cloud.tencent.com/docker-ce'
run || fail 'DOWNLOAD_URL compatibility'
cmp -s "$output" "$tmp_dir/tencent-cloud" || fail 'mirror and DOWNLOAD_URL differ'
echo 'PASS (offline): TencentCloud matches DOWNLOAD_URL'

download_url='https://example.com/docker-ce'
run || fail 'custom DOWNLOAD_URL'
contains "$download_url/linux/"
echo 'PASS (offline): custom DOWNLOAD_URL'

run --mirror TencentCloud || fail 'mirror precedence'
contains 'https://mirrors.cloud.tencent.com/docker-ce/linux/'
excludes "$download_url"
echo 'PASS (offline): --mirror overrides DOWNLOAD_URL'
download_url=''

run --mirror TencentCloud --channel test || fail 'test channel'
contains 'https://mirrors.cloud.tencent.com/docker-ce/linux/'
grep -E ' test"|docker-ce-test' "$output" > /dev/null || fail 'test channel missing'
echo 'PASS (offline): test channel argument (not package availability)'

run --mirror TencentCloud --setup-repo || fail 'repository-only mode'
contains 'https://mirrors.cloud.tencent.com/docker-ce/linux/'
excludes ' install docker-ce'
excludes 'systemctl enable --now docker.service'
echo 'PASS (offline): repository-only mode'

run --mirror TencentCloud --no-autostart || fail 'no-autostart mode'
contains ' install docker-ce'
excludes 'systemctl enable --now docker.service'
echo 'PASS (offline): no-autostart mode'

run --mirror TencentCloud --version 27.5 || fail 'version option'
contains 'VERSION pinning is not supported in DRY_RUN'
contains 'https://mirrors.cloud.tencent.com/docker-ce/linux/'
echo 'PASS (offline): version option (not version availability)'

run --help || fail 'help output'
contains '--mirror <Aliyun|AzureChinaCloud|TencentCloud>'
echo 'PASS (offline): help lists TencentCloud'

status=0
run --mirror UnknownMirror || status=$?
[ "$status" -eq 1 ] || fail 'unknown mirror should exit with status 1'
contains "unknown mirror 'UnknownMirror'"
contains "'Aliyun', 'AzureChinaCloud', or 'TencentCloud'"
echo 'PASS (offline): unknown mirror is rejected'

# Test only the network check's entry guards here, never its live requests.
run_script ./scripts/test-tencent-cloud-mirror.sh ubuntu resolute || fail 'network check default'
contains 'SKIP:'
contains 'Tencent Cloud private network'
excludes 'PASS'
echo 'PASS (offline): network smoke check is skipped by default'

network_test=yes
status=0
run_script ./scripts/test-tencent-cloud-mirror.sh ubuntu resolute || status=$?
[ "$status" -eq 2 ] || fail 'invalid opt-in must fail before network access'
contains 'must be 0 or 1'
echo 'PASS (offline): invalid network opt-in is rejected'

network_test=1
status=0
run_script ./scripts/test-tencent-cloud-mirror.sh || status=$?
[ "$status" -eq 2 ] || fail 'missing network-check arguments must be rejected'
contains 'Usage:'
echo 'PASS (offline): missing network-check arguments are rejected'

status=0
run_script ./scripts/test-tencent-cloud-mirror.sh centos 9 || status=$?
[ "$status" -eq 2 ] || fail 'unsupported smoke-check repository must be rejected'
contains 'Ubuntu and Debian APT repositories only'
echo 'PASS (offline): unsupported smoke-check repository is rejected'

status=0
run_script ./scripts/test-tencent-cloud-mirror.sh ubuntu '../resolute' || status=$?
[ "$status" -eq 2 ] || fail 'invalid repository codename must be rejected'
contains 'lowercase letters and digits'
echo 'PASS (offline): invalid repository codename is rejected'

echo 'Offline checks complete. Tencent Cloud intranet connectivity and installation were NOT tested.'
Loading