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
16 changes: 16 additions & 0 deletions .icons/mise.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions registry/droopy4096/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
display_name: "D Makovey"
bio: "Lifelong tinkerer"
avatar: "./.images/avatar.png"
github: "droopy4096"
status: "community"
---

# D Makovey

Lifelong tinkerer and breaker of things
153 changes: 153 additions & 0 deletions registry/droopy4096/modules/mise-install/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
display_name: mise install
Comment thread
bpmct marked this conversation as resolved.
description: Install mise and run `mise install` inside a cloned repository.
icon: ../../../../.icons/mise.svg
verified: false
tags: [helper, mise, devtools]
---

# mise install

Installs [`mise`](https://mise.jdx.dev/getting-started.html) on the workspace and runs `mise install` inside a repository directory so language runtimes and tools declared in `.mise.toml` / `.tool-versions` are provisioned before the user starts working.

Designed to compose with the [`coder/git-clone`](https://registry.coder.com/modules/coder/git-clone) module: pass `module.git_clone.repo_dir` as `repo_dir`.

```tf
module "mise_install" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/droopy4096/mise-install/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
repo_dir = module.git_clone[count.index].repo_dir
}
```

The install script downloads `mise` from `https://mise.run` into `install_dir` (defaults to `$HOME/.local/bin`) and appends `mise activate` to `~/.bashrc` and `~/.zshrc`. The post-install script then runs `mise trust` (optional) and `mise install` inside `repo_dir`.

## Examples

### Compose with `coder/git-clone`

```tf
module "git_clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "2.0.3"
agent_id = coder_agent.main.id
url = "https://github.com/example/repo"
}

module "mise_install" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/droopy4096/mise-install/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
repo_dir = module.git_clone[count.index].repo_dir
}
```

### Disable shell activation and `mise trust`

Use this when the workspace image already activates `mise` globally, or the repository does not use `.mise.toml`:

```tf
module "mise_install" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/droopy4096/mise-install/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
repo_dir = module.git_clone[count.index].repo_dir
activate_shells = []
mise_trust = false
}
```

### Custom install directory

```tf
module "mise_install" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/droopy4096/mise-install/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
repo_dir = module.git_clone[count.index].repo_dir
install_dir = "$HOME/.mise/bin"
}
```

### Air-gapped / mirrored installer

Point the installer at an internal mirror, pin the release, and skip the download entirely by using the `mise` already baked into the workspace image:

```tf
# Fully mirrored: download the installer from an internal HTTPS mirror
# and pin the mise release for reproducibility.
module "mise_install" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/droopy4096/mise-install/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
repo_dir = module.git_clone[count.index].repo_dir
install_url = "https://artifacts.internal.example.com/mise/install.sh"
mise_version = "v2024.9.0"
}

# Bring-your-own binary: the image already ships mise; skip the download.
module "mise_install_byo" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/droopy4096/mise-install/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
repo_dir = module.git_clone[count.index].repo_dir
install_mise = false
mise_bin = "/opt/mise/bin/mise" # optional; omit if 'mise' is on PATH
}
```

## Network egress

This module reaches the following external endpoints. Mirror or allow-list them (or use `install_mise = false`) in restricted environments:

| When | Endpoint | Overridable via |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Install phase (only when `install_mise = true`) | `https://mise.run` — installer shell script | `install_url` |
| Install phase (transitive from installer) | `https://github.com/jdx/mise/releases/*` — mise binary release assets | Mirror inside `install_url`'s script or use `install_mise = false` + `mise_bin` |
| Post-install phase (`mise install`) | `https://mise.jdx.dev/*` — plugin registry and metadata | `MISE_*` env vars (see mise docs); not exposed by this module |
| Post-install phase (transitive) | Language-runtime hosts (`python.org`, `nodejs.org`, `go.dev`, etc.) — driven by `.mise.toml` / `.tool-versions` in `repo_dir` | Configure `mise` plugins/mirrors in the image |

If your environment blocks all outbound traffic, set `install_mise = false`, ship `mise` in the image, and mirror the plugin/runtime hosts `mise install` needs at runtime.

## Execution order

This module uses [`coder/coder-utils`](https://registry.coder.com/modules/coder/coder-utils) to run two ordered scripts via `coder exp sync`:

1. **Install Script** — installs `mise` and (optionally) configures shell activation.
2. **Post-Install Script** — waits for the clone to complete, then runs `mise trust` and `mise install` in `repo_dir`.

Coder starts every `coder_script` on an agent in parallel, so this module's post-install script cannot use `coder exp sync` to wait on `git-clone` (git-clone does not participate in `coder exp sync`). Instead, the post-install script polls for `${repo_dir}/.git` and only runs `mise install` once the clone has finished. The bound is controlled by `wait_seconds` (default `300`). If the timeout expires, the script logs a message and exits `0` without running `mise install`.

### Custom wait timeout

```tf
module "mise_install" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/droopy4096/mise-install/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
repo_dir = module.git_clone[count.index].repo_dir
wait_seconds = 900 # wait up to 15 minutes for large clones
}
```

## Troubleshooting

Scripts and logs are written under a per-module root:

- Scripts: `$HOME/.coder-modules/droopy4096/mise-install/scripts/{install.sh,post_install.sh}`
- Logs: `$HOME/.coder-modules/droopy4096/mise-install/logs/{install.log,post_install.log}`

If `mise install` fails, inspect `post_install.log` for the exact `mise` output. Common causes:

- Missing `.mise.toml` / `.tool-versions` in `repo_dir`.
- Untrusted config (rerun with `mise_trust = true`).
- Network egress blocked to endpoints listed under [Network egress](#network-egress).
127 changes: 127 additions & 0 deletions registry/droopy4096/modules/mise-install/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
terraform {
required_version = ">= 1.0"

required_providers {
coder = {
source = "coder/coder"
version = ">= 2.13"
}
}
}

variable "agent_id" {
description = "The ID of a Coder agent."
type = string
}

variable "repo_dir" {
description = "Absolute path to the repository where `mise install` should run. Typically wired to `module.git-clone.repo_dir`."
type = string
}

variable "mise_trust" {
description = "Whether to run `mise trust` on the repository before `mise install`. Required for projects that ship a `.mise.toml`."
type = bool
default = true
}

variable "activate_shells" {
description = "Shell rc files to append `mise activate` to. Pass an empty list to skip activation."
type = list(string)
default = ["bash", "zsh"]

validation {
condition = alltrue([for s in var.activate_shells : contains(["bash", "zsh"], s)])
error_message = "activate_shells entries must be one of: bash, zsh."
}
}

variable "install_dir" {
description = "Directory to install the `mise` binary into. Passed to the mise installer as `MISE_INSTALL_PATH`'s parent."
type = string
default = "$HOME/.local/bin"
}

variable "install_mise" {
description = "Whether to download and install `mise`. Set to false in restricted or air-gapped environments where the workspace image already ships `mise`; the module will then only configure shell activation and run `mise install`."
type = bool
default = true
}

variable "install_url" {
description = "URL of the mise installer script. Override to point at an internal mirror in restricted environments. Ignored when `install_mise = false`."
type = string
default = "https://mise.run"
}

variable "mise_version" {
description = "Pin the mise release to install by setting `MISE_VERSION` before invoking the installer (e.g. `\"v2024.9.0\"`). Empty means install the latest release advertised by `install_url`. Ignored when `install_mise = false`."
type = string
default = ""
}

variable "mise_bin" {
description = "Absolute path to a pre-installed `mise` binary (e.g. `/opt/mise/bin/mise` baked into a workspace image). When non-empty, its directory is prepended to PATH so this module and downstream scripts pick it up. Combine with `install_mise = false` to fully opt out of downloading."
type = string
default = ""
}

variable "wait_seconds" {
description = "Maximum seconds to wait for `repo_dir/.git` to appear (i.e. for the git-clone module to finish) before skipping `mise install`."
type = number
default = 300

validation {
condition = var.wait_seconds >= 0
error_message = "wait_seconds must be non-negative."
}
}

variable "icon" {
description = "Icon shown in the Coder UI for the coder_script resources this module creates."
type = string
default = "/icon/code.svg"
}

locals {
module_directory = "$HOME/.coder-modules/droopy4096/mise-install"

install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {
ARG_INSTALL_DIR = var.install_dir
ARG_ACTIVATE_SHELLS = join(" ", var.activate_shells)
ARG_INSTALL_MISE = tostring(var.install_mise)
ARG_INSTALL_URL = var.install_url
ARG_MISE_VERSION = var.mise_version
ARG_MISE_BIN = var.mise_bin
})

post_install_script = templatefile("${path.module}/scripts/post_install.sh.tftpl", {
ARG_REPO_DIR = var.repo_dir
ARG_MISE_TRUST = tostring(var.mise_trust)
ARG_INSTALL_DIR = var.install_dir
ARG_MISE_BIN = var.mise_bin
ARG_WAIT_SECONDS = tostring(var.wait_seconds)
})
}

module "coder_utils" {
source = "registry.coder.com/coder/coder-utils/coder"
version = "0.0.1"

agent_id = var.agent_id
module_directory = local.module_directory
display_name_prefix = "mise"
icon = var.icon
install_script = local.install_script
post_install_script = local.post_install_script
}

output "repo_dir" {
description = "The repository directory where `mise install` was run."
value = var.repo_dir
}

output "scripts" {
description = "Ordered list of `coder exp sync` names produced by this module, in run order."
value = module.coder_utils.scripts
}
Loading