deploy: automatically install Docker and Compose when missing
This commit is contained in:
1 parent
7f50d512c7
commit
b0d40403b3
4 files changed
+260
-18
No files matched your search
@@ -62,13 +62,15 @@ SIMKEEP 是一个可自行部署的 SIM / eSIM 管理平台。把开通日期、
|
||||
|
||||
### 一键部署
|
||||
|
||||
服务器已安装 Docker、Compose 插件和 curl 后,执行:
|
||||
Linux 服务器准备好 curl 后,使用 root 或有 sudo 权限的用户执行:
|
||||
|
||||
```bash
|
||||
bash -o pipefail -c 'curl -fsSL https://raw.githubusercontent.com/rest-rain/simkeep/main/install.sh | bash'
|
||||
```
|
||||
|
||||
脚本默认部署到当前目录下的 `simkeep` 文件夹,首次运行询问端口和 HTTP 访问地址,下载配置并启动镜像。打开显示的地址后创建账号。
|
||||
脚本会自动安装缺少的 Docker Engine / Compose 插件并启动 Docker 服务,已有可用环境则直接复用。支持 Docker 官方仍支持的 Ubuntu、Debian、CentOS Stream、Rocky Linux、RHEL 和 Fedora,架构为 `amd64` / `arm64`。
|
||||
|
||||
默认部署到当前目录下的 `simkeep` 文件夹,首次运行询问端口和 HTTP 访问地址,下载配置并启动镜像。打开显示的地址后创建账号。
|
||||
|
||||
再次从同一目录执行相同命令会保留已有 `.env`、`docker-compose.yml` 和数据卷,拉取镜像并更新容器。指定目录、无人值守部署等用法见 [一键部署说明](docs/DOCKER.md#一键部署)。
|
||||
|
||||
|
||||
+8
-4
@@ -4,13 +4,17 @@
|
||||
|
||||
## 一键部署
|
||||
|
||||
准备好 Docker Engine、Compose 插件及 curl,在服务器执行:
|
||||
Linux 服务器准备好 curl 后,使用 root 或有 sudo 权限的用户执行:
|
||||
|
||||
```bash
|
||||
bash -o pipefail -c 'curl -fsSL https://raw.githubusercontent.com/rest-rain/simkeep/main/install.sh | bash'
|
||||
```
|
||||
|
||||
首次运行会询问宿主机端口和 HTTP 访问地址。远程部署时填写例如 `http://服务器IP:5180` 的地址;本机试用可直接回车。默认部署目录为当前目录下的 `simkeep`,镜像为 `ghcr.io/rest-rain/simkeep:latest`。Docker 需要已启动,当前用户需能执行 `docker info`。
|
||||
首次运行会询问宿主机端口和 HTTP 访问地址。远程部署时填写例如 `http://服务器IP:5180` 的地址;本机试用可直接回车。默认部署目录为当前目录下的 `simkeep`,镜像为 `ghcr.io/rest-rain/simkeep:latest`。
|
||||
|
||||
Docker Engine 缺失时,脚本从 `https://get.docker.com` 下载官方安装脚本并执行;只有 Compose 缺失时,从 Docker 官方 GitHub 发布页下载插件,校验 SHA256 后安装。Docker 服务未启动时,使用 systemd 或 service 启动;已有可用环境直接复用。
|
||||
|
||||
自动安装支持 Docker 官方仍支持的 Ubuntu、Debian、CentOS Stream、Rocky Linux、RHEL 和 Fedora,支持 `amd64` / `arm64`。安装组件、启动服务或访问受限的 Docker socket 时,需要 root 或 sudo 权限。非 root 用户按提示完成 sudo 验证后,脚本会通过 sudo 调用需要权限的命令。
|
||||
|
||||
脚本下载缺少的配置文件,创建权限为 `600` 的 `.env`,验证 Compose 配置、拉取镜像并等待健康检查。成功后显示配置目录和访问提示;登录网页后,每个人在“通知设置”配置自己的 Telegram / SMTP。
|
||||
|
||||
@@ -22,11 +26,11 @@ bash -o pipefail -c 'curl -fsSL https://raw.githubusercontent.com/rest-rain/simk
|
||||
bash -o pipefail -c 'curl -fsSL https://raw.githubusercontent.com/rest-rain/simkeep/main/install.sh | bash -s -- --dir ./simkeep --port 5180 --url http://your-server:5180 --non-interactive'
|
||||
```
|
||||
|
||||
`--port`、`--url` 仅用于首次创建 `.env`;已有配置时按文件内容部署。重复执行时使用同一个 `--dir`。没有交互终端且未提供参数时,默认使用端口 `5180` 和 `http://localhost:5180`;远程部署请填写实际地址。
|
||||
`--port`、`--url` 仅用于首次创建 `.env`;已有配置时按文件内容部署。重复执行时使用同一个 `--dir`。无人值守安装需要 root 或免密码 sudo。没有交互终端且未提供参数时,默认使用端口 `5180` 和 `http://localhost:5180`;远程部署请填写实际地址。
|
||||
|
||||
## 在新服务器部署
|
||||
|
||||
部署只需要 `docker-compose.yml` 和 `.env`。可下载配置文件及空白模板:
|
||||
手动部署需先安装 Docker Engine 和 Compose 插件。应用只需要 `docker-compose.yml` 和 `.env`,可下载配置文件及空白模板:
|
||||
|
||||
```bash
|
||||
mkdir -p simkeep
|
||||
|
||||
+130
-8
@@ -3,6 +3,10 @@ set -Eeuo pipefail
|
||||
|
||||
DOWNLOAD_BASE='https://raw.githubusercontent.com/rest-rain/simkeep/main'
|
||||
staging=''
|
||||
docker_staging=''
|
||||
root_command=()
|
||||
docker_command=(docker)
|
||||
root_ready=0
|
||||
|
||||
fail() {
|
||||
printf '错误:%s\n' "$*" >&2
|
||||
@@ -10,9 +14,12 @@ fail() {
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$staging" ]]; then
|
||||
rm -rf -- "$staging"
|
||||
fi
|
||||
local directory
|
||||
for directory in "$staging" "$docker_staging"; do
|
||||
if [[ -n "$directory" ]]; then
|
||||
rm -rf -- "$directory"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
@@ -25,7 +32,7 @@ usage() {
|
||||
--non-interactive 使用参数或默认值,不询问配置
|
||||
--help 显示帮助
|
||||
|
||||
需要已安装且可用的 Docker Engine、Docker Compose 插件和 curl。
|
||||
需要 curl;Linux 上缺少 Docker / Compose 时会自动安装,需要 root 或 sudo 权限。
|
||||
已有 .env 和 docker-compose.yml 会保留;端口、地址参数仅用于首次部署。
|
||||
再次执行会按已有配置拉取镜像并更新容器,数据保存在原命名卷中。
|
||||
USAGE
|
||||
@@ -60,6 +67,122 @@ download() {
|
||||
[[ -s "$staging/$1" ]] || fail "下载的 $1 为空,请重试。"
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if ((root_ready)); then
|
||||
return
|
||||
fi
|
||||
if [[ "$(id -u)" != '0' ]]; then
|
||||
command -v sudo >/dev/null 2>&1 || fail '自动安装或启动 Docker 需要 root 权限,请使用 root 或 sudo 运行。'
|
||||
if (($1)); then
|
||||
sudo -v || fail '无法取得 root 权限,请使用 root 或可用的 sudo 运行。'
|
||||
root_command=(sudo)
|
||||
else
|
||||
sudo -n true || fail '无人值守安装需要 root 或免密码 sudo 权限。'
|
||||
root_command=(sudo -n)
|
||||
fi
|
||||
fi
|
||||
root_ready=1
|
||||
}
|
||||
|
||||
require_linux() {
|
||||
[[ "$(uname -s)" == 'Linux' ]] || fail '自动安装 Docker / Compose 仅支持 Linux;其他系统请先安装 Docker Desktop。'
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64|aarch64|arm64) ;;
|
||||
*) fail '自动安装目前支持 amd64 / arm64。' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
prepare_docker_downloads() {
|
||||
if [[ -z "$docker_staging" ]]; then
|
||||
docker_staging=$(
|
||||
umask 077
|
||||
mktemp -d "${TMPDIR:-/tmp}/simkeep-docker.XXXXXX"
|
||||
)
|
||||
trap cleanup EXIT
|
||||
fi
|
||||
}
|
||||
|
||||
install_engine() {
|
||||
local distro
|
||||
require_linux
|
||||
[[ -r /etc/os-release ]] || fail '无法识别 Linux 发行版,请手动安装 Docker。'
|
||||
distro=$(awk -F= '$1 == "ID" { gsub(/"/, "", $2); print $2 }' /etc/os-release)
|
||||
case "$distro" in
|
||||
ubuntu|debian|centos|rocky|rhel|fedora) ;;
|
||||
*) fail "当前发行版 ${distro:-未知} 不支持自动安装,请手动安装 Docker 后重试。" ;;
|
||||
esac
|
||||
require_root "$1"
|
||||
prepare_docker_downloads
|
||||
printf '未检测到 Docker,正在使用 Docker 官方脚本安装。\n'
|
||||
curl -fsSL --connect-timeout 15 --max-time 60 --retry 2 \
|
||||
-o "$docker_staging/docker-install.sh" https://get.docker.com || fail 'Docker 官方安装脚本下载失败,请检查网络。'
|
||||
[[ -s "$docker_staging/docker-install.sh" ]] || fail 'Docker 官方安装脚本为空。'
|
||||
"${root_command[@]}" sh "$docker_staging/docker-install.sh" || fail 'Docker 自动安装失败,请检查安装输出后重试。'
|
||||
hash -r
|
||||
command -v docker >/dev/null 2>&1 || fail '安装完成后仍找不到 Docker 命令。'
|
||||
}
|
||||
|
||||
install_compose() {
|
||||
local architecture filename expected actual directory='/usr/local/lib/docker/cli-plugins'
|
||||
require_linux
|
||||
command -v sha256sum >/dev/null 2>&1 || fail '安装 Compose 需要 sha256sum,请先安装 coreutils。'
|
||||
require_root "$1"
|
||||
prepare_docker_downloads
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) architecture=x86_64 ;;
|
||||
aarch64|arm64) architecture=aarch64 ;;
|
||||
esac
|
||||
filename="docker-compose-linux-$architecture"
|
||||
printf '未检测到可用的 Compose 插件,正在下载官方版本。\n'
|
||||
curl -fsSL --connect-timeout 15 --max-time 180 --retry 2 \
|
||||
-o "$docker_staging/$filename" "https://github.com/docker/compose/releases/latest/download/$filename" || fail 'Compose 下载失败。'
|
||||
curl -fsSL --connect-timeout 15 --max-time 60 --retry 2 \
|
||||
-o "$docker_staging/$filename.sha256" "https://github.com/docker/compose/releases/latest/download/$filename.sha256" || fail 'Compose 校验文件下载失败。'
|
||||
expected=$(awk 'NR == 1 { print $1 }' "$docker_staging/$filename.sha256")
|
||||
actual=$(sha256sum "$docker_staging/$filename")
|
||||
actual=${actual%% *}
|
||||
[[ "$expected" =~ ^[[:xdigit:]]{64}$ && "$actual" == "$expected" ]] || fail 'Compose 文件校验失败,已停止安装,请重试。'
|
||||
"${root_command[@]}" install -d -m 0755 "$directory"
|
||||
"${root_command[@]}" install -m 0755 "$docker_staging/$filename" "$directory/docker-compose"
|
||||
}
|
||||
|
||||
ensure_docker() {
|
||||
local context _attempt
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
[[ -z "${DOCKER_HOST:-}" && -z "${DOCKER_CONTEXT:-}" ]] || fail '已设置 DOCKER_HOST / DOCKER_CONTEXT,请先安装并配置所选 Docker 客户端。'
|
||||
install_engine "$1"
|
||||
fi
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
[[ -z "${DOCKER_HOST:-}" ]] || fail '无法连接 DOCKER_HOST 指定的 Docker,请检查连接配置。'
|
||||
context=$(docker context show 2>/dev/null) || fail '无法读取 Docker context,请检查 Docker 客户端。'
|
||||
[[ "$context" == 'default' ]] || fail "无法连接 Docker context $context,请先启动或修复该环境。"
|
||||
require_linux
|
||||
require_root "$1"
|
||||
docker_command=("${root_command[@]}" docker)
|
||||
if ! "${docker_command[@]}" info >/dev/null 2>&1; then
|
||||
printf '正在启动 Docker 服务。\n'
|
||||
if command -v systemctl >/dev/null 2>&1 && "${root_command[@]}" systemctl enable --now docker; then
|
||||
:
|
||||
elif command -v service >/dev/null 2>&1 && "${root_command[@]}" service docker start; then
|
||||
:
|
||||
else
|
||||
fail '无法启动 Docker 服务,请检查 Docker 安装与系统服务管理器。'
|
||||
fi
|
||||
for _attempt in {1..20}; do
|
||||
if "${docker_command[@]}" info >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
"${docker_command[@]}" info >/dev/null 2>&1 || fail 'Docker 服务未就绪,请检查 Docker 服务日志。'
|
||||
fi
|
||||
fi
|
||||
if ! "${docker_command[@]}" compose version >/dev/null 2>&1; then
|
||||
install_compose "$1"
|
||||
"${docker_command[@]}" compose version >/dev/null 2>&1 || fail 'Compose 安装后仍不可用,请检查 Docker 客户端或已有插件。'
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local install_dir="$PWD/simkeep" port='' public_url='' interactive=1 has_terminal=0
|
||||
local new_env=1 new_compose=1
|
||||
@@ -87,11 +210,9 @@ main() {
|
||||
validate_url "$public_url"
|
||||
public_url=${public_url%/}
|
||||
fi
|
||||
for dependency in docker curl awk; do
|
||||
for dependency in curl awk; do
|
||||
command -v "$dependency" >/dev/null 2>&1 || fail "缺少 $dependency,请先安装。"
|
||||
done
|
||||
docker info >/dev/null 2>&1 || fail 'Docker 未启动或当前用户没有权限,请先确认 docker info 能正常执行。'
|
||||
docker compose version >/dev/null 2>&1 || fail '请先安装 Docker Compose 插件,确认 docker compose version 能正常执行。'
|
||||
|
||||
[[ ! -e "$install_dir" || -d "$install_dir" ]] || fail '部署目录已经存在,但不是文件夹。'
|
||||
for configuration in .env docker-compose.yml; do
|
||||
@@ -127,6 +248,7 @@ main() {
|
||||
printf '保留已有 .env,按其中的配置部署;如需调整端口或地址,请编辑该文件。\n'
|
||||
fi
|
||||
|
||||
ensure_docker "$interactive"
|
||||
umask 077
|
||||
mkdir -p -- "$install_dir"
|
||||
install_dir=$(cd -- "$install_dir" && pwd -P)
|
||||
@@ -154,7 +276,7 @@ main() {
|
||||
fi
|
||||
chmod 600 -- "$install_dir/.env"
|
||||
|
||||
local compose=(docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/docker-compose.yml")
|
||||
local compose=("${docker_command[@]}" compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/docker-compose.yml")
|
||||
"${compose[@]}" config --quiet || fail 'Compose 配置验证失败,请检查部署目录中的配置文件。'
|
||||
"${compose[@]}" pull web || fail '镜像拉取失败,请检查网络和 .env 中的 SIMKEEP_IMAGE 后重试。'
|
||||
"${compose[@]}" up -d --wait --wait-timeout 120 web || fail '容器未通过健康检查,请在部署目录执行 docker compose logs --tail=100 web。'
|
||||
|
||||
+118
-4
@@ -5,6 +5,7 @@ from pathlib import Path
|
||||
import pty
|
||||
import select
|
||||
import shlex
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -24,38 +25,93 @@ class InstallerTests(unittest.TestCase):
|
||||
self.log = self.root / 'commands.jsonl'
|
||||
bin_dir = self.root / 'bin'
|
||||
bin_dir.mkdir()
|
||||
for name in ('bash', 'sh', 'python3', 'cat', 'mkdir', 'mktemp', 'mv', 'chmod', 'rm', 'cp', 'touch', 'sha256sum', 'true', 'sleep'):
|
||||
(bin_dir / name).symlink_to(shutil.which(name))
|
||||
command = '''#!/usr/bin/env python3
|
||||
import json, os, pathlib, shutil, sys
|
||||
import hashlib, json, os, pathlib, shutil, subprocess, sys
|
||||
name = pathlib.Path(sys.argv[0]).name
|
||||
args = sys.argv[1:]
|
||||
with open(os.environ['SIMKEEP_TEST_LOG'], 'a') as output:
|
||||
output.write(json.dumps([name, *args]) + '\\n')
|
||||
state = pathlib.Path(os.environ['SIMKEEP_TEST_STATE'])
|
||||
if name == 'docker':
|
||||
if args == ['info']:
|
||||
if os.getenv('SIMKEEP_TEST_ROOT_SOCKET') and not os.getenv('SIMKEEP_TEST_PRIVILEGED'):
|
||||
sys.exit(1)
|
||||
if os.getenv('SIMKEEP_TEST_STOPPED') and not (state / 'started').exists():
|
||||
sys.exit(1)
|
||||
sys.exit(int(os.getenv('SIMKEEP_TEST_DOCKER_FAILURE', '0')))
|
||||
if args == ['context', 'show']:
|
||||
print(os.getenv('SIMKEEP_TEST_CONTEXT', 'default'))
|
||||
sys.exit(0)
|
||||
if 'pull' in args:
|
||||
sys.exit(int(os.getenv('SIMKEEP_TEST_PULL_FAILURE', '0')))
|
||||
if 'version' in args:
|
||||
if os.getenv('SIMKEEP_TEST_COMPOSE_MISSING') and not (state / 'plugin').exists():
|
||||
sys.exit(1)
|
||||
print('2.39.0')
|
||||
elif name == 'curl':
|
||||
destination = pathlib.Path(args[args.index('-o') + 1])
|
||||
url = args[-1]
|
||||
if url == 'https://get.docker.com':
|
||||
destination.write_text('cp "$SIMKEEP_TEST_DOCKER_FIXTURE" "$SIMKEEP_TEST_BIN/docker"\\ntouch "$SIMKEEP_TEST_STATE/engine"\\n')
|
||||
sys.exit(0)
|
||||
if '/docker/compose/releases/latest/download/' in url:
|
||||
binary = b'compose-plugin-test-fixture\\n'
|
||||
if url.endswith('.sha256'):
|
||||
digest = '0' * 64 if os.getenv('SIMKEEP_TEST_BAD_CHECKSUM') else hashlib.sha256(binary).hexdigest()
|
||||
destination.write_text(digest + ' ' + url.rsplit('/', 1)[-1].removesuffix('.sha256') + '\\n')
|
||||
else:
|
||||
destination.write_bytes(binary)
|
||||
sys.exit(0)
|
||||
source = pathlib.Path(os.environ['SIMKEEP_TEST_FIXTURES']) / args[-1].rsplit('/', 1)[-1]
|
||||
if source.name == '.env.example' and os.getenv('SIMKEEP_TEST_DOWNLOAD_FAILURE'):
|
||||
destination.write_text('partial download')
|
||||
sys.exit(22)
|
||||
shutil.copyfile(source, destination)
|
||||
elif name == 'uname':
|
||||
print(os.getenv('SIMKEEP_TEST_SYSTEM', 'Linux') if '-s' in args else os.getenv('SIMKEEP_TEST_ARCH', 'aarch64'))
|
||||
elif name == 'id':
|
||||
print(os.getenv('SIMKEEP_TEST_UID', '0'))
|
||||
elif name == 'awk':
|
||||
if args[-1] == '/etc/os-release':
|
||||
print(os.getenv('SIMKEEP_TEST_DISTRO', 'ubuntu'))
|
||||
else:
|
||||
os.execv(os.environ['SIMKEEP_TEST_AWK'], [os.environ['SIMKEEP_TEST_AWK'], *args])
|
||||
elif name in ('systemctl', 'service'):
|
||||
if os.getenv('SIMKEEP_TEST_DOCKER_FAILURE'):
|
||||
sys.exit(1)
|
||||
(state / 'started').touch()
|
||||
elif name == 'install':
|
||||
if args[-1].endswith('/docker-compose'):
|
||||
(state / 'plugin').touch()
|
||||
elif name == 'sudo':
|
||||
if os.getenv('SIMKEEP_TEST_SUDO_FAILURE'):
|
||||
sys.exit(1)
|
||||
args = [arg for arg in args if arg != '-n']
|
||||
if args == ['-v']:
|
||||
sys.exit(0)
|
||||
environment = dict(os.environ, SIMKEEP_TEST_PRIVILEGED='1')
|
||||
sys.exit(subprocess.call(args, env=environment))
|
||||
'''
|
||||
for name in ('docker', 'curl'):
|
||||
for name in ('docker', 'curl', 'uname', 'id', 'awk', 'systemctl', 'service', 'install', 'sudo'):
|
||||
executable = bin_dir / name
|
||||
executable.write_text(command)
|
||||
executable.chmod(0o755)
|
||||
self.environment = dict(os.environ)
|
||||
for name in ('SIMKEEP_IMAGE', 'SIMKEEP_PORT', 'SIMKEEP_PUBLIC_URL', 'SIMKEEP_ENV_FILE'):
|
||||
for name in ('SIMKEEP_IMAGE', 'SIMKEEP_PORT', 'SIMKEEP_PUBLIC_URL', 'SIMKEEP_ENV_FILE', 'DOCKER_HOST', 'DOCKER_CONTEXT'):
|
||||
self.environment.pop(name, None)
|
||||
(self.root / 'state').mkdir()
|
||||
docker_fixture = self.root / 'fake-docker'
|
||||
shutil.copy2(bin_dir / 'docker', docker_fixture)
|
||||
self.environment.update({
|
||||
'PATH': str(bin_dir) + os.pathsep + self.environment.get('PATH', ''),
|
||||
'PATH': str(bin_dir),
|
||||
'SIMKEEP_TEST_LOG': str(self.log),
|
||||
'SIMKEEP_TEST_FIXTURES': str(ROOT),
|
||||
'SIMKEEP_TEST_STATE': str(self.root / 'state'),
|
||||
'SIMKEEP_TEST_BIN': str(bin_dir),
|
||||
'SIMKEEP_TEST_DOCKER_FIXTURE': str(docker_fixture),
|
||||
'SIMKEEP_TEST_AWK': shutil.which('awk'),
|
||||
})
|
||||
|
||||
def install(self, *options, environment=None):
|
||||
@@ -92,6 +148,7 @@ elif name == 'curl':
|
||||
self.assertIn(str(self.destination / 'docker-compose.yml'), docker[up])
|
||||
self.assertIn('http://example.invalid:5301', result.stdout)
|
||||
self.assertFalse(any('down' in args or 'build' in args for args in docker))
|
||||
self.assertFalse(any(args[0] in ('sudo', 'systemctl', 'service', 'install') for args in self.commands()))
|
||||
|
||||
def test_noninteractive_defaults_work_without_a_terminal(self):
|
||||
result = self.install()
|
||||
@@ -151,6 +208,63 @@ elif name == 'curl':
|
||||
self.assertFalse(any('up' in args for args in self.commands()))
|
||||
self.assertNotIn('部署完成', result.stdout)
|
||||
|
||||
def test_missing_docker_is_installed_from_the_official_script(self):
|
||||
(Path(self.environment['SIMKEEP_TEST_BIN']) / 'docker').unlink()
|
||||
result = self.install()
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertTrue((self.root / 'state/engine').exists())
|
||||
self.assertTrue(any(args[0] == 'curl' and args[-1] == 'https://get.docker.com' for args in self.commands()))
|
||||
self.assertTrue((self.destination / '.env').is_file())
|
||||
|
||||
def test_missing_compose_is_installed_without_reinstalling_the_engine(self):
|
||||
result = self.install(environment=dict(self.environment, SIMKEEP_TEST_COMPOSE_MISSING='1'))
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertTrue((self.root / 'state/plugin').exists())
|
||||
self.assertFalse((self.root / 'state/engine').exists())
|
||||
self.assertFalse(any(args[-1] == 'https://get.docker.com' for args in self.commands()))
|
||||
|
||||
def test_corrupt_compose_download_is_rejected_before_installation(self):
|
||||
environment = dict(self.environment, SIMKEEP_TEST_COMPOSE_MISSING='1', SIMKEEP_TEST_BAD_CHECKSUM='1')
|
||||
result = self.install(environment=environment)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertTrue(any(args[0] == 'curl' and args[-1].endswith('.sha256') for args in self.commands()))
|
||||
self.assertFalse((self.root / 'state/plugin').exists())
|
||||
self.assertFalse(any('up' in args for args in self.commands()))
|
||||
|
||||
def test_stopped_local_docker_is_started(self):
|
||||
result = self.install(environment=dict(self.environment, SIMKEEP_TEST_STOPPED='1'))
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertTrue((self.root / 'state/started').exists())
|
||||
|
||||
def test_nonroot_user_can_deploy_with_sudo_without_changing_group_membership(self):
|
||||
environment = dict(self.environment, SIMKEEP_TEST_UID='1000', SIMKEEP_TEST_ROOT_SOCKET='1')
|
||||
result = self.install(environment=environment)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertTrue(any(args[0] == 'sudo' and 'up' in args for args in self.commands()))
|
||||
self.assertFalse((self.root / 'state/started').exists())
|
||||
|
||||
def test_unavailable_sudo_fails_before_changing_the_system(self):
|
||||
(Path(self.environment['SIMKEEP_TEST_BIN']) / 'docker').unlink()
|
||||
environment = dict(self.environment, SIMKEEP_TEST_UID='1000', SIMKEEP_TEST_SUDO_FAILURE='1')
|
||||
result = self.install(environment=environment)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('root', result.stderr)
|
||||
self.assertFalse(any(args[0] == 'curl' for args in self.commands()))
|
||||
|
||||
def test_unsupported_system_is_rejected_before_running_the_docker_installer(self):
|
||||
(Path(self.environment['SIMKEEP_TEST_BIN']) / 'docker').unlink()
|
||||
result = self.install(environment=dict(self.environment, SIMKEEP_TEST_DISTRO='arch'))
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('arch', result.stderr)
|
||||
self.assertFalse(any(args[0] == 'curl' for args in self.commands()))
|
||||
|
||||
def test_unreachable_remote_docker_does_not_start_a_local_service(self):
|
||||
environment = dict(self.environment, SIMKEEP_TEST_DOCKER_FAILURE='1', DOCKER_HOST='tcp://example.invalid:2376')
|
||||
result = self.install(environment=environment)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('DOCKER_HOST', result.stderr)
|
||||
self.assertFalse(any(args[0] in ('sudo', 'systemctl', 'service') for args in self.commands()))
|
||||
|
||||
@unittest.skipUnless(hasattr(pty, 'fork'), 'Requires a Unix controlling terminal')
|
||||
def test_piped_script_reads_installation_answers_from_the_terminal(self):
|
||||
shell = f'cat {shlex.quote(str(ROOT / "install.sh"))} | bash -s -- --dir {shlex.quote(str(self.destination))}'
|
||||
|
||||
Reference in new issue
Block a user