我的 OpenCode 桌面后台服务(SEA 单文件打包的 Node v26.8.2 进程)内存涨到了 1.1 GiB。我把整个排查和修复交给了 Codex,前后一共做了四轮工作:先定位并修复一个藏在 Node ESM 缓存里的插件模块泄漏,再追查第二个进程的"高内存",最后发现那其实不是泄漏,而是打包方式导致的巨大基线,于是又做了一轮瘦身,把启动内存从 500 MiB 压到 177 MiB。
这篇文章按时间顺序记录完整的操作过程,重点是把每一步用了什么命令、看到什么、下一步怎么判断写清楚。
我的 OpenCode 桌面后台服务(SEA 单文件打包的 Node v26.8.2 进程)内存涨到了 1.1 GiB。我把整个排查和修复交给了 Codex,前后一共做了四轮工作:先定位并修复一个藏在 Node ESM 缓存里的插件模块泄漏,再追查第二个进程的"高内存",最后发现那其实不是泄漏,而是打包方式导致的巨大基线,于是又做了一轮瘦身,把启动内存从 500 MiB 压到 177 MiB。
这篇文章按时间顺序记录完整的操作过程,重点是把每一步用了什么命令、看到什么、下一步怎么判断写清楚。
This guide upgrades a dedicated Rocky Linux 9.2 server from PostgreSQL 16.x to
17.x in place with pg_upgrade. PostgreSQL 17 is installed alongside PostgreSQL
16, and the old data directory is retained for rollback.
The commands use --copy, not --link. Copy mode requires enough free disk
space for another copy of the cluster, but PostgreSQL 16 remains usable until
PostgreSQL 17 receives new production writes.
The paths below match installations made with the official PostgreSQL Yum repository:
|
Adjust them if your current server uses different paths.
Confirm the running version and data directory:
|
Record the cluster encoding, locale, and checksum setting. The new cluster must be initialized compatibly.
|
Check tablespaces because their data also needs space during a copy-mode upgrade:
|
List extensions in every connectable database:
|
Install a PostgreSQL 17-compatible build of every extension containing native
code before running pg_upgrade. Do not manually create those extensions in the
empty PostgreSQL 17 cluster; pg_upgrade migrates their definitions.
First update the existing 16.x packages. Plan a short outage for this step.
|
Verify that the server is ready:
|
If pgvector was installed from PGXN rather than an RPM, omit pgvector_16* and
update it using the same installation method originally used.
Stop here if there is no tested backup. Create a logical backup of all databases and roles:
|
Back up the configuration files:
|
Check disk space for the data directory and every user tablespace:
|
A filesystem or VM snapshot taken while PostgreSQL is stopped provides an additional rollback layer.
Install PostgreSQL 17 alongside PostgreSQL 16:
|
If pgvector was installed from the PostgreSQL Yum repository, install its PostgreSQL 17 package:
|
Prefer the RPM package when available and do not install pgvector with both RPM
and PGXN. If an extension is available only through PGXN, select the PostgreSQL
17 pg_config explicitly:
|
Confirm that both versions are installed:
|
Initialize the new cluster with the encoding and locale recorded earlier. Based
on the original Rocky Linux installation, these are likely UTF8 and
en_US.UTF-8:
|
If pg_controldata reported checksum version 1, remove the newly initialized
empty directory and initialize it with checksums instead:
|
Only remove NEW_DATA at this point, while it is a newly initialized empty
cluster. Do not start PostgreSQL 17 yet.
Stop applications, scheduled jobs, backup jobs, monitoring, and connection pools. Review remaining sessions:
|
Stop both database services:
|
Both services should report inactive.
Create a working directory owned by postgres, then run the PostgreSQL 17
pg_upgrade binary with --check:
|
Do not continue unless the final result is:
|
For example, an error about not loading $libdir/vector means the PostgreSQL 17
pgvector library is missing. Install pgvector_17, then rerun the check.
Run the real upgrade in copy mode:
|
Do not use --no-sync on a production server. Read the complete output and run
any required extension-update or index-rebuild scripts exactly as printed. Keep
the generated old-cluster deletion script, but do not run it yet.
Do not overwrite PostgreSQL 17's entire postgresql.conf with the PostgreSQL 16
file. Compare the files and manually reapply custom settings that remain valid:
|
Review settings such as listen_addresses, port, memory limits, WAL settings,
logging, SSL, and shared_preload_libraries. Any library named in
shared_preload_libraries must also be installed for PostgreSQL 17.
The client authentication files can normally be copied after review:
|
Review postgresql.auto.conf separately and reapply only valid PostgreSQL 17
settings:
|
Check key configuration values before starting the service:
|
Switch the enabled service and start PostgreSQL 17:
|
Verify the server identity, data directory, encoding, and locale:
|
Check that all databases are present:
|
First run any extension-update script produced by pg_upgrade. Then list the
installed extension versions:
|
Update pgvector in each database that uses it:
|
Update other extensions only where they are installed:
|
pg_upgrade does not transfer optimizer statistics. Regenerate them in stages
before returning the server to normal traffic:
|
After normal operation resumes, run a complete analyze:
|
Review the service log:
|
Check for invalid indexes in each important database:
|
Validate database and table counts, important row counts, application login,
reads and writes, scheduled jobs, backups, monitoring, connection pools, and
pgvector searches and indexes. Keep the PostgreSQL 16 packages and
/var/lib/pgsql/16/data for an agreed retention period.
Because the upgrade used --copy, PostgreSQL 16 remains unchanged. Before
PostgreSQL 17 receives production writes, rollback is straightforward:
|
After clients write to PostgreSQL 17, restarting PostgreSQL 16 would discard those new writes. Stop all clients and plan how to export or reconcile the PostgreSQL 17 changes before attempting rollback.
Only after PostgreSQL 17 has passed validation and fresh backups are working, disable PostgreSQL 16 permanently:
|
After the retention period, remove the old packages. Review the DNF transaction before accepting it so that libraries required by other software are not removed:
|
Finally, delete the old cluster only when you are certain it is no longer needed.
Use the deletion script generated by pg_upgrade, or archive the old data
directory according to your backup and retention policy.
My previous Snell installation note was written for CentOS 7 in 2020. Several years later, CentOS 7 is obsolete, predictable network interface names are common, and Snell has moved to version 5.
This guide installs Snell Server on Rocky Linux 9, manages it with systemd, opens the required firewall ports, and optionally limits the server’s outbound network throughput using Linux Traffic Control.
Snell is a lightweight encrypted proxy protocol developed for Surge. The official server is distributed as a single binary with no external runtime dependencies other than glibc. Snell v5 also adds a QUIC proxy mode, which requires the server’s UDP port to be reachable.
Log in as root, or prefix the commands with sudo.
|
The iproute-tc package provides the tc command used later for bandwidth limiting.
Enable and start the firewall:
|
Rocky Linux 9 uses firewalld for common firewall management. Its runtime and permanent configurations are separate, so permanent rules must be explicitly added when they should survive a reboot.
Snell does not need an interactive login account:
|
Create the application directory:
|
At the time of writing, the latest official release is Snell Server 5.0.1.
For a typical x86-64 VPS:
|
For an ARM64 server, use the official AArch64 package instead:
|
The official Snell page currently provides builds for AMD64, i386, AArch64, and ARMv7.
Confirm that the binary runs:
|
Run the built-in configuration wizard as the snell user:
|
The wizard asks for values such as:
For example, select port 11666.
After the wizard finishes, verify the generated file:
|
A typical configuration resembles:
|
Protect the configuration because it contains the PSK:
|
Do not publish the real PSK in screenshots, shell history, or a blog post.
Create /etc/systemd/system/snell.service:
|
Reload systemd and start Snell:
|
Check its status:
|
View recent logs:
|
Follow the logs in real time:
|
Confirm that the selected port is listening:
|
Snell accepts its primary connection over TCP. Snell v5’s QUIC proxy mode additionally uses UDP, so open the same port for both protocols.
|
Verify the rules:
|
Expected output should include:
|
If the server uses a cloud-provider firewall or security group, the same TCP and UDP ports must also be opened there.
Add a proxy entry to the Surge profile:
|
Replace:
YOUR_SERVER_IP with the VPS public IP address.YOUR_RANDOM_SECRET with the PSK from snell-server.conf.The Snell v5 server remains backward compatible with v4 clients. However, QUIC proxy mode is a v5 feature.
Some VPS providers charge for excess bandwidth or impose fair-use limits. Linux Traffic Control can apply a maximum outbound rate to a network interface.
Linux tc manages queueing disciplines that schedule packets as they leave an interface. The Token Bucket Filter, or TBF, is suitable for applying a simple maximum transmission rate.
Do not assume that the interface is named eth0. Rocky Linux systems frequently use names such as:
ens3ens18enp1s0enp0s3Find the interface used by the default route:
|
For example:
|
In this example, the interface is eth0.
You can extract it directly with:
|
Rocky Linux recommends modern tools such as ip and nmcli for network inspection and configuration.
The following command limits outbound traffic on eth0 to approximately 36 Mbit/s:
|
Check the active queueing discipline:
|
Remove the rule:
|
The limit applies to traffic transmitted through eth0. It therefore affects Snell, SSH, package downloads, web servers, and any other outbound service using that interface.
It does not directly limit inbound traffic. TCP downloads may nevertheless slow down indirectly because acknowledgements and response traffic leave through the rate-limited interface.
Create /etc/systemd/system/netlimit.service:
|
Replace eth0 with the actual public network interface found earlier.
The use of replace, rather than add, makes the service more tolerant of an existing root queueing discipline. The leading - in ExecStop tells systemd not to treat a missing queueing discipline as a fatal stop error.
Enable and start the service:
|
Check its status:
|
Verify the active rate:
|
Example output:
|
Restarting the service reapplies the configured rate:
|
To disable bandwidth limiting:
|
Then confirm that the TBF rule is gone:
|
The most reliable test is to transfer a sufficiently large file from the Snell server or run a speed test from a remote client.
Remember the unit conversion:
|
Protocol overhead means that the observed application-level transfer speed will normally be slightly lower than 4.5 MB/s.
You can watch the TBF counters while testing:
|
Pay attention to:
An increasing overlimits counter is normal: it shows that TBF is delaying packets to enforce the configured rate. A large number of dropped packets or a continuously growing backlog may indicate that the burst or latency values need adjustment.
Restart Snell:
|
Check Snell:
|
View logs:
|
Review the configuration:
|
Check the firewall:
|
Check bandwidth limiting:
|
Check both services after a reboot:
|
Download and extract the newer binary into a temporary directory, then stop the service and replace the existing executable:
|
The configuration file can normally remain in place, but release notes should be reviewed before each upgrade.
Compared with the old CentOS 7 setup, the Rocky Linux 9 version is mostly familiar:
firewalld exposes the selected TCP and UDP ports.tc and TBF provide a simple outbound bandwidth ceiling.The main detail to watch is the network interface name. Copying eth0 blindly may cause netlimit.service to fail on servers whose public interface is named ens3, ens18, or enp1s0.
Finally, remember that the simple TBF rule limits the entire network interface. It is suitable for a dedicated Snell VPS. On a shared server, more advanced tc classes and filters would be required to limit only Snell traffic.
Today I encountered a classic "works on my machine... wait, no it doesn't" moment while setting up the Opencode Desktop project. Running bun run dev failed with a cryptic Error: Electron uninstall message. Here's the full story of how I diagnosed and fixed it.
Like the previous years 2022 / 2023 / 2024 and 2025, here is my 2026 subscription list. The first number is RMB per month.
My pay:
TH pay:
So totally 777.9 RMB per month to pay. In the previous 2025 year it was 773 RMB, so almost same compared to 2025.
When building a Windows Electron app on Apple Silicon macOS, electron-builder --win defaults to the host architecture — so running:
|
produces arm64 Windows binaries instead of the expected x64.
Force x64 explicitly with the --x64 flag when invoking the package command:
|
Or call electron-builder directly:
|
This produces the standard NSIS x64 artifacts:
|
To pin a specific version (e.g. 1.14.42), set the OPENCODE_VERSION env var before the build:
|
After building with --x64, the .exe may launch but crash with:
|
Root cause: bun run build resolves native modules (e.g. @lydell/node-pty) based on the host platform and architecture — darwin-arm64 — rather than the target win32-x64. The bundle ends up importing the macOS native binding, which fails when the Windows .exe tries to load it at runtime.
Set RUST_TARGET=x86_64-pc-windows-msvc so the build step selects the correct native bindings for the target platform:
|
After the build, confirm the main bundle imports the correct platform binding:
|
And check the packaged app.asar includes the right native module:
|
Both env vars are needed together for a working Windows build:
|
Without RUST_TARGET, you get a working .exe installer but a broken main process that can't find its native modules.
For Windows ARM64:
|
The .exe built on macOS will not be Windows-code-signed. Electron-builder's signing routines typically gate on process.platform === "win32" (and often GITHUB_ACTIONS === "true"), so they intentionally skip signing on macOS. If you need a signed Windows release artifact, you must either build and sign on a Windows machine or add a macOS-compatible signing path to your config.
|
New bun 1.3.12 release but I got:
|
In fact, the binary was fine. The code signature was not.
A quick check with codesign revealed the real culprit:
|
On Apple Silicon, macOS enforces code signing strictly. A malformed or corrupt LC_CODE_SIGNATURE load command will cause the kernel to terminate the process immediately with SIGKILL (Killed: 9)—before a single line of your code runs.
You can confirm the signature load command exists with otool:
|
Remove the broken signature and replace it with a local ad-hoc signature:
|
Then verify:
|
Make pnpm install success on the MacOS machine already install libvips via brew.
|
|