# install — for idiots (install a real service, step by step)
> **Class:** `idiot`. Every step, nothing assumed. Goal: **watch Skidbladnir install a service two
> different ways (systemd and OpenRC) from the SAME description, and prove it — without touching a
> real machine.** If you already know Rust, read [`install-guide.md`](install-guide.md).
## What you need first
- A terminal. In it, type this and press **Enter**:
```
cd /run/media/rickard/T9/git/Skidbladnir
```
- Rust (`cargo`). Check it — type and press **Enter**:
```
cargo --version
```
## Step 1 — the big idea, in one sentence
You write **one** description of your service (what to run, as which user, which ports). Skidbladnir
turns it into the right files and commands for whatever init system the target uses — systemd on
Ubuntu, OpenRC on Alpine — and it does this as **data you can inspect** before anything runs.
## Step 2 — run the install tests and watch them pass
The install lifecycle has tests that install a service into a temporary folder (so no root, no real
machine) and check every file and command. Run them — type and press **Enter**:
```
cargo test --release install_product
```
Look for the green `test result: ok`. Two tests just ran:
- `install_product_on_systemd_system` — installed the service the **systemd** way.
- `install_product_on_openrc` — installed the **same** service the **OpenRC** way.
## Step 3 — see the "one spec, two backends" proof
Type and press **Enter**:
```
cargo test --release one_spec_renders_to_systemd_and_openrc
```
That single test takes ONE `ServiceSpec` and renders it to BOTH systemd and OpenRC, each in its
own native shape. That is the whole point of the install tree: describe once, install anywhere.
The SAME description now also renders to **SysV** (`/etc/init.d/<name>` LSB script), **runit**
(a `/etc/sv/<name>/` service directory), **dinit** (a `/etc/dinit.d/<name>` textual service
description), **s6-rc** (a `/etc/s6-rc/source/<name>/` compiled-source definition dir), **OCI**
(a `podman run` container invocation), **Quadlet** (a systemd `.container` unit `podman-systemd`
turns into a `<name>.service`) and **Windows SCM** (a WiX `.wxs` service fragment driven by
`sc.exe`) — **all eleven** backends from one spec, full coverage. Watch the runit, dinit, s6-rc,
OCI, Quadlet and Windows ones:
```
cargo test --release runit_renders_a_service_dir_with_run_finish_conf_and_log
cargo test --release dinit_renders_a_service_description_and_env_file
cargo test --release s6rc_renders_a_source_definition_dir_with_run_finish_deps_and_env
cargo test --release oci_renders_a_podman_run_invocation_and_env_file
cargo test --release quadlet_renders_a_container_unit_and_verbs
cargo test --release windows_service_renders_a_wix_fragment_and_sc_verbs
```
## Step 4 — understand the three verbs
Skidbladnir treats these as three different jobs (run each test to watch it):
- **install** = make the service + unpack the product + create its data and log folders.
```
cargo test --release install_product_on_systemd_system
```
- **update** = swap in a new version but **keep** the data and logs.
```
cargo test --release update_product_preserves_data_and_log_and_swaps_version
```
- **uninstall** = remove the service; keep the data unless you say "purge".
```
cargo test --release uninstall_product_purge_flag_controls_data_retention
```
Each green line is Skidbladnir proving that job works, into a throwaway folder.
## Step 5 — why "verbs as data" means you can trust it
Notice none of those tests needed root or a real service manager. That is because Skidbladnir does
not *do* the install directly — it produces the list of files to write and the list of commands to
run (`enable`, `start`, …) as plain data. The test reads that data and checks it. Only the real
`apply` step actually runs the commands. So you can always see exactly what *would* happen first.
## Step 6 — where secrets go (and where they must NOT)
A service's password or token is a secret. The systemd unit file lives at
`/etc/systemd/system/<name>.service`, which **anyone on the machine can read** (mode 0644). So a
secret must never be written into it. Skidbladnir gives you two knobs: `with_env(key, value)` for
ordinary settings (these end up in the unit, in the clear) and `with_secret_env(key, value)` for
secrets (these go **only** into a locked-down env file that is `chmod 600` — readable by nobody but
the owner). If you set a secret but forget to say where the env file lives, Skidbladnir refuses to
install rather than leak it. Watch the guard prove it:
```
cargo test --release secret_env_never_leaks_into_world_readable_unit
cargo test --release write_private_file_is_0600_even_over_a_loose_preexisting_file
```
The first line checks the token is nowhere in the unit but is in the private file; the second checks
the private file is `chmod 600` the instant it is created — never briefly world-readable.
## Step 6 — see the identity a real install would mint
Type and press **Enter**:
```
cargo test --release fresh_dirs_mint_different_install_ids
```
This proves that installing onto the same machine twice keeps the same **machine fingerprint** but
gets a fresh **install id** each time — that is how Skidbladnir tells "same machine, reinstalled"
apart from "a different machine".
## Where to go next
- To drive a real install (with root, on a real target), read [`install-guide.md`](install-guide.md).
- To understand *why* it is a tree of init backends and not just systemd, read
[`install-reasoning.md`](install-reasoning.md).