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 disk

Connect 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), --project and --zone (gcp): where new things go. Defaults: your login's region or zone, else us-east-1 or us-central1-a; the service account's or gcloud's project, else connect asks 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.small or e2-small.
  • --region <region|zone>: where. Default: the account's.
  • --cpus, --memory, --gpus, --disk, --spot and the rest: what machine, as a box spec says it.
  • --no-login: don't sign lnk in 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 installed

run 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 instead

lnk 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 make

A 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-1

A 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 setup runs the script with sh -e, so it stops at the first command that fails, and says which: its line in the script, or, where sh doesn'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" --yes as 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 app does 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 disk

Saving 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 image

On 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 one

The 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" --yes

On 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 scripts

list 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 asking

Removing 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 it

Its 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 account

The 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_ed25519 and known_hosts: Link's SSH key for boxes, and their host keys.
  • ~/.config/lnk/machine.toml on a box: its machine ID and name.

Troubleshooting

SymptomFix
"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 boxlnk 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 idAn interrupted start: lnk box remove <name>.
A box made elsewhere doesn't showlnk box list --all-regions; check lnk cloud list and your GitHub login.
A box made before tags doesn't showRun lnk box list on the computer that made it, with the box running.
A box named aws is now aws-1Expected: 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 boxAsk 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.