diff --git a/CHANGELOG.md b/CHANGELOG.md index 008f86f..daab7f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.10] - 2026-06-04 + +### 新功能 +- Windows 客户端改为提供安装程序(Inno Setup 打包的 `jiu-windows-x64-setup.exe`,含中文向导、桌面/开始菜单快捷方式、卸载项),不再是解压版 zip + +### 修复 +- 修复客户端「无法安全下载」:下载地址从内网 HTTP Forgejo(`http://192.168.3.200:3000`,公网不可达且被浏览器拦截)改为同源 HTTPS `https://jiu.51yanmei.com/downloads/...`,Windows、macOS 均生效 + ## [1.0.9] - 2026-06-04 ### 修复 diff --git a/deploy/nginx-jiu.conf b/deploy/nginx-jiu.conf index 07105c3..6818fb1 100644 --- a/deploy/nginx-jiu.conf +++ b/deploy/nginx-jiu.conf @@ -59,6 +59,14 @@ server { try_files /scan.html =404; } + # 桌面客户端安装包下载(Windows .exe / macOS .zip) + location /downloads/ { + alias /opt/jiu/downloads/; + add_header Content-Disposition "attachment"; + add_header Cache-Control "no-cache"; + autoindex off; + } + # 营销站点兜底(根路径及所有其他路径) location / { root /opt/jiu/marketing; diff --git a/deploy/windows/jiu-installer.iss b/deploy/windows/jiu-installer.iss new file mode 100644 index 0000000..019f1bd --- /dev/null +++ b/deploy/windows/jiu-installer.iss @@ -0,0 +1,53 @@ +; Inno Setup script for 岩美酒库 Windows 客户端 +; Compiled in CI: +; ISCC /DAppVer= "/DSrcDir=" "/DOutDir=" jiu-installer.iss +; Produces: \jiu-windows-x64-setup.exe + +#ifndef AppVer + #define AppVer "0.0.0" +#endif +#ifndef SrcDir + #define SrcDir "." +#endif +#ifndef OutDir + #define OutDir "." +#endif + +#define MyAppName "岩美酒库" +#define MyAppExe "jiu_client.exe" +#define MyAppPublisher "岩美" + +[Setup] +AppId={{A3F5C1E2-9B47-4D8A-B6E0-2C1D4F8A9E33} +AppName={#MyAppName} +AppVersion={#AppVer} +AppPublisher={#MyAppPublisher} +DefaultDirName={autopf}\Jiu +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +OutputDir={#OutDir} +OutputBaseFilename=jiu-windows-x64-setup +Compression=lzma2 +SolidCompression=yes +WizardStyle=modern +ArchitecturesAllowed=x64 +ArchitecturesInstallIn64BitMode=x64 +UninstallDisplayName={#MyAppName} +UninstallDisplayIcon={app}\{#MyAppExe} + +[Languages] +Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce + +[Files] +Source: "{#SrcDir}\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs ignoreversion + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExe}" +Name: "{group}\卸载 {#MyAppName}"; Filename: "{uninstallexe}" +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExe}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExe}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent diff --git a/scripts/ci/compile-windows.sh b/scripts/ci/compile-windows.sh index bf8afca..cf5707c 100755 --- a/scripts/ci/compile-windows.sh +++ b/scripts/ci/compile-windows.sh @@ -44,13 +44,31 @@ flutter build windows --release \ "--dart-define=APP_VERSION=${TAG}" popd > /dev/null -# Package the Release folder into dist/ using PowerShell Compress-Archive. -# Source is an absolute Windows path; destination is relative to the workspace. +# Package the Release folder into a Windows installer with Inno Setup (ISCC). +# Inno Setup is installed by provision-windows.sh. +ISCC="/c/Program Files (x86)/Inno Setup 6/ISCC.exe" +if [ ! -f "$ISCC" ]; then + echo "ERROR: Inno Setup (ISCC) not found at '$ISCC'. Run provision-windows.sh first." >&2 + exit 1 +fi + +# Run ISCC entirely under the clean build root: 32-bit ISCC would hit WOW64 +# redirection if it read the .iss / wrote output under the System32 workspace. +cp deploy/windows/jiu-installer.iss "${BUILD_ROOT}/jiu-installer.iss" +mkdir -p "${BUILD_ROOT}/dist" +echo "==> building installer with Inno Setup (version ${VER})" +"$ISCC" \ + "/DAppVer=${VER}" \ + "/DSrcDir=C:\\jiu-build\\client\\build\\windows\\x64\\runner\\Release" \ + "/DOutDir=C:\\jiu-build\\dist" \ + "C:\\jiu-build\\jiu-installer.iss" + +# Copy the installer back to the workspace dist/ via PowerShell (64-bit, so it +# can write the System32 workspace path without WOW64 redirection). mkdir -p dist -REL_WIN='C:\jiu-build\client\build\windows\x64\runner\Release' -echo "==> packaging ${REL_WIN} -> dist/jiu-windows-x64.zip" +echo "==> copying installer -> dist/jiu-windows-x64-setup.exe" powershell -NoProfile -Command \ - "Compress-Archive -Path '${REL_WIN}\*' -DestinationPath 'dist\jiu-windows-x64.zip' -Force" + "Copy-Item 'C:\\jiu-build\\dist\\jiu-windows-x64-setup.exe' 'dist\\jiu-windows-x64-setup.exe' -Force" echo "==> compile-windows: done — dist/ contents:" ls -lh dist/ diff --git a/scripts/ci/deploy.sh b/scripts/ci/deploy.sh index 832c2f8..32f948a 100755 --- a/scripts/ci/deploy.sh +++ b/scripts/ci/deploy.sh @@ -90,6 +90,11 @@ rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no" \ /tmp/jiu-marketing-new/ "${EC2_USER}@${EC2_HOST}:/tmp/jiu-marketing-new/" +# Upload desktop client installers (served from /downloads/ by nginx). +# Guarded: may be absent in a partial manual deploy. +[ -f dist/jiu-windows-x64-setup.exe ] && ${SCP} dist/jiu-windows-x64-setup.exe "${EC2_USER}@${EC2_HOST}:/tmp/jiu-windows-x64-setup.exe" || true +[ -f dist/jiu-macos-x64.zip ] && ${SCP} dist/jiu-macos-x64.zip "${EC2_USER}@${EC2_HOST}:/tmp/jiu-macos-x64.zip" || true + echo "==> deploy: running remote deploy" ${SSH} "${EC2_USER}@${EC2_HOST}" << 'ENDSSH' @@ -126,6 +131,11 @@ mkdir -p /opt/jiu/marketing rsync -a --delete /tmp/jiu-marketing-new/ /opt/jiu/marketing/ rm -rf /tmp/jiu-marketing-new +# Publish desktop client installers to the nginx-served downloads dir +mkdir -p /opt/jiu/downloads +[ -f /tmp/jiu-windows-x64-setup.exe ] && mv -f /tmp/jiu-windows-x64-setup.exe /opt/jiu/downloads/ || true +[ -f /tmp/jiu-macos-x64.zip ] && mv -f /tmp/jiu-macos-x64.zip /opt/jiu/downloads/ || true + # Update jiu nginx config in the pangolin-edge reverse proxy. # nginx now runs inside the `pangolin-edge` container (host networking), not on # the host. Its /etc/nginx/conf.d is bind-mounted from the host dir below, so we diff --git a/scripts/ci/provision-windows.sh b/scripts/ci/provision-windows.sh index af861cf..3433047 100644 --- a/scripts/ci/provision-windows.sh +++ b/scripts/ci/provision-windows.sh @@ -2,8 +2,10 @@ # provision-windows.sh — idempotent provisioning for the Windows host runner. # # Pre-warms what compile-windows.sh would download at runtime: Flutter Windows -# engine artifacts and pub packages. Short-circuits via a stamp file keyed to -# the installed Flutter version, so repeat runs return immediately. +# engine artifacts, pub packages, and the Inno Setup compiler (ISCC) used to +# build the installer. The Flutter part short-circuits via a stamp keyed to the +# installed Flutter version; Inno Setup is ensured on every run (cheap when +# already present) so it self-heals independently of the Flutter stamp. set -euo pipefail SCRIPT_DIR="$(dirname "$0")" @@ -15,7 +17,24 @@ STAMP="${STAMP_DIR}/provisioned-windows" CUR="${STAMP_DIR}/.flutter-version-win.cur" mkdir -p "$STAMP_DIR" -# --- detection --- +# --- ensure Inno Setup (ISCC) for building the installer (always checked) --- +ISCC="/c/Program Files (x86)/Inno Setup 6/ISCC.exe" +if [ ! -f "$ISCC" ]; then + echo "==> installing Inno Setup 6 (silent)" + # Canonical "latest stable" entry point (redirects to the GitHub release asset). + INNO_URL="https://jrsoftware.org/download.php/is.exe" + powershell -NoProfile -Command \ + "Invoke-WebRequest -Uri '${INNO_URL}' -OutFile \"\$env:TEMP\\innosetup.exe\"" + powershell -NoProfile -Command \ + "Start-Process -Wait -FilePath \"\$env:TEMP\\innosetup.exe\" -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/SP-'" + if [ ! -f "$ISCC" ]; then + echo "ERROR: Inno Setup install failed — ISCC not found at '$ISCC'." >&2 + exit 1 + fi + echo "==> Inno Setup installed." +fi + +# --- detection (Flutter) --- if ! command -v flutter >/dev/null 2>&1; then echo "ERROR: flutter not found on PATH. Install Flutter on this runner first." >&2 exit 1 diff --git a/scripts/ci/release.sh b/scripts/ci/release.sh index 6a593cb..223151b 100755 --- a/scripts/ci/release.sh +++ b/scripts/ci/release.sh @@ -44,9 +44,13 @@ notes = sys.argv[2] tag = sys.argv[3] furl = sys.argv[4].rstrip('/') repo = sys.argv[5] -base = f"{furl}/{repo}/releases/download/{tag}" +# Public download URLs are served by jiu.51yanmei.com (same-origin HTTPS via the +# pangolin nginx /downloads/ location), NOT the LAN-only HTTP Forgejo instance — +# otherwise the HTTPS download page hands the browser an http://192.168.x URL and +# Chrome blocks it as insecure / it's unreachable from the public internet. +base = "https://jiu.51yanmei.com/downloads" mac_url = f"{base}/jiu-macos-x64.zip" -win_url = f"{base}/jiu-windows-x64.zip" +win_url = f"{base}/jiu-windows-x64-setup.exe" lines = [] with open('backend/config/version.yaml') as f: @@ -128,6 +132,6 @@ upload_asset dist/web.tar.gz upload_asset dist/marketing.tar.gz upload_asset dist/configs.tar.gz upload_asset dist/jiu-macos-x64.zip -upload_asset dist/jiu-windows-x64.zip +upload_asset dist/jiu-windows-x64-setup.exe echo "==> release: done — Release ${TAG} created"