Zapic's Blog
type: channel
link: https://blog.zapic.moe/
description: Just like a pigeon,and gu gu gu!
items: 智能家居改造中
items: 配置容器中的 Gitea SSH 与主机 SSH 共存
items: OPAQUE: 最好的密码不会离开你的设备
items: 较新的Electron APP在Adreno 640 + Freedreno驱动下渲染花屏
items: Linux Wireguard + nftable 实现内网穿透并保留源IP (DNAT)
items: Longhorn 与 Multipath 冲突导致卷无法挂载
items: 超次元游戏 海王星：重生1/2 (Neptunia Rebirth 1/2) Xbox 手柄图标MOD
items: 小米平板5 + Linux
items: 装饰器, ESLint与代码检查
items: PHP + Vite(with HMR)开发: 从Matecho开发过程说起

智能家居改造中
type: item
date: 2026-08-04T18:53:00+08:00
description: 现状只改卧室，卧室两个灯，一台空调，一个电脑，一个风扇，已有一个米家温湿度计2.选型Apple的电子产品用得多，但架不住米家能接入的东西多且便宜，最后选择了米家。排除了多模网关（300元左右），没有ZigBee需求，离线自动化能力弱。排除了Play音箱增强版（带红外，90元左右），盲网关，没有离线自动化能力，虽然可以语音唤醒，但我不喜欢说话。盲网关不能直接向设备发送指令，最后一步到位选择中枢网关。名称用途数量总价（元）米家中枢网关2网关1273米家空调伴侣2用于控制空调160第三方米家通断器（无灵动）改造吸顶灯和风扇231智能插座（非计量款）控制电蚊香116智能插座（计量款）监测电脑耗电量128电脑PCIe开机卡远程开关电脑主机112第三方接入米家圆按钮手边的场景触发器22586尺寸开关型mesh随意贴替换原有墙面开关238调光智能小灯替换电脑背投USB调光灯1341.8米半面灯珠落地长条形细杆氛围灯卧室氛围照明190合计 598.34改造大多数改造都简单。电似猛虎，电死二百五。若无专业经验，请不要拆开改造任何电路电器，本文描述的强电相关内容仅供参考。空调空调伴侣2安装配置后即用，但是唯一的缺点是不能开关状态灯。想了一下，并没有太多场景需要关灯，只有睡眠场景需要，于是单独录一套睡眠场景的固定设置遥控码。在米家的安卓版（苹果不知道为什么不可用），点击添加设备，搜索自定义遥控，自定义遥控选择红外发射设备为空调伴侣2，该型号虽然没有宣传支持学习功能，但能够通过这种方式来学习红外码。空调的红外码通常包括了空调的完整运行状态，而不是独立的按键码，这能保证只要空调收到信号，就会与遥控器上的状态完全一致。因此我们在设置完成后想要的基础参数后，再让空调伴侣2进入学习状态，按一下灯光键，让遥控器发送一套我们想要的预设+灯光关闭，之后想要调用这套预设时，只需要点一下自定义遥控上的按钮即可。落地扇落地扇的改造比较大胆，原本是不支持智能控制的落地扇，我的计划原本是将一个智能通断器藏进风扇的外壳内，结果通断器太大了藏不进外壳内，只能做成外置器官。落地扇的控制电路都在能摆动的机头里，倒也不复杂。直接拆开外壳，将通断器固定在外壳上，把线缆通过散热孔穿入外壳内部。使用准备好的延长线，将原本零火分别延长（共计4根线），做好绝缘后，将通断器固定在外壳上。然后剪断机身内部原本的输入线，将一侧接到通断器输入上通往插头，另一侧接到输出上通往电机，通断器有方向，接反了会导致无法正常工作，并做好绝缘。接线完成后，由于机头可以摆动，内部线缆可能会随着摆动导致接线处松动，安装时考虑使用可靠的接线端子，将其夹扁使得尽可能牢固，然后将接线处尽可能固定住，不参与机头的摆动，安装时适当留出完整的线，使其可以活动。安装后，通过手电筒观察外壳内部，看看是否有活动机械结构会跟线发生碰撞，是否有接线处会被带着乱动，尽可能避免这些情况。灯与其他原先的吸顶灯是那种带线夹的灯盘式安装的LED灯，只需要简单的将通断器接入到两端，合上装饰盖即可。其他灯都是插电即用的。电蚊香使用一个智能插座来控制，插上即可。电脑开关机卡是PCIe的，开机卡能从PCIe接口上读取电脑的状态，需要将原本的开机跳线接到开机卡上，再从开机卡上接接到主板上。其他的灯插上电就能用。快捷操作按钮智能化改造完成后也不能只在手机上能点，在没有手机时也要能用。墙壁开关原本是118型双槽开关，单刀双控型，即有两个开关控制一个灯。不改造强电电路，保留原本装饰板，直接物理翘掉开关的装饰盖（那个能被按下去的活动跷跷板塑料件），让开关表面平整后直接把新的86型点动开关贴上去。短按开关吸顶灯长按关闭屋内所有电器（除了电脑）剩余一个墙壁开关未用，留着未来备用。电脑桌电脑桌在角落背面贴了一个圆形按钮。短按开关屏幕背面的氛围灯双击打开氛围灯并打开电脑长按开关房间内的吸顶灯没有设置关闭电脑的路径，防止误操作。电脑桌的插座接入了一个能计算功率的智能插座，通过功率触发一些自动化。功率低于20瓦时，关闭氛围灯功率高于40瓦时，打开氛围灯由于我有一个Mac和一台PC，没办法简单的通过判断PC是否开机来判断电脑是否在使用，只能通过判断插座功率，看看显示器是否打开。床头粘贴安装了一个圆形按钮。短按开关吸顶灯双击开关落地扇长按进入睡眠场景：关闭所有灯，关闭所有风扇，空调调节至睡眠状态（禁用状态指示灯，风速调至最低，风向调节至不吹人）HomeKit使用Home Assistant，安装官方的米家集成（其他第三方不支持本地连接到中枢网关，只有官方维护的那个集成支持），导入所有电器后，再通过Home Assistant的HomeKit Bridge集成，接入到HomeKit中。自动化仍然主要在米家中进行，手机上桥接到HomeKit主要是为了在控制中心添加快捷按钮和接入Siri，…

配置容器中的 Gitea SSH 与主机 SSH 共存
type: item
date: 2026-04-30T10:58:00+08:00
description: 默认情况下，想要启用Gitea的SSH代码拉取，必须要修改SSH端口或者禁用主机上的SSH。若想要直接使用宿主机上的SSHD，需要进行一些配置。Method A: 通过SSH透传建议使用在宿主机上创建git用户切换到新用户，使用id命令确定git用户的UID和GID，并将容器中运行的Gitea设置为相同的UID和GID（通过容器的USER_UID和USER_GID环境变量）将容器的SSH映射到主机上，检查端口是否可以正常访问，例如映射到2222端口: ssh git@127.0.0.1 -p2222，应当得到鉴权失败之类的信息，而不是等待许久之后报错无法连接使用git用户创建一对SSH密钥用于连接到Gitea：ssh-keygen将公钥写回到authorized_keys：cat ~/.ssh/id_*.pub >> ~/.ssh/authorized_keys将/home/git/.ssh 映射到容器中Gitea写入.ssh的位置，一般是/home/git/.ssh，可以通过Gitea app.ini的SSH_ROOT_PATH配置（配置此项请参考Gitea文档的Config Cheatsheet）。尝试在Gitea中添加一些密钥，检查宿主机目录中的/home/git/.ssh/authorized_keys中是否有内容，如果没有内容，则需要在Gitea后台中的“计划任务”页面手动执行“更新.ssh/authorized_keys”任务。然后找到要执行的命令是什么，即command=后的内容，一般是/usr/local/bin/gitea ...。在宿主机上同样的位置创建一个转发用的脚本并赋予执行权限，将SSH命令转发到容器中。#!/bin/bash ssh -p 2222 -o StrictHostKeyChecking=no git@127.0.0.1 "SSH_ORIGINAL_COMMAND=\"$SSH_ORIGINAL_COMMAND\" $0 $@"尝试拉取代码Method B: 通过容器命令透传在宿主机上创建git用户切换到新用户，使用id命令确定git用户的UID和GID，并将容器中运行的Gitea设置为相同的UID和GID（通过容器的USER_UID和USER_GID环境变量）允许新用户访问docker: usermod -G docker，针对k8s，请为其创建一份有效的kubeconfig，使其可以访问容器并执行命令将/home/git/.ssh 映射到容器中Gitea写入.ssh的位置，一般是/home/git/.ssh，可以通过Gitea app.ini的SSH_ROOT_PATH配置（配置此项请参考Gitea文档的Config Cheatsheet）。尝试在Gitea中添加一些密钥，检查宿主机目录中的/home/git/.ssh/authorized_keys中是否有内容，如果没有内容，则需要在Gitea后台中的“计划任务”页面手动执行“更新.ssh/authorized_keys”任务。然后找到要执行的命令是什么，即command=后的内容，一般是/usr/local/bin/gitea ...。在宿主机上同样的位置创建一个转发用的脚本并赋予执行权限，将SSH命令转发到容器中。#!/bin/bash docker exec -u git -i -e SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea /usr/local/bin/gitea $@ # For K8s #_PARAMETERS=$@ #kubectl --kubeconfig /path/to/kubeconfig exec deployments/gitea -i -q -- su git -l -s /bin/sh -- -c "SSH_ORIGINAL_COMMAND=\"$SSH_ORIGINAL_COMMAND\" /usr/local/bin/gitea $_PARAMETERS""测试拉取推送代码

OPAQUE: 最好的密码不会离开你的设备
type: item
date: 2025-12-17T16:45:00+08:00
description: 原文: OPAQUE: The Best Passwords Never Leave your Device作者: Tatiana Bradley更新于: 2022/01/19@2025/12/17 文章已经大体翻译完成，但是一些词句仍需斟酌密码很麻烦，相信大多数读者都有亲身体验。在Cloudflare，密码相关的问题深入并存在于我们的各种业务中。当下各个网站对密码的要求越来越复杂，相信大多数读者都会觉得密码非常难以记忆和管理。幸运的是，有一些很好用的软件和浏览器插件能帮助我们管理密码。然而，某些存在于更深层次的根本问题已经超出的这些软件的能力范围。密码本身的根本问题很容易阐明，却非常难解决：无论密码多复杂，多难以猜测，一旦离开了你的掌控，密码的安全就很难保证。换而言之，密码存在的本身就是一种不安全因素。你可能会说：“但密码都是加密储存的！”那再好不过了。更准确的说法是密码很可能以加盐的摘要形式储存（接下来会解释什么是“加盐的摘要形式”）。但很不幸，你压根没办法确认密码是以何种形式存储的，因为无法确认，我们可以猜测某些服务商可能正在以明文形式存储密码。但即使厂商负责任的存储了密码，密码仍然可能被泄漏和爆破，不过需要付出巨大的成本（至少这一点值得庆幸）。一个由密码本质而带来的问题正日益显现：当下任何需要使用密码的地方，都意味着需要明文处理密码。你说：“密码是通过HTTPS安全传输的！”这是事实。你说：“我知道服务器以摘要形式存储我的密码，非常安全，没有任何人能获取他！”好吧，也许你对服务商很有信心。忽略那些不负责任的服务商，这倒也是事实。即便如此，整个过程还遗留着一个重要的漏洞：密码的使用过程中存在一个安全真空。当服务器通过加密安全地传输并收到一个密码，并在安全地存储该密码之前，密码必须被读取和处理——没错，以明文的形式。更糟糕的是，人们更常从软件层面来考虑这些问题，很容易忽略硬件带来的漏洞。即使软件万无一失，密码也必定在某一时刻存在于内存中，也必须在某一时刻存在于共享总线以传输到CPU。这向旁路攻击者暴露了许多种形式的攻击面。当然，这些攻击面的存在可能性远远低于那些存在于传输和存储过程中的问题，但他们的严重性可一点不低（例如最近的一些CPU漏洞，幽灵（Spectre）和熔断（Meltdown），只能说警钟撅烂）。解决这些问题的唯一办法就是把所有密码全部干掉。这确实有可能！研究和私营部门正在努力尝试去达成这一目标。新标准正在如雨后春笋般冒出，并日渐成熟。理想很丰满，但密码太常见了，需要非常长的时间才能就新标准和新技术达成共识并用于取代密码。在Cloudflare，我们正在寻找一些能立刻、马上实现的手段。今天我们要探讨的OPAQUE是其中一种可能的解决方案。OPAQUE是一种在密码不离开你的掌控范围的情况下，确保密码有效的众多系统之一。没人喜欢密码，但密码仍然在被使用，就算如此，至少我们可以保证密码不被泄漏。如果要说有谁要承认基于密码的认证很恼人，那我就是第一个。密码——难以记住，难以输入，难以保证安全。减少密码使用或完全替代密码的倡议未来可期。例如，WebAuthn就是一种主要使用硬件（或软件）密钥来使用公钥进行Web身份认证的标准。即便如此，密码仍然非常不幸的以一种身份验证机制存在，大概是因为密码认证很容易实现，用户学习成本低，或者只是因为密码在互联网和其他地方很常见。我们只能让基于密码的认证方式在被替代之前尽可能的安全。我在Cloudflare的实习主要聚焦于OPAQUE，OPAQUE是一种加密协议，解决基于密码的认证系统里了一个显而易见的安全问题：尽管大多数情况下密码在传输过程中已经被HTTPS加密保护，服务器仍然需要以明文方式来校验密码的正确与否。明文处理密码很危险，若不小心被缓存，或被记录到日志中，都会导致灾难性的安全漏洞。这个项目的目标并不是推荐众多方案中的某一种，而是为了证明OPAQUE是其中可行的一种方案。我比较熟悉Web技术，而且基于Web的技术受众大概更广，接下来我会使用Web作为主要的例子。Web认证基础课：使用TLS加密传输密码（Password-over-TLS）当你在网页上输入密码时到底发生了什么？网站需要确认你输入的密码与你原本注册这个网站时使用的密码相同。但这一切是怎么运作的？通常来说，你的用户名和密码被发送到服务器，服务器会检查与用户名关联的密码是否与你提供的密码相同。当然，为了防止攻击者窃听你的网络流量来窃取你的密码，你与服务器之间的连接会使用HTTPS加密（HTTP-over-TLS）。就算使用了HTTPS，这个过程中仍然存在一个显而易见的问题：服务器必须在某处以某种形式保存你的密码。服务器很难保证完全安全，被黑入是常有的事情。泄漏保存的内容会导致灾难性的安全问题。（要想了解最近的服务器入侵…

较新的Electron APP在Adreno 640 + Freedreno驱动下渲染花屏
type: item
date: 2025-11-30T10:23:00+08:00
description: 2026.01.23VSCode 貌似又更新Electron版本，新的libGLESv2.so不会导致花屏。但实测Vulkan会显著提升渲染效率，降低CPU占用，考虑开启Vulkan仍是一个好选择。0.问题表现很多Electron App，例如VSCode，QQ，在更新到较新的版本时（大约在2025/07后）会发生花屏：有两种解决方法。1. 启用Vulkan可以通过命令行参数或者修改启动入口JS文件，一般直接修改命令行参数最为方便，直接在命令后添加--enable-features=Vulkan即可。例如VSCode，需要修改几个文件:/usr/share/code/bin/code, /usr/bin/code, /usr/share/applications/code.desktop, /usr/share/applications/code-url-handler.desktop。2. 替换libGLESv2.so为旧版本部分App不支持通过命令行参数启用Vulkan，这时可以通过替换libGLESv2.so为旧版本解决，这个方案可能在未来导致应用崩溃，若能通过Vulkan解决尽量不要使用此方案。大部分Electron App都有通用的一些类库，我们可以直接从VSCode的旧版本提取。VSCode在2025.07升级了Electron版本，我们使用2025.05（1.101）版：https://update.code.visualstudio.com/1.101/linux-arm64/stable下载解压后找到libGLESv2.so，直接替换目标应用的libGLESv2.so，例如QQ的位于/opt/QQ/，重启应用即可解决。

Linux Wireguard + nftable 实现内网穿透并保留源IP (DNAT)
type: item
date: 2025-08-16T12:42:00+08:00
description: 两台机器, 一台内网机器N, 一台有公网G.仅能保留TCP的连接的IP, UDP连接配置过于繁琐, 故直接使用动态伪装以简化操作, 此时看到的IP为公网机器G的IP.一些配置中的可变量, 可以按需更改:变量名用途wg0Wireguard的接口名称, 文章同时用作了Nftable转发规则的表名称172.16.0.1/24Wireguard接口IP, 组网后网络中的IP, 可以任意修改为任何你喜欢的IP段Table = 1Wireguard使用的路由表, 在内网机器N配置时的PostUp和PostDown中有引用, 路由表ID范围为0~32768, 0为系统默认占用, 任意修改首先开启公网机器G的转发:echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf sysctl -p在两台机器上都安装Wireguard, 并都生成一对公私钥.cd /etc/wireguard wg genkey > wg0.key cat wg0.key | wg pubkey > wg0.pub输出:# wg genkey > wg0.key Warning: writing to world accessible file. Consider setting the umask to 077 and trying again. # cat wg0.key | wg pubkey > wg0.pub # cat wg0.pub LTzVnao892Iifn835NNdi398jNNIO39dj53MczezRH0= # cat wg0.key GHjJFZ7K8diUdk23MidPc991VOll+ZsiVdnMa8aiWlI=该操作需要在两台机器上都完成一次.配置公网机器G:/etc/wireguard/wg0.conf[Interface] # 监听端口, 任何你想要的端口都行 ListenPort = 1145 # 公网机器G在Wireguard中的IP, 建议设置成内网IP段 Address = 172.16.0.1/24 # 生成密钥时, 在公网机器G中/etc/wireguard/wg0.key的内容 PrivateKey = GHjJFZ7K8diUdk23MidPc991VOll+ZsiVdnMa8aiWlI= # 启动时加载转发规则 PostUp = nft -f /etc/wireguard/wg0.nft # 关闭时清除转发规则 PostDown = nft delete table wg0 [Peer] # 生成密钥时, 在内网机器N中/etc/wireguard/wg0.pub的内容 PublicKey = LTzVnao892Iifn835NNdi398jNNIO39dj53MczezRH0= # 需要设置成同一网段 AllowedIPs = 172.16.0.0/24配置内网机器N:/etc/wireguard/wg0.conf:[Interface] # 内网机器N在Wireguard中的IP, 需要与公网机器同一网段 Address = 172.16.0.2/24 # 生成密钥时, 在内网机器N中/etc/wireguard/wg0.key的内容 PrivateKey = QE6m/gMDgJ2LQA7lj3+5kz7fouX3awNzQy2ruXV/KX0= # 使用另外的路由表处理转发, 可以是任何小于32768的数字 Table = 1 # 配置路由使流量发回到Wireguard中 PostUp = ip rule add from 172.16.0.2 table 1; # 关闭时清除路由 PostDown = ip rule del from 172.16.0.2 table 1; [Peer] # 生成密钥时, 在公网机器G中/etc/wireguard/wg0.pub的内容 PublicKey = LTzVnao892Iifn835NNdi398jNNIO39dj53MczezRH0= # 公网机器G的IP与你设置的端口 Endpoint = 1.2.3.4:1145 # 特别的, 需要设置成接受所有流量, 以处理转发 AllowedIPs = 0.0.0.0/0 # 保持隧道开启, 否则公网机器在一段时间后无法访问内网机器 PersistentKeepalive = 20配置转发, 只需要在公网机器G上操作:/etc/wireguard/wg0.nft:table ip wg0 { # 内网机器N在Wireguard中的IP define dest = 172.16.0.2 # 需要转发的TCP端口 define tcp_ports = { 22, 255…

Longhorn 与 Multipath 冲突导致卷无法挂载
type: item
date: 2025-03-16T03:56:00+08:00
description: 升级系统到Ubuntu 24.04之后发现之前的longhorn卷全部无法挂载，包括新创建的卷，虽然能正常附加到服务器上，但是无法mount，mount就在dmesg里提示Can't open blockdev，通过fsck检查提示卷被占用，进行只读式的检查未发现问题。可以通过dd导出卷镜像到文件，导出到文件后正常挂载。此时判断Longhorn出现问题，经过多方查阅，发现其与Multipath冲突，虽然不知道为什么这东西现在才冲突。解决方法是在multipath的配置中排除掉Longhorn创建的磁盘：/etc/multipathd.conf:添加到末尾:blacklist { device { vendor "IET" product "VIRTUAL-DISK" } }

超次元游戏 海王星：重生1/2 (Neptunia Rebirth 1/2) Xbox 手柄图标MOD
type: item
date: 2025-03-09T00:58:00+08:00
description: 简介:本人用Xbox手柄玩nep，玩了一段时间被强行带偏使用习惯，变成B确认A取消，玩其他游戏的时候受到重大打击。故自己整合了网上的成果，本人只做了一点小小的工作，感谢互联网的大佬无私分享。* 只交换了图标，实际按键未改变，需要手动在设置里重新映射，使用游戏内设置将Button 1分配到A键，Button 2分配到B键即可。Mod效果:下载：适用于 超次元游戏 海王星: 重生1的Mod文件包适用于 超次元游戏 海王星: 重生2的Mod文件包使用教程：下载mod解压到游戏根目录，steam用户可以通过浏览游戏文件打开游戏根目录，其他情况根目录即为NeptuniaReBirth(1/2).exe文件所在的文件夹。解压后NeptuniaReBirth(1/2).exe文件同目录下应该有dinput8.dll。 直接启动游戏。在游戏设置中交换A/B键的绑定，建议使用鼠标进行选择和确定，有点点绕: 爽玩引用资源:neptards/neptoolsXbox Series X Button Prompts for Re;Birth1

小米平板5 + Linux
type: item
date: 2025-01-17T00:27:00+08:00
description: 很早之前就想要一个比轻薄本还便携的设备了，最好能像iPad一样优雅就好了。然后就锚定了一块小米平板5，买了个12+512的终极扩容版本，再加一块Doqo的磁吸悬浮支架键盘，性能够用，这玩意带上键盘也足够轻薄，长得像iPad但是不装iPad OS，加一起1700RMB拿下，简直薄纱iPad。 本来计划是用Windows的，因为Windows下驱动比较完善，而且部署方便，常用软件都有，而且还符合我长期的操作习惯。但是，Windows下没法驱动这块键盘的触摸板...我反反复复找了好几块键盘，发现都做得很屎，还很丑，果然还是放不下这块键盘，思来想去直接用Linux，虽然驱动不是特别完善，但是至少能发挥这块键盘的全部实力，实现完整形态。软件的问题都不是问题，硬件才让人头大，头铁刷入Linux。 故作次踩坑笔记，方便后来之人。安装参考教程: https://github.com/nabu-alarm/installation-guide/blob/main/guide/English/install.md可以使用他的UEFI Installer和nabu-alarm/images提供的rEFInd，可以dualboot到安卓和Linux。已过时的步骤，timoxa0将托管在他服务器上的数据全部删掉了，文件失传，仅供参考。使用lon-tool一键安装，非常方便，会设置好rootfs和UEFI启动，之后可以方便的把自己编译的内核替换上去。参考教程: https://github.com/nabu-alarm/installation-guide/blob/main/guide/English/install.md简述安装工具: powershell.exe -C "irm https://s.tx0.su/ltw | iex"下载镜像: https://timoxa0.su/?dir=share/nabu/images/v3/latest刷入: lon-tool deploy /path/to/image.lni编译6.12内核上一步镜像使用的是6.1的镜像，通常来说也够用，不必自行编译内核。实际上可以使用6.12的内核，记录在Ubuntu 24.04 x64上的编译步骤。思路参考: Nabu merge request in postmarketOS安装依赖: sudo apt install build-essential gcc-aarch64-linux-gnu bc flex bison 7zip kmod bash cpio binutils tar git wget dpkg libssl-dev (参考map220v/ubuntu-xiaomi-nabu的workflow)拉取6.12内核的代码: git clone https://gitlab.postmarketos.org/panpanpanpan/sm8150-mainline.git --branch 6.12-wip --depth 1修补内核: 请参考在小米平板 5 上安装 Arch Linux 只需要参考修补的内容，编译步骤在Ubuntu上完全不同针对patch文件，使用git apply /path/to/patch，其他文件找到相应的文件并直接替换。修改后编译出来的内核会有dirty标识，可以使用git commit -am 'patch'去除。准备编译，指定编译器: export ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-加载配置: make defconfig sm8150.config按需修改配置: make -j16 nconfig建议启用framebuffer console的rotate功能，因为一般横着用，但是默认是竖起来显示启动日志的。一些自用额外的配置# put these to arch/arm64/configs/extra.config # make defconfig sm8150.config extra.config CONFIG_LOCALVERSION="-nabu" # Xbox controller supporting CONFIG_INPUT_JOYSTICK=y CONFIG_JOYSTICK_XPAD=y CONFIG_JOYSTICK_XPAD_FF=y CONFIG_JOYSTICK_XPAD_LEDS=y # Wacom stylus supporting CONFIG_INPUT_TABLET=y CONFIG_TABLET_SERIAL_WACOM4=y # Framebuffer console CONFIG_FONTS=y CONFIG_FONT_TER…

装饰器, ESLint与代码检查
type: item
date: 2024-11-29T00:03:00+08:00
description: 在装饰器, HTTP请求与Typescript中, 我们使用装饰器实现了声明式的API描述与调用, 但问题在于ESLint并不认为这是好的.为了让ESLint能够识别这个新的用法, 我们需要写一些新的ESLint规则.1. 这是好的首先我们需要让ESLint停止将这些变量标记为未使用, 由于我能力有限, 实在没有找到合适的办法, 只能hook原来的no-unused-vars检查函数, 并检查每个被报告为未使用的函数参数是否被装饰器装饰:import { plugin } from "typescript-eslint"; const OrigNoUnUsedVars = plugin.rules["no-unused-vars"]; const rules = { "no-unused-vars": { meta: OrigNoUnUsedVars.meta, defaultOptions: OrigNoUnUsedVars.defaultOptions, create(ctx) { const hookedCtx = new Proxy({}, { get(target, name, receiver) { if (name === "report") { return function report(data) { const node = ctx.sourceCode.getNodeByRangeIndex(ctx.sourceCode.getIndexFromLoc(data.loc.start)); if (node.type === "Identifier") { // 普通参数, e.g. function a (@Deco p: string) {} // ^ if (node.decorators?.length > 0 && node.parent.type === "FunctionExpression") { return; // 带默认值的参数, e.g. function a (@Deco p = "default") {} // ^ } else if (node.parent?.type === "AssignmentPattern" && node.parent.decorators?.length > 0) { return; } } return ctx.report(data); }; // 啥也不是 } else { return Reflect.get(ctx, name, receiver); } } }); return OrigNoUnUsedVars.create(hookedCtx); } } }然后再将其添加回ESLint的ruleset中, 覆盖掉原来的no-unused-vars, 就会发现被装饰器装饰的函数参数不再被报未使用了.但是这样显然太简单了, 既然都开始写了, 那我们也可以写一些更详细的检查.2. 人能有多坏?在一个方法/参数上同时使用多个装饰器@Get("/") @Post("/") // 这是坏的! public a () {}@Get("/") public a ( @Param("a") @Param("b") a: string // ^^^^^^^^^^^ 这是坏的! ) {}@Get等装饰器的URL参数变量未被声明@Get("/{id}") // ^^^^ 这是坏的! public a () {}@Path装饰器声明的参数在URL中没有对应的变量@Get("/") public a ( @Path("a") a: string //^^^^^^^^^^ 这是坏的! ) {}参数名与装饰器对应的字段的不匹配由于入参时并不知道每个参数会映射到哪个字段, 所以需要进行一定的约束, 否则会出现@Param("pageNum") pageSize之类的不易被发现的问题.@Get("/") public a ( @Path("a") b: string // ^ 这是坏的! ) {}我们需要识别这些问题并给出提示.3. 找出坏人由于装饰器只能被应用到class上, 考虑只分析MethodExpression./** @type { import("eslint").ESLint.Plugin } */ const plugin = { rules: { "bad-decorator-usage": { create(ctx) { return { MethodExpression(node) { // 准备分析... } } } } } }先使用typescript-eslint Playground分析我们的错误用例, 将代码贴入编辑框后查看ESTre…

PHP + Vite(with HMR)开发: 从Matecho开发过程说起
type: item
date: 2024-09-21T16:33:00+08:00
description: 始因我的博客一直都使用基于PHP的Typecho作为后端, 在PHP日渐式微的今天, 人们的目光更多的放在的Rust和Go之类的新兴语言上, Typecho社区也再也少见令人眼前一亮的新主题. 倒也不是说之前的那些主题就不能用, 只是都不对我的口味. 我想要的是非常纯粹的阅读体验, 当人读文章的时候, 他就只能看到文章. 这也就是我想自己做一个主题的原因, 做一个极致简陋, 逼人只能看文章的主题. 5年前这个计划就开始了, 但是由于时间和技术能力受限, 实在是做不出心里想要的效果, 只得搁置.后来, 在本篇文章码下的半年前, 突然心血来潮, 直接开干. 恰逢MDUI发布了新的版本, 用上了新的Material Design, 在短短一星期之内就写出了最初的版本.Make things interesting在这过程中, 我觉得最有意思的部分正是: 将PHP与Vite集成, 使用最新的Web开发技术栈和构建工具链大幅提升开发体验.听着好像就是1+1的问题, 实际上有很多难点:如何使Vite处理PHP文件? PHP通过内嵌在HTML的特殊标记来向HTML中注入内容, Vite显然不能识别.自动动态导入, JS和Web Component对于PHP都是透明的, 如何从PHP文件中分析需要导入的组件?Typecho的主题结构显然与普通的SPA/MPA网页不同, 如何集成?前期调研在Github上已经有很多将PHP与Vite集成的插件/方法.donnikitos/vite-plugin-phpandrefelipe/vite-php-setup表现出两种思路:使用Vite的Build Hooks在transform过程中调用PHP, 将内容注入进HTML中后再交由Vite继续处理.将PHP与Vite主动集成, PHP主动适配并完成部分Vite的工作(注入link和script src标签), 使浏览器能够从Vite拉取内容.第二种方案对PHP侵入性太高, 需要修改PHP代码, 而且随着Vite更新, 注入方式可能会不同, 需要再次编写集成Vite的代码, 导致了PHP与Vite非常强的耦合, 故不选用.而第一种方案听起来非常美好, 对于简单的项目足够使用了. 但是Typecho并不简单, 通过模板引入的方式要求整个Typecho的所有代码必须经过Vite处理才能正常使用, 而且PHP中有些功能依赖于Apache/Nginx的预处理, 比如伪静态, 这种不使用HTTP服务器的方案与实际情况存在巨大差异, 很可能导致问题.大体架构目前能找到的两种方案都被抛弃了, 我们需要思考自己的解决方案:让PHP尽可能与Vite解耦, PHP中不应该存在针对Vite的特殊处理.让Vite能够处理PHP中的HTML代码, 并正确注入Vite入口(link和script src标签).故PHP代码应该让Apache处理, Vite可以将处理好的HTML代码交给Apache, 或者让Apache处理好PHP之后再对HTML进行transform.在build之后PHP代码不应该与Vite继续交互, 但dev时Vite是必然存在的. 所以考虑分开处理dev和build:在dev时:flowchart LR 原始代码 --> PHP --> Vite --> 浏览器这样可以在dev时获得Vite的一些特性, 例如HMR和按需编译.当然可以build --watch, 但是随着项目增大, build的时间在关掉所有优化压缩之后也变得越来越不可接受(~10s).在build时:flowchart LR 原始代码 --> Vite --> PHP --> 浏览器这样在发布代码时可以直接分发编译后的代码, 不需要使用者自行编译.实现接下来就着手解决两个问题: Vite+PHP集成, 自动导入Vite+PHP集成dev和build需求不同, dev环境存在Vite, 而且需要快速返回修改; 而build要求对代码进行优化, 并打包JS模块提高兼容性. 故可以分类考虑两种情况的解决方案.dev在dev期间, PHP代码被完全处理后才交由Vite处理, 不需要处理PHP代码.考虑实现一个proxy, 使其内容经过Vite处理管线.首先就需要将文件交给Apache处理, 我们的文件都在自己的repo下面, 难道要每改一次就复制一次到Apache那边吗? 显然不是.我们可以用软链接来实现这一切, 直接将src/链接到typecho/usr/themes/Matecho/.不过为了覆盖更多的情况, 比如测试build后的结果, 我们应当考虑链接dist/.ln -s /home/zapic/dist/ /var/www/typecho/usr/themes/Matecho然后再在bu…