Files
ykxmz/installer/ykxmz.iss
T
xmz7891 566d9e384f 阶段1+2:恢复 HTTP/2 加速 + 重做官网
阶段1 网络加速
- 发现隧道在跑 QUIC(ExecStart 丢了 --protocol http2),
  实测 QUIC 可用率 48% vs HTTP/2 80%+,是重大性能回退。
  恢复后公网延迟从 4141ms 降到 690ms(快 6 倍),可用率 92%。
- 优选 IP 结论:无效。hosts 覆盖确实生效了(日志里连的就是指定 IP),
  但落点仍是 lax —— 运营商把所有 Cloudflare anycast 都路由到美西。
  已清掉这段无效配置。
- BBR 不可用:设备是自定义内核 6.4.0-rc4-jsbsbxjxh66,
  没编 tcp_bbr 模块也没配置项。改用 cubic + 调优:
  TCP 缓冲 4MB、tcp_fastopen=3、tcp_slow_start_after_idle=0
  (长轮询场景关键)、tcp_tw_reuse=1。

阶段2 官网重做
- 拆出独立模块 relay/landing.py(relay.py 已两千多行,
  再塞 HTML 没法看)
- 信息结构参考成熟产品站:吸顶导航 / Hero / 九宫格功能 /
  三步上手 / 下载 / FAQ / 页脚
- 保持白色简洁:底白、浅灰面板、细边框、大量留白
- logo 用内联 SVG(ICO 放 img 里实测是破图)
- 管理入口从官网移除(管理不对外开放)

过程中被自己的测试抓到两个 bug:
- 模块叫 site.py 会和 Python 标准库重名,import site 拿到的是标准库
- _logo() 里把 % 格式化和字符串拼接混用,直接抛 TypeError
2026-10-06 23:53:19 +08:00

256 lines
9.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
; ============================================================================
; ykxmz 教室电脑手表遥控 · 被控端安装包
;
; 用 Inno Setup 6 编译:
; "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer\ykxmz.iss
;
; ── 设计说明 ──
;
; 尽量用 Inno 自带的机制,不手搓:
; * 许可协议页 -> LicenseFile(三份文档合并成一份,点"我接受"才能继续)
; * 开机自启 -> [Tasks](标准勾选框)
; * 装完启动 -> [Run] postinstall(标准勾选框)
; * 安装目录 -> DisableDirPage=no(让用户自己选,默认 Program Files)
; * 卸载 -> [UninstallRun] 调被控端自己的 --uninstall
; 只有"填 6 位配对码"这一页 Inno 没有现成的,用标准的
; CreateInputQueryPage —— 那是 Inno 的官方 API,不算自己造轮子。
;
; ── 用户确定的设置 ──
; 安装位置 默认 C:\Program Files\ykxmz,安装时可改
; 数据目录 能写 exe 旁就写,不能就转 ProgramData(程序内部处理)
; 三份文档 放安装包里,统一接受才能下一步
; 配对码 安装向导里填 6 位
; 快捷方式 不建开始菜单、不建桌面
; 开机自启 默认勾上
; 装完启动 立即静默启动
; 卸载 一并删掉,什么都不留
; ============================================================================
#define MyAppName "ykxmz"
#define MyAppVersion "1.7.1"
#define MyAppPublisher "小麦子"
#define MyAppExeName "ykxmz.exe"
[Setup]
AppId={{8F3C1D42-7A5E-4B91-9C6D-2E8F4A1B7C30}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
VersionInfoVersion={#MyAppVersion}
; 默认装到 Program Files\ykxmz,但安装向导里有一页让用户自己改
DefaultDirName={autopf}\ykxmz
DisableDirPage=no
; 三份文档合并成一份许可页:看完点「我接受」才能继续
LicenseFile=license-combined.txt
; 不建开始菜单组(用户明确不要)
DisableProgramGroupPage=yes
AllowNoIcons=yes
; 装到 Program Files 和改防火墙都要管理员
PrivilegesRequired=admin
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
OutputDir=..\dist
OutputBaseFilename=ykxmz-setup-{#MyAppVersion}
SetupIconFile=..\windows\app.ico
UninstallDisplayIcon={app}\{#MyAppExeName}
UninstallDisplayName={#MyAppName}
Compression=lzma2/max
SolidCompression=yes
WizardStyle=modern
ShowLanguageDialog=no
DisableWelcomePage=no
[Languages]
Name: "chinese"; MessagesFile: "compiler:Default.isl"
[Tasks]
; 用户要的:默认勾上开机自启(取消勾选也允许)
Name: "autostart"; Description: "开机自动启动(推荐给教室电脑)"; \
GroupDescription: "启动选项:"; Flags: checkedonce
[Files]
; 被控端整个目录(ykxmz.exe + _internal)
Source: "..\dist_exe\ykxmz\*"; DestDir: "{app}"; \
Flags: recursesubdirs createallsubdirs ignoreversion
; 三份文档也放一份到安装目录,装完还能翻
Source: "..\docs\LICENSE.txt"; DestDir: "{app}\docs"; Flags: ignoreversion
Source: "..\docs\BEFORE-INSTALL.txt"; DestDir: "{app}\docs"; Flags: ignoreversion
Source: "..\docs\AFTER-INSTALL.txt"; DestDir: "{app}\docs"; Flags: ignoreversion
[Dirs]
; ⚠ 这一节很关键 —— 不配的话装完一运行就报 WinError 5 拒绝访问。
;
; 被控端的配置存在 %ProgramData%\ykxmz\data 下(exe 旁边写不进去时的回退)。
; 安装包是管理员跑的,它建的目录默认只有管理员能改;
; 之后普通用户(老师的账号)跑被控端时:
; 能新建文件,但覆盖不了管理员建的那份 settings.json
; 结果就是"装完打开报权限错误"。
;
; Permissions: users-modify 把所有用户的写权限打开,才是真的能用。
; 注意末尾不带反斜杠,否则 Inno 会给它单独建一层同名子目录。
Name: "{commonappdata}\ykxmz"; Permissions: users-modify
Name: "{commonappdata}\ykxmz\data"; Permissions: users-modify
[InstallDelete]
; 早期版本把配置写在程序目录下的 data\ 里。
; 现在数据统一放 ProgramData(见 config.py 的 data_dir),
; 这里把旧的那份清掉 —— 留着的话,两份配置并存,
; 以后排查问题会被误导("我明明改了配对码怎么没变")。
Type: filesandordirs; Name: "{app}\data"
[Run]
; 装完立即静默启动(标准 postinstall 勾选框,默认勾上)
Filename: "{app}\{#MyAppExeName}"; Parameters: "--silent"; \
Description: "立即启动(后台运行,不显示窗口)"; \
Flags: nowait postinstall skipifsilent
; 让用户看一眼装完须知
Filename: "{app}\docs\AFTER-INSTALL.txt"; \
Description: "打开「安装后须知」"; \
Flags: postinstall shellexec unchecked skipifsilent
[UninstallRun]
; 卸载前先让被控端自己收尾:停进程、删开机自启、清数据目录。
; 这样"卸载后什么都不留"才是真的干净。
Filename: "{app}\{#MyAppExeName}"; Parameters: "--uninstall"; \
Flags: runhidden waituntilterminated; RunOnceId: "StopAgent"
[UninstallDelete]
; 被控端会自己删数据目录,这里兜底再清一遍安装目录里的残留
Type: filesandordirs; Name: "{app}\data"
Type: filesandordirs; Name: "{app}\docs"
Type: filesandordirs; Name: "{app}\_internal"
[Code]
var
CodePage: TInputQueryWizardPage;
ForcedCode: String;
{ 把字符串里的非数字滤掉。 }
function OnlyDigits(const raw: String): String;
var
i: Integer;
ch: Char;
begin
Result := '';
for i := 1 to Length(raw) do
begin
ch := raw[i];
if (ch >= '0') and (ch <= '9') then
Result := Result + ch;
end;
end;
{ 配对码那一页。Inno 没有现成的输入页,用官方的 CreateInputQueryPage。 }
procedure InitializeWizard();
begin
{ 静默部署可以带 /CODE=123456 直接把码传进来(电教批量装用这个)。
注意:Inno 的 [Code] 段连**注释里的花括号**都会当常量去解析,
所以这里不能直接写 param 那种写法,否则编译器报语法错误。 }
ForcedCode := OnlyDigits(ExpandConstant('{param:CODE|}'));
CodePage := CreateInputQueryPage(wpSelectTasks,
'配对码',
'给这台电脑定一个 6 位配对码',
'手表上要输这一串数字才能连上这台电脑。自己定个好记的,' +
'比如 789113。装完之后也可以在被控端窗口里改。');
CodePage.Add('6 位配对码:', False);
if ForcedCode <> '' then
CodePage.Values[0] := ForcedCode;
end;
{ 校验:必须是 6 位数字。
注意 —— **静默模式下这一页不会显示**,值是空的,
这时候绝不能拦(否则 /VERYSILENT 安装必然失败,我第一版就栽在这)。 }
function NextButtonClick(CurPageID: Integer): Boolean;
var
raw: String;
digits: String;
begin
Result := True;
if CurPageID <> CodePage.ID then
Exit;
if WizardSilent then
Exit;
raw := CodePage.Values[0];
digits := OnlyDigits(raw);
if (Length(digits) <> 6) and (raw <> '') then
begin
MsgBox('配对码要 6 位数字。' + #13#10 + #13#10 +
'你现在填的是:' + raw + #13#10 +
'(过滤掉非数字后剩 ' + IntToStr(Length(digits)) + ' 位)' + #13#10 + #13#10 +
'重新填一下,或者直接点「取消」退出安装。',
mbError, MB_OK);
Result := False;
Exit;
end;
{ 规范化,后面 [Run] 阶段用得到 }
CodePage.Values[0] := digits;
end;
{ 最终用的配对码:命令行 /CODE 优先,其次是向导里填的。
两个都没有就返回空串 —— 那就跳过 --code,让被控端自己生成一个。 }
function EffectiveCode(): String;
begin
Result := ForcedCode;
if Result = '' then
Result := OnlyDigits(CodePage.Values[0]);
if Length(Result) <> 6 then
Result := '';
end;
{ 把向导里收集的东西交给被控端。
注意这里**不能带 --silent** —— 那是常驻守护进程,永远不返回,
用 ewWaitUntilTerminated 等它会把安装程序挂死(第一版就栽在这)。
写配置和启动服务分成两次调用:
--code / --autostart 一次性命令,秒退
--silent 常驻,交给 [Run] 段用 nowait 启动 }
procedure CurStepChanged(CurStep: TSetupStep);
var
ResultCode: Integer;
Params: String;
Code: String;
begin
if CurStep <> ssPostInstall then
Exit;
Code := EffectiveCode();
if Code <> '' then
begin
Params := '--code ' + Code;
if not Exec(ExpandConstant('{app}\{#MyAppExeName}'), Params,
ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated,
ResultCode) then
begin
if not WizardSilent then
MsgBox('配对码写入失败(错误码 ' + IntToStr(ResultCode) + ')。' + #13#10 +
'装完之后双击程序图标,重新设一次也行。',
mbInformation, MB_OK);
end;
end;
{ 开机自启:单独调一次,也是一次性命令 }
if WizardIsTaskSelected('autostart') then
Exec(ExpandConstant('{app}\{#MyAppExeName}'), '--autostart on',
ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
{ 卸载前先停掉正在跑的进程,不然文件删不干净。 }
function InitializeUninstall(): Boolean;
var
ResultCode: Integer;
begin
Result := True;
Exec(ExpandConstant('{app}\{#MyAppExeName}'), '--uninstall',
ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;