* perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> perf(memory): keep install, update and runtime inside a small-RAM budget (#83) * perf(memory): keep install, update and runtime inside a small-RAM budget Small NAT/VPS targets have 128-512MB of RAM and no swap, so anything that cannot be reclaimed is fatal. This tuning touches no swap and no mmap; it works by not allocating in the first place. Installer (covers install and update; update.sh only execs install.sh): - Put the scratch directory under DATA_DIR instead of mktemp -d. /tmp is tmpfs on many of these hosts, and without swap every byte there is pinned in RAM: bundle, extracted release and a copy of the data directory were held at once, exceeding total RAM of the machines being targeted. - Hash assets on the way to disk instead of re-reading them. Download is a single pass now; verification still completes before tar sees the bytes. - Release the intermediate data.tar right after extraction rather than holding the data directory twice for the rest of the run. - Check scratch capacity before downloading, so we fail before the old service is stopped rather than halfway through. - Refresh Environment=GOMEMLIMIT/GOGC/THREE_M_UI_CORE_LOW_MEMORY and MemoryMax on an existing unit. write_service() keeps an operator's unit, which meant the values baked in at install time never tracked updates. Panel: - Bound the Go heap from the process itself via debug.SetMemoryLimit, so the tuning reaches manual runs, containers and the installer's own init and healthcheck instead of only a systemd unit. It acts in-process, so the Mihomo core never inherits the panel's tighter GC target. An explicit GOMEMLIMIT or GOGC always wins. - Emit geodata-loader: memconservative for the core when low-memory mode is on. The key is omitempty and pinned after user fragments merge, so nothing changes for anyone who does not opt in, and no fragment can undo it. No new calls to Mmap/Munmap/Madvise, no swap or mmap anywhere in the tree. * fix(ci): keep the new scratch directory out of the installer's own paths Two problems CI caught on the small-memory change, both from moving the scratch directory from mktemp into DATA_DIR. restore_files() rebuilds the data directory by deleting everything in it except backups. The scratch directory now lives inside it, so a rollback deleted the very directory it was reading its own rollback out of, and the restore died looking for restore/ENABLED. Rollback therefore had to be skipped, exempting the scratch directory from that purge; the cleanup trap already removes it. The test curl also modelled only one of curl's output modes. Writing to stdout and answering -I are both normal curl behaviour, and both are now covered, so the mock no longer pins the installer to a single invocation shape. The distribution fake likewise emits its tampered asset on stdout instead of through --output, keeping that test about checksum rejection rather than about which flag was used. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
65 lines
2.9 KiB
Python
65 lines
2.9 KiB
Python
"""Release channel and pinned download failure regression tests (no network)."""
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SPEC = importlib.util.spec_from_file_location("release_metadata", ROOT / "scripts/release-metadata.py")
|
|
metadata = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(metadata)
|
|
|
|
|
|
class DistributionTests(unittest.TestCase):
|
|
def test_stable_and_candidate_channels(self):
|
|
stable = metadata.metadata("v1.2.3")
|
|
self.assertEqual((stable["latest"], stable["prerelease"]), ("true", "false"))
|
|
candidate = metadata.metadata("v1.3.0-rc.1")
|
|
self.assertEqual((candidate["latest"], candidate["prerelease"]), ("false", "true"))
|
|
|
|
def test_build_metadata_does_not_collide_with_prerelease_image(self):
|
|
build = metadata.metadata("v1.2.3+rc.1")
|
|
candidate = metadata.metadata("v1.2.3-rc.1")
|
|
self.assertEqual(build["latest"], "true")
|
|
self.assertNotEqual(build["image_tag"], candidate["image_tag"])
|
|
self.assertNotIn("+", build["image_tag"])
|
|
|
|
def test_invalid_and_rolling_tags_cannot_be_formal_releases(self):
|
|
for tag in ("pre", "v1.2", "v01.2.3", "v1.2.3-01", "v1.2.3-", "v1.2.3\nlatest=true"):
|
|
with self.subTest(tag=tag), self.assertRaises(ValueError):
|
|
metadata.metadata(tag)
|
|
|
|
def test_root_management_script_uses_only_verified_update_paths(self):
|
|
script = (ROOT / "scripts/3m-ui").read_text(encoding="utf-8")
|
|
self.assertNotIn("raw.githubusercontent.com", script)
|
|
self.assertNotIn("THREE_M_UI_REPO_RAW", script)
|
|
self.assertNotIn("meta-rules-dat/releases/download/latest", script)
|
|
self.assertIn('run_script "$UPDATER" || return 1', script)
|
|
|
|
def test_changed_upstream_asset_is_rejected_before_installing_core(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
directory = Path(tmp)
|
|
fake_curl = directory / "curl"
|
|
fake_curl.write_text(
|
|
# Writes the tampered body to stdout, which is what a real curl
|
|
# does when no --output is given. The asset must be rejected on
|
|
# its checksum before anything is extracted from it.
|
|
'#!/bin/sh\nprintf tampered\n', encoding="utf-8"
|
|
)
|
|
fake_curl.chmod(0o755)
|
|
dest = directory / "output"
|
|
env = dict(os.environ, PATH=f"{directory}:{os.environ['PATH']}")
|
|
result = subprocess.run(
|
|
["sh", str(ROOT / "scripts/download-mihomo.sh"), "amd64", str(dest)],
|
|
env=env, capture_output=True, text=True, check=False,
|
|
)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("checksum mismatch", result.stderr)
|
|
self.assertFalse((dest / "mihomo").exists())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|