{"id":50897242,"url":"https://github.com/redwoodjs/machinen.dev","last_synced_at":"2026-06-16T01:03:44.050Z","repository":{"id":357280797,"uuid":"1235979359","full_name":"redwoodjs/machinen.dev","owner":"redwoodjs","description":"Pause, resume, fork Linux VMs across hosts","archived":false,"fork":false,"pushed_at":"2026-05-22T21:18:38.000Z","size":232,"stargazers_count":121,"open_issues_count":2,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T23:35:39.074Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/redwoodjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T20:51:14.000Z","updated_at":"2026-05-22T21:18:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/redwoodjs/machinen.dev","commit_stats":null,"previous_names":["redwoodjs/machinen.dev"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/redwoodjs/machinen.dev","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redwoodjs%2Fmachinen.dev","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redwoodjs%2Fmachinen.dev/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redwoodjs%2Fmachinen.dev/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redwoodjs%2Fmachinen.dev/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redwoodjs","download_url":"https://codeload.github.com/redwoodjs/machinen.dev/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redwoodjs%2Fmachinen.dev/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34386322,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-16T01:03:43.416Z","updated_at":"2026-06-16T01:03:44.043Z","avatar_url":"https://github.com/redwoodjs.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./docs/logo.svg\" alt=\"machinen\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eM A C H I N E N\u003c/h1\u003e\n\nHand off a running Linux VM between hosts. Freeze it on your laptop, thaw it\non a server, resume it next week. The program picks up exactly where it left\noff — like waking a laptop from sleep, except on a different computer.\n\nA native microVM runtime under the hood: arm64 on Apple Silicon/Linux and\namd64 on Linux/KVM. Node.js is the first-class target; Python, bash, and\nanything else that boots in a Linux VM works too.\n\n\u003e **Note:** the source code isn't published yet — it'll be available here soon.\n\n## Install\n\n```bash\nnpm i @machinen/cli @machinen/runtime\n```\n\nThen run the CLI with `npx machinen …` (or the shorter `npx mn …` — both\nnames install). Prefer it on your PATH? `npm i -g @machinen/cli` is fine\ntoo.\n\nThe right native package is pulled automatically via optional dependencies:\n`@machinen/native-arm64-darwin` on Apple Silicon Macs,\n`@machinen/native-arm64-linux` on arm64 Linux, and\n`@machinen/native-x64-linux` on amd64 Linux. No system dependencies.\n\nFirst run fetches the matching kernel + rootfs from a GitHub release on the\ncompanion repo over plain HTTPS — no auth required.\n\n## Quickstart\n\nBake an image, boot it, accumulate some state, then move the running process\nto another host.\n\n### 1. Bake\n\nA tiny HTTP server that counts hits in memory:\n\n```js\n// counter.mjs\nimport { createServer } from \"node:http\";\nlet count = 0;\ncreateServer((_, res) =\u003e {\n  res.end(JSON.stringify({ count: ++count }) + \"\\n\");\n}).listen(3000);\n```\n\nBake it into a rootfs tarball with `provision()`:\n\n```ts\n// bake.ts\nimport { readFileSync } from \"node:fs\";\nimport { provision } from \"@machinen/runtime\";\n\nawait provision({\n  install: async (vm) =\u003e {\n    await vm.exec(\"apt-get update \u0026\u0026 apt-get install -y nodejs\");\n    await vm.writeFile(\"/opt/counter.mjs\", readFileSync(\"./counter.mjs\"));\n  },\n  cmd: [\"/usr/bin/node\", \"/opt/counter.mjs\"],\n  out: \"./counter.tar.gz\",\n});\n```\n\n```bash\nnode bake.ts\n```\n\n### 2. Boot\n\n```bash\nnpx machinen boot --name counter -p 3000:3000 --detached ./counter.tar.gz\ncurl localhost:3000                        # { count: 1 }\ncurl localhost:3000                        # { count: 2 }\n```\n\nThe process is now sitting on host A with `count = 2` in its heap.\n\n### 3. Handoff\n\nFreeze it, copy the bundle to host B, thaw it:\n\n```bash\nnpx machinen snapshot counter ./counter.snap\nscp ./counter.tar.gz ./counter.snap host-b:\nssh host-b npx machinen restore ./counter.snap -p 3000:3000 \u0026\ncurl host-b:3000                           # { count: 3 }  ← same process\n```\n\nSame guest architecture only (arm64 ↔ arm64, amd64 ↔ amd64). Cross-ISA\nrestore is not supported. Memory, file descriptors, and timers come back\nexactly as they were.\n\nThe bundle remembers the absolute path of the rootfs tarball you booted\nfrom. On the same host that's all you need — `restore` reuses the same\ntarball so CRIU can re-open file-backed VMAs (executable, shared\nlibraries) at the paths they were dumped from. Across hosts, copy the\ntarball to the same path or pass `--image \u003ctarball\u003e` to override.\n\n## Fork\n\n`fork` is snapshot + restore without killing the source. The original keeps\nrunning; you get a sibling VM with the same heap, same open files, and a\ncopy-on-write disk. Both processes diverge from the same instant.\n\nPick up from Step 2 above — `counter` is running with `count = 2`:\n\n```bash\nnpx machinen fork counter --new-name counter-b --detach\n\nnpx machinen exec counter   -- curl -s localhost:3000   # { count: 3 }\nnpx machinen exec counter-b -- curl -s localhost:3000   # { count: 3 }\nnpx machinen exec counter-b -- curl -s localhost:3000   # { count: 4 }\nnpx machinen exec counter   -- curl -s localhost:3000   # { count: 4 }\n```\n\nBoth VMs branched from the same `count = 2` heap and now count\nindependently. Use it to clone a warmed-up process: a database with caches\nloaded, a test fixture in exactly the right state, a long-running compute\njob branched into N parallel explorations.\n\nThe fork doesn't inherit the source's `-p` host forwards — host ports are\nglobal, only one process can bind each one. Two ways to reach a fork:\n\n```bash\n# A) exec over vsock — works for any guest port, no host forward needed.\nnpx machinen exec counter-b -- curl -s localhost:3000\n\n# B) -p with non-conflicting host ports — forwards on the host.\nnpx machinen fork counter --new-name counter-b -p 3001:3000 --detach\ncurl localhost:3001                                            # the fork\ncurl localhost:3000                                            # still the source\n```\n\nPass `-p` multiple times for multiple ports. If you pick a host port the\nsource is already forwarding, `fork` errors with\n`BOOT_PORT_FORWARD_IN_USE` and names the VM that's holding it.\n\nFrom Node, same shape:\n\n```ts\nconst fork = await vm.fork({ name: \"counter-b\" });\n```\n\n## From Node\n\nSame arc, driven from TypeScript:\n\n```ts\nimport { readFileSync } from \"node:fs\";\nimport { boot, provision, restore } from \"@machinen/runtime\";\n\nawait provision({\n  install: async (vm) =\u003e {\n    await vm.exec(\"apt-get install -y nodejs\");\n    await vm.writeFile(\"/opt/counter.mjs\", readFileSync(\"./counter.mjs\"));\n  },\n  cmd: [\"/usr/bin/node\", \"/opt/counter.mjs\"],\n  out: \"./counter.tar.gz\",\n});\n\nconst vm = await boot({ image: \"./counter.tar.gz\", name: \"counter\" });\n// ... let it run, serve traffic, accumulate state ...\n\nawait vm.snapshot({ outDir: \"./counter.snap\" });\n\n// elsewhere (possibly on another host):\nconst restored = await restore({ snapDir: \"./counter.snap\" });\n```\n\n## Documentation\n\n- [Quickstart](./docs/quickstart.md) — the same three-step walkthrough\n  with more colour\n- [Guides](./docs/) — recipes for creating VMs, snapshots and forks,\n  mounts, and networking\n- [`@machinen/cli` reference](./docs/api/cli.md) — every command\n  and flag\n- [`@machinen/runtime` reference](./docs/api/runtime.md) — every\n  exported function, type, and error class (typedoc-generated)\n\nThree runnable demos live in [`examples/`](./examples):\n\n- [`quickstart`](./examples/quickstart) — the counter walkthrough above\n  as a runnable repo.\n- [`fork-pi`](./examples/fork-pi) — snapshot a VM with the `pi` coding\n  agent installed, then fork three siblings that each answer a different\n  prompt in parallel.\n- [`live-mount`](./examples/live-mount) — host directory mounted into\n  the guest over an in-VMM virtio-fs device; bidirectional, no rebuild\n  on edit.\n\n## Other ways to boot\n\n```bash\nnpx machinen boot -- /bin/sh                    # ad-hoc: boot base + run a cmd\nnpx machinen boot ./my-image.tar.gz             # boot a provisioned rootfs tarball\nnpx machinen install                            # pre-fetch base assets (CI / airgap)\nnpx machinen install --version \u003ctag\u003e            # pin to a specific release tag\n```\n\n## License\n\n[FSL-1.1-MIT](https://fsl.software/) — Functional Source License. Converts to MIT two\nyears after each release.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredwoodjs%2Fmachinen.dev","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredwoodjs%2Fmachinen.dev","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredwoodjs%2Fmachinen.dev/lists"}