A HashiCorp Packer builder plugin for the SIMH historical computer emulator. Build automated machine images for classic computers — VAX, PDP-11, Altair, IBM 7094, and dozens more.
Unlike QEMU-based builders that communicate with the VM through VNC, this plugin drives SIMH through command files for device configuration and a telnet connection to the simulated console for interactive boot sequences.
Key features:
- Command file generation — automatically builds SIMH configuration from structured HCL fields (CPU, memory, disks, network)
- Console interaction — uses expect/send
boot_stepsto drive the simulated console over telnet, with full Packer boot command syntax support (<enter>,<wait5>,<leftCtrl>, etc.) - Two-pass template interpolation — runtime variables like
{{ .ISOPath }}and{{ .HostPort }}are resolved at execution time - SSH communicator support — connect to the guest OS for provisioning via
SSH (or use
communicator = "none"for non-networked builds) - Optional ISO download — use
iso_urlto download install media, or provide pre-existing disk images directly
packer plugins install github.com/cross-platform-actions/simhDownload the appropriate binary from the releases page and place it in the Packer plugins directory:
~/.packer.d/plugins/packer-plugin-simhsource "simh" "pdp11-test" {
simh_binary = "pdp11"
output_directory = "output-pdp11"
vm_name = "pdp11-test"
memory = "256K"
disk_attachments {
device = "RL0"
path = "/path/to/existing/rt11.dsk"
set_commands = ["RL02"]
}
communicator = "none"
shutdown_timeout = "2m"
}
build {
sources = ["source.simh.pdp11-test"]
}source "simh" "vax-vms" {
simh_binary = "vax"
iso_url = "https://example.com/vms-install.iso"
iso_checksum = "sha256:abcdef1234567890..."
output_directory = "output-vax-vms"
vm_name = "vax-vms"
cpu_type = "MicroVAX"
memory = "128M"
cpu_options = ["CONHALT"]
disk_attachments {
device = "RQ0"
path = "{{ .OutputDir }}/system.dsk"
set_commands = ["RD54"]
}
disk_attachments {
device = "RQ3"
path = "{{ .ISOPath }}"
options = "-r"
}
network_device {
device = "XQ"
mac = "08-00-2B-AA-BB-CC"
attach_type = "nat:tcp={{ .HostPort }}:10.0.2.15:{{ .GuestPort }}"
}
boot_wait = "10s"
boot_steps = [
[">>>", "BOOT DUA0<enter>", "Boot from DUA0"],
["Username:", "SYSTEM<enter>", "Log in as SYSTEM"],
["Password:", "MANAGER<enter>", "Enter password"],
["$", "@SYS$SYSTEM:SHUTDOWN<enter>", "Initiate shutdown"],
["SHUTDOWN", "YES<enter>", "Confirm shutdown"],
]
communicator = "ssh"
ssh_username = "system"
ssh_password = "manager"
ssh_port = 22
host_port_min = 2222
host_port_max = 4444
shutdown_timeout = "10m"
http_directory = "http"
}
build {
sources = ["source.simh.vax-vms"]
provisioner "shell" {
inline = ["SHOW SYSTEM"]
}
}| Field | Type | Description |
|---|---|---|
simh_binary |
string |
Name or path of the SIMH simulator binary (e.g. "vax", "pdp11"). Resolved via $PATH. |
| Field | Type | Default | Description |
|---|---|---|---|
output_directory |
string |
"output-<BuildName>" |
Directory for build outputs. |
vm_name |
string |
"packer-<BuildName>" |
Base name for generated files. |
| Field | Type | Default | Description |
|---|---|---|---|
cpu_type |
string |
"" |
SIMH CPU type (e.g. "MicroVAX"). |
memory |
string |
"" |
Memory size (e.g. "128M"). |
cpu_options |
[]string |
[] |
Additional SET CPU operands. |
disk_attachments {
device = "RQ0" # Required: SIMH device name
path = "{{ .OutputDir }}/system.dsk" # Required: image path (supports templates)
set_commands = ["RD54"] # Optional: SET commands before ATTACH
options = "-r" # Optional: ATTACH options
}network_device {
device = "XQ" # Required
mac = "08-00-2B-AA-BB-CC" # Optional
attach_type = "nat:tcp={{ .HostPort }}:10.0.2.15:{{ .GuestPort }}" # Required
set_commands = [] # Optional
}network_commands — raw SIMH SCP commands for additional network setup.
Ordered list of [expect, send, description] tuples that interact with
the simulated console over telnet:
boot_steps = [
[">>>", "BOOT DUA0<enter>", "Boot from DUA0"],
["Username:", "SYSTEM<enter>", "Log in"],
]| Field | Type | Default | Description |
|---|---|---|---|
boot_wait |
duration |
"10s" |
Delay before connecting to console. |
boot_key_interval |
duration |
"0s" |
Minimum interval between keystrokes. |
boot_step_timeout |
duration |
"5m" |
Timeout per expect pattern. |
| Field | Type | Default | Description |
|---|---|---|---|
console_port_min |
int |
4000 |
Minimum telnet console port. |
console_port_max |
int |
4100 |
Maximum telnet console port. |
console_bind_address |
string |
"127.0.0.1" |
Console bind address. |
console_log |
bool |
true |
Enable console output logging. |
| Field | Type | Default | Description |
|---|---|---|---|
comm_host |
string |
"" |
Override communicator host. |
host_port_min |
int |
2222 |
Minimum host port for SSH. |
host_port_max |
int |
4444 |
Maximum host port for SSH. |
skip_port_forward |
bool |
false |
Skip host port selection. |
Plus all standard Packer communicator fields
(ssh_username, ssh_password, ssh_port, etc.).
| Field | Type | Default | Description |
|---|---|---|---|
command_file |
string |
"" |
External SIMH command file (overrides generated file). |
http_ip |
string |
"" |
Override HTTP server IP for guest. |
simh_args |
[]string |
[] |
Extra SCP commands before disk attachments. |
shutdown_timeout |
duration |
"5m" |
Max time to wait for SIMH to exit. |
Available in boot_steps, disk_attachments, network_device,
network_commands, and simh_args:
| Variable | Description |
|---|---|
{{ .HTTPIP }} |
IP to reach Packer's HTTP server |
{{ .HTTPPort }} |
HTTP server port |
{{ .Name }} |
vm_name value |
{{ .OutputDir }} |
Absolute output directory path |
{{ .ISOPath }} |
Path to downloaded ISO |
{{ .SSHPublicKey }} |
Generated SSH public key |
{{ .HostPort }} |
Selected host port for communicator |
{{ .GuestPort }} |
Guest communicator port |
go build -o packer-plugin-simh .go test -v ./...After modifying Config structs:
go install github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc@latest
go generate ./...Contributions are welcome. Please follow these guidelines:
- Fork the repository and create your branch from
main. - Write tests for any new functionality.
- Run the full test suite before submitting:
go test -race ./... - Format your code:
gofmt -w . - Open a pull request with a clear description of the change.
packer-plugin-simh/
├── main.go # Plugin entry point
├── version/version.go # Plugin version
├── builder/simh/
│ ├── builder.go # Builder struct, Run(), step sequence
│ ├── config.go # Config struct, Prepare(), validation
│ ├── config.hcl2spec.go # Generated HCL2 spec
│ ├── artifact.go # Build artifact
│ ├── driver.go # SIMH process management
│ ├── driver_mock.go # Mock driver for tests
│ ├── console_driver.go # Telnet BCDriver implementation
│ ├── ssh.go # Communicator helpers
│ ├── step_prepare_output_dir.go # Output directory management
│ ├── step_create_command_file.go # SIMH command file generation
│ ├── step_configure_console.go # Console port selection
│ ├── step_discover_http_ip.go # HTTP IP discovery
│ ├── step_forward_port.go # Host port selection
│ ├── step_run.go # SIMH process launch
│ ├── step_boot_command.go # Console interaction (boot steps)
│ ├── step_shutdown.go # Shutdown handling
│ └── *_test.go # Test files
├── .github/workflows/
│ ├── ci.yml # CI pipeline
│ └── release.yml # Release automation
└── .goreleaser.yml # GoReleaser config
Releases are automated via GitHub Actions and GoReleaser.
-
Tag the release:
git tag v0.1.0 git push origin v0.1.0
-
The
release.ymlworkflow will automatically:- Build binaries for Linux, macOS, and Windows (amd64 + arm64)
- Create a GitHub release with the binaries
- Generate a changelog from commit messages
-
Version format: Use semantic versioning with a
vprefix (e.g.v0.1.0,v1.0.0). -
Pre-release versions: To create a pre-release, update
version/version.goto setVersionPrereleaseto a non-empty string (e.g."rc1") and tag accordingly (e.g.v0.2.0-rc1).
This project is open source. See the repository for license details.