Get Started
A computer in your own cloud, running lnk. Connect your AWS or
Google Cloud account once, and one command makes a machine there with
lnk on it, signed in as you. The same account, and its one sign-in,
keeps your buckets.
Needs: macOS or Linux, with OpenSSH; lnk up boxes; your own AWS or
Google Cloud account and its CLI (aws, gcloud), which lnk cloud connect offers to install; lnk auth login to make boxes. Boxes cost
money, billed to your account by your cloud; Local Link bills nothing.
Quickstart
lnk up boxes # the cloud, box, vpn and accounts plugins
lnk auth login # GitHub: your boxes are tagged as yours
lnk cloud connect aws # your aws login, or an access key; makes nothing in it
lnk box start aws # aws-1: an Ubuntu machine in that account, lnk on it, signed in as you
lnk box ssh aws-1 # a shell on it
lnk box list # your boxes, also those made on your other computers
lnk box stop aws-1 # stopped: its disk stays, and is billed
lnk box start aws-1 # running again
lnk box remove aws-1 # gone, with its diskConnect a cloud account
lnk cloud connect aws # reuses ~/.aws, else asks for an access key
lnk cloud connect aws --profile work --region eu-west-2 # another profile of your aws login
lnk cloud connect aws --name work --key-id <id> # an access key; the secret is asked for
lnk cloud connect gcp --project my-project --zone europe-west1-b
lnk cloud connect gcp --name ci --key-file key.json # a service account, by its key
lnk cloud list # your accounts, and who each signs in as--name <n>: what to call it, to connect two accounts of one kind. Default: the kind.--region(aws),--projectand--zone(gcp): where new things go. Defaults: your login's region or zone, elseus-east-1orus-central1-a; the service account's or gcloud's project, elseconnectasks for one.
gcp uses gcloud's login and project, and signs you in first if there's
none. It then signs you in once more for buckets: Google's libraries
can't use gcloud's own login. Over SSH, gcloud prints a link to open on
any computer and asks for the code it shows. A service account connects
by its key instead (--key-file, from the Google Cloud console, IAM,
Service accounts, Keys), with no gcloud login. Connecting makes nothing
in the account.
Without the cloud's CLI, connect offers to install it into
~/.config/lnk/aws or ~/.config/lnk/gcp, with no administrator rights
and no change to your shell. Say no, and an aws account, or a gcp
service account connected by its key, still works for buckets, but not
boxes. A gcp login needs gcloud to connect at all.
Start a box
lnk box start aws # a new box, aws-1
lnk box start gcp --name big --size e2-standard-4 --region europe-west1-b
lnk box start aws --cpus 4 --memory 8GB..16GB # the smallest machine with those
lnk box start big # a stopped box, running again--name <n>: what to call it. Default:<account>-1, then-2, ...--size <type>: the machine type. Default:t3.smallore2-small.--region <region|zone>: where. Default: the account's.--cpus,--memory,--gpus,--disk,--spotand the rest: what machine, as a box spec says it.--no-login: don't signlnkin there.--yes: don't ask first.
It says which machine it picked, its disk, the lnk it gets, the price
when the cloud gives one, and that it's billed to you, then asks. By
default you get Ubuntu 24.04 on x86_64 with 20 GB of disk, only SSH
open, and lnk with your login (the accounts plugin). What runs on
the box adds its own plugins: lnk agent move and lnk agent new --on add the harness and its sandbox first. lnk agent move aws
starts a box itself when you have none in aws. At the end, lnk auth login runs there: open the link it prints on your computer.
A stopped box gets a new address when it starts again.
Get into a box
lnk box ssh # a shell on your only box
lnk box ssh big -- uptime # one command there
lnk box run big -- agent status # lnk on the box, wherever it's installedrun passes input and output through untouched, which is how lnk agent move carries an agent (agents).
Forward home to a box
lnk vpn exit big home # a SOCKS proxy on the box, leaving from here
lnk box forward big --socket proxy.sock --to 1080 # the box's ~/proxy.sock to your own SOCKS proxy on port 1080
lnk box forward big --port 40780 --to 1080 # the box's port 40780 insteadlnk vpn exit keeps the forward up in the background, to a proxy that
never reaches this computer or its network (vpn).
lnk box forward alone lasts until the SSH connection drops, and
needs --to, a SOCKS proxy on this computer's loopback: it never
uses ssh's own, which reaches everything here.
Set it up your way
Describe a box in a file
lnk box sizes aws --spec web.toml # the machines that fit in aws, best first; makes nothing
lnk box start aws --spec web.toml # web-1, the best fit for web.toml in aws
lnk box start gcp --spec web.toml --spot # the same box in gcp, a spot machine
lnk box spec web-1 > web-1.toml # the file that makes web-1 again
lnk box spec --cpus 4 --disk 40GB # the file those flags makeA box spec is a TOML file saying what machine you rent, the way you'd
describe one at a store, and the lnk on it:
# web.toml
name = "web" # its boxes: web-1, web-2, ...
[machine]
cpus = { min = 2, max = 8 }
memory = { min = "4 GB", max = "16 GB" }
disk = { min = "40 GB" }
[machine.aws]
region = "eu-west-2"The box gets the smallest machine that fits: min is what it needs,
max the most you'll pay for. The same file makes the best fit on AWS
and on Google Cloud. Every field is a flag too, and a flag wins over
the file. Every field, and how a cloud's own table works: box
spec.
lnk box spec <box> prints the spec a box was made from, with the
machine it got pinned for its cloud, so the file makes exactly that box
there (what it prints).
Set a box up in stages
lnk box copy web-1 clone.toml deps.toml build.toml setup # files into ~/setup on web-1, over SSH
lnk box setup web-1 setup.sh # your script, run here with LNK_BOX=web-1A setup script is yours, in plain sh, and each command in it is a
stage. Run each stage that fetches or builds in a sandbox spec of its
own, on the box, so it reaches only what it needs: cloning reaches
GitHub, installing reaches the package registry, and building reaches
nothing. It costs nothing but the box.
# setup.sh: lnk box setup web-1 setup.sh
box=${LNK_BOX:?}
# Outside any sandbox: installing system packages needs root, which no sandbox gives.
lnk box ssh "$box" -- sudo apt-get update -q
lnk box ssh "$box" -- sudo apt-get install -y python3-pip
lnk box run "$box" -- plugin add sandbox
lnk box run "$box" -- sandbox ready --yes
lnk box copy "$box" clone.toml deps.toml build.toml setup
lnk box ssh "$box" -- mkdir -p app
# Each stage in its own sandbox.
lnk box run "$box" -- sandbox run --spec setup/clone.toml --yes -- git clone https://github.com/me/app app
lnk box run "$box" -- sandbox run --spec setup/deps.toml --yes -- python3 -m pip install --no-cache-dir --target app/.deps -r app/requirements.txt
lnk box run "$box" -- sandbox run --spec setup/build.toml --yes -- sh app/build.sh# clone.toml: cloning reaches GitHub alone, as git, and writes ~/app
name = "clone"
version = 1
[files]
write = ["~/app"]
[internet]
hosts = ["github.com"]
[commands]
allow = ["git", "/usr/lib/git-core/"]# deps.toml: installing reaches PyPI alone, never GitHub
name = "deps"
version = 1
[files]
write = ["~/app"]
[internet]
hosts = ["pypi.org", "files.pythonhosted.org"]
[commands]
allow = ["python3"]build.toml is the same with no [internet], allowing sh and
python3. A spec's sections: sandbox.
lnk box setupruns the script withsh -e, so it stops at the first command that fails, and says which: its line in the script, or, whereshdoesn't number lines, its number. The stages before it ran: fix it, then run the rest by hand, or start again on a new box. By hand, the script runs the same:LNK_BOX=web-1 sh -e setup.sh.- The box can be one the script makes:
lnk box start aws --name "$LNK_BOX" --yesas its first stage. lnk box copy <box> <file>... <folder>puts files in a folder on the box, from its home folder, making it if it's missing. Each keeps its name and permissions. Write the folder without~: your shell would make it this computer's home.- A spec writes only a folder that's there, so make it first, as
mkdir -p appdoes above. A spec can't write a dot folder in your home (~/.cache,~/.local), so keep a stage's caches in its folder or off (--no-cache-dir).
Save a box as an image
lnk box image save web-1 web-ready # web-1's disk, kept as image web-ready in its cloud account
lnk box image list aws # the images you saved in aws
lnk box image remove aws web-ready # deletes it, and its copy of the diskSaving needs a running box with no agent on it: an image would carry
an agent to every box made from it. It takes a few minutes. It signs
the box's lnk out, so the image holds no login, then stops the box, so
nothing is saved half-written, saves its disk, and starts it again. In
a terminal it then signs the box in again: open the link it prints.
--yes doesn't ask first.
Saving refuses a box holding secrets where they're usually left, and lists what it found (where it looks): remove them, then save again.
The image keeps a copy of the disk in your account, billed every month by its size until you remove it. On AWS it's an AMI and its snapshot, in the box's region; on Google Cloud, an image in the project.
Start a box from an image
lnk box start aws --image web-ready # a new box, its disk as web-1's was
lnk box start aws --image web-ready --spec web.toml # the machine web.toml picks, from the imageOn AWS, a box from an image loads each part of its disk from the image's snapshot the first time it's read, so its first reads are slower than a stopped box's.
The box is made where the image is: its AWS region, or any zone of the
Google Cloud project. Its disk is at least the image's, raised to it if
you asked for less, and it says so. Its processor is the image's, and
its lnk is the one on the image, as are the keys in its
~/.ssh/authorized_keys. It gets its own machine ID, SSH host
key and lnk login, as any new box does. An image is in one account,
so --image is never a field of a spec.
Reuse an image while what made it stays the same
lnk box image save build-1 app-3f2a9c --hash 3f2a9c # the image, tagged with a hash of what made it
lnk box image list aws --hash 3f2a9c # the images with that hash, newest first
lnk box start aws --name web-1 --image-hash 3f2a9c # a box from the newest ready oneThe hash is yours, of your setup script, its specs and the lockfile its dependencies come from, so a change to any of them makes a new hash. Lowercase letters, digits and dashes, at most 63. A script then saves the image once, and starts every box after from it:
# boxes.sh: a box set up as setup.sh says, from an image when nothing that made it changed
hash=$(cat setup.sh clone.toml deps.toml build.toml requirements.txt | shasum -a 256 | cut -c1-32)
if [ "$(lnk box image list aws --hash "$hash" --json)" = "[]" ]; then
lnk box start aws --name build-1 --yes --no-login
lnk box setup build-1 setup.sh
lnk box image save build-1 "app-$(printf '%s' "$hash" | cut -c1-8)" --hash "$hash" --yes
lnk box remove build-1 --yes
fi
lnk box start aws --name web-1 --image-hash "$hash" --yesOn a box from the image, only what changed since needs a stage: a
git -C app pull in the clone's spec, then the dependencies' stage
again.
See, stop or remove
See your boxes
lnk box list # name, account, place, size, address, state
lnk box list --refresh # ask each cloud for each box's state first
lnk box list --all-regions # on AWS, look in every enabled region
lnk box list --here # only what this computer knows; asks no cloud
lnk box list --json # for scriptslist asks each connected account for the boxes tagged as yours. On AWS
it looks in the regions your boxes are in, or every enabled region when
it knows none, and says which regions your login may not look in.
Stop or remove a box
lnk box stop big # stops the machine; its disk stays, and is billed
lnk box remove big # deletes the machine and its disk, after asking
lnk box remove big --yes # without askingRemoving the last box in a region (AWS) or project (Google Cloud) also deletes what Link made there for boxes: the firewall rules or security group, and on AWS the key pair.
Disconnect an account
lnk cloud disconnect work # forgets the account and its key; deletes nothing in itIts buckets and boxes wait until you connect it again; lnk bucket disconnect forgets its buckets. A Google Cloud account's credential
file goes too. An access key keeps working until you delete it in the
cloud's console.
Every flag: lnk cloud <command> --help, lnk box <command> --help.
Get into your boxes from a new computer
lnk auth login # the same GitHub account
lnk cloud connect aws # the same cloud account
lnk box list # finds your boxes by their tags
lnk box ssh aws-1 # lets this computer in through the cloud accountThe first connection pins the box's host keys from the cloud and adds
this computer's SSH key through your account. Each computer's key has
the comment lnk <machine id> <machine name>, so you can find and
remove a lost computer's key on a box.
Where things live
~/.config/lnk/cloud/accounts.toml: your accounts, access keys included. On a Mac, secrets are in the Keychain.~/.config/lnk/box/boxes.toml: this computer's copy of your boxes, each with the spec it was made from. The clouds hold the record, by tags.~/.config/lnk/box/id_ed25519andknown_hosts: Link's SSH key for boxes, and their host keys.~/.config/lnk/machine.tomlon a box: its machine ID and name.
Troubleshooting
| Symptom | Fix |
|---|---|
| "Link on AWS needs its CLI" or "Link on Google Cloud needs its CLI" | lnk cloud connect in a terminal installs it. On Linux, AWS's installer needs unzip. |
| "run this in a terminal to be asked, or pass --yes" | Run in a terminal, or pass --yes. |
| "which box?" | Name one: lnk box ssh <name>. |
| "... is stopped" | lnk box start <name>. A spot box AWS took back starts again on its own once AWS has room. |
| "... is made but not set up" | lnk box ssh <name> to look, or remove it and start again. |
lnk isn't signed in on a box | lnk box ssh <name>, then lnk auth login there. |
| "Link tags your boxes with your login's GitHub id" | lnk auth login again, then retry. |
| A box listed with no id | An interrupted start: lnk box remove <name>. |
| A box made elsewhere doesn't show | lnk box list --all-regions; check lnk cloud list and your GitHub login. |
| A box made before tags doesn't show | Run lnk box list on the computer that made it, with the box running. |
A box named aws is now aws-1 | Expected: old boxes get a numbered name. |
| "... is from before box specs" | lnk upgrade updates the cloud adapters; or leave out the fields it names. |
| "nothing there fits" | Loosen the range it names; lnk box sizes <account> lists what fits. |
| The cloud refuses a GPU or spot box | Ask it to raise your account's quota, in its console. |
| "lnk-aws can't list images" | lnk upgrade: an adapter from before images makes none. |
| "... has agents on it" | Save a box before an agent comes, or move the agents off it first. |
| "... holds secrets an image would carry" | Remove what it lists from the box, then save again. |
| "... stopped at the stage that failed" | Fix the command it names (sh -x on your script shows each); the stages before it ran. |
| "no ready image with hash ..." | None saved with that hash yet, or still saving: lnk box image list <account> --hash <hash>. |
| "lnk-aws doesn't say an image's hash" | lnk upgrade: an adapter from before hashes finds no image by its hash. |
What Link guarantees, and its gaps: Security. Why it works this way: Decisions.
