修复bug
This commit is contained in:
1 parent
fc9bbc20e4
commit
e87c4ee6a8
90 files changed
+3828
-452
No files matched your search
+1
-1
@@ -169,7 +169,7 @@ if (-not $Component) {
|
||||
# CHUSQL_ADMIN_PASSWORD。口令只经 stdin 传给 csql-bootstrap,不落 settings.toml。
|
||||
$adminPassword = "$env:CHUSQL_ADMIN_PASSWORD"
|
||||
if ($Password) { $adminPassword = $Password }
|
||||
if ($canPrompt) {
|
||||
if ($canPrompt -and $adminPassword.Length -eq 0) {
|
||||
Write-Host ''
|
||||
Write-Host ('The administrator account is "{0}".' -f $RootUser)
|
||||
while ($true) {
|
||||
|
||||
+64
-24
@@ -28,14 +28,13 @@ usage: ./install.sh [--component web|cli|both] [options]
|
||||
asks (English, y/n), command line client first:
|
||||
command line client (csql)? default yes [Y/n]
|
||||
web front end (browser UI)? default no [y/N]
|
||||
it then asks whether to give the administrator account a
|
||||
password (default no); that password goes straight into the
|
||||
it then asks for the administrator password; it goes into the
|
||||
system catalog and is never written to settings.toml
|
||||
|
||||
--interactive ask even when there is no terminal; the answers are
|
||||
read from stdin (one per line)
|
||||
--install-dir DIR where to install (default: $HOME/.local/share/chusql)
|
||||
--data-dir DIR where to keep data (default: <install-dir>/data)
|
||||
--data-dir DIR where to keep data (default: XDG data directory)
|
||||
--user NAME administrator name (default: root)
|
||||
--keep-package do not delete the downloaded/extracted package
|
||||
--password VALUE password of the administrator account (required when
|
||||
@@ -132,6 +131,19 @@ case "$self" in
|
||||
esac
|
||||
|
||||
[ -n "$install_dir" ] || install_dir="$HOME/.local/share/chusql"
|
||||
config_file="${XDG_CONFIG_HOME:-$HOME/.config}/ChuSQL/settings.toml"
|
||||
if [ "$list_versions" = 'no' ] && [ -f "$config_file" ]; then
|
||||
requested_data_dir=$data_dir
|
||||
data_dir=$(awk '/^[[:space:]]*\[storage\]/ { storage=1; next } /^[[:space:]]*\[/ { storage=0 } storage && /^[[:space:]]*data_dir[[:space:]]*=[[:space:]]*"[^"\\]*"[[:space:]]*(#.*)?$/ { sub(/^[^=]*=[[:space:]]*"/, ""); sub(/"[[:space:]]*(#.*)?$/, ""); print; exit }' "$config_file")
|
||||
[ -n "$data_dir" ] || fail 'existing settings.toml needs an explicit double-quoted storage.data_dir without escapes'
|
||||
case "$data_dir" in /*) ;; *) data_dir="$install_dir/$data_dir" ;; esac
|
||||
if [ -n "$requested_data_dir" ]; then
|
||||
requested_data_dir=$(CDPATH= cd -- "$requested_data_dir" && pwd -P) || fail 'requested data directory does not exist'
|
||||
existing_data_dir=$(CDPATH= cd -- "$data_dir" && pwd -P) || fail 'configured data directory does not exist'
|
||||
[ "$requested_data_dir" = "$existing_data_dir" ] ||
|
||||
fail 'existing settings.toml: edit its data_dir instead of passing a different --data-dir'
|
||||
fi
|
||||
fi
|
||||
[ -n "$data_dir" ] || data_dir="${XDG_DATA_HOME:-$HOME/.local/share}/chusql/data"
|
||||
log_dir="${XDG_STATE_HOME:-$HOME/.local/state}/chusql/logs"
|
||||
|
||||
@@ -213,7 +225,7 @@ if [ "$list_versions" = 'no' ]; then
|
||||
if [ -n "$password_arg" ]; then
|
||||
admin_password="$password_arg"
|
||||
fi
|
||||
if [ "$can_prompt" = 'yes' ]; then
|
||||
if [ "$can_prompt" = 'yes' ] && [ -z "$admin_password" ]; then
|
||||
echo
|
||||
echo "The administrator account is $root_user."
|
||||
while :; do
|
||||
@@ -511,7 +523,7 @@ if [ "$from_source" = 'yes' ]; then
|
||||
mkdir -p "$pack/bin" "$pack/resources"
|
||||
cp "$storage_lib" "$pack/bin/"
|
||||
cp "$src/resources/settings.toml.linux" "$pack/resources/"
|
||||
cp "$src/scripts/install.sh" "$pack/"
|
||||
cp "$src/scripts/install.sh" "$src/scripts/uninstall.sh" "$pack/"
|
||||
for comp in $wanted; do
|
||||
if [ "$comp" = 'web' ]; then
|
||||
comp_dir='chusql-web'; front_name='chusql-web'
|
||||
@@ -626,15 +638,37 @@ if [ "$(uname -s)" = 'Linux' ] && [ "$no_service" = 'no' ]; then
|
||||
command -v systemctl >/dev/null 2>&1 || fail 'systemd is required; use --no-service to opt out'
|
||||
command -v loginctl >/dev/null 2>&1 || fail 'loginctl is required for boot autostart'
|
||||
systemctl --user show-environment >/dev/null || fail 'cannot connect to user systemd; run in a login session or use --no-service'
|
||||
loginctl enable-linger "$(id -un)" || fail 'cannot enable boot autostart; run sudo loginctl enable-linger for this OS user and retry'
|
||||
os_user=$(id -un)
|
||||
linger=$(loginctl show-user "$os_user" -p Linger --value) || fail 'cannot query boot autostart for this OS user'
|
||||
if [ "$linger" != 'yes' ]; then
|
||||
loginctl enable-linger "$os_user" || fail 'cannot enable boot autostart; run sudo loginctl enable-linger for this OS user and retry'
|
||||
fi
|
||||
use_service='yes'
|
||||
if [ -f "$service_file" ]; then
|
||||
systemctl --user stop chusql-server.service || fail 'cannot stop the existing service'
|
||||
fi
|
||||
fi
|
||||
# 停止安装目录中的进程,避免更新运行中的文件。
|
||||
stop_installed() {
|
||||
for executable in chusql-web chusql-server; do
|
||||
for process in /proc/[0-9]*/exe; do
|
||||
target=$(readlink "$process" 2>/dev/null) || continue
|
||||
[ "$target" = "$install_dir/bin/$executable" ] || continue
|
||||
pid=${process#/proc/}; pid=${pid%/exe}
|
||||
kill "$pid" || fail "cannot stop $executable ($pid)"
|
||||
attempts=0
|
||||
while [ -e "$process" ]; do
|
||||
attempts=$((attempts + 1))
|
||||
[ "$attempts" -lt 100 ] || fail "timed out stopping $executable ($pid)"
|
||||
sleep 0.1
|
||||
done
|
||||
done
|
||||
done
|
||||
}
|
||||
step 'Installing files'
|
||||
mkdir -p "$install_dir/bin" "$log_dir" "$data_dir"
|
||||
install_dir=$(CDPATH= cd -- "$install_dir" && pwd)
|
||||
install_dir=$(CDPATH= cd -- "$install_dir" && pwd -P)
|
||||
if [ "$(uname -s)" = Linux ]; then stop_installed; fi
|
||||
data_dir=$(CDPATH= cd -- "$data_dir" && pwd)
|
||||
log_dir=$(CDPATH= cd -- "$log_dir" && pwd)
|
||||
cp "$pack/bin/chusql-server" "$install_dir/bin/"
|
||||
@@ -657,7 +691,11 @@ if [ "$want_web" = 'yes' ]; then
|
||||
chmod +x "$install_dir/csql-web.sh"
|
||||
fi
|
||||
fi
|
||||
chmod +x "$install_dir/bin/"* 2>/dev/null || true
|
||||
chmod +x "$install_dir/bin/"*
|
||||
if [ -f "$pack/uninstall.sh" ]; then
|
||||
cp "$pack/uninstall.sh" "$install_dir/"
|
||||
chmod +x "$install_dir/uninstall.sh"
|
||||
fi
|
||||
|
||||
# ---- 写全局配置 ----
|
||||
step 'Writing settings.toml'
|
||||
@@ -666,9 +704,11 @@ config_file="$config_dir/settings.toml"
|
||||
mkdir -p "$config_dir"
|
||||
config_dir=$(CDPATH= cd -- "$config_dir" && pwd)
|
||||
config_file="$config_dir/settings.toml"
|
||||
sed -e "s|@DATA_DIR@|$(printf '%s' "$data_dir" | sed 's/[\&|]/\\&/g')|g" \
|
||||
-e "s|@LOG_DIR@|$(printf '%s' "$log_dir" | sed 's/[\&|]/\\&/g')|g" \
|
||||
"$pack/resources/settings.toml.linux" > "$config_file"
|
||||
if [ ! -f "$config_file" ]; then
|
||||
sed -e "s|@DATA_DIR@|$(printf '%s' "$data_dir" | sed 's/[\&|]/\\&/g')|g" \
|
||||
-e "s|@LOG_DIR@|$(printf '%s' "$log_dir" | sed 's/[\&|]/\\&/g')|g" \
|
||||
"$pack/resources/settings.toml.linux" > "$config_file"
|
||||
fi
|
||||
echo " config file $config_file"
|
||||
|
||||
# ---- 引导系统目录 ----
|
||||
@@ -676,7 +716,7 @@ echo " config file $config_file"
|
||||
step 'Initializing the administrator identity and preinstalled types'
|
||||
bootstrap="$install_dir/bin/csql-bootstrap"
|
||||
[ -n "$admin_password" ] || fail 'the administrator password is required'
|
||||
printf '%s\n' "$admin_password" | "$bootstrap" --config "$config_file" --user "$root_user" --password-stdin ||
|
||||
( cd "$install_dir" && printf '%s\n' "$admin_password" | "$bootstrap" --config "$config_file" --user "$root_user" --password-stdin ) ||
|
||||
fail 'the bootstrap program failed; identities or preinstalled types are not ready'
|
||||
|
||||
# ---- 命令入口 ----
|
||||
@@ -700,17 +740,17 @@ fi
|
||||
step 'Updating PATH'
|
||||
profile="$HOME/.profile"
|
||||
marker='# ChuSQL'
|
||||
if [ -f "$profile" ] && grep -q "^$marker" "$profile"; then
|
||||
echo "PATH entry already present in $profile"
|
||||
else
|
||||
{
|
||||
echo ''
|
||||
echo "$marker"
|
||||
echo "PATH=\"\$PATH:$install_dir\""
|
||||
echo "export PATH"
|
||||
} >> "$profile"
|
||||
echo "added $install_dir to PATH in $profile (login again or: . \"$profile\")"
|
||||
if [ -f "$profile" ] && grep -q "^$marker$" "$profile"; then
|
||||
awk -v m="$marker" 'skip > 0 { skip--; next } $0 == m { skip=2; next } { print }' "$profile" > "$profile.chusql-install"
|
||||
mv "$profile.chusql-install" "$profile"
|
||||
fi
|
||||
{
|
||||
echo ''
|
||||
echo "$marker"
|
||||
echo "PATH=\"\$PATH:$install_dir:$install_dir/bin\""
|
||||
echo "export PATH"
|
||||
} >> "$profile"
|
||||
echo "added $install_dir to PATH in $profile (login again or: . \"$profile\")"
|
||||
|
||||
# ---- 删掉安装包 ----
|
||||
if [ "$keep_package" = 'no' ]; then
|
||||
@@ -753,7 +793,7 @@ Description=ChuSQL database server
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory="$(unit_quote "$install_dir")"
|
||||
WorkingDirectory=$(printf '%s' "$install_dir" | sed 's/%/%%/g')
|
||||
ExecStart=:"$(unit_quote "$server_bin")" --config "$(unit_quote "$config_file")" --user "$(unit_quote "$root_user")"
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
@@ -778,7 +818,7 @@ elif [ "$no_start" = 'yes' ]; then
|
||||
else
|
||||
step 'Starting the server'
|
||||
mkdir -p "$log_dir"
|
||||
nohup "$server_bin" --config "$config_file" --user "$root_user" > "$log_dir/server.log" 2> "$log_dir/server.err.log" &
|
||||
( cd "$install_dir" && exec nohup "$server_bin" --config "$config_file" --user "$root_user" ) > "$log_dir/server.log" 2> "$log_dir/server.err.log" &
|
||||
server_pid=$!
|
||||
sleep 1
|
||||
if ! kill -0 "$server_pid" 2>/dev/null; then
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
from functools import partial
|
||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
# 用真实发行包验证 Linux 安装、更新及卸载,数据与配置隔离在临时目录。
|
||||
|
||||
|
||||
# 执行命令并将失败日志原样输出。
|
||||
def run(args, env, cwd=None):
|
||||
result = subprocess.run(args, env=env, cwd=cwd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60)
|
||||
if result.returncode:
|
||||
raise RuntimeError(result.stdout)
|
||||
return result.stdout
|
||||
|
||||
|
||||
# 选择本机空闲端口。
|
||||
def free_port():
|
||||
with socket.socket() as sock:
|
||||
sock.bind(("127.0.0.1", 0))
|
||||
return sock.getsockname()[1]
|
||||
|
||||
|
||||
# 登录真实服务器并执行 SQL。
|
||||
def query(port, statements):
|
||||
with socket.create_connection(("127.0.0.1", port), timeout=5) as sock:
|
||||
with sock.makefile("rwb") as stream:
|
||||
answers = []
|
||||
for request in [{"method": "hello", "protocol": 1},
|
||||
{"method": "login", "user": "root", "password": "Lifecycle-test-123!"}] + [
|
||||
{"method": "query", "sql": sql} for sql in statements]:
|
||||
stream.write(json.dumps(request).encode() + b"\n")
|
||||
stream.flush()
|
||||
answer = json.loads(stream.readline())
|
||||
assert answer["status"] != "error", answer
|
||||
answers.append(answer)
|
||||
return answers[2:]
|
||||
|
||||
|
||||
# 等待服务器可接受登录。
|
||||
def wait_server(port):
|
||||
deadline = time.monotonic() + 15
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
query(port, [])
|
||||
return
|
||||
except (OSError, ValueError):
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError("server did not become ready")
|
||||
|
||||
|
||||
# 用真实用户 systemd 验证注册、更新启动和卸载。
|
||||
def test_systemd(repo, archive, root, env):
|
||||
systemctl = shutil.which("systemctl")
|
||||
state = run([systemctl, "--user", "show", "chusql-server.service", "-p", "LoadState"], env)
|
||||
assert "LoadState=not-found" in state, "existing chusql-server.service: refusing to change it"
|
||||
runtime_unit = Path(os.environ["XDG_RUNTIME_DIR"]) / "systemd/user/chusql-server.service"
|
||||
assert not runtime_unit.is_symlink(), "existing runtime service link: refusing to change it"
|
||||
user = run(["id", "-un"], env).strip()
|
||||
linger = run(["loginctl", "show-user", user, "-p", "Linger"], env).strip()
|
||||
home = root / "service-home"
|
||||
home.mkdir()
|
||||
env = dict(env, HOME=str(home), XDG_CONFIG_HOME=str(home / ".config"),
|
||||
XDG_STATE_HOME=str(home / ".local/state"), CHUSQL_ADMIN_PASSWORD="Lifecycle-test-123!")
|
||||
install = root / "service program"
|
||||
unit = home / ".config/systemd/user/chusql-server.service"
|
||||
bridge = root / "service-tools"
|
||||
bridge.mkdir()
|
||||
wrapper = bridge / "systemctl"
|
||||
wrapper.write_text(f'''#!/bin/sh
|
||||
# 将隔离配置中的单元链接到用户 systemd 的运行目录。
|
||||
if [ "$*" = '--user daemon-reload' ] && [ -f "$CHUSQL_TEST_UNIT" ]; then
|
||||
"{systemctl}" --user link --runtime "$CHUSQL_TEST_UNIT" || exit 1
|
||||
fi
|
||||
exec "{systemctl}" "$@"
|
||||
''')
|
||||
wrapper.chmod(0o755)
|
||||
env.update(PATH=str(bridge) + ":" + env["PATH"], CHUSQL_TEST_UNIT=str(unit))
|
||||
installer = ["sh", str(repo / "scripts/install.sh"), "--url", archive,
|
||||
"--component", "both", "--install-dir", str(install)]
|
||||
try:
|
||||
run(installer + ["--data-dir", str(root / "service-data"), "--no-start"], env)
|
||||
config = home / ".config/ChuSQL/settings.toml"
|
||||
port = free_port()
|
||||
config.write_text(config.read_text().replace("port = 7777", f"port = {port}"))
|
||||
run(installer, env)
|
||||
wait_server(port)
|
||||
run([systemctl, "--user", "is-active", "--quiet", "chusql-server.service"], env)
|
||||
run(["sh", str(install / "uninstall.sh"), "--install-dir", str(install), "--yes"], env)
|
||||
assert not unit.exists() and not (root / "service-data").exists()
|
||||
print("PASS real systemd registration, update/start and uninstall", flush=True)
|
||||
finally:
|
||||
state = run([systemctl, "--user", "show", "chusql-server.service", "-p", "LoadState"], env)
|
||||
if "LoadState=loaded" in state:
|
||||
run([systemctl, "--user", "stop", "chusql-server.service"], env)
|
||||
user_units = Path(os.environ["HOME"]) / ".config/systemd/user"
|
||||
for link in [runtime_unit, user_units / "chusql-server.service",
|
||||
user_units / "default.target.wants/chusql-server.service"]:
|
||||
if link.is_symlink() and link.resolve() == unit.resolve():
|
||||
link.unlink()
|
||||
run([systemctl, "--user", "daemon-reload"], dict(env, CHUSQL_TEST_UNIT="/nonexistent"))
|
||||
if linger == "Linger=no" and run(["loginctl", "show-user", user, "-p", "Linger"], env).strip() == "Linger=yes":
|
||||
run(["loginctl", "disable-linger", user], env)
|
||||
|
||||
|
||||
# 用本机发行站验证版本查询、HTTP 下载与校验。
|
||||
def test_online(repo, archive, root, env):
|
||||
site = root / "release-site"
|
||||
api = site / "api/v3/repos/test/ChuSQL/releases"
|
||||
api.parent.mkdir(parents=True)
|
||||
api.write_text(json.dumps([{"tag_name": "v-test", "prerelease": False,
|
||||
"assets": [{"name": "chusql-linux-x86_64.tar.gz"}]}], indent=2))
|
||||
assets = site / "test/ChuSQL/releases/download/v-test"
|
||||
assets.mkdir(parents=True)
|
||||
shutil.copy2(archive, assets / Path(archive).name)
|
||||
shutil.copy2(archive + ".sha256", assets / (Path(archive).name + ".sha256"))
|
||||
server = ThreadingHTTPServer(("127.0.0.1", 0), partial(SimpleHTTPRequestHandler, directory=str(site)))
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
install = root / "online-program"
|
||||
env = dict(env, CHUSQL_GITHUB_BASE=f"http://127.0.0.1:{server.server_port}")
|
||||
installer = ["sh", str(repo / "scripts/install.sh"), "--repo", "test/ChuSQL"]
|
||||
try:
|
||||
assert "v-test" in run(installer + ["--list-versions"], env)
|
||||
output = run(installer + ["--component", "cli", "--install-dir", str(install),
|
||||
"--data-dir", str(root / "online-data"), "--no-service", "--no-start"], env)
|
||||
assert "checksum" in output and "(ok)" in output
|
||||
assert (install / "bin/csql").exists() and not (install / "bin/chusql-web").exists()
|
||||
run(["sh", str(install / "uninstall.sh"), "--install-dir", str(install), "--yes"], env)
|
||||
assert not (root / "online-data").exists()
|
||||
print("PASS release API, latest version selection, HTTP download and checksum (local fixture)", flush=True)
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
thread.join()
|
||||
|
||||
|
||||
# 在隔离目录验证配置、SQL 数据和卸载保留选项。
|
||||
def main():
|
||||
if sys.platform != "linux" or len(sys.argv) not in (2, 3) or (len(sys.argv) == 3 and sys.argv[2] != "--systemd"):
|
||||
raise SystemExit("usage: python3 scripts/test-linux-lifecycle.py <linux-package.tar.gz> [--systemd]")
|
||||
archive = str(Path(sys.argv[1]).resolve())
|
||||
repo = Path(__file__).resolve().parent.parent
|
||||
(repo / "logs").mkdir(exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix="linux-lifecycle-", dir=repo / "logs") as temporary:
|
||||
root = Path(temporary)
|
||||
home = root / "home"
|
||||
home.mkdir()
|
||||
env = dict(os.environ, HOME=str(home), XDG_CONFIG_HOME=str(home / ".config"),
|
||||
XDG_DATA_HOME=str(home / ".local/share"), XDG_STATE_HOME=str(home / ".local/state"),
|
||||
CHUSQL_ADMIN_PASSWORD="Lifecycle-test-123!")
|
||||
env.pop("LD_LIBRARY_PATH", None)
|
||||
install = root / "program"
|
||||
data = install / "custom/nested/data"
|
||||
config = home / ".config/ChuSQL/settings.toml"
|
||||
installer = ["sh", str(repo / "scripts/install.sh"), "--url", archive,
|
||||
"--install-dir", str(install), "--component", "both", "--no-service", "--no-start"]
|
||||
run(installer + ["--data-dir", str(data)], env)
|
||||
assert (data / "system/catalog.json").is_file()
|
||||
assert (install / "uninstall.sh").is_file()
|
||||
assert "@DATA_DIR@" not in config.read_text()
|
||||
assert run([str(install / "bin/csql-bootstrap"), "--help"], env)
|
||||
print("PASS clean installation and dynamic library lookup", flush=True)
|
||||
port = free_port()
|
||||
settings = config.read_text().replace("port = 7777", f"port = {port}").replace(str(data), "custom/nested/data") + "\n# preserve custom settings\n"
|
||||
config.write_text(settings)
|
||||
server = subprocess.Popen([str(install / "bin/chusql-server"), "--config", str(config)],
|
||||
env=env, cwd=install, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
try:
|
||||
wait_server(port)
|
||||
query(port, ["create database lifecycle", "use lifecycle", "create table saved (id int)",
|
||||
"insert into saved (id) values (42)"])
|
||||
env["CHUSQL_ADMIN_PASSWORD"] = "Different-update-password-123!"
|
||||
run(installer, env)
|
||||
server.wait(timeout=5)
|
||||
assert config.read_text() == settings
|
||||
assert (home / ".profile").read_text().count("# ChuSQL\n") == 1
|
||||
server = subprocess.Popen([str(install / "bin/chusql-server"), "--config", str(config)],
|
||||
env=env, cwd=install, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
wait_server(port)
|
||||
result = query(port, ["use lifecycle", "select id from saved"])[-1]
|
||||
assert result["rowCount"] == 1 and "42" in json.dumps(result["rows"]), result
|
||||
print("PASS running update preserves configuration, credentials and SQL data", flush=True)
|
||||
uninstall = ["sh", str(install / "uninstall.sh"), "--install-dir", str(install), "--yes"]
|
||||
run(uninstall + ["--keep-data", "--keep-config"], env)
|
||||
server.wait(timeout=5)
|
||||
assert data.is_dir() and config.read_text() == settings
|
||||
assert not (install / "bin").exists()
|
||||
assert "# ChuSQL" not in (home / ".profile").read_text()
|
||||
print("PASS uninstall stops server and keeps nested data and configuration", flush=True)
|
||||
run(installer, env)
|
||||
run(uninstall, env)
|
||||
assert not install.exists() and not data.exists() and not config.exists()
|
||||
print("PASS reinstall and full uninstall discover configured data path", flush=True)
|
||||
finally:
|
||||
if server.poll() is None:
|
||||
server.terminate()
|
||||
server.wait(timeout=10)
|
||||
test_online(repo, archive, root, env)
|
||||
if len(sys.argv) == 3:
|
||||
with tempfile.TemporaryDirectory(prefix="chusql-systemd-") as service_temporary:
|
||||
test_systemd(repo, archive, Path(service_temporary), env)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+40
-14
@@ -17,7 +17,7 @@ Uninstall ChuSQL (Linux/macOS).
|
||||
Usage: uninstall.sh [options]
|
||||
|
||||
--install-dir DIR program directory (default: $HOME/.local/share/chusql)
|
||||
--data-dir DIR data directory (default: <install-dir>/data)
|
||||
--data-dir DIR data directory (default: settings.toml, then XDG data directory)
|
||||
--keep-data keep the data directory, remove programs only
|
||||
--keep-config keep settings.toml
|
||||
--yes do not ask (required when there is no terminal)
|
||||
@@ -36,6 +36,21 @@ fail() {
|
||||
stop_by_path() {
|
||||
target=$1
|
||||
[ -n "$target" ] || return 0
|
||||
if [ "$(uname -s)" = Linux ]; then
|
||||
for process in /proc/[0-9]*/exe; do
|
||||
executable=$(readlink "$process" 2>/dev/null) || continue
|
||||
[ "$executable" = "$target" ] || continue
|
||||
pid=${process#/proc/}; pid=${pid%/exe}
|
||||
kill "$pid" || fail "cannot stop $target ($pid)"
|
||||
attempts=0
|
||||
while [ -e "$process" ]; do
|
||||
attempts=$((attempts + 1))
|
||||
[ "$attempts" -lt 100 ] || fail "timed out stopping $target ($pid)"
|
||||
sleep 0.1
|
||||
done
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
pids=$(ps -eo pid=,args= 2>/dev/null |
|
||||
awk -v t="$target" '{ pid = $1; $1 = ""; sub(/^ +/, ""); if (index($0, t) == 1) print pid }' || true)
|
||||
[ -n "$pids" ] || return 0
|
||||
@@ -73,9 +88,27 @@ while [ $# -gt 0 ]; do
|
||||
done
|
||||
|
||||
install_dir=${install_dir:-$HOME/.local/share/chusql}
|
||||
data_dir=${data_dir:-${XDG_DATA_HOME:-$HOME/.local/share}/chusql/data}
|
||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/ChuSQL"
|
||||
config_file="$config_dir/settings.toml"
|
||||
if [ -z "$data_dir" ] && [ -f "$config_file" ]; then
|
||||
data_dir=$(awk '/^[[:space:]]*\[storage\]/ { storage=1; next } /^[[:space:]]*\[/ { storage=0 } storage && /^[[:space:]]*data_dir[[:space:]]*=[[:space:]]*"[^"\\]*"[[:space:]]*(#.*)?$/ { sub(/^[^=]*=[[:space:]]*"/, ""); sub(/"[[:space:]]*(#.*)?$/, ""); print; exit }' "$config_file")
|
||||
[ -n "$data_dir" ] || fail 'settings.toml needs an explicit double-quoted storage.data_dir without escapes; pass --data-dir'
|
||||
case "$data_dir" in /*) ;; *) data_dir="$install_dir/$data_dir" ;; esac
|
||||
fi
|
||||
data_dir=${data_dir:-${XDG_DATA_HOME:-$HOME/.local/share}/chusql/data}
|
||||
if [ "$(uname -s)" = Linux ]; then
|
||||
install_dir=$(realpath -m -- "$install_dir")
|
||||
data_dir=$(realpath -m -- "$data_dir")
|
||||
else
|
||||
if [ -d "$install_dir" ]; then install_dir=$(CDPATH= cd -- "$install_dir" && pwd -P); fi
|
||||
if [ -d "$data_dir" ]; then data_dir=$(CDPATH= cd -- "$data_dir" && pwd -P); fi
|
||||
fi
|
||||
case "$install_dir" in /|"$HOME") fail 'refusing to remove the root or home directory' ;; esac
|
||||
case "$data_dir" in /|"$HOME") fail 'refusing to remove the root or home data directory' ;; esac
|
||||
case "$install_dir" in "$data_dir"|"$data_dir"/*) fail 'data directory must not contain the installation' ;; esac
|
||||
for entry in bin static csql csql-web csql-web.sh uninstall.sh; do
|
||||
case "$data_dir" in "$install_dir/$entry"|"$install_dir/$entry"/*) fail 'data directory overlaps installed program files' ;; esac
|
||||
done
|
||||
profile="$HOME/.profile"
|
||||
|
||||
if [ "$assume_yes" = 'no' ]; then
|
||||
@@ -108,23 +141,16 @@ step 'Removing files'
|
||||
if [ ! -d "$install_dir" ]; then
|
||||
note "not found: $install_dir"
|
||||
elif [ "$keep_data" = 'yes' ]; then
|
||||
for entry in "$install_dir"/*; do
|
||||
for entry in "$install_dir"/bin "$install_dir"/static "$install_dir"/csql "$install_dir"/csql-web "$install_dir"/csql-web.sh "$install_dir"/uninstall.sh; do
|
||||
[ -e "$entry" ] || continue
|
||||
if [ "$entry" = "$data_dir" ]; then
|
||||
note "kept data: $entry"
|
||||
continue
|
||||
fi
|
||||
rm -rf "$entry"
|
||||
done
|
||||
note "kept $install_dir (data only)"
|
||||
note "kept data: $data_dir"
|
||||
else
|
||||
rm -rf "$install_dir"
|
||||
case "$data_dir" in
|
||||
"$install_dir" | "$install_dir"/*) ;;
|
||||
*) rm -rf "$data_dir" ;;
|
||||
esac
|
||||
note "removed $install_dir"
|
||||
fi
|
||||
if [ "$keep_data" = 'no' ] && [ -d "$data_dir" ]; then rm -rf "$data_dir"; fi
|
||||
|
||||
step 'Removing settings.toml'
|
||||
if [ "$keep_config" = 'yes' ]; then
|
||||
@@ -139,11 +165,11 @@ step 'Removing the PATH entry'
|
||||
marker='# ChuSQL'
|
||||
if [ ! -f "$profile" ]; then
|
||||
note "no $profile"
|
||||
elif grep -q "^$marker" "$profile"; then
|
||||
elif grep -q "^$marker$" "$profile"; then
|
||||
tmp="$profile.chusql-uninstall"
|
||||
awk -v m="$marker" '
|
||||
skip > 0 { skip--; next }
|
||||
index($0, m) == 1 { skip = 2; next }
|
||||
$0 == m { skip = 2; next }
|
||||
{ print }
|
||||
' "$profile" >"$tmp" && mv "$tmp" "$profile"
|
||||
note "removed the $marker block from $profile"
|
||||
|
||||
Reference in new issue
Block a user