大改,整理项目结构,完成rust ffi调用,写好tcp接口
This commit is contained in:
1 parent
df6d348995
commit
9758439b82
118 files changed
+8103
-6321
No files matched your search
+10
-8
@@ -1,10 +1,10 @@
|
||||
# ChuSQL 全局配置。
|
||||
#
|
||||
# 整条链路共用这一份文件,按分区各管各的,互不影响:
|
||||
# 存储进程:[page] [btree] [buffer] [storage] [server] [log]
|
||||
# Web / 命令行:[web](账号口令、会话、分页上限、静态目录、存储进程路径)
|
||||
# 端点也在这里:[server] pipe_name 是管名,[storage] data_dir 是数据目录,
|
||||
# 谁需要谁读,不再靠环境变量传递。data_dir 这行是安装时会替换的占位:
|
||||
# 数据库服务:[server](TCP 监听地址、端口、单行上限、单次结果行数上限)
|
||||
# —— 只有它装入 chusql_core_storage 动态库并独占数据目录
|
||||
# Web / 命令行:[web](账号口令、会话、分页上限、静态目录),都经 [server] 走
|
||||
# 数据目录在 [storage] data_dir;这一行是安装时会替换的占位:
|
||||
# 不写就按平台惯例(Windows %LOCALAPPDATA%\ChuSQL\data,
|
||||
# Unix ${XDG_DATA_HOME:-$HOME/.local/share}/chusql/data)。
|
||||
# 每一层只读自己那几段,读完就关,不长期占着文件句柄,别的层照样能读。
|
||||
@@ -27,18 +27,20 @@ pool_size = 1024
|
||||
# Windows %LOCALAPPDATA%\ChuSQL\data,Unix ${XDG_DATA_HOME:-$HOME/.local/share}/chusql/data
|
||||
data_dir = "../localdata"
|
||||
|
||||
# TCP 数据库服务(chusql-server)。单独一段,别层的进程不认识它、也不需要认识。
|
||||
[server]
|
||||
pipe_name = "chusql-joint"
|
||||
host = "127.0.0.1"
|
||||
port = 7777
|
||||
max_message = 1048576
|
||||
max_rows = 1000
|
||||
|
||||
[log]
|
||||
level = "info"
|
||||
|
||||
[web]
|
||||
host = "127.0.0.1"
|
||||
port = 7777
|
||||
port = 7778
|
||||
static_dir = "static"
|
||||
# 存储进程的可执行文件;留空表示存储已在别处运行,只按 [server] pipe_name 去连
|
||||
storage_server = ""
|
||||
# 管理员账号与口令。口令留空表示"只允许管理员免密登录",普通账号一律不能登录。
|
||||
user = "root"
|
||||
password = ""
|
||||
|
||||
+13
-10
@@ -1,9 +1,10 @@
|
||||
# csql-web:只负责启动 Web 前端(先拉起存储进程,再起 Web 服务)。
|
||||
# 配置走 chusql.toml,两层各读各的分区;这里只取 [web] 的 host/port 拼地址。
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# csql-web:先起独占数据目录的 server,再起 Web 服务,配置读 chusql.toml。
|
||||
|
||||
# 打印错误并退出 1。
|
||||
function Fail([string]$msg) { Write-Host "!! $msg" -ForegroundColor Red; exit 1 }
|
||||
# 打印一行输出。
|
||||
function Say([string]$msg) { Write-Host $msg }
|
||||
|
||||
$home_dir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
@@ -13,6 +14,7 @@ $config_dir = if ($env:APPDATA) { Join-Path $env:APPDATA 'ChuSQL' } else { Join-
|
||||
$config = Join-Path $config_dir 'chusql.toml'
|
||||
if (-not (Test-Path $config)) { Fail "config not found: $config" }
|
||||
|
||||
# 从配置里取一个 [web] 段的值。
|
||||
function Get-WebSetting([string]$Key, [string]$Default) {
|
||||
$section = ''
|
||||
$pattern = '^\s*' + [regex]::Escape($Key) + '\s*='
|
||||
@@ -27,21 +29,22 @@ function Get-WebSetting([string]$Key, [string]$Default) {
|
||||
return $Default
|
||||
}
|
||||
|
||||
$storage_exe = Join-Path $bin 'chusql-storage.exe'
|
||||
$server_exe = Join-Path $bin 'chusql-server.exe'
|
||||
$web_exe = Join-Path $bin 'chusql-web.exe'
|
||||
if (-not (Test-Path $storage_exe)) { Fail "storage binary not found in $bin" }
|
||||
if (-not (Test-Path $server_exe)) { Fail "server binary not found in $bin" }
|
||||
if (-not (Test-Path $web_exe)) { Fail "web binary not found in $bin" }
|
||||
|
||||
$host_addr = Get-WebSetting 'host' '127.0.0.1'
|
||||
$port = Get-WebSetting 'port' '7777'
|
||||
$port = Get-WebSetting 'port' '7778'
|
||||
$log_dir = Join-Path $home_dir 'logs'
|
||||
New-Item -ItemType Directory -Force -Path $log_dir | Out-Null
|
||||
|
||||
$storage = $null
|
||||
$server = $null
|
||||
$web = $null
|
||||
try {
|
||||
$storage = Start-Process -FilePath $storage_exe -WorkingDirectory $home_dir -PassThru -RedirectStandardOutput (Join-Path $log_dir 'storage.log') -RedirectStandardError (Join-Path $log_dir 'storage.err.log')
|
||||
$web = Start-Process -FilePath $web_exe -WorkingDirectory $home_dir -PassThru -RedirectStandardOutput (Join-Path $log_dir 'web.log') -RedirectStandardError (Join-Path $log_dir 'web.err.log')
|
||||
# server 先起来:它进程内装入存储库、独占数据目录,Web 的存储请求都经它转发
|
||||
$server = Start-Process -FilePath $server_exe -ArgumentList '--config', $config -WorkingDirectory $home_dir -PassThru -RedirectStandardOutput (Join-Path $log_dir 'server.log') -RedirectStandardError (Join-Path $log_dir 'server.err.log')
|
||||
$web = Start-Process -FilePath $web_exe -ArgumentList '--config', $config -WorkingDirectory $home_dir -PassThru -RedirectStandardOutput (Join-Path $log_dir 'web.log') -RedirectStandardError (Join-Path $log_dir 'web.err.log')
|
||||
|
||||
Say 'starting web ...'
|
||||
Say "ready: http://${host_addr}:${port}/"
|
||||
@@ -51,7 +54,7 @@ try {
|
||||
Say "!! web exited with code $($web.ExitCode)"
|
||||
}
|
||||
finally {
|
||||
foreach ($proc in @($web, $storage)) {
|
||||
foreach ($proc in @($web, $server)) {
|
||||
if ($proc -and -not $proc.HasExited) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
Say 'stopped.'
|
||||
|
||||
+13
-10
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
# csql-web:只负责启动 Web 前端(先拉起存储进程,再起 Web 服务)。
|
||||
# 配置走 chusql.toml,两层各读各的分区;这里只取 [web] 的 host/port 拼地址。
|
||||
set -eu
|
||||
|
||||
# csql-web:先起独占数据目录的 server,再起 Web 服务,配置读 chusql.toml。
|
||||
|
||||
home_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
bin="$home_dir/bin"
|
||||
|
||||
@@ -14,6 +14,7 @@ config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/ChuSQL"
|
||||
config="$config_dir/chusql.toml"
|
||||
[ -f "$config" ] || { echo "!! config not found: $config" >&2; exit 1; }
|
||||
|
||||
# 从配置里取一个 [web] 段的值。
|
||||
web_setting() {
|
||||
awk -v key="$1" -v def="$2" '
|
||||
/^[[:space:]]*\[/ { section = $0; gsub(/[][[:space:]]/, "", section); next }
|
||||
@@ -34,24 +35,26 @@ web_setting() {
|
||||
' "$config"
|
||||
}
|
||||
|
||||
storage_exe="$bin/chusql-storage"
|
||||
server_exe="$bin/chusql-server"
|
||||
web_exe="$bin/chusql-web"
|
||||
[ -x "$storage_exe" ] || { echo "!! storage binary not found in $bin" >&2; exit 1; }
|
||||
[ -x "$server_exe" ] || { echo "!! server binary not found in $bin" >&2; exit 1; }
|
||||
[ -x "$web_exe" ] || { echo "!! web binary not found in $bin" >&2; exit 1; }
|
||||
|
||||
host=$(web_setting host 127.0.0.1)
|
||||
port=$(web_setting port 7777)
|
||||
port=$(web_setting port 7778)
|
||||
log_dir="$home_dir/logs"
|
||||
mkdir -p "$log_dir"
|
||||
|
||||
"$storage_exe" >>"$log_dir/storage.log" 2>>"$log_dir/storage.err.log" &
|
||||
storage_pid=$!
|
||||
"$web_exe" >>"$log_dir/web.log" 2>>"$log_dir/web.err.log" &
|
||||
# server 先起来:它进程内装入存储库、独占数据目录,Web 的存储请求都经它转发
|
||||
"$server_exe" --config "$config" >>"$log_dir/server.log" 2>>"$log_dir/server.err.log" &
|
||||
server_pid=$!
|
||||
"$web_exe" --config "$config" >>"$log_dir/web.log" 2>>"$log_dir/web.err.log" &
|
||||
web_pid=$!
|
||||
|
||||
# 退出时杀掉 web 与 server 进程。
|
||||
cleanup() {
|
||||
kill "$web_pid" "$storage_pid" 2>/dev/null || true
|
||||
wait "$web_pid" "$storage_pid" 2>/dev/null || true
|
||||
kill "$web_pid" "$server_pid" 2>/dev/null || true
|
||||
wait "$web_pid" "$server_pid" 2>/dev/null || true
|
||||
echo stopped.
|
||||
}
|
||||
trap cleanup INT TERM EXIT
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
-- ChuSQL 系统库 schema。
|
||||
--
|
||||
-- 系统库(system)不存在就自动建,里面的表都是系统表;这份脚本是它们唯一的定义,
|
||||
-- 存储进程启动时按它声明表结构(见 chusql-storage/src/initsql.rs)。
|
||||
-- 存储进程启动时按它声明表结构(见 chusql-core/storage/src/initsql.rs)。
|
||||
-- 这里只描述"应该长什么样",不包含任何历史数据搬迁。
|
||||
|
||||
CREATE TABLE __chusql_users (
|
||||
|
||||
+280
-29
@@ -1,60 +1,299 @@
|
||||
# ChuSQL 安装脚本(Windows)。
|
||||
#
|
||||
# 包目录结构(解压后就是这个样子):
|
||||
# bin/ chusql-storage.exe(必装)、chusql-web.exe 或 csql.exe(二选一)
|
||||
# static/ Web 前端静态资源(装 web 组件时带)
|
||||
# scripts/ chusql.toml、init.sql
|
||||
# csql-web.ps1
|
||||
# install.ps1
|
||||
#
|
||||
# 安装 = 释放到目标目录 + 配好环境变量 + 写全局配置,装完把包目录删掉。
|
||||
# storage/engine 一定装;web 与 cli 二选一。
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][ValidateSet('web', 'cli')][string]$Component,
|
||||
[string]$Component = '',
|
||||
[string]$InstallDir = '',
|
||||
[string]$DataDir = '',
|
||||
[string]$RootUser = 'root',
|
||||
[string]$RootPassword = '',
|
||||
[string]$Version = 'latest',
|
||||
[string]$Repo = 'TheBadRoger/ChuSQL',
|
||||
[switch]$ListVersions,
|
||||
[switch]$Interactive,
|
||||
[switch]$KeepPackage
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Fail([string]$msg) { Write-Host "!! $msg" -ForegroundColor Red; exit 1 }
|
||||
# Windows 安装脚本:从包内或 GitHub 发行版装 cli/web,
|
||||
# 写全局配置、改用户 PATH,装完删除安装包。
|
||||
|
||||
$script:tempDir = ''
|
||||
|
||||
# 报错退出,并清掉半截临时目录。
|
||||
function Fail([string]$msg) {
|
||||
Write-Host "!! $msg" -ForegroundColor Red
|
||||
if ($script:tempDir -and (Test-Path $script:tempDir)) {
|
||||
Remove-Item -Recurse -Force $script:tempDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
# 打印步骤标题。
|
||||
function Step([string]$msg) { Write-Host ''; Write-Host "==> $msg" -ForegroundColor Cyan }
|
||||
# 打印一行输出。
|
||||
function Say([string]$msg) { Write-Host $msg }
|
||||
# 打印缩进提示行。
|
||||
function Note([string]$msg) { Write-Host " note: $msg" }
|
||||
|
||||
# ---- 发行站、平台标签 ----
|
||||
$githubBase = if ($env:CHUSQL_GITHUB_BASE) { $env:CHUSQL_GITHUB_BASE.TrimEnd('/') } else { 'https://github.com' }
|
||||
$apiBase = if ($githubBase -match '^https?://github\.com$') { 'https://api.github.com' } else { "$githubBase/api/v3" }
|
||||
# 查版本走 API,未认证只有 60 次/小时;给了 token 就带上(CI 里 GITHUB_TOKEN 一般就有)
|
||||
$githubToken = if ($env:CHUSQL_GITHUB_TOKEN) { $env:CHUSQL_GITHUB_TOKEN } elseif ($env:GITHUB_TOKEN) { $env:GITHUB_TOKEN } else { '' }
|
||||
$apiHeaders = @{ 'User-Agent' = 'chusql-install'; 'Accept' = 'application/vnd.github+json' }
|
||||
if ($githubToken) { $apiHeaders['Authorization'] = "Bearer $githubToken" }
|
||||
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x86_64' }
|
||||
$labels = @("windows-$arch", 'windows')
|
||||
|
||||
# 取一份 release 的资源文件名列表。
|
||||
function Get-AssetNames($release) { @($release.assets | ForEach-Object { $_.name }) }
|
||||
|
||||
# 这份 release 里有没有本平台的包。
|
||||
function Test-HasPackage($release) {
|
||||
$names = Get-AssetNames $release
|
||||
foreach ($label in $labels) {
|
||||
if ($names -contains "chusql-$label.zip") { return $true }
|
||||
}
|
||||
return ($names -contains 'chusql.zip')
|
||||
}
|
||||
|
||||
# 按正式版与带包两档条件挑第一个 tag。
|
||||
function Select-Tag([bool]$stableOnly, [bool]$needPackage, $releases) {
|
||||
foreach ($release in $releases) {
|
||||
if ($stableOnly -and $release.prerelease) { continue }
|
||||
if ($needPackage -and -not (Test-HasPackage $release)) { continue }
|
||||
return $release.tag_name
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
# 取 URL 的文本内容(字节数组先转字符串)。
|
||||
function Get-ResponseText([string]$url) {
|
||||
$response = Invoke-WebRequest -Uri $url -Headers $apiHeaders -UseBasicParsing
|
||||
if ($response.Content -is [byte[]]) { return [System.Text.Encoding]::UTF8.GetString($response.Content) }
|
||||
return [string]$response.Content
|
||||
}
|
||||
|
||||
# 拉取 release 列表并逐个输出。
|
||||
function Get-Releases {
|
||||
try { $text = Get-ResponseText "$apiBase/repos/$Repo/releases?per_page=100" } catch { return }
|
||||
try { $parsed = ConvertFrom-Json -InputObject $text } catch { return }
|
||||
if ($null -eq $parsed) { return }
|
||||
foreach ($release in @($parsed)) {
|
||||
if ($null -ne $release) { Write-Output $release }
|
||||
}
|
||||
}
|
||||
|
||||
# ---- 列版本 ----
|
||||
if ($ListVersions) {
|
||||
Say ("ChuSQL releases in {0}" -f $Repo)
|
||||
$releases = @(Get-Releases)
|
||||
if ($releases.Count -eq 0) {
|
||||
Fail "cannot list the releases of $Repo from $apiBase`n (offline, rate limited, or the repository has no release yet)`n open $githubBase/$Repo/releases and pass a tag: -Version <tag>"
|
||||
}
|
||||
foreach ($release in $releases) {
|
||||
$mark = ''
|
||||
if ($release.prerelease) { $mark += ' (prerelease)' }
|
||||
if (-not (Test-HasPackage $release)) { $mark += " (no package for windows-$arch)" }
|
||||
Say (" {0}{1}" -f $release.tag_name, $mark)
|
||||
}
|
||||
Say ''
|
||||
Say 'install one with: .\install.ps1 -Version <tag>'
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Component -notin @('web', 'cli', 'both', '')) { Fail "pass -Component web, cli or both (got: $Component)" }
|
||||
|
||||
# ---- 交互:控制台里直接问答;-Interactive 时没有终端也从 stdin 逐行读(自动化用)----
|
||||
$askFromStdin = $false
|
||||
$canPrompt = $false
|
||||
if ($Interactive) { $askFromStdin = $true; $canPrompt = $true }
|
||||
elseif (-not [Console]::IsInputRedirected) { $canPrompt = $true }
|
||||
|
||||
# 读一行输入(可选从 stdin 读)。
|
||||
function Ask-Line([string]$prompt) {
|
||||
if ($askFromStdin) {
|
||||
Write-Host $prompt -NoNewline
|
||||
$line = [Console]::In.ReadLine()
|
||||
if ($null -eq $line) { $line = '' }
|
||||
return $line
|
||||
}
|
||||
return (Read-Host $prompt)
|
||||
}
|
||||
|
||||
# 问 y/n,回车用缺省值,返回布尔。
|
||||
function Ask-YesNo([string]$prompt, [bool]$defaultYes) {
|
||||
while ($true) {
|
||||
$answer = (Ask-Line $prompt).Trim()
|
||||
if ($answer -eq '') { return $defaultYes }
|
||||
if ($answer -match '^(y|yes)$') { return $true }
|
||||
if ($answer -match '^(n|no)$') { return $false }
|
||||
Write-Host ' please answer y or n'
|
||||
}
|
||||
}
|
||||
|
||||
# 读取隐藏输入的密码。
|
||||
function Ask-Secret([string]$prompt) {
|
||||
if ($askFromStdin) { return (Ask-Line $prompt) }
|
||||
$secure = Read-Host $prompt -AsSecureString
|
||||
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
|
||||
try { return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
|
||||
finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
|
||||
}
|
||||
|
||||
$wantWeb = $false
|
||||
$wantCli = $false
|
||||
if ($Component -eq 'web') { $wantWeb = $true }
|
||||
if ($Component -eq 'cli') { $wantCli = $true }
|
||||
if ($Component -eq 'both') { $wantWeb = $true; $wantCli = $true }
|
||||
|
||||
if (-not $Component) {
|
||||
if (-not $canPrompt) {
|
||||
Fail '-Component is required (web, cli or both) when there is no terminal to ask on'
|
||||
}
|
||||
Write-Host ''
|
||||
Write-Host 'ChuSQL installer: which parts do you want?'
|
||||
$wantCli = Ask-YesNo 'Install the command line client (csql)? [Y/n]' $true
|
||||
$wantWeb = Ask-YesNo 'Install the web front end (browser UI)? [y/N]' $false
|
||||
if (-not $wantWeb -and -not $wantCli) {
|
||||
Say ' neither selected: only the storage library and the server get installed'
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $PSBoundParameters.ContainsKey('RootPassword') -and $canPrompt) {
|
||||
Write-Host ''
|
||||
Write-Host ('The administrator account is "{0}".' -f $RootUser)
|
||||
$firstPassword = Ask-Secret ('Root password for {0} (empty means no password) []:' -f $RootUser)
|
||||
if ($firstPassword) {
|
||||
$againPassword = Ask-Secret 'Repeat the root password:'
|
||||
if ($firstPassword -ne $againPassword) { Fail 'the two passwords do not match' }
|
||||
Write-Host 'root password set'
|
||||
} else {
|
||||
Write-Host 'no root password: administrator-only sign-in'
|
||||
}
|
||||
$RootPassword = $firstPassword
|
||||
}
|
||||
|
||||
$wanted = @()
|
||||
if ($wantCli) { $wanted += 'cli' }
|
||||
if ($wantWeb) { $wanted += 'web' }
|
||||
$componentLabel = ($wanted -join '+')
|
||||
if (-not $componentLabel) { $componentLabel = 'none (storage only)' }
|
||||
|
||||
$pack = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$downloaded = $false
|
||||
$tempDir = ''
|
||||
|
||||
if (-not $InstallDir) { $InstallDir = Join-Path $env:LOCALAPPDATA 'ChuSQL' }
|
||||
if (-not $DataDir) { $DataDir = Join-Path $InstallDir 'data' }
|
||||
$InstallDir = [System.IO.Path]::GetFullPath($InstallDir)
|
||||
$DataDir = [System.IO.Path]::GetFullPath($DataDir)
|
||||
|
||||
# ---- 取包:脚本旁边有包就装本地的,否则去发行站下载 ----
|
||||
if (-not (Test-Path (Join-Path $pack 'bin\chusql_core_storage.dll'))) {
|
||||
Step 'Looking for the package'
|
||||
|
||||
if ($Version -eq 'latest') {
|
||||
$releases = @(Get-Releases)
|
||||
if ($releases.Count -eq 0) {
|
||||
Note "version latest (cannot reach $apiBase; using the latest release redirect)"
|
||||
} else {
|
||||
$picked = Select-Tag $true $true $releases
|
||||
if (-not $picked) { $picked = Select-Tag $false $true $releases }
|
||||
if (-not $picked) { $picked = Select-Tag $true $false $releases }
|
||||
if (-not $picked) { $picked = Select-Tag $false $false $releases }
|
||||
if ($picked) { $Version = $picked; Note "version $Version" }
|
||||
else { Note 'no release lists a matching package; falling back to the latest release redirect' }
|
||||
}
|
||||
}
|
||||
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("chusql-install-" + [Guid]::NewGuid().ToString('N'))
|
||||
$script:tempDir = $tempDir
|
||||
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
||||
|
||||
# 一个平台一个包:web 和 cli 都在里面,装哪几个由上面的选择决定
|
||||
Step "Looking for a chusql package for windows-$arch"
|
||||
$zip = Join-Path $tempDir 'chusql.zip'
|
||||
$asset = ''
|
||||
$assetUrl = ''
|
||||
$candidates = @()
|
||||
foreach ($label in $labels) { $candidates += "chusql-$label.zip" }
|
||||
$candidates += 'chusql.zip'
|
||||
foreach ($candidate in $candidates) {
|
||||
$tryUrl = if ($Version -eq 'latest') { "$githubBase/$Repo/releases/latest/download/$candidate" }
|
||||
else { "$githubBase/$Repo/releases/download/$Version/$candidate" }
|
||||
try {
|
||||
Invoke-WebRequest -Uri $tryUrl -Headers $apiHeaders -OutFile $zip -UseBasicParsing
|
||||
$asset = $candidate
|
||||
$assetUrl = $tryUrl
|
||||
break
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (-not $asset) {
|
||||
Fail "no package for windows-$arch in $Repo ($Version); try -ListVersions, or install from an archive you downloaded yourself"
|
||||
}
|
||||
Note "asset $asset"
|
||||
|
||||
$published = ''
|
||||
try { $published = Get-ResponseText "$assetUrl.sha256" } catch { $published = '' }
|
||||
if ($published) {
|
||||
$expected = ($published.Trim() -split '\s+')[0].ToLower()
|
||||
$actual = (Get-FileHash -Path $zip -Algorithm SHA256).Hash.ToLower()
|
||||
if ($actual -ne $expected) { Fail "checksum mismatch: expected $expected, got $actual" }
|
||||
Note "checksum $expected (ok)"
|
||||
} else {
|
||||
Note 'checksum no .sha256 published for this asset (skipped)'
|
||||
}
|
||||
|
||||
Step 'Unpacking the package'
|
||||
$expanded = Join-Path $tempDir 'package'
|
||||
New-Item -ItemType Directory -Force -Path $expanded | Out-Null
|
||||
Expand-Archive -Path $zip -DestinationPath $expanded -Force
|
||||
if (-not (Test-Path (Join-Path $expanded 'bin\chusql_core_storage.dll'))) {
|
||||
# 归档里多包了一层目录也能装
|
||||
$inner = Get-ChildItem -Path $expanded -Directory | Select-Object -First 1
|
||||
if ($inner -and (Test-Path (Join-Path $inner.FullName 'bin\chusql_core_storage.dll'))) { $expanded = $inner.FullName }
|
||||
}
|
||||
$pack = $expanded
|
||||
$downloaded = $true
|
||||
}
|
||||
|
||||
# ---- 校验包内容 ----
|
||||
$storageExe = Join-Path $pack 'bin\chusql-storage.exe'
|
||||
if (-not (Test-Path $storageExe)) { Fail "package is incomplete: bin\chusql-storage.exe not found in $pack" }
|
||||
if ($Component -eq 'web') {
|
||||
$storageLib = Join-Path $pack 'bin\chusql_core_storage.dll'
|
||||
if (-not (Test-Path $storageLib)) { Fail "package is incomplete: bin\chusql_core_storage.dll not found in $pack" }
|
||||
if (-not (Test-Path (Join-Path $pack 'bin\chusql-server.exe'))) { Fail 'package is incomplete: bin\chusql-server.exe not found' }
|
||||
if ($wantWeb) {
|
||||
if (-not (Test-Path (Join-Path $pack 'bin\chusql-web.exe'))) { Fail 'package is incomplete: bin\chusql-web.exe not found' }
|
||||
if (-not (Test-Path (Join-Path $pack 'static'))) { Fail 'package is incomplete: static\ not found' }
|
||||
} else {
|
||||
}
|
||||
if ($wantCli) {
|
||||
if (-not (Test-Path (Join-Path $pack 'bin\csql.exe'))) { Fail 'package is incomplete: bin\csql.exe not found' }
|
||||
}
|
||||
$template = Join-Path $pack 'scripts\chusql.toml'
|
||||
if (-not (Test-Path $template)) { Fail 'package is incomplete: scripts\chusql.toml not found' }
|
||||
|
||||
Say "ChuSQL installer"
|
||||
Say (" component {0}" -f $Component)
|
||||
Say (" version {0}" -f $Version)
|
||||
Say (" components {0}" -f $componentLabel)
|
||||
if ($downloaded) { Say (" release {0}" -f $Version) }
|
||||
Say (" install to {0}" -f $InstallDir)
|
||||
Say (" data dir {0}" -f $DataDir)
|
||||
Say (" root user {0}{1}" -f $RootUser, $(if ($RootPassword) { '' } else { ' (no password: administrator-only sign-in)' }))
|
||||
|
||||
# ---- 释放文件 ----
|
||||
# 包是合在一起的(web 和 cli 都在),这里按这次的选择逐个释放:没选的组件不落地
|
||||
Step 'Installing files'
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir 'bin'), (Join-Path $InstallDir 'logs'), $DataDir | Out-Null
|
||||
Copy-Item (Join-Path $pack 'bin\*') (Join-Path $InstallDir 'bin') -Recurse -Force
|
||||
if ($Component -eq 'web') {
|
||||
Copy-Item $storageLib (Join-Path $InstallDir 'bin') -Force
|
||||
Copy-Item (Join-Path $pack 'bin\chusql-server.exe') (Join-Path $InstallDir 'bin') -Force
|
||||
Get-ChildItem (Join-Path $pack 'bin') -Filter '*.dll' -File -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { Copy-Item $_.FullName (Join-Path $InstallDir 'bin') -Force }
|
||||
if ($wantCli) {
|
||||
Copy-Item (Join-Path $pack 'bin\csql.exe') (Join-Path $InstallDir 'bin') -Force
|
||||
}
|
||||
if ($wantWeb) {
|
||||
Copy-Item (Join-Path $pack 'bin\chusql-web.exe') (Join-Path $InstallDir 'bin') -Force
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir 'static') | Out-Null
|
||||
Copy-Item (Join-Path $pack 'static\*') (Join-Path $InstallDir 'static') -Recurse -Force
|
||||
$launcher = Join-Path $pack 'csql-web.ps1'
|
||||
@@ -80,12 +319,13 @@ Copy-Item (Join-Path $pack 'scripts\init.sql') $InstallDir -Force -ErrorAction S
|
||||
|
||||
# ---- 命令入口 ----
|
||||
Step 'Creating commands'
|
||||
if ($Component -eq 'web') {
|
||||
if ($wantWeb) {
|
||||
Set-Content -Path (Join-Path $InstallDir 'csql-web.cmd') -Encoding ASCII -Value @'
|
||||
@echo off
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0csql-web.ps1" %*
|
||||
'@
|
||||
} else {
|
||||
}
|
||||
if ($wantCli) {
|
||||
Set-Content -Path (Join-Path $InstallDir 'csql.cmd') -Encoding ASCII -Value @'
|
||||
@echo off
|
||||
"%~dp0bin\csql.exe" %*
|
||||
@@ -108,17 +348,28 @@ if ($entries -notcontains $InstallDir) {
|
||||
# ---- 删掉安装包 ----
|
||||
if (-not $KeepPackage) {
|
||||
Step 'Removing the package'
|
||||
$marker = Join-Path $pack 'bin\chusql-storage.exe'
|
||||
$safe = (Test-Path $marker) -and ($pack -ne $InstallDir) -and (-not $InstallDir.StartsWith($pack, [StringComparison]::OrdinalIgnoreCase))
|
||||
if ($safe) {
|
||||
Remove-Item -Recurse -Force $pack
|
||||
if ($downloaded) {
|
||||
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
|
||||
Say 'package removed'
|
||||
} else {
|
||||
Say ("kept the package (not safe to delete automatically): {0}" -f $pack)
|
||||
$marker = Join-Path $pack 'bin\chusql_core_storage.dll'
|
||||
$safe = (Test-Path $marker) -and ($pack -ne $InstallDir) -and (-not $InstallDir.StartsWith($pack, [StringComparison]::OrdinalIgnoreCase))
|
||||
if ($safe) {
|
||||
Remove-Item -Recurse -Force $pack
|
||||
Say 'package removed'
|
||||
} else {
|
||||
Say ("kept the package (not safe to delete automatically): {0}" -f $pack)
|
||||
}
|
||||
}
|
||||
} elseif ($downloaded) {
|
||||
Say ("kept the downloaded package: {0}" -f $tempDir)
|
||||
}
|
||||
|
||||
Step 'Done'
|
||||
Say (" chusql.toml {0}" -f $configFile)
|
||||
Say (" data {0}" -f $DataDir)
|
||||
if ($Component -eq 'web') { Say ' start with csql-web' } else { Say ' start with csql' }
|
||||
$startWith = @()
|
||||
if ($wantWeb) { $startWith += 'csql-web' }
|
||||
if ($wantCli) { $startWith += 'csql' }
|
||||
Say (" start with {0}" -f ($startWith -join ', '))
|
||||
Say (" tcp server {0} (listens on [server] host/port, defaults 127.0.0.1:7777)" -f (Join-Path $InstallDir 'bin\chusql-server.exe'))
|
||||
+378
-96
@@ -1,55 +1,60 @@
|
||||
#!/bin/sh
|
||||
# ChuSQL 安装脚本(Linux / macOS / POSIX sh)。
|
||||
#
|
||||
# 三种来源,脚本自己判断走哪条:
|
||||
#
|
||||
# 1) 包内安装:解压一个发行包,脚本旁边就是 bin/,直接装本地文件
|
||||
# ./install.sh --component web
|
||||
#
|
||||
# 2) 在线安装:脚本从 GitHub 取发行包(也可用 --url 指任意 URL 或本地归档)
|
||||
# curl -fsSL https://raw.githubusercontent.com/TheBadRoger/ChuSQL/main/scripts/install.sh \
|
||||
# | sh -s -- --component web
|
||||
# sh install.sh --component web --url dist/chusql-web-linux-x86_64.tar.gz
|
||||
#
|
||||
# 3) 源码安装:拉仓库、本地编译(要有 git + cargo + stack + ghc)
|
||||
# ./install.sh --component web --from-source
|
||||
# ./install.sh --component web --from-source --source-dir ../ChuSQL
|
||||
#
|
||||
# 安装 = 释放到目标目录 + 配好环境变量 + 写全局配置,装完把包目录删掉。
|
||||
# storage/engine 一定装;web 与 cli 二选一。
|
||||
#
|
||||
# 包目录结构(解压后就是这个样子):
|
||||
# bin/ chusql-storage(必装)、chusql-web 或 csql(二选一)
|
||||
# static/ Web 前端静态资源(装 web 组件时带)
|
||||
# scripts/ chusql.toml、init.sql
|
||||
# csql-web.sh
|
||||
# install.sh
|
||||
|
||||
set -eu
|
||||
|
||||
# ChuSQL 安装脚本(Linux/macOS):从包内、发行版或源码装 cli/web,
|
||||
# 写全局配置、改 PATH,装完删除安装包。
|
||||
|
||||
default_repo='TheBadRoger/ChuSQL'
|
||||
|
||||
# GitHub 站点:github.com 用 api.github.com;其它(GitHub Enterprise、镜像、本地夹具)
|
||||
# 按 GHE 的约定走 <base>/api/v3,下载也走 <base>/<owner>/<repo>/releases/download/。
|
||||
github_base="${CHUSQL_GITHUB_BASE:-https://github.com}"
|
||||
github_base="${github_base%/}"
|
||||
case "$github_base" in
|
||||
https://github.com|http://github.com) api_base='https://api.github.com' ;;
|
||||
*) api_base="$github_base/api/v3" ;;
|
||||
esac
|
||||
|
||||
# 查版本走 API,未认证只有 60 次/小时;给了 token 就带上(CI 里 GITHUB_TOKEN 一般就有)
|
||||
github_token="${CHUSQL_GITHUB_TOKEN:-${GITHUB_TOKEN:-}}"
|
||||
|
||||
# 打印用法、选项与环境变量说明。
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
usage: ./install.sh --component web|cli [options]
|
||||
usage: ./install.sh [--component web|cli|both] [options]
|
||||
|
||||
--component web|cli which front end to install (required)
|
||||
--component web|cli|both
|
||||
which parts to install. Leave it out and the installer
|
||||
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]
|
||||
|
||||
--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)
|
||||
--user NAME administrator name (default: root)
|
||||
--password PW administrator password, empty means no password
|
||||
(administrator-only sign-in)
|
||||
(administrator-only sign-in). Leave it out and the
|
||||
installer asks for it, twice, with the echo off.
|
||||
--keep-package do not delete the downloaded/extracted package
|
||||
|
||||
where the files come from:
|
||||
(default) a package next to this script, else the newest GitHub release
|
||||
--url URL|PATH install from this archive (URL, file:// URL or local path)
|
||||
--repo OWNER/NAME GitHub repository (default: TheBadRoger/ChuSQL)
|
||||
--version TAG release tag to download / git ref to build (default: latest)
|
||||
--version TAG release tag to download / git ref to build
|
||||
(default: latest = the newest release that has your package)
|
||||
--list-versions list the release tags in the repository, then exit
|
||||
--from-source clone and build locally (needs git, cargo, stack, ghc)
|
||||
--source-dir DIR build from this checkout instead of cloning
|
||||
-h, --help this text
|
||||
|
||||
environment:
|
||||
CHUSQL_GITHUB_BASE GitHub base URL: GitHub Enterprise, a mirror, or a
|
||||
local test fixture (default: https://github.com)
|
||||
CHUSQL_GITHUB_TOKEN token for the version API (or GITHUB_TOKEN); without
|
||||
one the API allows 60 requests per hour
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -64,6 +69,11 @@ version='latest'
|
||||
url_arg=''
|
||||
from_source='no'
|
||||
source_dir=''
|
||||
list_versions='no'
|
||||
interactive_flag='no'
|
||||
password_given='no'
|
||||
want_web='no'
|
||||
want_cli='no'
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
@@ -71,11 +81,13 @@ while [ $# -gt 0 ]; do
|
||||
--install-dir) install_dir="${2:-}"; shift 2 ;;
|
||||
--data-dir) data_dir="${2:-}"; shift 2 ;;
|
||||
--user) root_user="${2:-}"; shift 2 ;;
|
||||
--password) root_password="${2:-}"; shift 2 ;;
|
||||
--password) root_password="${2:-}"; password_given='yes'; shift 2 ;;
|
||||
--keep-package) keep_package='yes'; shift ;;
|
||||
--url) url_arg="${2:-}"; shift 2 ;;
|
||||
--repo) repo="${2:-}"; shift 2 ;;
|
||||
--version) version="${2:-}"; shift 2 ;;
|
||||
--list-versions) list_versions='yes'; shift ;;
|
||||
--interactive) interactive_flag='yes'; shift ;;
|
||||
--from-source) from_source='yes'; shift ;;
|
||||
--source-dir) source_dir="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
@@ -83,15 +95,23 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
# 打印错误并退出 1。
|
||||
fail() { echo "!! $1" >&2; exit 1; }
|
||||
# 打印步骤标题。
|
||||
step() { echo; echo "==> $1"; }
|
||||
# 打印缩进提示行。
|
||||
note() { echo " $1"; }
|
||||
|
||||
case "$component" in
|
||||
web|cli) ;;
|
||||
'') fail '--component is required (web or cli)' ;;
|
||||
*) fail "--component must be web or cli, got: $component" ;;
|
||||
esac
|
||||
# --list-versions 只问仓库有什么版本,不需要 --component
|
||||
if [ "$list_versions" = 'no' ]; then
|
||||
case "$component" in
|
||||
web) want_web='yes' ;;
|
||||
cli) want_cli='yes' ;;
|
||||
both) want_web='yes'; want_cli='yes' ;;
|
||||
'') : ;; # 没给就交互询问(下面),问不了才报错
|
||||
*) fail "--component must be web, cli or both, got: $component" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ---- 本脚本在哪(管道进来时 $0 是 sh,这时没有本地包)----
|
||||
self=$0
|
||||
@@ -104,27 +124,137 @@ esac
|
||||
[ -n "$install_dir" ] || install_dir="$HOME/.local/share/chusql"
|
||||
[ -n "$data_dir" ] || data_dir="$install_dir/data"
|
||||
|
||||
# ---- 交互:先确定答案从哪来 ----
|
||||
# 管道安装(curl ... | sh -s --)时 stdin 是脚本本身,所以优先开 /dev/tty;
|
||||
# 没有控制终端时只认 --interactive(答案按行从 stdin 读),否则保持原来的非交互行为。
|
||||
answers=''
|
||||
can_prompt='no'
|
||||
if [ "$interactive_flag" = 'yes' ]; then
|
||||
answers='stdin'
|
||||
can_prompt='yes'
|
||||
elif (exec 3</dev/tty) 2>/dev/null; then
|
||||
answers='/dev/tty'
|
||||
can_prompt='yes'
|
||||
elif [ -t 0 ]; then
|
||||
answers='stdin'
|
||||
can_prompt='yes'
|
||||
fi
|
||||
|
||||
read_line() { # -> $line(读到空行也算成功)
|
||||
if [ "$answers" = 'stdin' ]; then
|
||||
IFS= read -r line || line=''
|
||||
else
|
||||
IFS= read -r line < "$answers" || line=''
|
||||
fi
|
||||
}
|
||||
|
||||
read_secret() { # read_secret PROMPT -> 值写到 stdout(提示和换行都走 stderr,回显关掉)
|
||||
printf '%s' "$1" >&2
|
||||
if [ "$answers" = 'stdin' ]; then
|
||||
IFS= read -r secret_line || secret_line=''
|
||||
else
|
||||
saved=''
|
||||
if command -v stty >/dev/null 2>&1; then
|
||||
saved=$(stty -g < "$answers" 2>/dev/null) || saved=''
|
||||
stty -echo < "$answers" 2>/dev/null || true
|
||||
fi
|
||||
IFS= read -r secret_line < "$answers" || secret_line=''
|
||||
if [ -n "$saved" ]; then
|
||||
stty "$saved" < "$answers" 2>/dev/null || true
|
||||
elif command -v stty >/dev/null 2>&1; then
|
||||
stty echo < "$answers" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
printf '\n' >&2
|
||||
printf '%s' "$secret_line"
|
||||
}
|
||||
|
||||
ask_yes_no() { # ask_yes_no PROMPT DEFAULT(y|n) -> $reply = yes|no
|
||||
while :; do
|
||||
printf '%s ' "$1" >&2
|
||||
read_line
|
||||
case "$line" in
|
||||
'') reply="$2" ;;
|
||||
[Yy]|[Yy][Ee][Ss]) reply='yes' ;;
|
||||
[Nn]|[Nn][Oo]) reply='no' ;;
|
||||
*) echo ' please answer y or n' >&2; continue ;;
|
||||
esac
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
if [ "$component" = '' ] && [ "$list_versions" = 'no' ]; then
|
||||
if [ "$can_prompt" = 'no' ]; then
|
||||
fail '--component is required (web, cli or both) when there is no terminal to ask on'
|
||||
fi
|
||||
echo
|
||||
echo 'ChuSQL installer: which parts do you want?'
|
||||
ask_yes_no 'Install the command line client (csql)? [Y/n]' yes
|
||||
want_cli="$reply"
|
||||
ask_yes_no 'Install the web front end (browser UI)? [y/N]' no
|
||||
want_web="$reply"
|
||||
if [ "$want_web" = 'no' ] && [ "$want_cli" = 'no' ]; then
|
||||
echo ' neither selected: only the storage library and the server get installed' >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$list_versions" = 'no' ] && [ "$password_given" = 'no' ] && [ "$can_prompt" = 'yes' ]; then
|
||||
echo
|
||||
echo "The administrator account is $root_user."
|
||||
first=$(read_secret "Root password for $root_user (empty means no password) []: ")
|
||||
if [ -n "$first" ]; then
|
||||
again=$(read_secret 'Repeat the root password: ')
|
||||
[ "$first" = "$again" ] || fail 'the two passwords do not match'
|
||||
echo 'root password set' >&2
|
||||
else
|
||||
echo 'no root password: administrator-only sign-in' >&2
|
||||
fi
|
||||
root_password="$first"
|
||||
fi
|
||||
|
||||
# 选中的组件:空格分隔的清单,外加一个显示名(cli 或 web,两个就是 cli+web)
|
||||
wanted=''
|
||||
if [ "$want_cli" = 'yes' ]; then wanted='cli'; fi
|
||||
if [ "$want_web" = 'yes' ]; then
|
||||
if [ -n "$wanted" ]; then wanted="$wanted web"; else wanted='web'; fi
|
||||
fi
|
||||
component_label=''
|
||||
for comp in $wanted; do
|
||||
if [ -n "$component_label" ]; then component_label="$component_label+$comp"; else component_label="$comp"; fi
|
||||
done
|
||||
if [ -z "$component_label" ]; then component_label='none (storage only)'; fi
|
||||
|
||||
tmp=''
|
||||
# 退出时删掉临时目录。
|
||||
cleanup() { if [ -n "$tmp" ]; then rm -rf "$tmp"; fi; }
|
||||
# 建临时目录并登记退出清理。
|
||||
new_tmp() {
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/chusql-install.XXXXXX") || fail 'cannot create a temporary directory'
|
||||
# --keep-package 时不注册清理,临时目录就留着
|
||||
if [ "$keep_package" = 'no' ]; then trap cleanup EXIT INT TERM HUP; fi
|
||||
}
|
||||
|
||||
# 用 curl 或 wget 把 URL 下载到文件。
|
||||
download() {
|
||||
# download URL FILE
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL -o "$2" "$1" || return 1
|
||||
if [ -n "${3:-}" ]; then
|
||||
curl -fsSL -H "$3" -H 'Accept: application/vnd.github+json' -o "$2" "$1" || return 1
|
||||
else
|
||||
curl -fsSL -o "$2" "$1" || return 1
|
||||
fi
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q -O "$2" "$1" || return 1
|
||||
if [ -n "${3:-}" ]; then
|
||||
wget -q --header="$3" --header='Accept: application/vnd.github+json' -O "$2" "$1" || return 1
|
||||
else
|
||||
wget -q -O "$2" "$1" || return 1
|
||||
fi
|
||||
else
|
||||
fail 'need curl or wget to download the package'
|
||||
fi
|
||||
}
|
||||
|
||||
# 校验包的 sha256,没有就跳过。
|
||||
verify_checksum() {
|
||||
# verify_checksum ASSET_URL FILE —— 发布带 .sha256 就校验,没带就说明一下
|
||||
sums="$tmp/asset.sha256"
|
||||
if ! download "$1.sha256" "$sums" 2>/dev/null; then
|
||||
note 'checksum no .sha256 published for this asset (skipped)'
|
||||
@@ -142,13 +272,13 @@ verify_checksum() {
|
||||
note "checksum $expected (ok)"
|
||||
}
|
||||
|
||||
# 按扩展名解包 zip 或 tar.gz。
|
||||
unpack() {
|
||||
# unpack ARCHIVE DEST
|
||||
mkdir -p "$2"
|
||||
case "$1" in
|
||||
*.zip)
|
||||
command -v unzip >/dev/null 2>&1 || fail 'unzip is required to open a .zip package'
|
||||
unzip -q "$1" -d "$2"
|
||||
unzip -q -o "$1" -d "$2"
|
||||
;;
|
||||
*)
|
||||
tar -xzf "$1" -C "$2"
|
||||
@@ -156,7 +286,7 @@ unpack() {
|
||||
esac
|
||||
}
|
||||
|
||||
# 平台标签(全局:os、arch、ext、labels)
|
||||
# 探测平台标签(os、arch、ext、labels)。
|
||||
detect_platform() {
|
||||
os=$(uname -s 2>/dev/null || echo unknown)
|
||||
case "$os" in
|
||||
@@ -173,13 +303,127 @@ detect_platform() {
|
||||
labels="$os-$arch $os"
|
||||
}
|
||||
|
||||
# 发行资源(全局:url、archive),成功返回 0
|
||||
# ---- 发行 tag:问 GitHub API(不依赖 jq,用 awk 读 JSON)----
|
||||
# 请求 GitHub API 并落盘。
|
||||
api_get() {
|
||||
if [ -n "$github_token" ]; then
|
||||
download "$api_base$1" "$2" "Authorization: Bearer $github_token"
|
||||
else
|
||||
download "$api_base$1" "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
# 把 release JSON 解析成 tag 与资源行。
|
||||
parse_releases() {
|
||||
awk '
|
||||
in_assets {
|
||||
if ($0 ~ /^[[:space:]]*\]/) { in_assets = 0; next }
|
||||
if ($0 ~ /"name":/) {
|
||||
s = $0; sub(/.*"name": *"/, "", s); sub(/".*/, "", s)
|
||||
if (s != "") { assets = assets " " s }
|
||||
}
|
||||
next
|
||||
}
|
||||
/"assets": *\[\]/ { next }
|
||||
/"assets": *\[/ { in_assets = 1; next }
|
||||
/"tag_name":/ {
|
||||
if (tag != "") { print tag "\t" pre "\t" assets }
|
||||
s = $0; sub(/.*"tag_name": *"/, "", s); sub(/".*/, "", s)
|
||||
tag = s; pre = 0; assets = ""
|
||||
next
|
||||
}
|
||||
/"prerelease":/ { pre = ($0 ~ /true/) ? 1 : 0; next }
|
||||
END { if (tag != "") { print tag "\t" pre "\t" assets } }
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# 这份 release 里有没有本平台的包。
|
||||
tag_has_asset() {
|
||||
for label in $labels; do
|
||||
case " $1 " in
|
||||
*" chusql-$label.$ext "*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
case " $1 " in
|
||||
*" chusql.$ext "*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# 从 tags.txt 里挑第一个符合条件的 tag。
|
||||
pick_tag() {
|
||||
[ -s "$tmp/tags.txt" ] || return 1
|
||||
while IFS="$(printf '\t')" read -r tag pre assets; do
|
||||
[ -n "$tag" ] || continue
|
||||
if [ "$1" = 'yes' ] && [ "$pre" = '1' ]; then continue; fi
|
||||
if [ "$2" = 'yes' ] && ! tag_has_asset "$assets"; then continue; fi
|
||||
printf '%s\n' "$tag"
|
||||
return 0
|
||||
done < "$tmp/tags.txt"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 列出仓库的 release tag 与预发布标记。
|
||||
list_release_versions() {
|
||||
new_tmp
|
||||
json="$tmp/releases.json"
|
||||
if ! api_get "/repos/$repo/releases?per_page=100" "$json" 2>/dev/null ||
|
||||
! parse_releases "$json" > "$tmp/tags.txt" 2>/dev/null ||
|
||||
[ ! -s "$tmp/tags.txt" ]; then
|
||||
echo "!! cannot list the releases of $repo from $api_base" >&2
|
||||
echo ' (offline, rate limited, or the repository has no release yet)' >&2
|
||||
echo " open $github_base/$repo/releases and pass a tag: --version <tag>" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "ChuSQL releases in $repo"
|
||||
while IFS="$(printf '\t')" read -r tag pre assets; do
|
||||
[ -n "$tag" ] || continue
|
||||
marker=''
|
||||
if [ "$pre" = '1' ]; then marker=' (prerelease)'; fi
|
||||
if ! tag_has_asset "$assets"; then
|
||||
marker="$marker (no package for $os-$arch)"
|
||||
fi
|
||||
echo " $tag$marker"
|
||||
done < "$tmp/tags.txt"
|
||||
echo
|
||||
echo "install one with: ./install.sh --version <tag>"
|
||||
}
|
||||
|
||||
# 把 version=latest 解析成具体 tag。
|
||||
resolve_version() {
|
||||
[ "$version" = 'latest' ] || return 0
|
||||
json="$tmp/releases.json"
|
||||
if ! api_get "/repos/$repo/releases?per_page=100" "$json" 2>/dev/null; then
|
||||
note "version latest (cannot reach $api_base; using the latest release redirect)"
|
||||
return 0
|
||||
fi
|
||||
parse_releases "$json" > "$tmp/tags.txt" 2>/dev/null || true
|
||||
picked=''
|
||||
for want_stable in yes no; do
|
||||
picked=$(pick_tag "$want_stable" 'yes') && break
|
||||
picked=''
|
||||
done
|
||||
if [ -z "$picked" ]; then
|
||||
for want_stable in yes no; do
|
||||
picked=$(pick_tag "$want_stable" 'no') && break
|
||||
picked=''
|
||||
done
|
||||
fi
|
||||
if [ -n "$picked" ]; then
|
||||
version="$picked"
|
||||
note "version $version"
|
||||
else
|
||||
note 'version latest (no release lists a matching package; trying the latest release)'
|
||||
fi
|
||||
}
|
||||
|
||||
# 下载本平台的发行包归档。
|
||||
fetch_asset() {
|
||||
asset="$1"
|
||||
if [ "$version" = 'latest' ]; then
|
||||
url="https://github.com/$repo/releases/latest/download/$asset"
|
||||
url="$github_base/$repo/releases/latest/download/$asset"
|
||||
else
|
||||
url="https://github.com/$repo/releases/download/$version/$asset"
|
||||
url="$github_base/$repo/releases/download/$version/$asset"
|
||||
fi
|
||||
archive="$tmp/$asset"
|
||||
if download "$url" "$archive" 2>/dev/null; then
|
||||
@@ -193,6 +437,12 @@ pack=''
|
||||
url=''
|
||||
archive=''
|
||||
|
||||
if [ "$list_versions" = 'yes' ]; then
|
||||
detect_platform
|
||||
list_release_versions
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$from_source" = 'yes' ]; then
|
||||
# ---------- 源码安装 ----------
|
||||
if [ -z "$source_dir" ]; then
|
||||
@@ -208,17 +458,17 @@ if [ "$from_source" = 'yes' ]; then
|
||||
else
|
||||
src="$tmp/src"
|
||||
if [ "$version" = 'latest' ]; then
|
||||
step "Cloning https://github.com/$repo.git"
|
||||
git clone --depth 1 "https://github.com/$repo.git" "$src" >/dev/null 2>&1 ||
|
||||
fail "cannot clone https://github.com/$repo.git"
|
||||
step "Cloning $github_base/$repo.git"
|
||||
git clone --depth 1 "$github_base/$repo.git" "$src" >/dev/null 2>&1 ||
|
||||
fail "cannot clone $github_base/$repo.git"
|
||||
else
|
||||
step "Cloning https://github.com/$repo.git at $version"
|
||||
git clone --depth 1 --branch "$version" "https://github.com/$repo.git" "$src" >/dev/null 2>&1 ||
|
||||
step "Cloning $github_base/$repo.git at $version"
|
||||
git clone --depth 1 --branch "$version" "$github_base/$repo.git" "$src" >/dev/null 2>&1 ||
|
||||
fail "cannot clone $repo at $version"
|
||||
fi
|
||||
note "source $src"
|
||||
fi
|
||||
[ -f "$src/chusql-storage/Cargo.toml" ] || fail "not a ChuSQL checkout (chusql-storage/Cargo.toml missing): $src"
|
||||
[ -f "$src/chusql-core/storage/Cargo.toml" ] || fail "not a ChuSQL checkout (chusql-core/storage/Cargo.toml missing): $src"
|
||||
[ -f "$src/scripts/chusql.toml" ] || fail "not a ChuSQL checkout (scripts/chusql.toml missing): $src"
|
||||
|
||||
if command -v pkg-config >/dev/null 2>&1 && ! pkg-config --exists zlib 2>/dev/null; then
|
||||
@@ -226,39 +476,48 @@ if [ "$from_source" = 'yes' ]; then
|
||||
fi
|
||||
|
||||
step 'Building the Rust storage (cargo build --release)'
|
||||
( cd "$src/chusql-storage" && cargo build --release ) || fail 'cargo build --release failed'
|
||||
storage_bin="$src/chusql-storage/target/release/chusql-storage"
|
||||
[ -f "$storage_bin" ] || fail "storage binary not found after the build: $storage_bin"
|
||||
|
||||
if [ "$component" = 'web' ]; then
|
||||
comp_dir='chusql-web'; front_name='chusql-web'
|
||||
else
|
||||
comp_dir='chusql-cli'; front_name='csql'
|
||||
fi
|
||||
step "Building the Haskell binaries (stack build --fast in $comp_dir)"
|
||||
note 'the first build downloads the Hackage index (about 139 MB)'
|
||||
( cd "$src/$comp_dir" && stack build --fast ) || fail "stack build failed in $comp_dir"
|
||||
stack_root=$( cd "$src/$comp_dir" && stack path --local-install-root 2>/dev/null | tr -d '\r' )
|
||||
[ -n "$stack_root" ] || fail 'cannot ask stack for --local-install-root'
|
||||
front_bin="$stack_root/bin/$front_name"
|
||||
[ -f "$front_bin" ] || fail "front end binary not found after the build: $front_bin"
|
||||
( cd "$src/chusql-core/storage" && cargo build --release ) || fail 'cargo build --release failed'
|
||||
storage_lib="$src/chusql-core/storage/target/release/libchusql_core_storage.so"
|
||||
[ -f "$storage_lib" ] || fail "storage library not found after the build: $storage_lib"
|
||||
|
||||
step 'Staging the package'
|
||||
pack="$tmp/stage"
|
||||
mkdir -p "$pack/bin" "$pack/scripts"
|
||||
cp "$storage_bin" "$pack/bin/"
|
||||
cp "$front_bin" "$pack/bin/"
|
||||
for extra in "$stack_root/bin/"*.so "$stack_root/bin/"*.dll; do
|
||||
if [ -f "$extra" ]; then cp "$extra" "$pack/bin/"; fi
|
||||
done
|
||||
cp "$storage_lib" "$pack/bin/"
|
||||
cp "$src/scripts/chusql.toml" "$src/scripts/init.sql" "$pack/scripts/"
|
||||
cp "$src/scripts/install.sh" "$pack/"
|
||||
if [ "$component" = 'web' ]; then
|
||||
[ -d "$src/chusql-web/static" ] || fail "static assets not found: $src/chusql-web/static"
|
||||
mkdir -p "$pack/static"
|
||||
cp -R "$src/chusql-web/static/." "$pack/static/"
|
||||
cp "$src/scripts/csql-web.sh" "$pack/"
|
||||
fi
|
||||
for comp in $wanted; do
|
||||
if [ "$comp" = 'web' ]; then
|
||||
comp_dir='chusql-web'; front_name='chusql-web'
|
||||
else
|
||||
comp_dir='chusql-cli'; front_name='csql'
|
||||
fi
|
||||
step "Building the Haskell binaries (stack build --fast in $comp_dir)"
|
||||
note 'the first build downloads the Hackage index (about 139 MB)'
|
||||
( cd "$src/$comp_dir" && stack build --fast ) || fail "stack build failed in $comp_dir"
|
||||
stack_root=$( cd "$src/$comp_dir" && stack path --local-install-root 2>/dev/null | tr -d '\r' )
|
||||
[ -n "$stack_root" ] || fail 'cannot ask stack for --local-install-root'
|
||||
front_bin="$stack_root/bin/$front_name"
|
||||
[ -f "$front_bin" ] || fail "front end binary not found after the build: $front_bin"
|
||||
cp "$front_bin" "$pack/bin/"
|
||||
for extra in "$stack_root/bin/"*.so "$stack_root/bin/"*.dll; do
|
||||
if [ -f "$extra" ]; then cp "$extra" "$pack/bin/"; fi
|
||||
done
|
||||
if [ "$comp" = 'web' ]; then
|
||||
[ -d "$src/chusql-web/static" ] || fail "static assets not found: $src/chusql-web/static"
|
||||
mkdir -p "$pack/static"
|
||||
cp -R "$src/chusql-web/static/." "$pack/static/"
|
||||
cp "$src/scripts/csql-web.sh" "$pack/"
|
||||
fi
|
||||
done
|
||||
|
||||
# TCP 数据库服务必装,跟组件选择无关:它住在 chusql-server 这个 stack 工程里
|
||||
step 'Building the TCP database server (stack build --fast chusql-server:exe:chusql-server)'
|
||||
( cd "$src/chusql-server" && stack build --fast chusql-server:exe:chusql-server ) || fail 'stack build failed in chusql-server'
|
||||
server_root=$( cd "$src/chusql-server" && stack path --local-install-root 2>/dev/null | tr -d '\r' )
|
||||
[ -n "$server_root" ] || fail 'cannot ask stack for --local-install-root'
|
||||
[ -f "$server_root/bin/chusql-server" ] || fail "chusql-server not found after the build: $server_root/bin/chusql-server"
|
||||
cp "$server_root/bin/chusql-server" "$pack/bin/"
|
||||
elif [ -n "$url_arg" ]; then
|
||||
# ---------- 指定 URL / 本地归档 ----------
|
||||
new_tmp
|
||||
@@ -278,43 +537,48 @@ elif [ -n "$url_arg" ]; then
|
||||
step 'Unpacking'
|
||||
unpack "$archive" "$tmp/pkg"
|
||||
pack="$tmp/pkg"
|
||||
elif [ -n "$script_dir" ] && [ -f "$script_dir/bin/chusql-storage" ]; then
|
||||
elif [ -n "$script_dir" ] && ls "$script_dir/bin/"libchusql_core_storage.* >/dev/null 2>&1; then
|
||||
# ---------- 包内安装 ----------
|
||||
pack="$script_dir"
|
||||
else
|
||||
# ---------- 从 GitHub 发行版下载 ----------
|
||||
new_tmp
|
||||
detect_platform
|
||||
step "Looking for a ChuSQL package for $os-$arch (component $component) in $repo"
|
||||
resolve_version
|
||||
mkdir -p "$tmp/pkg"
|
||||
step "Looking for a ChuSQL package for $os-$arch in $repo"
|
||||
found=''
|
||||
for label in $labels; do
|
||||
if fetch_asset "chusql-$component-$label.$ext"; then
|
||||
if fetch_asset "chusql-$label.$ext"; then
|
||||
found="$asset"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$found" ]; then
|
||||
if fetch_asset "chusql-$component.$ext"; then found="$asset"; fi
|
||||
if fetch_asset "chusql.$ext"; then found="$asset"; fi
|
||||
fi
|
||||
[ -n "$found" ] || fail "no package for $os-$arch in $repo ($version); try --url or --from-source"
|
||||
[ -n "$found" ] || fail "no package for $os-$arch in $repo ($version); try --list-versions, --url or --from-source"
|
||||
verify_checksum "$url" "$archive"
|
||||
step 'Unpacking'
|
||||
step "Unpacking $found"
|
||||
unpack "$archive" "$tmp/pkg"
|
||||
pack="$tmp/pkg"
|
||||
fi
|
||||
|
||||
# ---- 校验包内容 ----
|
||||
[ -f "$pack/bin/chusql-storage" ] || fail "package is incomplete: bin/chusql-storage not found in $pack"
|
||||
if [ "$component" = 'web' ]; then
|
||||
ls "$pack/bin/"libchusql_core_storage.* >/dev/null 2>&1 || fail "package is incomplete: bin/libchusql_core_storage.* not found in $pack"
|
||||
[ -f "$pack/bin/chusql-server" ] || fail "package is incomplete: bin/chusql-server not found in $pack"
|
||||
if [ "$want_web" = 'yes' ]; then
|
||||
[ -f "$pack/bin/chusql-web" ] || fail 'package is incomplete: bin/chusql-web not found'
|
||||
[ -d "$pack/static" ] || fail 'package is incomplete: static/ not found'
|
||||
else
|
||||
fi
|
||||
if [ "$want_cli" = 'yes' ]; then
|
||||
[ -f "$pack/bin/csql" ] || fail 'package is incomplete: bin/csql not found'
|
||||
fi
|
||||
[ -f "$pack/scripts/chusql.toml" ] || fail 'package is incomplete: scripts/chusql.toml not found'
|
||||
|
||||
echo 'ChuSQL installer'
|
||||
echo " component $component"
|
||||
echo " version $version"
|
||||
echo " components $component_label"
|
||||
echo " install to $install_dir"
|
||||
echo " data dir $data_dir"
|
||||
if [ -n "$root_password" ]; then
|
||||
@@ -324,11 +588,21 @@ else
|
||||
fi
|
||||
|
||||
# ---- 释放文件 ----
|
||||
# 包是合在一起的(web 和 cli 都在),这里按这次的选择逐个释放:没选的组件不落地
|
||||
step 'Installing files'
|
||||
mkdir -p "$install_dir/bin" "$install_dir/logs" "$data_dir"
|
||||
cp -R "$pack/bin/." "$install_dir/bin/"
|
||||
chmod +x "$install_dir/bin/"* 2>/dev/null || true
|
||||
if [ "$component" = 'web' ]; then
|
||||
cp "$pack/bin/chusql-server" "$install_dir/bin/"
|
||||
for lib in "$pack/bin/"libchusql_core_storage.*; do
|
||||
if [ -f "$lib" ]; then cp "$lib" "$install_dir/bin/"; fi
|
||||
done
|
||||
for lib in "$pack/bin/"*.so "$pack/bin/"*.dylib "$pack/bin/"*.dll; do
|
||||
if [ -f "$lib" ]; then cp "$lib" "$install_dir/bin/"; fi
|
||||
done
|
||||
if [ "$want_cli" = 'yes' ]; then
|
||||
cp "$pack/bin/csql" "$install_dir/bin/"
|
||||
fi
|
||||
if [ "$want_web" = 'yes' ]; then
|
||||
cp "$pack/bin/chusql-web" "$install_dir/bin/"
|
||||
mkdir -p "$install_dir/static"
|
||||
cp -R "$pack/static/." "$install_dir/static/"
|
||||
if [ -f "$pack/csql-web.sh" ]; then
|
||||
@@ -336,6 +610,7 @@ if [ "$component" = 'web' ]; then
|
||||
chmod +x "$install_dir/csql-web.sh"
|
||||
fi
|
||||
fi
|
||||
chmod +x "$install_dir/bin/"* 2>/dev/null || true
|
||||
|
||||
# ---- 写全局配置 ----
|
||||
step 'Writing chusql.toml'
|
||||
@@ -351,13 +626,14 @@ if [ -f "$pack/scripts/init.sql" ]; then cp "$pack/scripts/init.sql" "$install_d
|
||||
|
||||
# ---- 命令入口 ----
|
||||
step 'Creating commands'
|
||||
if [ "$component" = 'web' ]; then
|
||||
if [ "$want_web" = 'yes' ]; then
|
||||
cat > "$install_dir/csql-web" <<'EOF'
|
||||
#!/bin/sh
|
||||
exec "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/csql-web.sh" "$@"
|
||||
EOF
|
||||
chmod +x "$install_dir/csql-web"
|
||||
else
|
||||
fi
|
||||
if [ "$want_cli" = 'yes' ]; then
|
||||
cat > "$install_dir/csql" <<'EOF'
|
||||
#!/bin/sh
|
||||
exec "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/bin/csql" "$@"
|
||||
@@ -387,7 +663,7 @@ if [ "$keep_package" = 'no' ]; then
|
||||
case "$install_dir" in
|
||||
"$pack"*|"$pack") echo "kept the package (install dir is inside it): $pack" ;;
|
||||
*)
|
||||
if [ -f "$pack/bin/chusql-storage" ]; then
|
||||
if ls "$pack/bin/"libchusql_core_storage.* >/dev/null 2>&1; then
|
||||
( cd / && rm -rf "$pack" )
|
||||
echo 'package removed'
|
||||
else
|
||||
@@ -403,4 +679,10 @@ fi
|
||||
step 'Done'
|
||||
echo " chusql.toml $config_file"
|
||||
echo " data $data_dir"
|
||||
if [ "$component" = 'web' ]; then echo ' start with csql-web'; else echo ' start with csql'; fi
|
||||
start_with=''
|
||||
if [ "$want_web" = 'yes' ]; then start_with='csql-web'; fi
|
||||
if [ "$want_cli" = 'yes' ]; then
|
||||
if [ -n "$start_with" ]; then start_with="$start_with, csql"; else start_with='csql'; fi
|
||||
fi
|
||||
echo " start with $start_with"
|
||||
echo " tcp server $install_dir/bin/chusql-server (listens on [server] host/port, defaults 127.0.0.1:7777)"
|
||||
@@ -0,0 +1,241 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutDir = '',
|
||||
[string]$Platform = '',
|
||||
[string]$Version = '',
|
||||
[switch]$NoBuild,
|
||||
[switch]$NoVerify
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Windows 打包脚本:构建、装配、打 zip、出 sha256,产物落 releases\,命名与 CI 一致。
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
if (-not $OutDir) { $OutDir = Join-Path $repoRoot 'releases' }
|
||||
$OutDir = [System.IO.Path]::GetFullPath($OutDir)
|
||||
|
||||
# 打印错误并退出 1。
|
||||
function Fail([string]$msg) {
|
||||
Write-Host "!! $msg" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
# 打印步骤标题。
|
||||
function Step([string]$msg) { Write-Host ''; Write-Host "==> $msg" -ForegroundColor Cyan }
|
||||
# 打印一行输出。
|
||||
function Say([string]$msg) { Write-Host $msg }
|
||||
|
||||
if (-not $Platform) {
|
||||
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x86_64' }
|
||||
$Platform = "windows-$arch"
|
||||
}
|
||||
if ($Platform -notlike 'windows-*') {
|
||||
Fail 'the Linux / macOS package is scripts/package.sh; this script only makes windows-* zips'
|
||||
}
|
||||
if (-not $Version) {
|
||||
$Version = ''
|
||||
}
|
||||
|
||||
# 算文件的 sha256(小写)。
|
||||
function Get-Sha256([string]$path) {
|
||||
(Get-FileHash -Path $path -Algorithm SHA256).Hash.ToLower()
|
||||
}
|
||||
|
||||
# 归档里该有什么,这里先对一遍:两部分都得在,装配漏了东西别等发布出去才发现
|
||||
# 核对归档里必需的文件都在。
|
||||
function Assert-Stage([string]$stage) {
|
||||
$must = @(
|
||||
'bin\chusql_core_storage.dll',
|
||||
'bin\csql.exe',
|
||||
'bin\chusql-web.exe',
|
||||
'bin\chusql-server.exe',
|
||||
'static\index.html',
|
||||
'csql-web.ps1',
|
||||
'scripts\chusql.toml',
|
||||
'install.ps1',
|
||||
'install.sh'
|
||||
)
|
||||
foreach ($rel in $must) {
|
||||
if (-not (Test-Path (Join-Path $stage $rel))) {
|
||||
Fail "$rel is missing from the package"
|
||||
}
|
||||
}
|
||||
# bin 里带 chusql 名字的文件只能是已知产物;改名前的残留或别的垃圾都算装配错误
|
||||
$known = @('chusql_core_storage.dll', 'chusql-server.exe', 'chusql-web.exe')
|
||||
$unknown = @(Get-ChildItem (Join-Path $stage 'bin') -Filter 'chusql*' -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $known -notcontains $_.Name })
|
||||
if ($unknown.Count -gt 0) {
|
||||
$names = ($unknown | ForEach-Object Name) -join ', '
|
||||
Fail "unexpected artifacts in bin/: $names"
|
||||
}
|
||||
}
|
||||
|
||||
# 真装一遍,核对落地与未选组件。
|
||||
function Test-Installed([string]$name, [string]$comp) {
|
||||
$archive = Join-Path $OutDir "$name.zip"
|
||||
$vtmp = Join-Path ([System.IO.Path]::GetTempPath()) ("chusql-packcheck-" + [Guid]::NewGuid().ToString('N'))
|
||||
$pkg = Join-Path $vtmp 'pkg'
|
||||
$opt = Join-Path $vtmp 'opt'
|
||||
$data = Join-Path $vtmp 'data'
|
||||
$appdata = Join-Path $vtmp 'appdata'
|
||||
New-Item -ItemType Directory -Force -Path $pkg, $opt, $appdata | Out-Null
|
||||
|
||||
$oldAppData = $env:APPDATA
|
||||
$oldLocalAppData = $env:LOCALAPPDATA
|
||||
$oldUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
$log = Join-Path $vtmp 'install.log'
|
||||
try {
|
||||
Expand-Archive -Path $archive -DestinationPath $pkg -Force
|
||||
$env:APPDATA = $appdata
|
||||
$env:LOCALAPPDATA = $vtmp
|
||||
$pwsh = Join-Path $PSHOME 'pwsh.exe'
|
||||
if (-not (Test-Path $pwsh)) { $pwsh = 'powershell' }
|
||||
& $pwsh -NoProfile -File (Join-Path $pkg 'install.ps1') -Component $comp -InstallDir $opt `
|
||||
-DataDir $data -RootUser root -RootPassword package-check *>&1 | Out-File -FilePath $log -Encoding UTF8
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Get-Content $log | Select-Object -First 120 | Write-Host
|
||||
Fail "$name`: installing the $comp part from the package failed (log above)"
|
||||
}
|
||||
$ok = $true
|
||||
$checks = @(
|
||||
(Join-Path $opt 'bin\chusql_core_storage.dll'),
|
||||
(Join-Path $opt 'bin\chusql-server.exe'),
|
||||
(Join-Path $opt 'init.sql'),
|
||||
(Join-Path $appdata 'ChuSQL\chusql.toml')
|
||||
)
|
||||
$forbidden = @()
|
||||
if ($comp -eq 'web') {
|
||||
$checks += @(
|
||||
(Join-Path $opt 'bin\chusql-web.exe'),
|
||||
(Join-Path $opt 'static\index.html'),
|
||||
(Join-Path $opt 'csql-web.ps1'),
|
||||
(Join-Path $opt 'csql-web.cmd')
|
||||
)
|
||||
$forbidden = @((Join-Path $opt 'bin\csql.exe'), (Join-Path $opt 'csql.cmd'))
|
||||
} else {
|
||||
$checks += @(
|
||||
(Join-Path $opt 'bin\csql.exe'),
|
||||
(Join-Path $opt 'csql.cmd')
|
||||
)
|
||||
$forbidden = @((Join-Path $opt 'bin\chusql-web.exe'), (Join-Path $opt 'static'), (Join-Path $opt 'csql-web.cmd'))
|
||||
}
|
||||
foreach ($f in $checks) { if (-not (Test-Path $f)) { $ok = $false; Write-Host " missing: $f" } }
|
||||
foreach ($f in $forbidden) { if (Test-Path $f) { $ok = $false; Write-Host " should not be there: $f" } }
|
||||
$config = Join-Path $appdata 'ChuSQL\chusql.toml'
|
||||
$expectDataDir = $data -replace '\\', '/'
|
||||
if (-not (Test-Path $config) -or -not (Select-String -Path $config -SimpleMatch "data_dir = ""$expectDataDir""" -Quiet)) {
|
||||
$ok = $false
|
||||
Write-Host " config does not point at $expectDataDir"
|
||||
}
|
||||
if (-not $ok) {
|
||||
Get-Content $log | Select-Object -First 120 | Write-Host
|
||||
Fail "$name`: the $comp part is not what the installer promises (log above)"
|
||||
}
|
||||
# 装的时候往用户 PATH 里写过一行,收尾要原样写回
|
||||
$nowUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
if ($nowUserPath -ne $oldUserPath) {
|
||||
[Environment]::SetEnvironmentVariable('Path', $oldUserPath, 'User')
|
||||
Write-Host " note: the installer touched the user PATH; restored it"
|
||||
}
|
||||
} finally {
|
||||
$env:APPDATA = $oldAppData
|
||||
$env:LOCALAPPDATA = $oldLocalAppData
|
||||
Remove-Item -Recurse -Force $vtmp -ErrorAction SilentlyContinue
|
||||
}
|
||||
Say " ok $name installs the $comp part, and only the $comp part"
|
||||
}
|
||||
|
||||
# ---------- 构建 ----------
|
||||
if (-not $NoBuild) {
|
||||
Step 'Building storage (Rust)'
|
||||
Push-Location (Join-Path $repoRoot 'chusql-core\storage')
|
||||
try {
|
||||
& cargo build --release
|
||||
if ($LASTEXITCODE -ne 0) { Fail 'cargo build --release failed' }
|
||||
} finally { Pop-Location }
|
||||
|
||||
foreach ($target in @('chusql-web:exe:chusql-web', 'chusql-cli:exe:csql', 'chusql-server:exe:chusql-server')) {
|
||||
Step "Building $target (Haskell)"
|
||||
Push-Location (Join-Path $repoRoot 'chusql-cli')
|
||||
try {
|
||||
& stack build --fast $target
|
||||
if ($LASTEXITCODE -ne 0) { Fail "stack build --fast $target failed" }
|
||||
} finally { Pop-Location }
|
||||
}
|
||||
}
|
||||
|
||||
# ---------- 装配 + 打归档 ----------
|
||||
$name = "chusql-$Platform"
|
||||
$stageRoot = Join-Path $OutDir '.stage'
|
||||
$stage = Join-Path $stageRoot $name
|
||||
$zip = Join-Path $OutDir "$name.zip"
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
try {
|
||||
Step "Packing $name"
|
||||
if (Test-Path $stageRoot) { Remove-Item -Recurse -Force $stageRoot }
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'bin'), (Join-Path $stage 'scripts') | Out-Null
|
||||
|
||||
$storage = Join-Path $repoRoot 'chusql-core\storage\target\release\chusql_core_storage.dll'
|
||||
if (-not (Test-Path $storage)) { Fail 'chusql-core\storage\target\release\chusql_core_storage.dll is not built (run without -NoBuild)' }
|
||||
Copy-Item $storage (Join-Path $stage 'bin')
|
||||
|
||||
# 从真正用于构建的工程目录取安装根(package.sh 同样锚在 chusql-cli),别靠调用者的当前目录
|
||||
Push-Location (Join-Path $repoRoot 'chusql-cli')
|
||||
try { $stackRoot = (& stack path --local-install-root | Select-Object -Last 1).Trim() } finally { Pop-Location }
|
||||
foreach ($frontName in @('chusql-web.exe', 'chusql-server.exe', 'csql.exe')) {
|
||||
$front = Join-Path $stackRoot "bin/$frontName"
|
||||
if (-not (Test-Path $front)) {
|
||||
$front = Get-ChildItem -Path (Join-Path $repoRoot 'chusql-web\.stack-work'), (Join-Path $repoRoot 'chusql-cli\.stack-work'), (Join-Path $repoRoot 'chusql-server\.stack-work') `
|
||||
-Recurse -Filter $frontName -File -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
|
||||
}
|
||||
if (-not $front -or -not (Test-Path $front)) { Fail "$frontName is not built (run without -NoBuild)" }
|
||||
Copy-Item $front (Join-Path $stage 'bin')
|
||||
}
|
||||
|
||||
# 第三方 dll 才要连带上:名字带 chusql 的是本 crate 产物或改名前的残留,上面已按名拷过
|
||||
Get-ChildItem (Join-Path $repoRoot 'chusql-core\storage\target\release') -Filter '*.dll' -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -notlike 'chusql*' } |
|
||||
ForEach-Object { Copy-Item $_.FullName (Join-Path $stage 'bin') }
|
||||
|
||||
Copy-Item (Join-Path $repoRoot 'scripts\chusql.toml'), (Join-Path $repoRoot 'scripts\init.sql') (Join-Path $stage 'scripts')
|
||||
Copy-Item (Join-Path $repoRoot 'scripts\install.ps1'), (Join-Path $repoRoot 'scripts\install.sh') $stage
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'static') | Out-Null
|
||||
Copy-Item (Join-Path $repoRoot 'chusql-web\static\*') (Join-Path $stage 'static') -Recurse
|
||||
Copy-Item (Join-Path $repoRoot 'scripts\csql-web.sh') $stage
|
||||
$webPs1 = Join-Path $repoRoot 'scripts\csql-web.ps1'
|
||||
if (Test-Path $webPs1) { Copy-Item $webPs1 $stage }
|
||||
|
||||
Assert-Stage $stage
|
||||
|
||||
if (Test-Path $zip) { Remove-Item -Force $zip }
|
||||
if (Test-Path "$zip.sha256") { Remove-Item -Force "$zip.sha256" }
|
||||
Compress-Archive -Path (Join-Path $stage '*') -DestinationPath $zip -Force
|
||||
Remove-Item -Recurse -Force $stageRoot
|
||||
|
||||
$hash = Get-Sha256 $zip
|
||||
Set-Content -Path "$zip.sha256" -Value ("{0} {1}" -f $hash, "$name.zip") -NoNewline -Encoding ASCII
|
||||
Say " wrote $zip"
|
||||
Say " checksum $hash"
|
||||
|
||||
if (-not $NoVerify) {
|
||||
Test-Installed $name 'cli'
|
||||
Test-Installed $name 'web'
|
||||
}
|
||||
} finally {
|
||||
if (Test-Path $stageRoot) { Remove-Item -Recurse -Force $stageRoot }
|
||||
}
|
||||
|
||||
# ---------- 收尾 ----------
|
||||
Step 'Done'
|
||||
Get-ChildItem (Join-Path $OutDir "$name.zip*") | Select-Object Name, Length | Format-Table
|
||||
if ($Version) {
|
||||
Say ''
|
||||
Say "release $Version with (tag 必须是真实存在的 git tag):"
|
||||
Say " gh release create $Version `"$OutDir\*`" --generate-notes"
|
||||
} else {
|
||||
Say ''
|
||||
Say 'publish with (先用 -Version 指定 tag):'
|
||||
Say " gh release create <tag> `"$OutDir\*`" --generate-notes"
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
#!/bin/sh
|
||||
# sh scripts/package.sh # 本机平台,web + cli 合成一个包
|
||||
# sh scripts/package.sh --no-build # 用已有编译产物(快)
|
||||
# sh scripts/package.sh --no-verify # 跳过「装一遍试试」
|
||||
# sh scripts/package.sh --out-dir /tmp/rel # 换输出目录
|
||||
# sh scripts/package.sh --platform linux-x86_64 # 覆盖平台标签(默认按 uname 猜)
|
||||
# sh scripts/package.sh --version v0.1.0 # 只用于发布提示,不写进文件名
|
||||
#
|
||||
# 打完发出去(资源名必须跟这里一致,install.sh 才认得):
|
||||
# gh release create v0.1.0 releases/* --generate-notes
|
||||
#
|
||||
# 收尾会按「只要 cli」「只要 web」各装一遍:核对落盘文件,也核对没选的组件没被释放。
|
||||
# Windows 包用 scripts/package.ps1 打(这个脚本只出 tar.gz)。
|
||||
set -eu
|
||||
|
||||
# 本机打发行包:构建、装配、打 tar.gz、出 sha256,产物落 releases/,命名与 CI 一致。
|
||||
repo_root=$(cd "$(dirname "$0")/.." && pwd)
|
||||
out_dir=$repo_root/releases
|
||||
platform=''
|
||||
version=''
|
||||
build=yes
|
||||
verify=yes
|
||||
|
||||
# 打印一行输出。
|
||||
Say() { printf '%s\n' "$*"; }
|
||||
# 打印步骤标题。
|
||||
Step() { printf '\n== %s\n' "$*"; }
|
||||
# 报错到 stderr 并退出 1。
|
||||
Fail() {
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--platform)
|
||||
shift; [ "$#" -gt 0 ] || Fail '--platform needs a label like linux-x86_64'
|
||||
platform=$1; shift ;;
|
||||
--version)
|
||||
shift; [ "$#" -gt 0 ] || Fail '--version needs a tag like v0.1.0'
|
||||
version=$1; shift ;;
|
||||
--out-dir)
|
||||
shift; [ "$#" -gt 0 ] || Fail '--out-dir needs a path'
|
||||
out_dir=$1; shift ;;
|
||||
--no-build) build=no; shift ;;
|
||||
--no-verify) verify=no; shift ;;
|
||||
-h|--help) usage=yes; shift ;;
|
||||
*) Fail "unknown option: $1 (try --no-build, --out-dir DIR, --platform LABEL)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "${usage:-no}" = 'yes' ]; then
|
||||
sed -n '2,/^set -eu$/p' "$0" | sed -e 's/^# \{0,1\}//' -e '$d'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$platform" ]; then
|
||||
case "$(uname -s)" in
|
||||
Linux) os=linux ;;
|
||||
Darwin) os=macos ;;
|
||||
*) Fail "cannot tell the platform from $(uname -s); pass --platform linux-x86_64" ;;
|
||||
esac
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) arch=x86_64 ;;
|
||||
arm64|aarch64) arch=arm64 ;;
|
||||
*) Fail "cannot tell the architecture from $(uname -m); pass --platform $os-x86_64" ;;
|
||||
esac
|
||||
platform=$os-$arch
|
||||
fi
|
||||
|
||||
case "$platform" in
|
||||
windows-*) Fail 'the Windows package is scripts/package.ps1; this script only makes tar.gz archives' ;;
|
||||
esac
|
||||
|
||||
# 用可用的工具算文件 sha256。
|
||||
sha256_of() {
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
openssl dgst -sha256 "$1" | awk '{print $NF}'
|
||||
fi
|
||||
}
|
||||
|
||||
# 核对归档里必需的文件都在。
|
||||
check_stage() {
|
||||
stage=$1
|
||||
ls "$stage/bin/"libchusql_core_storage.* >/dev/null 2>&1 || Fail 'bin/libchusql_core_storage.* is missing from the package'
|
||||
[ -f "$stage/bin/csql" ] || Fail 'bin/csql is missing from the package'
|
||||
[ -f "$stage/bin/chusql-web" ] || Fail 'bin/chusql-web is missing from the package'
|
||||
[ -f "$stage/bin/chusql-server" ] || Fail 'bin/chusql-server is missing from the package'
|
||||
[ -f "$stage/static/index.html" ] || Fail 'static/index.html is missing from the package'
|
||||
[ -f "$stage/csql-web.sh" ] || Fail 'csql-web.sh is missing from the package'
|
||||
[ -f "$stage/scripts/chusql.toml" ] || Fail 'scripts/chusql.toml is missing from the package'
|
||||
[ -f "$stage/install.sh" ] || Fail 'install.sh is missing from the package'
|
||||
[ -f "$stage/install.ps1" ] || Fail 'install.ps1 is missing from the package'
|
||||
# bin 里带 chusql 名字的文件只能是已知产物;改名前的残留或别的垃圾都算装配错误
|
||||
for f in "$stage"/bin/chusql* "$stage"/bin/libchusql*; do
|
||||
[ -e "$f" ] || continue
|
||||
case "$(basename "$f")" in
|
||||
libchusql_core_storage.so|libchusql_core_storage.dylib|libchusql_core_storage.dll|chusql-server|chusql-web) ;;
|
||||
*) Fail "unexpected artifact in bin/: $(basename "$f")" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# 按 cli / web 各装一遍,核对落地文件。
|
||||
verify_package() {
|
||||
name=$1
|
||||
archive="$out_dir/$name.tar.gz"
|
||||
for comp in cli web; do
|
||||
vtmp=$(mktemp -d)
|
||||
mkdir -p "$vtmp/pkg" "$vtmp/home" "$vtmp/opt"
|
||||
if ! tar -xzf "$archive" -C "$vtmp/pkg"; then
|
||||
rm -rf "$vtmp"
|
||||
Fail "$name: the archive does not unpack"
|
||||
fi
|
||||
if ! ( cd "$vtmp/pkg" && HOME="$vtmp/home" sh install.sh --component "$comp" \
|
||||
--install-dir "$vtmp/opt" --data-dir "$vtmp/data" --user root --password package-check ) \
|
||||
>"$vtmp/log" 2>&1; then
|
||||
sed -n '1,120p' "$vtmp/log" >&2
|
||||
rm -rf "$vtmp"
|
||||
Fail "$name: installing the $comp part from the package failed (log above)"
|
||||
fi
|
||||
ok=yes
|
||||
ls "$vtmp/opt/bin/"libchusql_core_storage.* >/dev/null 2>&1 || ok=no
|
||||
[ -f "$vtmp/opt/bin/chusql-server" ] || ok=no
|
||||
[ -f "$vtmp/opt/init.sql" ] || ok=no
|
||||
[ -f "$vtmp/home/.config/ChuSQL/chusql.toml" ] || ok=no
|
||||
grep -q "data_dir = \"$vtmp/data\"" "$vtmp/home/.config/ChuSQL/chusql.toml" || ok=no
|
||||
if [ "$comp" = web ]; then
|
||||
[ -f "$vtmp/opt/bin/chusql-web" ] || ok=no
|
||||
[ -f "$vtmp/opt/static/index.html" ] || ok=no
|
||||
[ -f "$vtmp/opt/csql-web.sh" ] || ok=no
|
||||
[ -f "$vtmp/opt/csql-web" ] || ok=no
|
||||
# 没要 cli,就不许把 csql 释放出去
|
||||
if [ -f "$vtmp/opt/bin/csql" ] || [ -f "$vtmp/opt/csql" ]; then ok=no; fi
|
||||
else
|
||||
[ -f "$vtmp/opt/bin/csql" ] || ok=no
|
||||
[ -f "$vtmp/opt/csql" ] || ok=no
|
||||
# 没要 web,就不许把 chusql-web / static / 启动器释放出去
|
||||
if [ -f "$vtmp/opt/bin/chusql-web" ] || [ -e "$vtmp/opt/static" ] || [ -f "$vtmp/opt/csql-web" ]; then
|
||||
ok=no
|
||||
fi
|
||||
fi
|
||||
if [ "$ok" != yes ]; then
|
||||
sed -n '1,120p' "$vtmp/log" >&2
|
||||
rm -rf "$vtmp"
|
||||
Fail "$name: the $comp part is not what the installer promises (log above)"
|
||||
fi
|
||||
rm -rf "$vtmp"
|
||||
Say " ok $name installs the $comp part, and only the $comp part"
|
||||
done
|
||||
}
|
||||
|
||||
# ---------- 构建 ----------
|
||||
if [ "$build" = yes ]; then
|
||||
Step 'Building storage (Rust)'
|
||||
( cd "$repo_root/chusql-core/storage" && cargo build --release )
|
||||
Step 'Building the web front end (Haskell)'
|
||||
( cd "$repo_root/chusql-cli" && stack build --fast chusql-web:exe:chusql-web )
|
||||
Step 'Building the command line client (Haskell)'
|
||||
( cd "$repo_root/chusql-cli" && stack build --fast chusql-cli:exe:csql )
|
||||
Step 'Building the TCP database server (Haskell)'
|
||||
( cd "$repo_root/chusql-cli" && stack build --fast chusql-server:exe:chusql-server )
|
||||
fi
|
||||
|
||||
# ---------- 装配 + 打归档 ----------
|
||||
name="chusql-$platform"
|
||||
stage="$out_dir/.stage/$name"
|
||||
archive="$out_dir/$name.tar.gz"
|
||||
mkdir -p "$out_dir"
|
||||
trap 'rm -rf "$out_dir/.stage"' EXIT INT TERM
|
||||
|
||||
Step "Packing $name"
|
||||
rm -rf "$out_dir/.stage"
|
||||
mkdir -p "$stage/bin" "$stage/scripts"
|
||||
|
||||
cp "$repo_root/chusql-core/storage/target/release/"libchusql_core_storage.* "$stage/bin/" \
|
||||
|| Fail "chusql-core/storage/target/release/libchusql_core_storage.* is not built (run without --no-build)"
|
||||
|
||||
stack_root=$(cd "$repo_root/chusql-cli" && stack path --local-install-root | tail -n 1 | tr -d '\r')
|
||||
for front_name in chusql-web chusql-server csql; do
|
||||
front="$stack_root/bin/$front_name"
|
||||
if [ ! -f "$front" ]; then
|
||||
front=$(find "$repo_root/chusql-web/.stack-work" "$repo_root/chusql-cli/.stack-work" "$repo_root/chusql-server/.stack-work" \
|
||||
-name "$front_name" -type f 2>/dev/null | head -n 1 || true)
|
||||
fi
|
||||
[ -n "$front" ] && [ -f "$front" ] || Fail "$front_name is not built (run without --no-build)"
|
||||
cp "$front" "$stage/bin/"
|
||||
done
|
||||
|
||||
# Haskell 一般静态链接,靶目录里真有共享库就一起带上;改名前的旧库(libchusql_storage.*)不能进包
|
||||
for lib in "$repo_root"/chusql-core/storage/target/release/*.so "$repo_root"/chusql-core/storage/target/release/*.dylib; do
|
||||
[ -f "$lib" ] || continue
|
||||
case "$(basename "$lib")" in
|
||||
libchusql_storage.*|libchusql_storage-*) continue ;;
|
||||
esac
|
||||
cp "$lib" "$stage/bin/"
|
||||
done
|
||||
|
||||
cp "$repo_root/scripts/chusql.toml" "$repo_root/scripts/init.sql" "$stage/scripts/"
|
||||
cp "$repo_root/scripts/install.sh" "$repo_root/scripts/install.ps1" "$stage/"
|
||||
mkdir -p "$stage/static"
|
||||
cp -R "$repo_root"/chusql-web/static/. "$stage/static/"
|
||||
cp "$repo_root/scripts/csql-web.sh" "$stage/"
|
||||
if [ -f "$repo_root/scripts/csql-web.ps1" ]; then cp "$repo_root/scripts/csql-web.ps1" "$stage/"; fi
|
||||
chmod +x "$stage/install.sh" "$stage/csql-web.sh"
|
||||
|
||||
check_stage "$stage"
|
||||
|
||||
rm -f "$archive" "$archive.sha256"
|
||||
( cd "$stage" && tar -czf "$archive" . )
|
||||
rm -rf "$out_dir/.stage"
|
||||
hash=$(sha256_of "$archive")
|
||||
printf '%s %s\n' "$hash" "$name.tar.gz" > "$archive.sha256"
|
||||
Say " wrote $archive"
|
||||
Say " checksum $hash"
|
||||
|
||||
if [ "$verify" = yes ]; then
|
||||
verify_package "$name"
|
||||
fi
|
||||
|
||||
# ---------- 收尾 ----------
|
||||
Step 'Done'
|
||||
ls -l "$out_dir/$name.tar.gz" "$out_dir/$name.tar.gz.sha256"
|
||||
if [ -n "$version" ]; then
|
||||
Say ''
|
||||
Say "release $version with (tag 必须是真实存在的 git tag):"
|
||||
Say " gh release create $version \"$out_dir\"/* --generate-notes"
|
||||
else
|
||||
Say ''
|
||||
Say 'publish with (先用 --version 指定 tag):'
|
||||
Say " gh release create <tag> \"$out_dir\"/* --generate-notes"
|
||||
fi
|
||||
Reference in new issue
Block a user