Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
170baed
chore: ignore local git worktrees
wilrnh Sep 14, 2026
73fd6f8
chore: scaffold ghostdrive tooling and shared shell helpers
wilrnh Sep 14, 2026
966de0b
feat: add config schema contract and validator
wilrnh Sep 14, 2026
ce01756
feat: add install-to-usb orchestration entrypoint
wilrnh Sep 14, 2026
9c536ea
feat: add ansible curated baseline provisioning
wilrnh Sep 14, 2026
34921ef
fix: target rootfs provisioning via chroot inventory
wilrnh Sep 14, 2026
fcf4a59
feat: add image generalize capture and restore scripts
wilrnh Sep 14, 2026
e2f3d15
feat: add CI checks and boot verification entrypoints
wilrnh Sep 14, 2026
770a2ea
fix: require explicit boot signals in smoke verification
wilrnh Sep 14, 2026
7ae2c1f
docs: add user and operator runbooks
wilrnh Sep 14, 2026
b3147f5
docs: strengthen README safety and prereq guidance
wilrnh Sep 14, 2026
51fa5b8
fix: harden boot checks and destructive image safety
wilrnh Sep 14, 2026
98c7cdd
fix: require restore integrity input and harden guard coverage
wilrnh Sep 14, 2026
7f8f676
fix: align CI PATH and harden manifest JSON output
wilrnh Sep 14, 2026
4d2bd61
fix: tighten safety checks and CI validator reliability
wilrnh Sep 14, 2026
8e8549d
test: mock schema validator for deterministic config tests
wilrnh Sep 14, 2026
7eb9a2c
fix: block symlink traversal in image generalization
wilrnh Sep 14, 2026
77312e4
test: exercise boot signal paths in verify-boot CI
wilrnh Sep 14, 2026
69a1e05
fix: require explicit target root for ansible inventory
wilrnh Sep 14, 2026
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
19 changes: 19 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
root = true

[*.sh]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
charset = utf-8

[Makefile]
indent_style = tab
end_of_line = lf
insert_final_newline = true
charset = utf-8

[*.md]
end_of_line = lf
insert_final_newline = true
charset = utf-8
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: ci

on:
pull_request:
push:
branches:
- main

jobs:
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update
- run: sudo apt-get install -y python3-pip
- run: python3 -m pip install --user check-jsonschema
- run: ~/.local/bin/check-jsonschema --help
- run: PATH="$HOME/.local/bin:$PATH" scripts/validate-config.sh config/defaults.yaml

test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update
- run: sudo apt-get install -y bats ansible python3-pip
- run: python3 -m pip install --user check-jsonschema
- run: PATH="$HOME/.local/bin:$PATH" make ci-check

verify-boot:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update
- run: sudo apt-get install -y qemu-system-x86 ovmf bats
- run: bats tests/bats/boot_smoke_signals.bats
- run: truncate -s 128M /tmp/ghostdrive-boot-smoke.img
- run: if scripts/verify/boot_smoke_bios.sh --image /tmp/ghostdrive-boot-smoke.img --timeout-seconds 10; then echo "unexpected BIOS smoke success for blank image"; exit 1; fi
- run: if scripts/verify/boot_smoke_uefi.sh --image /tmp/ghostdrive-boot-smoke.img --timeout-seconds 10; then echo "unexpected UEFI smoke success for blank image"; exit 1; fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.worktrees/
.env
.env.*
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.PHONY: test ci-check

test:
bats tests/bats

ci-check:
scripts/validate-config.sh config/defaults.yaml
bats tests/bats
111 changes: 110 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,111 @@
# ghostdrive
A bootable USB with everything to start hacking

Ghostdrive builds a reproducible Ubuntu engineering USB image using an install-to-USB pipeline.

## V1 Scope

- Linux build host only.
- Ubuntu LTS target only (`24.04`, `x86_64`).
- Install-to-USB architecture (not live+persistence mode).
- Curated baseline provisioning through Ansible.
- Golden image capture and restore workflow.

## Safety and Security Rules

- All destructive operations require an explicit `--device` argument.
- For non-CI runs, require an interactive human confirmation after printing the selected block device before any destructive write step.
- Validate configuration before install or CI checks.
- Keep secrets out of the repository.
- Generalize a target filesystem before capture to avoid cloned identity collisions.

## Prerequisites

Install required tools on a Linux host:

```bash
sudo apt-get update
sudo apt-get install -y \
bats \
ansible \
qemu-system-x86 \
ovmf \
zstd \
python3-pip
python3 -m pip install --user check-jsonschema
export PATH="$HOME/.local/bin:$PATH"
command -v check-jsonschema
```

If `command -v check-jsonschema` prints nothing, your shell is missing `$HOME/.local/bin` on PATH.

## Quickstart

1. Validate config:

```bash
scripts/validate-config.sh config/defaults.yaml
```

2. Prepare install orchestration for a target device:

```bash
scripts/build/install_to_usb.sh \
--config config/defaults.yaml \
--device /dev/sdX \
--workdir .ghostdrive-work
```

For non-CI usage, review the printed `/dev/sdX` target and complete the interactive confirmation before allowing the install step to proceed.

3. Apply curated baseline to the mounted target root filesystem:

```bash
scripts/provision/apply_baseline.sh --target-root /mnt/ghostdrive-root
```

4. Generalize and capture a golden image:

```bash
scripts/image/generalize.sh --root /mnt/ghostdrive-root
scripts/image/capture.sh --device /dev/sdX --output artifacts/ghostdrive.img.zst
scripts/image/write_manifest.sh --image artifacts/ghostdrive.img.zst --output artifacts/ghostdrive.manifest.json
```

5. Restore to another USB device:

```bash
scripts/image/restore.sh \
--image artifacts/ghostdrive.img.zst \
--manifest artifacts/ghostdrive.manifest.json \
--device /dev/sdY
```

## Verification

Run all local checks:

```bash
make test
```

CI-oriented checks:

```bash
make ci-check
```

Boot smoke checks (BIOS + UEFI):

```bash
truncate -s 128M /tmp/ghostdrive-boot-smoke.img
scripts/verify/boot_smoke_bios.sh --image /tmp/ghostdrive-boot-smoke.img --timeout-seconds 10
scripts/verify/boot_smoke_uefi.sh --image /tmp/ghostdrive-boot-smoke.img --timeout-seconds 10
```

## Documentation

- `docs/architecture.md`
- `docs/provisioning.md`
- `docs/cloning-workflow.md`
- `docs/threat-model.md`
- `docs/compatibility-matrix.md`
2 changes: 2 additions & 0 deletions ansible/inventory/local.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[ghostdrive_target]
target ansible_connection=chroot ansible_host=/__ghostdrive_target_root_required__
6 changes: 6 additions & 0 deletions ansible/roles/curated_default/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
curated_default_packages:
- git
- curl
- vim
- build-essential
5 changes: 5 additions & 0 deletions ansible/roles/curated_default/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- name: Install curated baseline packages
ansible.builtin.package:
name: "{{ curated_default_packages }}"
state: present
6 changes: 6 additions & 0 deletions ansible/site.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: Apply Ghostdrive curated baseline
hosts: ghostdrive_target
become: true
roles:
- curated_default
3 changes: 3 additions & 0 deletions config/defaults.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ubuntu_release: "24.04"
target_arch: "x86_64"
baseline_profile: "curated-default"
27 changes: 27 additions & 0 deletions config/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"ubuntu_release",
"target_arch",
"baseline_profile"
],
"properties": {
"ubuntu_release": {
"enum": [
"24.04"
]
},
"target_arch": {
"enum": [
"x86_64"
]
},
"baseline_profile": {
"enum": [
"curated-default"
]
}
},
"additionalProperties": true
}
34 changes: 34 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Architecture

Ghostdrive v1 is an install-to-USB pipeline for Ubuntu 24.04 x86_64.

## Modules

- Installer Orchestrator: `scripts/build/install_to_usb.sh`
- Seed Renderer: `scripts/build/render_autoinstall_seed.sh`
- Provisioner: `scripts/provision/apply_baseline.sh` and `ansible/`
- Image Pipeline: `scripts/image/generalize.sh`, `scripts/image/capture.sh`, `scripts/image/write_manifest.sh`, `scripts/image/restore.sh`
- Verifier: `scripts/verify/boot_smoke_bios.sh`, `scripts/verify/boot_smoke_uefi.sh`, and Bats tests under `tests/bats/`

## Data Flow

1. Validate the user configuration (`scripts/validate-config.sh`) against `config/schema.json`.
2. Prepare install orchestration for a specific target block device.
3. Apply the curated baseline profile with Ansible to the target root filesystem.
4. Generalize machine identity artifacts before capture.
5. Capture a compressed image and write checksum/size metadata.
6. Restore the image to additional USB devices.
7. Verify behavior in CI and with boot smoke checks.

## Constraints

- Build host: Linux only.
- Target OS and architecture: Ubuntu 24.04 LTS x86_64.
- Output model: fully installed USB image.
- Repository policy: no secrets in source control.

## Operational Boundaries

- Device writes are destructive and require explicit `--device` selection.
- Image scripts require real block devices and image files.
- Boot smoke scripts validate firmware-level boot signals for BIOS and UEFI paths.
51 changes: 51 additions & 0 deletions docs/cloning-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Cloning Workflow

This workflow captures a golden image from a prepared USB install and restores it to additional devices.

## 1. Generalize Before Capture

Generalize the mounted target root so clones do not share host identity artifacts.

```bash
scripts/image/generalize.sh --root /mnt/ghostdrive-root
```

The generalization step clears machine identity and volatile runtime artifacts:

- `/etc/machine-id`
- `/var/lib/dbus/machine-id`
- `/etc/ssh/ssh_host_*`
- `/var/log/*`

## 2. Capture Compressed Image

```bash
scripts/image/capture.sh --device /dev/sdX --output artifacts/ghostdrive.img.zst
```

## 3. Write Artifact Manifest

```bash
scripts/image/write_manifest.sh --image artifacts/ghostdrive.img.zst --output artifacts/ghostdrive.manifest.json
```

Manifest fields include image path, SHA-256 checksum, and size in bytes.

## 4. Restore to a New USB Device

```bash
scripts/image/restore.sh --image artifacts/ghostdrive.img.zst --device /dev/sdY --manifest artifacts/ghostdrive.manifest.json
```

Restore requires integrity verification on every run. Provide either `--manifest` or `--sha256`.

## 5. Validate Restored Media

- Confirm device partitions and filesystem mount cleanly.
- Run BIOS and UEFI boot smoke scripts when validating pipeline behavior.
- Perform first-boot checks on hardware before fleet replication.

## Safety Notes

- `capture.sh` and `restore.sh` require real block devices.
- Always verify selected `--device` values before running destructive commands.
19 changes: 19 additions & 0 deletions docs/compatibility-matrix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Compatibility Matrix

| Area | Supported in v1 | Notes |
|---|---|---|
| Build host OS | Linux | Ubuntu LTS recommended |
| Build host architecture | x86_64 | CI runs on Ubuntu 24.04 x86_64 |
| Target OS | Ubuntu 24.04 LTS | Fixed by config schema |
| Target architecture | x86_64 | Fixed by config schema |
| Output format | Installed USB image | Captured as compressed `.img.zst` |
| Provisioning | Ansible curated baseline | `ansible/site.yml` |
| VM boot verification | BIOS and UEFI smoke checks | QEMU + OVMF |
| Cross-platform builders | No | Out of scope for v1 |
| ARM targets | No | Out of scope for v1 |

## Validation Sources

- Config constraints: `config/schema.json`
- CI checks: `.github/workflows/ci.yml`
- Boot smoke scripts: `scripts/verify/boot_smoke_bios.sh`, `scripts/verify/boot_smoke_uefi.sh`
39 changes: 39 additions & 0 deletions docs/provisioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Provisioning Runbook

This runbook applies the curated Ghostdrive baseline to an installed target root filesystem.

## Inputs

- Target root mount path (required): `--target-root`
- Optional inventory override: `--inventory`
- Baseline playbook: `ansible/site.yml`
- Baseline profile in config: `curated-default`

## Prerequisites

- Linux build host.
- `ansible-playbook` available on `PATH`.
- Target root filesystem mounted (for example `/mnt/ghostdrive-root`).

## Apply Baseline

```bash
scripts/provision/apply_baseline.sh --target-root /mnt/ghostdrive-root
```

The script creates a temporary chroot inventory and runs:

```bash
ansible-playbook -i ansible/inventory/local.ini -i <tmp-inventory> ansible/site.yml -e target_root=/mnt/ghostdrive-root
```

## Common Failures

- `--target-root is required`: pass a mount path.
- `target root does not exist`: mount the target filesystem first.
- `inventory does not exist`: check the `--inventory` path.

## Post-Run Checks

- Confirm baseline packages and configuration are present on target.
- Continue to generalization and image capture workflow.
Loading
Loading