npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nvwa-os/wormhole

v0.1.0

Published

CLI: Aliyun ECS spot wormhole (Shadowsocks) helper

Readme

wormhole

CLI to manage a single Aliyun ECS spot instance tagged for this tool: create, status, renew auto-release, delete.

Setup

bun install

Publish / npm install

Published tarball includes dist/cli.js (esbuild bundle) and README.md onlysrc/ is not in the package (package.json files).

npm install -g @nvwa-os/wormhole
wormhole --help

Needs Node 18+. Maintainers: npm run build (or let prepublishOnly run it on npm publish).

First run: interactive setup asks for AccessKey (or uses env) and region + availability zone, then creates or reuses a VPC (172.16.0.0/16), vSwitch, and security group tagged WormholeNetworkProfile=<your profile>. Instance image/size/spot settings use built-in defaults (edit config.json later if needed).

You can also run wormhole init / wormhole init --force, or copy config.example.json manually and fill IDs yourself.

Credentials via environment variables:

  • ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET, or
  • ACCESS_KEY_ID / ACCESS_KEY_SECRET

RAM custom policy (script-style)

Create a RAM policy (e.g. attach to a RAM user used only for wormhole) with the actions below. They match what this CLI calls today: init / network bootstrap (VPC + vSwitch + security group; default egress; optional AuthorizeSecurityGroup for the configured listener port range TCP+UDP) and instance lifecycle (RunInstances with inline tags, DescribeInstances, ModifyInstanceAutoReleaseTime periodically while watch is alive and the instance is Running).

Resource: "*" is the simplest shape; you can later tighten with resource-level authorization if your org requires it.

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecs:AuthorizeSecurityGroup",
        "ecs:AuthorizeSecurityGroupEgress",
        "ecs:CreateSecurityGroup",
        "ecs:DeleteInstance",
        "ecs:DescribeInstances",
        "ecs:DescribeRegions",
        "ecs:DescribeSecurityGroups",
        "ecs:DescribeZones",
        "ecs:ModifyInstanceAutoReleaseTime",
        "ecs:RunInstances",
        "vpc:CreateVSwitch",
        "vpc:CreateVpc",
        "vpc:DescribeVSwitches",
        "vpc:DescribeVpcs"
      ],
      "Resource": "*"
    }
  ]
}

Not included (unused by wormhole today): ecs:RevokeSecurityGroup / ecs:DescribeSecurityGroupAttribute — wormhole does not revoke ingress or list rule details. ecs:RevokeSecurityGroupEgress — wormhole only adds egress rules, it does not revoke them. resourcemanager:ListResourceGroups — only if you extend init to pick a non-default resource group for CreateVpc. If Aliyun returns errors about custom images, keys, spot quotas, or tagging, add the corresponding actions for your account (for example ecs:DescribeImages, ecs:TagResources, or related APIs) per the error text.

Usage

bun run src/cli.ts                    # default = up (+ foreground watch; Ctrl+C deletes VM)
bun run src/cli.ts --keep-instance    # Ctrl+C only exits CLI; VM stays up
bun run src/cli.ts -d                 # up then detach watch daemon (pid under ~/.config/wormhole/)
bun run src/cli.ts init
bun run src/cli.ts status
bun run src/cli.ts status --probe    # TCP check public_ip:wormhole_port (stderr + exit 1 if fail)
bun run src/cli.ts status --sip002    # print ss://… (password from config or last wormhole up)
bun run src/cli.ts up
bun run src/cli.ts renew
bun run src/cli.ts down --yes

Optional: bun run src/cli.ts --config /path/to/config.json or … up -d

Install globally: bun link in this repo (or run via bun /path/to/src/cli.ts).

Behavior

  • One managed instance per aliyun.profile, identified by tags WormholeManaged=true and WormholeProfile=<profile>.
  • Default command is up (running wormhole with no subcommand is the same as wormhole up). If config is missing and stdin/stdout are a TTY, interactive init runs first (same as before).
  • After up succeeds (including “already running”), the CLI keeps running: it polls DescribeInstances on watchPollSeconds (default 60) and prints a status line only when something changed (status, IP, port, auto_release, etc.) so the scrollback is not filled with duplicates and ss:// stays easy to copy. While Running, it calls ModifyInstanceAutoReleaseTime at most every autoReleaseRefreshSeconds (default 300). Aliyun still requires that horizon to be at least ~30 minutes, so leaseMinutes below 30 is raised (effectiveLeaseMinutes). Ctrl+C / SIGTERM delete the instance unless --keep-instance. -d keeps the same behavior in a child process.
  • renew is still available for a one-off push when you are not running the watch.
  • up picks a new random port on each run inside the configured pool (default 42000–42999), stores it in tag WormholePort, and configures Shadowsocks + client output for that port. Override the pool with aliyun.wormholePortMin / aliyun.wormholePortMax. Unless authorizeSecurityGroup is false, the security group gets TCP+UDP for the whole pool once (idempotent on each up / init).
  • Shadowsocks server: first boot runs UserData (no custom image): apt installs curl/xz, then downloads the shadowsocks-rust Linux .tar.xz and enables wormhole-ss.service. DescribeInstancesRunning only means the VM is up — apt + download + systemd still run afterward (often ~1–3 minutes to a listening port; GitHub can be slower from some regions).
    • Faster cold start: put the exact tarball your CPU needs (x86_64-unknown-linux-gnu or aarch64-unknown-linux-gnu) on same-region OSS / CDN and set aliyun.shadowsocksRustTarballUrl to that https://… URL (still paired with shadowsocksRustVersion only when using the default GitHub URL).
    • Fastest: build a custom image with wormhole-ssserver + config already on disk and point imageId at it; then trim or replace UserData (not automated here — you’d fork or extend the provider). UserData is visible to anyone with ECS/API access on the account—same as any cloud bootstrap secret.
  • Security group ports: Inbound: one TCP and one UDP rule for wormholePortMin/wormholePortMax (descriptions wormhole: ss listener range tcp|udp). Outbound for a proxy must allow connections to arbitrary remote ports (web, DNS, etc.). On init and on each up, wormhole idempotently ensures an IPv4 egress rule ALL → 0.0.0.0/0 (and tries IPv6 ::/0 if applicable). down --yes deletes the instance only; pool ingress rules stay on the security group until you remove them in the console (or change the pool and re-authorize).

How to tell if Shadowsocks is ready / debugging

  1. wormhole status --probeTCP only (checks something listens). It cannot speak Shadowsocks/AEAD by design — that needs a real SS client and your ss:// line.

  2. wormhole status --sip002 — Prints one ss://… line to stdout. Password comes from aliyun.shadowsocksPassword if set, otherwise from ~/.config/wormhole/last-session.json (written after a successful up on this machine, keyed by profile). Import into Shadowrocket / Clash / Surge / Outline / shadowsocks-android, then browse — end‑to‑end check.

  3. wormhole up — Also prints sip002= once and saves the same credentials (permissions 0600) for status --sip002 later.

  4. SSH on the instance (when you have a key/login path): UserData log is typically /var/log/cloud-init-output.log. Service checks:

    • sudo systemctl status wormhole-ss.service
    • sudo journalctl -u wormhole-ss.service -b --no-pager
    • sudo ss -tlnp | grep <wormhole_port> (see listener)
    • Config path from bootstrap: /opt/wormhole/ss-config.json, binary: /usr/local/bin/wormhole-ssserver.

APIs used for network bootstrap

Aligned with your list: DescribeRegions, DescribeZones, DescribeSecurityGroups, CreateSecurityGroup, CreateVpc, DescribeVpcs.

Also required (ECS needs a subnet): CreateVSwitch + DescribeVSwitches (VPC API).

Not wired yet: ListResourceGroups — only needed if you want new VPCs under a non-default resource group (ResourceGroupId on CreateVpc). Optional: DescribeVpcAttribute for richer VPC status polling.

See RAM custom policy above for the exact Action list.