Merge branch 'main' into stevenmasley/subdomain_vscode
						commit
						ce5af66257
					
				@ -0,0 +1,6 @@
 | 
			
		||||
version: 2
 | 
			
		||||
updates:
 | 
			
		||||
  - package-ecosystem: "github-actions"
 | 
			
		||||
    directory: "/"
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: "weekly"
 | 
			
		||||
@ -1,42 +0,0 @@
 | 
			
		||||
name: Update README on Tag
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  push:
 | 
			
		||||
    tags:
 | 
			
		||||
      - 'v*'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  update-readme:
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: write
 | 
			
		||||
      pull-requests: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout code
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - name: Get the latest tag
 | 
			
		||||
        id: get-latest-tag
 | 
			
		||||
        run: echo "TAG=$(git describe --tags --abbrev=0 | sed 's/^v//')" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Run update script
 | 
			
		||||
        run: ./update-version.sh
 | 
			
		||||
 | 
			
		||||
      - name: Create Pull Request
 | 
			
		||||
        id: create-pr
 | 
			
		||||
        uses: peter-evans/create-pull-request@v5
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: 'chore: bump version to ${{ env.TAG }} in README.md files'
 | 
			
		||||
          title: 'chore: bump version to ${{ env.TAG }} in README.md files'
 | 
			
		||||
          body: 'This is an auto-generated PR to update README.md files of all modules with the new tag ${{ env.TAG }}'
 | 
			
		||||
          branch: 'update-readme-branch'
 | 
			
		||||
          base: 'main'
 | 
			
		||||
        env:
 | 
			
		||||
          TAG: ${{ steps.get-latest-tag.outputs.TAG }}
 | 
			
		||||
 | 
			
		||||
      - name: Auto-approve
 | 
			
		||||
        uses: hmarr/auto-approve-action@v4
 | 
			
		||||
        if: github.ref == 'refs/heads/update-readme-branch'
 | 
			
		||||
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								| 
		 After Width: | Height: | Size: 1.5 MiB  | 
@ -0,0 +1,35 @@
 | 
			
		||||
---
 | 
			
		||||
display_name: Cursor IDE
 | 
			
		||||
description: Add a one-click button to launch Cursor IDE
 | 
			
		||||
icon: ../.icons/cursor.svg
 | 
			
		||||
maintainer_github: coder
 | 
			
		||||
verified: true
 | 
			
		||||
tags: [ide, cursor, helper]
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Cursor IDE
 | 
			
		||||
 | 
			
		||||
Add a button to open any workspace with a single click in Cursor IDE.
 | 
			
		||||
 | 
			
		||||
Uses the [Coder Remote VS Code Extension](https://github.com/coder/cursor-coder).
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "cursor" {
 | 
			
		||||
  source   = "registry.coder.com/modules/cursor/coder"
 | 
			
		||||
  version  = "1.0.19"
 | 
			
		||||
  agent_id = coder_agent.example.id
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
### Open in a specific directory
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "cursor" {
 | 
			
		||||
  source   = "registry.coder.com/modules/cursor/coder"
 | 
			
		||||
  version  = "1.0.19"
 | 
			
		||||
  agent_id = coder_agent.example.id
 | 
			
		||||
  folder   = "/home/coder/project"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,88 @@
 | 
			
		||||
import { describe, expect, it } from "bun:test";
 | 
			
		||||
import {
 | 
			
		||||
  runTerraformApply,
 | 
			
		||||
  runTerraformInit,
 | 
			
		||||
  testRequiredVariables,
 | 
			
		||||
} from "../test";
 | 
			
		||||
 | 
			
		||||
describe("cursor", async () => {
 | 
			
		||||
  await runTerraformInit(import.meta.dir);
 | 
			
		||||
 | 
			
		||||
  testRequiredVariables(import.meta.dir, {
 | 
			
		||||
    agent_id: "foo",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("default output", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const coder_app = state.resources.find(
 | 
			
		||||
      (res) => res.type === "coder_app" && res.name === "cursor",
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(coder_app).not.toBeNull();
 | 
			
		||||
    expect(coder_app?.instances.length).toBe(1);
 | 
			
		||||
    expect(coder_app?.instances[0].attributes.order).toBeNull();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds folder", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      folder: "/foo/bar",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds folder and open_recent", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      folder: "/foo/bar",
 | 
			
		||||
      open_recent: "true",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds folder but not open_recent", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      folder: "/foo/bar",
 | 
			
		||||
      openRecent: "false",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds open_recent", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      open_recent: "true",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("expect order to be set", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      order: "22",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const coder_app = state.resources.find(
 | 
			
		||||
      (res) => res.type === "coder_app" && res.name === "cursor",
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(coder_app).not.toBeNull();
 | 
			
		||||
    expect(coder_app?.instances.length).toBe(1);
 | 
			
		||||
    expect(coder_app?.instances[0].attributes.order).toBe(22);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,62 @@
 | 
			
		||||
terraform {
 | 
			
		||||
  required_version = ">= 1.0"
 | 
			
		||||
 | 
			
		||||
  required_providers {
 | 
			
		||||
    coder = {
 | 
			
		||||
      source  = "coder/coder"
 | 
			
		||||
      version = ">= 0.23"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "agent_id" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The ID of a Coder agent."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "folder" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The folder to open in Cursor IDE."
 | 
			
		||||
  default     = ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "open_recent" {
 | 
			
		||||
  type        = bool
 | 
			
		||||
  description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
 | 
			
		||||
  default     = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "order" {
 | 
			
		||||
  type        = number
 | 
			
		||||
  description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
 | 
			
		||||
  default     = null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data "coder_workspace" "me" {}
 | 
			
		||||
data "coder_workspace_owner" "me" {}
 | 
			
		||||
 | 
			
		||||
resource "coder_app" "cursor" {
 | 
			
		||||
  agent_id     = var.agent_id
 | 
			
		||||
  external     = true
 | 
			
		||||
  icon         = "/icon/cursor.svg"
 | 
			
		||||
  slug         = "cursor"
 | 
			
		||||
  display_name = "Cursor Desktop"
 | 
			
		||||
  order        = var.order
 | 
			
		||||
  url = join("", [
 | 
			
		||||
    "cursor://coder.coder-remote/open",
 | 
			
		||||
    "?owner=",
 | 
			
		||||
    data.coder_workspace_owner.me.name,
 | 
			
		||||
    "&workspace=",
 | 
			
		||||
    data.coder_workspace.me.name,
 | 
			
		||||
    var.folder != "" ? join("", ["&folder=", var.folder]) : "",
 | 
			
		||||
    var.open_recent ? "&openRecent" : "",
 | 
			
		||||
    "&url=",
 | 
			
		||||
    data.coder_workspace.me.access_url,
 | 
			
		||||
    "&token=$SESSION_TOKEN",
 | 
			
		||||
  ])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
output "cursor_url" {
 | 
			
		||||
  value       = coder_app.cursor.url
 | 
			
		||||
  description = "Cursor IDE Desktop URL."
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
email=${ARTIFACTORY_EMAIL}
 | 
			
		||||
%{ for REPO in REPOS ~}
 | 
			
		||||
${REPO.SCOPE}registry=${JFROG_URL}/artifactory/api/npm/${REPO.NAME}
 | 
			
		||||
//${JFROG_HOST}/artifactory/api/npm/${REPO.NAME}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
@ -1,19 +1,129 @@
 | 
			
		||||
import { serve } from "bun";
 | 
			
		||||
import { describe } from "bun:test";
 | 
			
		||||
import { describe, expect, it } from "bun:test";
 | 
			
		||||
import {
 | 
			
		||||
  createJSONResponse,
 | 
			
		||||
  findResourceInstance,
 | 
			
		||||
  runTerraformInit,
 | 
			
		||||
  runTerraformApply,
 | 
			
		||||
  testRequiredVariables,
 | 
			
		||||
} from "../test";
 | 
			
		||||
 | 
			
		||||
describe("jfrog-oauth", async () => {
 | 
			
		||||
  type TestVariables = {
 | 
			
		||||
    agent_id: string;
 | 
			
		||||
    jfrog_url: string;
 | 
			
		||||
    package_managers: string;
 | 
			
		||||
 | 
			
		||||
    username_field?: string;
 | 
			
		||||
    jfrog_server_id?: string;
 | 
			
		||||
    external_auth_id?: string;
 | 
			
		||||
    configure_code_server?: boolean;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  await runTerraformInit(import.meta.dir);
 | 
			
		||||
 | 
			
		||||
  testRequiredVariables(import.meta.dir, {
 | 
			
		||||
  const fakeFrogApi = "localhost:8081/artifactory/api";
 | 
			
		||||
  const fakeFrogUrl = "http://localhost:8081";
 | 
			
		||||
  const user = "default";
 | 
			
		||||
 | 
			
		||||
  it("can run apply with required variables", async () => {
 | 
			
		||||
    testRequiredVariables<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
    jfrog_url: "http://localhost:8081",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: "{}",
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("generates an npmrc with scoped repos", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        npm: ["global", "@foo:foo", "@bar:bar"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    const npmrcStanza = `cat << EOF > ~/.npmrc
 | 
			
		||||
email=${user}@example.com
 | 
			
		||||
registry=http://${fakeFrogApi}/npm/global
 | 
			
		||||
//${fakeFrogApi}/npm/global/:_authToken=
 | 
			
		||||
@foo:registry=http://${fakeFrogApi}/npm/foo
 | 
			
		||||
//${fakeFrogApi}/npm/foo/:_authToken=
 | 
			
		||||
@bar:registry=http://${fakeFrogApi}/npm/bar
 | 
			
		||||
//${fakeFrogApi}/npm/bar/:_authToken=
 | 
			
		||||
 | 
			
		||||
EOF`;
 | 
			
		||||
    expect(coderScript.script).toContain(npmrcStanza);
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'jf npmc --global --repo-resolve "global"',
 | 
			
		||||
    );
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured npm',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("generates a pip config with extra-indexes", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        pypi: ["global", "foo", "bar"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    const pipStanza = `cat << EOF > ~/.pip/pip.conf
 | 
			
		||||
[global]
 | 
			
		||||
index-url = https://${user}:@${fakeFrogApi}/pypi/global/simple
 | 
			
		||||
extra-index-url =
 | 
			
		||||
    https://${user}:@${fakeFrogApi}/pypi/foo/simple
 | 
			
		||||
    https://${user}:@${fakeFrogApi}/pypi/bar/simple
 | 
			
		||||
 | 
			
		||||
EOF`;
 | 
			
		||||
    expect(coderScript.script).toContain(pipStanza);
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'jf pipc --global --repo-resolve "global"',
 | 
			
		||||
    );
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured pypi',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("registers multiple docker repos", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        docker: ["foo.jfrog.io", "bar.jfrog.io", "baz.jfrog.io"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    const dockerStanza = ["foo", "bar", "baz"]
 | 
			
		||||
      .map((r) => `register_docker "${r}.jfrog.io"`)
 | 
			
		||||
      .join("\n");
 | 
			
		||||
    expect(coderScript.script).toContain(dockerStanza);
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured docker',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("sets goproxy with multiple repos", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        go: ["foo", "bar", "baz"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const proxyEnv = findResourceInstance(state, "coder_env", "goproxy");
 | 
			
		||||
    const proxies = ["foo", "bar", "baz"]
 | 
			
		||||
      .map((r) => `https://${user}:@${fakeFrogApi}/go/${r}`)
 | 
			
		||||
      .join(",");
 | 
			
		||||
    expect(proxyEnv.value).toEqual(proxies);
 | 
			
		||||
 | 
			
		||||
//TODO add more tests
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'jf goc --global --repo-resolve "foo"',
 | 
			
		||||
    );
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured go',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
[global]
 | 
			
		||||
index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${try(element(REPOS, 0), "")}/simple
 | 
			
		||||
extra-index-url =
 | 
			
		||||
%{ for REPO in try(slice(REPOS, 1, length(REPOS)), []) ~}
 | 
			
		||||
    https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPO}/simple
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
email=${ARTIFACTORY_EMAIL}
 | 
			
		||||
%{ for REPO in REPOS ~}
 | 
			
		||||
${REPO.SCOPE}registry=${JFROG_URL}/artifactory/api/npm/${REPO.NAME}
 | 
			
		||||
//${JFROG_HOST}/artifactory/api/npm/${REPO.NAME}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
[global]
 | 
			
		||||
index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${try(element(REPOS, 0), "")}/simple
 | 
			
		||||
extra-index-url =
 | 
			
		||||
%{ for REPO in try(slice(REPOS, 1, length(REPOS)), []) ~}
 | 
			
		||||
    https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPO}/simple
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
---
 | 
			
		||||
display_name: KasmVNC
 | 
			
		||||
description: A modern open source VNC server
 | 
			
		||||
icon: ../.icons/kasmvnc.svg
 | 
			
		||||
maintainer_github: coder
 | 
			
		||||
verified: true
 | 
			
		||||
tags: [helper, vnc, desktop]
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# KasmVNC
 | 
			
		||||
 | 
			
		||||
Automatically install [KasmVNC](https://kasmweb.com/kasmvnc) in a workspace, and create an app to access it via the dashboard.
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "kasmvnc" {
 | 
			
		||||
  source              = "registry.coder.com/modules/kasmvnc/coder"
 | 
			
		||||
  version             = "1.0.22"
 | 
			
		||||
  agent_id            = coder_agent.example.id
 | 
			
		||||
  desktop_environment = "xfce"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
> **Note:** This module only works on workspaces with a pre-installed desktop environment. As an example base image you can use `codercom/enterprise-desktop` image.
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
import { describe, expect, it } from "bun:test";
 | 
			
		||||
import {
 | 
			
		||||
  runTerraformApply,
 | 
			
		||||
  runTerraformInit,
 | 
			
		||||
  testRequiredVariables,
 | 
			
		||||
} from "../test";
 | 
			
		||||
 | 
			
		||||
const allowedDesktopEnvs = ["xfce", "kde", "gnome", "lxde", "lxqt"] as const;
 | 
			
		||||
type AllowedDesktopEnv = (typeof allowedDesktopEnvs)[number];
 | 
			
		||||
 | 
			
		||||
type TestVariables = Readonly<{
 | 
			
		||||
  agent_id: string;
 | 
			
		||||
  desktop_environment: AllowedDesktopEnv;
 | 
			
		||||
  port?: string;
 | 
			
		||||
  kasm_version?: string;
 | 
			
		||||
}>;
 | 
			
		||||
 | 
			
		||||
describe("Kasm VNC", async () => {
 | 
			
		||||
  await runTerraformInit(import.meta.dir);
 | 
			
		||||
  testRequiredVariables<TestVariables>(import.meta.dir, {
 | 
			
		||||
    agent_id: "foo",
 | 
			
		||||
    desktop_environment: "gnome",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Successfully installs for all expected Kasm desktop versions", async () => {
 | 
			
		||||
    for (const v of allowedDesktopEnvs) {
 | 
			
		||||
      const applyWithEnv = () => {
 | 
			
		||||
        runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
          agent_id: "foo",
 | 
			
		||||
          desktop_environment: v,
 | 
			
		||||
        });
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      expect(applyWithEnv).not.toThrow();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,63 @@
 | 
			
		||||
terraform {
 | 
			
		||||
  required_version = ">= 1.0"
 | 
			
		||||
 | 
			
		||||
  required_providers {
 | 
			
		||||
    coder = {
 | 
			
		||||
      source  = "coder/coder"
 | 
			
		||||
      version = ">= 0.12"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "agent_id" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The ID of a Coder agent."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "port" {
 | 
			
		||||
  type        = number
 | 
			
		||||
  description = "The port to run KasmVNC on."
 | 
			
		||||
  default     = 6800
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "kasm_version" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "Version of KasmVNC to install."
 | 
			
		||||
  default     = "1.3.2"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "desktop_environment" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "Specifies the desktop environment of the workspace. This should be pre-installed on the workspace."
 | 
			
		||||
  validation {
 | 
			
		||||
    condition     = contains(["xfce", "kde", "gnome", "lxde", "lxqt"], var.desktop_environment)
 | 
			
		||||
    error_message = "Invalid desktop environment. Please specify a valid desktop environment."
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
resource "coder_script" "kasm_vnc" {
 | 
			
		||||
  agent_id     = var.agent_id
 | 
			
		||||
  display_name = "KasmVNC"
 | 
			
		||||
  icon         = "/icon/kasmvnc.svg"
 | 
			
		||||
  script = templatefile("${path.module}/run.sh", {
 | 
			
		||||
    PORT : var.port,
 | 
			
		||||
    DESKTOP_ENVIRONMENT : var.desktop_environment,
 | 
			
		||||
    VERSION : var.kasm_version
 | 
			
		||||
  })
 | 
			
		||||
  run_on_start = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
resource "coder_app" "kasm_vnc" {
 | 
			
		||||
  agent_id     = var.agent_id
 | 
			
		||||
  slug         = "kasm-vnc"
 | 
			
		||||
  display_name = "kasmVNC"
 | 
			
		||||
  url          = "http://localhost:${var.port}"
 | 
			
		||||
  icon         = "/icon/kasmvnc.svg"
 | 
			
		||||
  subdomain    = true
 | 
			
		||||
  share        = "owner"
 | 
			
		||||
  healthcheck {
 | 
			
		||||
    url       = "http://localhost:${var.port}/app"
 | 
			
		||||
    interval  = 5
 | 
			
		||||
    threshold = 5
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,179 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# Function to check if vncserver is already installed
 | 
			
		||||
check_installed() {
 | 
			
		||||
  if command -v vncserver &> /dev/null; then
 | 
			
		||||
    echo "vncserver is already installed."
 | 
			
		||||
    return 0 # Don't exit, just indicate it's installed
 | 
			
		||||
  else
 | 
			
		||||
    return 1 # Indicates not installed
 | 
			
		||||
  fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Function to download a file using wget, curl, or busybox as a fallback
 | 
			
		||||
download_file() {
 | 
			
		||||
  local url=$1
 | 
			
		||||
  local output=$2
 | 
			
		||||
  if command -v wget &> /dev/null; then
 | 
			
		||||
    wget $url -O $output
 | 
			
		||||
  elif command -v curl &> /dev/null; then
 | 
			
		||||
    curl -fsSL $url -o $output
 | 
			
		||||
  elif command -v busybox &> /dev/null; then
 | 
			
		||||
    busybox wget -O $output $url
 | 
			
		||||
  else
 | 
			
		||||
    echo "Neither wget, curl, nor busybox is installed. Please install one of them to proceed."
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Function to install kasmvncserver for debian-based distros
 | 
			
		||||
install_deb() {
 | 
			
		||||
  local url=$1
 | 
			
		||||
  download_file $url /tmp/kasmvncserver.deb
 | 
			
		||||
  sudo apt-get update
 | 
			
		||||
  DEBIAN_FRONTEND=noninteractive sudo apt-get install --yes -qq --no-install-recommends --no-install-suggests /tmp/kasmvncserver.deb
 | 
			
		||||
  sudo adduser $USER ssl-cert
 | 
			
		||||
  rm /tmp/kasmvncserver.deb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Function to install kasmvncserver for Oracle 8
 | 
			
		||||
install_rpm_oracle8() {
 | 
			
		||||
  local url=$1
 | 
			
		||||
  download_file $url /tmp/kasmvncserver.rpm
 | 
			
		||||
  sudo dnf config-manager --set-enabled ol8_codeready_builder
 | 
			
		||||
  sudo dnf install oracle-epel-release-el8 -y
 | 
			
		||||
  sudo dnf localinstall /tmp/kasmvncserver.rpm -y
 | 
			
		||||
  sudo usermod -aG kasmvnc-cert $USER
 | 
			
		||||
  rm /tmp/kasmvncserver.rpm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Function to install kasmvncserver for CentOS 7
 | 
			
		||||
install_rpm_centos7() {
 | 
			
		||||
  local url=$1
 | 
			
		||||
  download_file $url /tmp/kasmvncserver.rpm
 | 
			
		||||
  sudo yum install epel-release -y
 | 
			
		||||
  sudo yum install /tmp/kasmvncserver.rpm -y
 | 
			
		||||
  sudo usermod -aG kasmvnc-cert $USER
 | 
			
		||||
  rm /tmp/kasmvncserver.rpm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Function to install kasmvncserver for rpm-based distros
 | 
			
		||||
install_rpm() {
 | 
			
		||||
  local url=$1
 | 
			
		||||
  download_file $url /tmp/kasmvncserver.rpm
 | 
			
		||||
  sudo rpm -i /tmp/kasmvncserver.rpm
 | 
			
		||||
  rm /tmp/kasmvncserver.rpm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Function to install kasmvncserver for Alpine Linux
 | 
			
		||||
install_alpine() {
 | 
			
		||||
  local url=$1
 | 
			
		||||
  download_file $url /tmp/kasmvncserver.tgz
 | 
			
		||||
  tar -xzf /tmp/kasmvncserver.tgz -C /usr/local/bin/
 | 
			
		||||
  rm /tmp/kasmvncserver.tgz
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Detect system information
 | 
			
		||||
distro=$(grep "^ID=" /etc/os-release | awk -F= '{print $2}')
 | 
			
		||||
version=$(grep "^VERSION_ID=" /etc/os-release | awk -F= '{print $2}' | tr -d '"')
 | 
			
		||||
arch=$(uname -m)
 | 
			
		||||
 | 
			
		||||
echo "Detected Distribution: $distro"
 | 
			
		||||
echo "Detected Version: $version"
 | 
			
		||||
echo "Detected Architecture: $arch"
 | 
			
		||||
 | 
			
		||||
# Map arch to package arch
 | 
			
		||||
if [[ "$arch" == "x86_64" ]]; then
 | 
			
		||||
  if [[ "$distro" == "ubuntu" || "$distro" == "debian" || "$distro" == "kali" ]]; then
 | 
			
		||||
    arch="amd64"
 | 
			
		||||
  else
 | 
			
		||||
    arch="x86_64"
 | 
			
		||||
  fi
 | 
			
		||||
elif [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
 | 
			
		||||
  if [[ "$distro" == "ubuntu" || "$distro" == "debian" || "$distro" == "kali" ]]; then
 | 
			
		||||
    arch="arm64"
 | 
			
		||||
  else
 | 
			
		||||
    arch="aarch64"
 | 
			
		||||
  fi
 | 
			
		||||
else
 | 
			
		||||
  echo "Unsupported architecture: $arch"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Check if vncserver is installed, and install if not
 | 
			
		||||
if ! check_installed; then
 | 
			
		||||
  echo "Installing KASM version: ${VERSION}"
 | 
			
		||||
  case $distro in
 | 
			
		||||
    ubuntu | debian | kali)
 | 
			
		||||
      case $version in
 | 
			
		||||
        "20.04")
 | 
			
		||||
          install_deb "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_focal_${VERSION}_$${arch}.deb"
 | 
			
		||||
          ;;
 | 
			
		||||
        "22.04")
 | 
			
		||||
          install_deb "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_jammy_${VERSION}_$${arch}.deb"
 | 
			
		||||
          ;;
 | 
			
		||||
        "24.04")
 | 
			
		||||
          install_deb "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_noble_${VERSION}_$${arch}.deb"
 | 
			
		||||
          ;;
 | 
			
		||||
        *)
 | 
			
		||||
          echo "Unsupported Ubuntu/Debian/Kali version: $${version}"
 | 
			
		||||
          exit 1
 | 
			
		||||
          ;;
 | 
			
		||||
      esac
 | 
			
		||||
      ;;
 | 
			
		||||
    oracle)
 | 
			
		||||
      if [[ "$version" == "8" ]]; then
 | 
			
		||||
        install_rpm_oracle8 "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_oracle_8_${VERSION}_$${arch}.rpm"
 | 
			
		||||
      else
 | 
			
		||||
        echo "Unsupported Oracle version: $${version}"
 | 
			
		||||
        exit 1
 | 
			
		||||
      fi
 | 
			
		||||
      ;;
 | 
			
		||||
    centos)
 | 
			
		||||
      if [[ "$version" == "7" ]]; then
 | 
			
		||||
        install_rpm_centos7 "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_centos_core_${VERSION}_$${arch}.rpm"
 | 
			
		||||
      else
 | 
			
		||||
        install_rpm "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_centos_core_${VERSION}_$${arch}.rpm"
 | 
			
		||||
      fi
 | 
			
		||||
      ;;
 | 
			
		||||
    alpine)
 | 
			
		||||
      if [[ "$version" == "3.17" || "$version" == "3.18" || "$version" == "3.19" || "$version" == "3.20" ]]; then
 | 
			
		||||
        install_alpine "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvnc.alpine_$${version}_$${arch}.tgz"
 | 
			
		||||
      else
 | 
			
		||||
        echo "Unsupported Alpine version: $${version}"
 | 
			
		||||
        exit 1
 | 
			
		||||
      fi
 | 
			
		||||
      ;;
 | 
			
		||||
    fedora | opensuse)
 | 
			
		||||
      install_rpm "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_$${distro}_$${version}_${VERSION}_$${arch}.rpm"
 | 
			
		||||
      ;;
 | 
			
		||||
    *)
 | 
			
		||||
      echo "Unsupported distribution: $${distro}"
 | 
			
		||||
      exit 1
 | 
			
		||||
      ;;
 | 
			
		||||
  esac
 | 
			
		||||
else
 | 
			
		||||
  echo "vncserver already installed. Skipping installation."
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Coder port-forwarding from dashboard only supports HTTP
 | 
			
		||||
sudo bash -c "cat > /etc/kasmvnc/kasmvnc.yaml <<EOF
 | 
			
		||||
network:
 | 
			
		||||
  protocol: http
 | 
			
		||||
  websocket_port: ${PORT}
 | 
			
		||||
  ssl:
 | 
			
		||||
    require_ssl: false
 | 
			
		||||
  udp:
 | 
			
		||||
    public_ip: 127.0.0.1
 | 
			
		||||
EOF"
 | 
			
		||||
 | 
			
		||||
# This password is not used since we start the server without auth.
 | 
			
		||||
# The server is protected via the Coder session token / tunnel
 | 
			
		||||
# and does not listen publicly
 | 
			
		||||
echo -e "password\npassword\n" | vncpasswd -wo -u $USER
 | 
			
		||||
 | 
			
		||||
# Start the server
 | 
			
		||||
printf "🚀 Starting KasmVNC server...\n"
 | 
			
		||||
sudo -u $USER bash -c "vncserver -select-de ${DESKTOP_ENVIRONMENT} -disableBasicAuth" > /tmp/kasmvncserver.log 2>&1 &
 | 
			
		||||
@ -1,264 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "modules",
 | 
			
		||||
  "lockfileVersion": 3,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "name": "modules",
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "bun-types": "^1.0.18",
 | 
			
		||||
        "gray-matter": "^4.0.3",
 | 
			
		||||
        "marked": "^12.0.0",
 | 
			
		||||
        "prettier": "^3.2.5",
 | 
			
		||||
        "prettier-plugin-sh": "^0.13.1",
 | 
			
		||||
        "prettier-plugin-terraform-formatter": "^1.2.1"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "typescript": "^5.3.3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/node": {
 | 
			
		||||
      "version": "20.12.14",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz",
 | 
			
		||||
      "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "undici-types": "~5.26.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/ws": {
 | 
			
		||||
      "version": "8.5.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
 | 
			
		||||
      "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/argparse": {
 | 
			
		||||
      "version": "1.0.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
 | 
			
		||||
      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "sprintf-js": "~1.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/bun-types": {
 | 
			
		||||
      "version": "1.1.16",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.16.tgz",
 | 
			
		||||
      "integrity": "sha512-LpAh8dQe4NKvhSW390Rkftw0ume0moSkRm575e1JZ1PwI/dXjbXyjpntq+2F0bVW1FV7V6B8EfWx088b+dNurw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "~20.12.8",
 | 
			
		||||
        "@types/ws": "~8.5.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/esprima": {
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "esparse": "bin/esparse.js",
 | 
			
		||||
        "esvalidate": "bin/esvalidate.js"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/extend-shallow": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "is-extendable": "^0.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/gray-matter": {
 | 
			
		||||
      "version": "4.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "js-yaml": "^3.13.1",
 | 
			
		||||
        "kind-of": "^6.0.2",
 | 
			
		||||
        "section-matter": "^1.0.0",
 | 
			
		||||
        "strip-bom-string": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=6.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/is-extendable": {
 | 
			
		||||
      "version": "0.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/js-yaml": {
 | 
			
		||||
      "version": "3.14.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
 | 
			
		||||
      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "argparse": "^1.0.7",
 | 
			
		||||
        "esprima": "^4.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "js-yaml": "bin/js-yaml.js"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/kind-of": {
 | 
			
		||||
      "version": "6.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/marked": {
 | 
			
		||||
      "version": "12.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "marked": "bin/marked.js"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mvdan-sh": {
 | 
			
		||||
      "version": "0.10.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mvdan-sh/-/mvdan-sh-0.10.1.tgz",
 | 
			
		||||
      "integrity": "sha512-kMbrH0EObaKmK3nVRKUIIya1dpASHIEusM13S4V1ViHFuxuNxCo+arxoa6j/dbV22YBGjl7UKJm9QQKJ2Crzhg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/prettier": {
 | 
			
		||||
      "version": "3.3.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
 | 
			
		||||
      "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "peer": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "prettier": "bin/prettier.cjs"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=14"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/prettier/prettier?sponsor=1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/prettier-plugin-sh": {
 | 
			
		||||
      "version": "0.13.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.13.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ytMcl1qK4s4BOFGvsc9b0+k9dYECal7U29bL/ke08FEUsF/JLN0j6Peo0wUkFDG4y2UHLMhvpyd6Sd3zDXe/eg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "mvdan-sh": "^0.10.1",
 | 
			
		||||
        "sh-syntax": "^0.4.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=16.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://opencollective.com/unts"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "prettier": "^3.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/prettier-plugin-terraform-formatter": {
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prettier-plugin-terraform-formatter/-/prettier-plugin-terraform-formatter-1.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-rdzV61Bs/Ecnn7uAS/vL5usTX8xUWM+nQejNLZxt3I1kJH5WSeLEmq7LYu1wCoEQF+y7Uv1xGvPRfl3lIe6+tA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "prettier": ">= 1.16.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "prettier": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/section-matter": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "extend-shallow": "^2.0.1",
 | 
			
		||||
        "kind-of": "^6.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/sh-syntax": {
 | 
			
		||||
      "version": "0.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.4.2.tgz",
 | 
			
		||||
      "integrity": "sha512-/l2UZ5fhGZLVZa16XQM9/Vq/hezGGbdHeVEA01uWjOL1+7Ek/gt6FquW0iKKws4a9AYPYvlz6RyVvjh3JxOteg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "tslib": "^2.6.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=16.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://opencollective.com/unts"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/sprintf-js": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/strip-bom-string": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/tslib": {
 | 
			
		||||
      "version": "2.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/typescript": {
 | 
			
		||||
      "version": "5.5.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
 | 
			
		||||
      "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
 | 
			
		||||
      "peer": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "tsc": "bin/tsc",
 | 
			
		||||
        "tsserver": "bin/tsserver"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=14.17"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/undici-types": {
 | 
			
		||||
      "version": "5.26.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
 | 
			
		||||
      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +1,14 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "esnext",
 | 
			
		||||
    "module": "esnext",
 | 
			
		||||
    // If we were just compiling for the tests, we could safely target ESNext at
 | 
			
		||||
    // all times, but just because we've been starting to add more runtime logic
 | 
			
		||||
    // files to some of the modules, erring on the side of caution by having a
 | 
			
		||||
    // older compilation target
 | 
			
		||||
    "target": "ES6",
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "allowSyntheticDefaultImports": true,
 | 
			
		||||
    "moduleResolution": "nodenext",
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
    "types": ["bun-types"]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,77 @@
 | 
			
		||||
---
 | 
			
		||||
display_name: Hashicorp Vault Integration (JWT)
 | 
			
		||||
description: Authenticates with Vault using a JWT from Coder's OIDC provider
 | 
			
		||||
icon: ../.icons/vault.svg
 | 
			
		||||
maintainer_github: coder
 | 
			
		||||
partner_github: hashicorp
 | 
			
		||||
verified: true
 | 
			
		||||
tags: [helper, integration, vault, jwt, oidc]
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Hashicorp Vault Integration (JWT)
 | 
			
		||||
 | 
			
		||||
This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces by reusing the [OIDC](https://coder.com/docs/admin/auth#openid-connect) access token from Coder's OIDC authentication method. This requires configuring the Vault [JWT/OIDC](https://developer.hashicorp.com/vault/docs/auth/jwt#configuration) auth method.
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "vault" {
 | 
			
		||||
  source         = "registry.coder.com/modules/vault-jwt/coder"
 | 
			
		||||
  version        = "1.0.20"
 | 
			
		||||
  agent_id       = coder_agent.example.id
 | 
			
		||||
  vault_addr     = "https://vault.example.com"
 | 
			
		||||
  vault_jwt_role = "coder" # The Vault role to use for authentication
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Then you can use the Vault CLI in your workspaces to fetch secrets from Vault:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
vault kv get -namespace=coder -mount=secrets coder
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
or using the Vault API:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
### Configure Vault integration with a non standard auth path (default is "jwt")
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "vault" {
 | 
			
		||||
  source              = "registry.coder.com/modules/vault-jwt/coder"
 | 
			
		||||
  version             = "1.0.20"
 | 
			
		||||
  agent_id            = coder_agent.example.id
 | 
			
		||||
  vault_addr          = "https://vault.example.com"
 | 
			
		||||
  vault_jwt_auth_path = "oidc"
 | 
			
		||||
  vault_jwt_role      = "coder" # The Vault role to use for authentication
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Map workspace owner's group to a Vault role
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
data "coder_workspace_owner" "me" {}
 | 
			
		||||
 | 
			
		||||
module "vault" {
 | 
			
		||||
  source         = "registry.coder.com/modules/vault-jwt/coder"
 | 
			
		||||
  version        = "1.0.20"
 | 
			
		||||
  agent_id       = coder_agent.example.id
 | 
			
		||||
  vault_addr     = "https://vault.example.com"
 | 
			
		||||
  vault_jwt_role = data.coder_workspace_owner.me.groups[0]
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Install a specific version of the Vault CLI
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "vault" {
 | 
			
		||||
  source            = "registry.coder.com/modules/vault-jwt/coder"
 | 
			
		||||
  version           = "1.0.20"
 | 
			
		||||
  agent_id          = coder_agent.example.id
 | 
			
		||||
  vault_addr        = "https://vault.example.com"
 | 
			
		||||
  vault_jwt_role    = "coder" # The Vault role to use for authentication
 | 
			
		||||
  vault_cli_version = "1.17.5"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
import { describe } from "bun:test";
 | 
			
		||||
import { runTerraformInit, testRequiredVariables } from "../test";
 | 
			
		||||
 | 
			
		||||
describe("vault-jwt", async () => {
 | 
			
		||||
  await runTerraformInit(import.meta.dir);
 | 
			
		||||
 | 
			
		||||
  testRequiredVariables(import.meta.dir, {
 | 
			
		||||
    agent_id: "foo",
 | 
			
		||||
    vault_addr: "foo",
 | 
			
		||||
    vault_jwt_role: "foo",
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,64 @@
 | 
			
		||||
terraform {
 | 
			
		||||
  required_version = ">= 1.0"
 | 
			
		||||
 | 
			
		||||
  required_providers {
 | 
			
		||||
    coder = {
 | 
			
		||||
      source  = "coder/coder"
 | 
			
		||||
      version = ">= 0.12.4"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Add required variables for your modules and remove any unneeded variables
 | 
			
		||||
variable "agent_id" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The ID of a Coder agent."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "vault_addr" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The address of the Vault server."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "vault_jwt_auth_path" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The path to the Vault JWT auth method."
 | 
			
		||||
  default     = "jwt"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "vault_jwt_role" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The name of the Vault role to use for authentication."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "vault_cli_version" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The version of Vault to install."
 | 
			
		||||
  default     = "latest"
 | 
			
		||||
  validation {
 | 
			
		||||
    condition     = can(regex("^(latest|[0-9]+\\.[0-9]+\\.[0-9]+)$", var.vault_cli_version))
 | 
			
		||||
    error_message = "Vault version must be in the format 0.0.0 or latest"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
resource "coder_script" "vault" {
 | 
			
		||||
  agent_id     = var.agent_id
 | 
			
		||||
  display_name = "Vault (GitHub)"
 | 
			
		||||
  icon         = "/icon/vault.svg"
 | 
			
		||||
  script = templatefile("${path.module}/run.sh", {
 | 
			
		||||
    CODER_OIDC_ACCESS_TOKEN : data.coder_workspace_owner.me.oidc_access_token,
 | 
			
		||||
    VAULT_JWT_AUTH_PATH : var.vault_jwt_auth_path,
 | 
			
		||||
    VAULT_JWT_ROLE : var.vault_jwt_role,
 | 
			
		||||
    VAULT_CLI_VERSION : var.vault_cli_version,
 | 
			
		||||
  })
 | 
			
		||||
  run_on_start       = true
 | 
			
		||||
  start_blocks_login = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
resource "coder_env" "vault_addr" {
 | 
			
		||||
  agent_id = var.agent_id
 | 
			
		||||
  name     = "VAULT_ADDR"
 | 
			
		||||
  value    = var.vault_addr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data "coder_workspace_owner" "me" {}
 | 
			
		||||
@ -0,0 +1,112 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
# Convert all templated variables to shell variables
 | 
			
		||||
VAULT_CLI_VERSION=${VAULT_CLI_VERSION}
 | 
			
		||||
VAULT_JWT_AUTH_PATH=${VAULT_JWT_AUTH_PATH}
 | 
			
		||||
VAULT_JWT_ROLE=${VAULT_JWT_ROLE}
 | 
			
		||||
CODER_OIDC_ACCESS_TOKEN=${CODER_OIDC_ACCESS_TOKEN}
 | 
			
		||||
 | 
			
		||||
fetch() {
 | 
			
		||||
  dest="$1"
 | 
			
		||||
  url="$2"
 | 
			
		||||
  if command -v curl > /dev/null 2>&1; then
 | 
			
		||||
    curl -sSL --fail "$${url}" -o "$${dest}"
 | 
			
		||||
  elif command -v wget > /dev/null 2>&1; then
 | 
			
		||||
    wget -O "$${dest}" "$${url}"
 | 
			
		||||
  elif command -v busybox > /dev/null 2>&1; then
 | 
			
		||||
    busybox wget -O "$${dest}" "$${url}"
 | 
			
		||||
  else
 | 
			
		||||
    printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unzip_safe() {
 | 
			
		||||
  if command -v unzip > /dev/null 2>&1; then
 | 
			
		||||
    command unzip "$@"
 | 
			
		||||
  elif command -v busybox > /dev/null 2>&1; then
 | 
			
		||||
    busybox unzip "$@"
 | 
			
		||||
  else
 | 
			
		||||
    printf "unzip or busybox is not installed. Please install unzip in your image.\n"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
install() {
 | 
			
		||||
  # Get the architecture of the system
 | 
			
		||||
  ARCH=$(uname -m)
 | 
			
		||||
  if [ "$${ARCH}" = "x86_64" ]; then
 | 
			
		||||
    ARCH="amd64"
 | 
			
		||||
  elif [ "$${ARCH}" = "aarch64" ]; then
 | 
			
		||||
    ARCH="arm64"
 | 
			
		||||
  else
 | 
			
		||||
    printf "Unsupported architecture: $${ARCH}\n"
 | 
			
		||||
    return 1
 | 
			
		||||
  fi
 | 
			
		||||
  # Fetch the latest version of Vault if VAULT_CLI_VERSION is 'latest'
 | 
			
		||||
  if [ "$${VAULT_CLI_VERSION}" = "latest" ]; then
 | 
			
		||||
    LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v 'rc' | grep -oE 'vault/[0-9]+\.[0-9]+\.[0-9]+' | sed 's/vault\///' | sort -V | tail -n 1)
 | 
			
		||||
    printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}"
 | 
			
		||||
    if [ -z "$${LATEST_VERSION}" ]; then
 | 
			
		||||
      printf "Failed to determine the latest Vault version.\n"
 | 
			
		||||
      return 1
 | 
			
		||||
    fi
 | 
			
		||||
    VAULT_CLI_VERSION=$${LATEST_VERSION}
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # Check if the vault CLI is installed and has the correct version
 | 
			
		||||
  installation_needed=1
 | 
			
		||||
  if command -v vault > /dev/null 2>&1; then
 | 
			
		||||
    CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
 | 
			
		||||
    if [ "$${CURRENT_VERSION}" = "$${VAULT_CLI_VERSION}" ]; then
 | 
			
		||||
      printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
 | 
			
		||||
      installation_needed=0
 | 
			
		||||
    fi
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  if [ $${installation_needed} -eq 1 ]; then
 | 
			
		||||
    # Download and install Vault
 | 
			
		||||
    if [ -z "$${CURRENT_VERSION}" ]; then
 | 
			
		||||
      printf "Installing Vault CLI ...\n\n"
 | 
			
		||||
    else
 | 
			
		||||
      printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${VAULT_CLI_VERSION}"
 | 
			
		||||
    fi
 | 
			
		||||
    fetch vault.zip "https://releases.hashicorp.com/vault/$${VAULT_CLI_VERSION}/vault_$${VAULT_CLI_VERSION}_linux_$${ARCH}.zip"
 | 
			
		||||
    if [ $? -ne 0 ]; then
 | 
			
		||||
      printf "Failed to download Vault.\n"
 | 
			
		||||
      return 1
 | 
			
		||||
    fi
 | 
			
		||||
    if ! unzip_safe vault.zip; then
 | 
			
		||||
      printf "Failed to unzip Vault.\n"
 | 
			
		||||
      return 1
 | 
			
		||||
    fi
 | 
			
		||||
    rm vault.zip
 | 
			
		||||
    if sudo mv vault /usr/local/bin/vault 2> /dev/null; then
 | 
			
		||||
      printf "Vault installed successfully!\n\n"
 | 
			
		||||
    else
 | 
			
		||||
      mkdir -p ~/.local/bin
 | 
			
		||||
      if ! mv vault ~/.local/bin/vault; then
 | 
			
		||||
        printf "Failed to move Vault to local bin.\n"
 | 
			
		||||
        return 1
 | 
			
		||||
      fi
 | 
			
		||||
      printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
 | 
			
		||||
    fi
 | 
			
		||||
  fi
 | 
			
		||||
  return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TMP=$(mktemp -d)
 | 
			
		||||
if ! (
 | 
			
		||||
  cd "$TMP"
 | 
			
		||||
  install
 | 
			
		||||
); then
 | 
			
		||||
  echo "Failed to install Vault CLI."
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
rm -rf "$TMP"
 | 
			
		||||
 | 
			
		||||
# Authenticate with Vault
 | 
			
		||||
printf "🔑 Authenticating with Vault ...\n\n"
 | 
			
		||||
echo "$${CODER_OIDC_ACCESS_TOKEN}" | vault write auth/"$${VAULT_JWT_AUTH_PATH}"/login role="$${VAULT_JWT_ROLE}" jwt=-
 | 
			
		||||
printf "🥳 Vault authentication complete!\n\n"
 | 
			
		||||
printf "You can now use Vault CLI to access secrets.\n"
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue