Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on:
pull_request:
push:
branches: [master]
# The template resolves the SF Compute release index at build time, so
# the repo can break without a commit: a release that drops the LTS the
# default keys on, or an index that stops answering, fails validate.
# The daily run finds that before a reader does.
#
# GitHub disables scheduled workflows in a public repository after 60
# days without repository activity, and this repo is meant to go quiet.
# Re-enable it from the Actions tab when that happens; firing
# workflow_dispatch from the release pipeline instead would be immune.
schedule:
- cron: "17 6 * * *"
workflow_dispatch:

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
env:
# `packer init` fetches the qemu plugin from GitHub releases; the
# unauthenticated rate limit is low enough to flake a shared runner.
PACKER_GITHUB_API_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4

- uses: hashicorp/setup-packer@v3
with:
version: "1.15.0"

- name: Install plugins
run: packer init .

- name: Check formatting
run: packer fmt -check -diff .

# -evaluate-datasources makes validate fetch the release index and
# resolve the base image, which is the part worth checking: plain
# validate stops at an unresolved checksum. Firmware paths are not
# checked by validate, so the runner needs no OVMF.
- name: Validate, resolving the live release index
run: packer validate -evaluate-datasources .

- name: Lint shell scripts
run: shellcheck ./*.sh
141 changes: 100 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,69 +61,125 @@ packer build -only='qemu.sfc_base' -var 'accelerator=kvm' .
```

Expect roughly 15 minutes on Linux with KVM, and a few hours on macOS under
emulation. Packer downloads a 3.3 GB base image first. The result is a sparse
emulation. Packer downloads the base image first (~3.5 GB). The result is a sparse
20 GiB raw file — it takes far less room on disk, but `sf images upload`
transfers the full 20 GiB. Need more space for your software? Add
`-var 'disk_size=40G'` (the platform caps images at 75 GiB).

`-only` matters: without it Packer builds **both** starting points at once. If
you mistype the name, Packer runs nothing and still exits 0, so check that it
actually printed a build.
`-only` matters: without it Packer builds **both** starting points at once.
Mistype the name and Packer lists the valid ones and exits without building.

Want to check the template before building? `validate` doesn't fetch anything
unless you ask it to, so it can't yet know which base image to download — pass
`-evaluate-datasources`:

```bash
packer validate -evaluate-datasources .
```

Without the flag it fails with `invalid checksum: encoding/hex: invalid byte:
U+003C '<'`, which doesn't sound like the real problem. `packer build` needs no
such flag.

Re-running? Packer refuses to overwrite `output/`. Pass `-force`, or
`rm -rf output/` first.

The image lands at `output/sfc_base/ubuntu-24.04-cuda-13.2-custom.raw`.
Upload it:
The image lands at `output/sfc_base/sfc-base-custom.raw`. Upload it:

```bash
sf images upload --name my-image --file output/sfc_base/ubuntu-24.04-cuda-13.2-custom.raw
sf images upload --name my-image --file output/sfc_base/sfc-base-custom.raw
```

Before you push 20 GiB, confirm it is what the platform expects:

```bash
qemu-img info output/sfc_base/sfc-base-custom.raw
```

You want `file format: raw` and a virtual size at or under 75 GiB. The real
test is booting an instance from it — do that before you rely on the image.

To start from stock Ubuntu instead:

```bash
packer build -only='qemu.ubuntu' -var 'accelerator=kvm' .
packer build -only='qemu.ubuntu' .
```

That one lands at `output/ubuntu/ubuntu-noble-custom.raw` and uploads the same
way.

## Making it yours

Edit **`customize.sh`**. That is the whole customization surface — it
runs as root inside the build VM on both starting points. Everything
else in this repo is boilerplate that makes the resulting image boot
correctly on SF Compute.

If you only ever build on our base images, delete `install-nvidia.sh`, its
`provisioner` block in `build.pkr.hcl`, and the `source "qemu" "ubuntu"`
block — otherwise the repo still offers a path that no longer installs a
driver.
If you only ever build on our base images, you can delete the stock-Ubuntu
path — otherwise the repo still offers a route that no longer installs a
driver. Remove all four, or the template will not load:

- `install-nvidia.sh`
- its `provisioner` block in `build.pkr.hcl`
- the `source "qemu" "ubuntu"` block
- the `"source.qemu.ubuntu"` line from the `build` block's `sources` list

The `nvidia_driver_branch` variable becomes unused too, and can go.
`ubuntu_codename` stays: the base-image path uses it to pick the LTS.

Going the other way, keeping only stock Ubuntu, also means removing the
`data "http"` block, the `sfc_*` locals and the `manifest` post-processor.
Packer evaluates data sources for every build, so until they are gone the
stock-Ubuntu path still needs the release index to be reachable.

## Choosing a base image

`build.pkr.hcl` pins a specific release by default, so repeated builds
reproduce the same bytes. To see what is current:
By default the build resolves the current release at build time and, within
it, the Ubuntu 24.04 image with the newest CUDA — there is no version to keep
up to date in this repo. Look for the `Trying ...` line in Packer's output: it
names the release tag and the image it picked.

To see what is available in the SF Compute release index (`tiny-llama.sfcc.xyz`,
operated by SF Compute):

```bash
curl -fsSL https://tiny-llama.sfcc.xyz/latest.json | jq -r '.tag, (.images[].name)'
```

`latest.json` is the published index of the newest release: the tag, and
every image in it with its URL, SHA256 and size. Point the build at one
with `-var`:
Each release carries one image per CUDA minor for each of the two newest Ubuntu
LTS releases. To build on a different LTS, change `ubuntu_codename`; you still
get its newest CUDA:

```bash
packer build -only='qemu.sfc_base' -var 'ubuntu_codename=resolute' .
```

To build on a specific CUDA, name the image with `base_image_name`:

```bash
packer build -only='qemu.sfc_base' \
-var 'base_image_tag=<tag from latest.json>' \
-var 'base_image_name=ubuntu-22.04-cuda-13.1' .
packer build -only='qemu.sfc_base' -var 'base_image_name=<name from latest.json>' .
```

Then pin that tag in `build.pkr.hcl` so your builds stay reproducible.
The output filename is the same whichever image was picked;
`output/sfc_base/manifest.json` records which one it was.

Integrity is checked against the `SHA256SUMS` published alongside each
release; Packer fetches it and picks the line matching the image it is
downloading. Releases published before `SHA256SUMS` existed don't have
one, and Packer will fail with a 404 — for those, pass
`-var 'base_image_checksum=none'`.
**Pin the release if you need a repeatable starting point.** Tracking the latest
means a rebuild next month begins from a different base image — different driver,
CUDA and kernel. Pinning fixes that. It does not make the build bit-for-bit
reproducible: `customize.sh` still installs whatever the Ubuntu archive has that
day.

```bash
packer build -only='qemu.sfc_base' -var 'base_image_tag=<tag from latest.json>' .
```

You don't supply a checksum. The release index lists each image's sha256 beside
its URL and the build enforces it, so a corrupted or truncated download fails
instead of producing a broken image.

Each build writes `output/sfc_base/manifest.json` recording the release and
image it resolved. That is where to find the values to pass to `base_image_tag`
and `base_image_name` if you want to rebuild on the same base.

## What the boilerplate is doing

Expand All @@ -139,6 +195,24 @@ cloud-init datasource when an instance launches. The root password in
`cloud-init/user-data` exists only so Packer can log in during the
build, and `finalize.sh` removes it.

## When a build fails

| What you see | What to do |
|---|---|
| `E: Unable to locate package packer` | Packer is not in the Ubuntu archive. See [Requirements](#requirements). |
| `packer init` reports an unknown command | Your Packer predates 1.7. Ubuntu 22.04 ships 1.6.6; install a current one per Requirements. |
| `Output directory 'output/...' already exists` | `packer build -force ...`, or `rm -rf output/`. |
| `Datasource.Execute failed: ... Response code: 404` | The tag you pinned has no release index. Use the tag from `latest.json` (the current release) or from an earlier build's `output/sfc_base/manifest.json`, or drop `-var base_image_tag`. If you did not pin a tag, the index itself is missing: check with `curl -fsSL https://tiny-llama.sfcc.xyz/latest.json`. |
| `Datasource.Execute failed: ... dial tcp ... no such host`, or `connection refused` | The build looks up the base image over the network before it starts, for either starting point. Check you can reach `https://tiny-llama.sfcc.xyz/latest.json`; behind a proxy, set `HTTPS_PROXY`. |
| `Call to function "jsondecode" failed: invalid character '<'` | Something returned HTML instead of the release index — usually a captive portal or a proxy login page. Get past it in a browser, then retry. |
| `invalid checksum: encoding/hex: invalid byte` from `packer validate` | Plain `validate` does not run data sources, so the digest is still unresolved. Use `packer validate -evaluate-datasources .`. |
| `Call to function "max" failed: must pass at least one number` | No image in that release matches: `base_image_name` is not in it, or `ubuntu_codename` is an LTS it does not carry. List the names with the `jq` command under [Choosing a base image](#choosing-a-base-image). |
| `Could not access KVM kernel module` | No `/dev/kvm`, or you are not in the `kvm` group. Drop `-var 'accelerator=kvm'` to build under emulation. |
| QEMU exits immediately on macOS | Drop `-var 'accelerator=kvm'`; macOS has no KVM. |
| `Could not open '/usr/share/OVMF/...'` | Firmware auto-detection missed yours. Pass `-var 'efi_firmware_code=...' -var 'efi_firmware_vars=...'`; both must be the same variant, so a 4M code file needs a 4M vars file. |
| `Timeout waiting for SSH` | Usually a slow emulated boot. Build on Linux with KVM, or raise `ssh_timeout` in `build.pkr.hcl`. |
| A step you added to `customize.sh` failed | Packer deletes `output/` on failure. Re-run with `-on-error=abort` to keep the VM and disk so you can look. |

## Image requirements

If you build your own template from scratch rather than copying this
Expand All @@ -159,21 +233,6 @@ The SF Compute base images ship all of it.
See the [Images documentation](https://docs.sfcompute.com/preview/images)
for the full picture.

## When a build fails

| What you see | What to do |
|---|---|
| `E: Unable to locate package packer` | Packer is not in the Ubuntu archive. See [Requirements](#requirements). |
| `packer init` reports an unknown command | Your Packer predates 1.7. Ubuntu 22.04 ships 1.6.6; install a current one per Requirements. |
| `Output directory 'output/...' already exists` | `packer build -force ...`, or `rm -rf output/`. |
| A 404 fetching `SHA256SUMS` | The tag you pinned predates checksums. Add `-var 'base_image_checksum=none'`. |
| `Could not access KVM kernel module` | No `/dev/kvm`, or you are not in the `kvm` group. Drop `-var 'accelerator=kvm'` to build under emulation. |
| QEMU exits immediately on macOS | Drop `-var 'accelerator=kvm'`; macOS has no KVM. |
| `Could not open '/usr/share/OVMF/...'` | Firmware auto-detection missed yours. Pass `-var 'efi_firmware_code=...' -var 'efi_firmware_vars=...'`; both must be the same variant, so a 4M code file needs a 4M vars file. |
| `Timeout waiting for SSH` | Usually a slow emulated boot. Build on Linux with KVM, or raise `ssh_timeout` in `build.pkr.hcl`. |
| A step you added to `customize.sh` failed | Packer deletes `output/` on failure. Re-run with `-on-error=abort` to keep the VM and disk so you can look. |
| The build succeeded but `packer` printed no build | You mistyped `-only`. Packer exits 0 having run nothing. |

## Files

| File | Purpose |
Expand Down
Loading
Loading