internkim

Kim mini

How a box that arrives in the post becomes a company's host without a connection file.

Kim mini is a small computer sold with internkim installed. It is optional hardware. The same software runs on any computer a company already has, and the host is what both install.

This page describes how a box that has never been configured becomes one company's host, and how it proves itself afterwards. A Linux computer that installed the host package without a connection file waits in the same way, as an empty box.

Every box starts identical

No per-box password, sticker, serial registered to a buyer or key is put in at assembly. On first boot the box generates an Ed25519 signing key and an X25519 encryption key in /var/lib/internkim/box. The private halves never leave its disk.

A box started with --host-name-from-company, as every Kim mini is, is named kimmini while it belongs to no company and takes its company's address, such as yeomyeonggeori, once claimed. Disconnecting it names it kimmini again. Two empty boxes on one network do not collide: multicast DNS gives the second one to answer kimmini-2.local, and the box reports the name it was given. The box renames itself only when its name differs and points 127.0.1.1 in /etc/hosts at the new name. A Linux computer that installed the host package keeps the name it has.

Getting online

A box that has no company installed and is offline, and was started with --wifi-setup, opens its own Wi-Fi network named kimmini- plus the day the box was made as YYMMDD, for example kimmini-261002. The network appears only in that state and closes once the box is online. Boxes made on the same day share a name, and a phone joins one of them; once that box is online its network closes and the next one is the only one left. Its password is intern-kim, the same on every box.

A new box has no internet to learn the date from, so the day is written when the box is made, as YYYY-MM-DD in /boot/firmware/internkim-made-on. Without that file the name has no date.

Joining it opens the setup page, because every name resolves to the box. The page asks for the office network's name and password, and the box joins it. At boot the box first waits 60 seconds for a network it has saved before it opens its own.

Once a box is connected, an administrator can move it to another network from company setup. The change form is offered while the box has reported nearby networks. The browser seals the new name and password to the box key with the purpose internkim wifi network, and the central plane relays ciphertext only. The box reports whether it joined the new network or could not change it.

The shared password carries two risks. When the box falls back from WPA3 to WPA2, someone nearby who has the published password and captures a handshake can decrypt the plain-HTTP setup page and read the office network's password. And anyone can broadcast a look-alike network with the same name and password to collect the office password.

Making a box

A box is a Raspberry Pi with an SD card written from one image. tools/build-kim-mini-image --release <tag> builds it in an arm64 Linux container through Docker. It starts from Raspberry Pi OS Lite and installs that release's host package. The box service runs with --wifi-setup and --host-name-from-company, and Wi-Fi is on for the country given by --wifi-country, KR unless told otherwise. Raspberry Pi OS keeps Wi-Fi off until a country is chosen, and a new box has nobody to choose one. The box's clock keeps the time zone given by --time-zone, Asia/Seoul unless told otherwise, where Raspberry Pi OS would keep Europe/London. The package is installed while the image is built, so the box needs no internet before its first boot. A login shell opens with fastfetch beside the round mark drawn in braille dots. On the box's own monitor the console font has no braille, so fastfetch shows there without the mark.

tools/flash-kim-mini <image> <diskN> writes the image to a card on a Mac and records the day in internkim-made-on. It writes only to an SD card or USB disk, and asks for the disk name before erasing it.

Nobody on a box holds root

A box has one login account, admin, and it has no sudo. It is in systemd-journal, so it reads every log, and in internkim-admin, so it restarts the host's services. The host is updated from company setup, as any host is.

The image leaves admin without a password. Once the box is connected, an administrator sets one in company setup, on the monitor or over SSH it is the same password, and changes it there later. The browser seals it to the box key with the purpose internkim admin password, the central plane relays the ciphertext, and the box opens it and hands it to chpasswd, which keeps only a hash. The box asks for a new password every minute and reports whether it was applied, and its first ask is what tells company setup it has an admin to set. Because nothing on the box raises an account to root, admin cannot open another person's workspace either, and the file permissions that keep people apart hold for the company's administrators too.

The image removes Raspberry Pi OS's pi account, which is in sudo, and its first-boot dialog, which would otherwise give whoever reaches the console first a sudo account. SSH is on and refuses root. Anyone who needs root on the hardware installs Raspberry Pi OS and the host package themselves.

Claiming a box

  1. The box joins the office network and announces its public key to the central plane every thirty seconds, as a box that belongs to no company yet.
  2. An administrator opens intern.kim from the same office network. The address the box announced from and the address of the browser match, so the empty box appears in a list.
  3. The administrator types the code the box shows. The setup page then names the box, the last four characters of its key and the address it announced from, and binds its public key to the company only once the administrator confirms. The box's own page shows the same four characters. A company that already has a box chooses whether the new one is added beside it or replaces every box it has.
  4. The box stops announcing, trades a signed assertion for the company's settings and a session, and starts the host services as that company's host.

An empty list means the box was claimed already or cannot reach the network.

The address only narrows the list, since everyone behind it sees the same boxes. What binds a box is its pairing code. The central plane draws one when the box asks: eight characters from a 32-symbol alphabet without 0, O, 1 or I, good for fifteen minutes and kept only as its SHA-256 hash. Five wrong codes spend it and the box is given another, and twenty wrong codes in an hour stop that company from trying until the hour is up.

While it is empty, the box shows its current code on a read-only page on its own network, port 18088, at its .local name and its LAN address. It reports both when it announces, and the setup page tells the administrator where to look. Anyone on that network can read the code, which the owner accepted in exchange for a box that needs no screen. The page closes once the box is claimed. The box also logs each code, and sudo internkim box code prints the current one.

Disconnect in company setup undoes a claim. The box belongs to no company again, announces itself, is given a new code and reopens its page. The session it already holds is refused from that moment, by the record, the web app and the gateway alike, because a host session names the box it was issued to and holds only while that box is the company's. Replacing the company's boxes ends their sessions the same way.

Only the company that claimed a box can disconnect it, so a box someone else claimed by mistake is undone by whoever holds it. Put an empty file named internkim-reset on the card's boot partition, which a Mac mounts as bootfs, and start the box. It forgets the company it installed, signs a request with its own key asking the central plane to release it, retries until the plane answers, locks the admin password so that company's administrators can no longer log in, and deletes the file. It then announces itself as an empty box. The company's files stay on the card; writing the card again is what clears them.

More than one box

A company runs on one box or several. One of them is the service box: it runs the company's services, answers the gateway and holds the company's data. Every other box stands by. A standby box holds the company key and installs nothing, and the gateway refuses it, so two boxes never answer for one company at once.

The company key is what the model key, mail passwords and backups are sealed to. The first box's own encryption key becomes it. A box added later announces its own key, and a box that already holds the company key seals it to the newcomer at its next check, within five minutes. Until then the setup page says the new box is receiving the key.

Every fifteen minutes the service box takes a snapshot of everything a backup holds and keeps it in the hosted service, sealed to the company key, where any of the company's boxes can open it and nothing else can. A snapshot is cut into chunks by their content, and only chunks the service box has not kept before are sent, so a quiet quarter hour costs a few megabytes. Each standby box fetches the newest snapshot's chunks as they appear. Only the service box may write snapshots, so a box that has just stopped serving cannot leave a stale one behind. The setup page shows when the newest one was made, and SNAPSHOTS_KEPT in /etc/internkim/company-host.env says how many are kept, eight unless set.

When the service box's building loses power or its line, an administrator chooses Make this box serve on a standby box. The serving box, if it is still up, stops its services and stands by. The new service box rebuilds the newest snapshot from the chunks it holds, restores it while keeping its own identity, and starts the company from it. What the old box held after that snapshot stays on the old box. While the company keeps no snapshot, the switch is refused, since the new box would have nothing to start from, and a box that cannot open the snapshots the company keeps stops with an error instead of starting the company empty. A take-over that fails is not retried until systemctl reset-failed internkim-take-over says to.

A company can let its boxes do this by themselves. Every box is seen by the hosted service once a minute. With automatic take-over on, a standby box that holds the company key takes the company over once the service box has not been seen for the lapse the administrator chose, five minutes at the shortest of the offered choices. One on another site goes before one on the lapsed box's site, and among those the first to check in after the lapse. The record makes the change in one transaction, so two standby boxes never both serve, and it makes none while the company keeps no snapshot to take over from. The service box stops its own services once half the lapse passes without the hosted service seeing it, and starts them again when it is seen and still serves, so a box cut off from the internet and the box that took its place never serve at once. It is off until an administrator turns it on, since a lapse that was only a slow router costs what the old box held after the newest snapshot, up to fifteen minutes.

Standby boxes also share the work. The service box runs a browser call itself while one of its four browsers is free. When all four are busy it puts the call in the company's queue on the gateway, and whichever connected box says it has room takes it. A box only reports its own free room, so no box knows how many others there are, and adding one is four more people browsing at once. A person's later calls go to the box that took their first one while it has room, so a sign-in on a site lasts as long as that box keeps their browser. When no box has room, the person hears the browser is busy, as on a single box. Commands, documents and files stay on the service box, where the people's workspaces are.

Put the boxes where the power and the internet line are different, and give boxes that share both the same site in company setup. Two places keep the company running when one goes dark.

The box's identity replaces the agent key

A host installed from a connection file proves itself with a bearer agent key, a random secret the web app issues once. A box needs no shared secret. It signs a JWT naming its own public key, valid for one minute, and trades it at /api/box/session for an hour-long session as the company's host and for the model key sealed to the company key. Mail passwords are sealed to the same key.

The daemon writes each new session over the old one ten minutes before it expires, and every reader of that file reads it per request. A stolen session dies within the hour.

agent keybox keypair
secret that crosses the networkthe key, at issuance and on every callnone; only signatures and short sessions
a leaked request log givesthe keyan expired session
taking it backrevoke in the web appdisconnect it in the web app, reset it, or claim another

A computer the company brings follows the same shape. On Linux, sudo internkim install spends the downloaded key, and the machine signs in with one it generated from then on.

Models

The administrator pastes an OpenRouter key at intern.kim. The browser encrypts it to the company key, and the ciphertext travels through the central plane to the box, which decrypts it on its own disk. The plane stores and relays a value it cannot read.

On this page