diff --git a/Readme.MD b/Readme.MD
index 74a69f9..da26221 100644
--- a/Readme.MD
+++ b/Readme.MD
@@ -105,3 +105,48 @@ Create a python environment use:
```
python -m venv .venv --system-site-packages
```
+
+# macOS Dev Station
+
+Use the repository hybrid flow: bootstrap once with shell script, then manage day-2 changes with Ansible.
+
+## 1) Bootstrap prerequisites on the Mac
+
+```
+./scripts/setup-dev-station.sh
+```
+
+This installs only prerequisites (Homebrew, base tools, Ansible) and avoids persistent shell side effects.
+
+## 2) Configure the Mac with Ansible
+
+1. Add or adjust the host in `inventory/hosts` under `[darwin_dev]`.
+2. Configure host vars in `ansible/host_vars/mac-01.lab.alexpires.me.yml`.
+3. Run:
+
+```
+cd ansible
+sectool exec ansible-playbook playbook_macos_dev_station.yml --limit darwin_dev
+```
+
+The macOS role configures tools, tmux, code-server and opencode as system launchd daemons running under a dedicated service user. This allows services to start at boot without requiring an interactive login.
+
+OpenCode server auth is injected from environment variables. Ensure `OPENCODE_SERVER_USERNAME` and `OPENCODE_SERVER_PASSWORD` are available through `ansible/sectool.env` values before running the playbook.
+code-server password auth is also env-backed. Ensure `CODE_SERVER_PASSWORD` is available through `ansible/sectool.env` values before running the playbook.
+Hard mode (system daemons + dedicated service user) also requires sudo rights. Use passwordless sudo for your ansible user or set `ANSIBLE_BECOME_PASSWORD` through `ansible/sectool.env` values before running the playbook.
+
+## 3) Optional DNS entry in dev-01 CoreDNS
+
+From `terraform/apps/dev-01`, you can inject an optional DNS record for the Mac dev station:
+
+```
+export TF_VAR_mac_dev_station_dns_name="mac-mini"
+export TF_VAR_mac_dev_station_dns_type="A"
+export TF_VAR_mac_dev_station_dns_target="10.19.4.250"
+
+sectool -f ../../../sectool.json exec tofu validate
+sectool -f ../../../sectool.json exec tofu plan
+sectool -f ../../../sectool.json exec tofu apply -auto-approve
+```
+
+Use `CNAME` + hostname target instead of `A` when you want an alias.
diff --git a/ansible/host_vars/mac-01.lab.alexpires.me.yml b/ansible/host_vars/mac-01.lab.alexpires.me.yml
new file mode 100644
index 0000000..f737d8d
--- /dev/null
+++ b/ansible/host_vars/mac-01.lab.alexpires.me.yml
@@ -0,0 +1,178 @@
+hostname: mac-01.lab.alexpires.me
+ansible_user: "{{ lookup('env', 'ANSIBLE_USER') }}"
+
+ansible_become_password: "{{ lookup('env', 'ANSIBLE_BECOME_PASSWORD', default='') }}"
+
+macos_dev_station_launchd_mode: "daemon"
+macos_dev_station_service_user_enabled: true
+macos_dev_station_service_user: "devstation"
+macos_dev_station_service_group: "staff"
+macos_dev_station_service_home: "/Users/devstation"
+macos_dev_station_workspace_dir: "/Users/devstation/projects"
+macos_dev_station_scripts_dir: "/Users/devstation/scripts"
+macos_dev_station_logs_dir: "/Users/devstation/Library/Logs"
+
+macos_dev_station_code_server_host: "0.0.0.0"
+macos_dev_station_code_server_port: 3000
+macos_dev_station_code_server_auth: "password"
+
+macos_dev_station_colima_enabled: true
+macos_dev_station_colima_cpu: 4
+macos_dev_station_colima_memory: 8
+
+macos_dev_station_opencode_server_host: "0.0.0.0"
+macos_dev_station_opencode_server_port: 4096
+macos_dev_station_manage_launchd: true
+macos_dev_station_oh_my_zsh_enabled: true
+
+macos_dev_station_code_server_extensions_enabled: true
+macos_dev_station_code_server_extensions:
+ - ms-python.python
+ - ms-python.vscode-pylance
+ - ms-python.debugpy
+ - golang.go
+ - ms-azuretools.vscode-docker
+ - hashicorp.terraform
+ - opentofu.vscode-opentofu
+ - redhat.ansible
+ - redhat.vscode-yaml
+ - eamodio.gitlens
+ - sst-dev.opencode
+
+macos_dev_station_mcp_bootstrap_commands: []
+
+macos_dev_station_mcp_installers_enabled: true
+macos_dev_station_mcp_installers:
+ - name: "chrome-devtools-mcp"
+ kind: "npm"
+ package: "chrome-devtools-mcp"
+ version: "latest"
+ - name: "playwright-mcp"
+ kind: "npm"
+ package: "@playwright/mcp"
+ version: "latest"
+ - name: "aws-documentation-mcp"
+ kind: "uv"
+ package: "awslabs.aws-documentation-mcp-server"
+ version: "latest"
+ binary: "awslabs.aws-documentation-mcp-server"
+ - name: "gitea-mcp"
+ kind: "go"
+ module: "gitea.com/gitea/gitea-mcp"
+ version: "latest"
+ binary: "gitea-mcp"
+
+macos_dev_station_opencode_extra_environment:
+ GITEA_MCP_HOST: "https://code.alexpires.me"
+ GITEA_APP_TOKEN: "{{ lookup('env', 'GITEA_APP_TOKEN', default='') }}"
+
+macos_dev_station_opencode_config:
+ "$schema": "https://opencode.ai/config.json"
+ provider:
+ alexpires-me:
+ name: "alexpires.me"
+ npm: "@ai-sdk/openai-compatible"
+ options:
+ baseURL: "https://open-webui.lab.alexpires.me/api"
+ apiKey: "{{ lookup('env', 'OPEN_WEBUI_API_KEY', default='') }}"
+ models:
+ gemma-4-12b-q8-0:
+ name: "gemma-4-12b-q8-0"
+ gemma-4-26b-a4b-ud-iq4-xs:
+ name: "gemma-4-26b-a4b-ud-iq4-xs"
+ gemma-4-31b-iq4-xs:
+ name: "gemma-4-31b-iq4-xs"
+ gpt-oss-20b-q4-0:
+ name: "gpt-oss-20b-q4-0"
+ gpt-oss-20b-q8-0:
+ name: "gpt-oss-20b-q8-0"
+ qwen3-5-35b-a3b-q4-k-m:
+ name: "qwen3-5-35b-a3b-q4-k-m"
+ qwen3-5-9b-q8-0:
+ name: "qwen3-5-9b-q8-0"
+ qwen3-6-27b-mtp-q4-0:
+ name: "qwen3-6-27b-mtp-q4-0"
+ qwen3-6-27b-q4-k-m:
+ name: "qwen3-6-27b-q4-k-m"
+ qwen3-6-35b-a3b-mtp-ud-q4-m:
+ name: "qwen3-6-35b-a3b-mtp-ud-q4-m"
+ qwen3-6-35b-a3b-ud-q4-k-m:
+ name: "qwen3-6-35b-a3b-ud-q4-k-m"
+ "gemma4:e4b":
+ name: "gemma4:e4b (scaleway)"
+ "qwen3.5:9b":
+ name: "qwen3.5:9b (scaleway)"
+ gpt-oss-120b:
+ name: "gpt-oss-120b (scaleway)"
+ qwen3.5-397b-a17b:
+ name: "qwen3.5-397b-a17b (scaleway)"
+ qwen3.6-35b-a3b:
+ name: "qwen3.6-35b-a3b (scaleway)"
+ gemma-4-26b-a4b-it:
+ name: "gemma-4-26b-a4b-it (scaleway)"
+ mcp:
+ deepwiki:
+ type: "remote"
+ url: "https://mcp.deepwiki.com/mcp"
+ enabled: true
+ terraform:
+ type: "remote"
+ url: "http://terraform-mcp.lab.alexpires.me/mcp"
+ enabled: true
+ opentofu:
+ type: "remote"
+ url: "https://mcp.opentofu.org/mcp"
+ enabled: true
+ microsoft_learn:
+ type: "remote"
+ url: "https://learn.microsoft.com/api/mcp"
+ enabled: true
+ mozilla_developer:
+ type: "remote"
+ url: "https://mcp.mdn.mozilla.net/"
+ enabled: true
+ chrome_devtools:
+ type: "local"
+ command:
+ - "npx"
+ - "-y"
+ - "chrome-devtools-mcp@latest"
+ - "--headless"
+ - "--isolated"
+ enabled: true
+ playwright:
+ type: "local"
+ command:
+ - "npx"
+ - "-y"
+ - "@playwright/mcp@latest"
+ - "--headless"
+ - "--isolated"
+ enabled: true
+ aws_documentation:
+ type: "local"
+ command:
+ - "uvx"
+ - "awslabs.aws-documentation-mcp-server@latest"
+ enabled: true
+ gitea:
+ type: "local"
+ command:
+ - "gitea-mcp"
+ - "-t"
+ - "stdio"
+ - "-H"
+ - "{env:GITEA_MCP_HOST}"
+ - "-T"
+ - "{env:GITEA_APP_TOKEN}"
+ enabled: true
+ compaction:
+ auto: true
+ prune: true
+ reserved: 32768
+
+macos_dev_station_service_user_ssh_enabled: true
+macos_dev_station_service_user_ssh_authorized_keys:
+ - >-
+ ecdsa-sha2-nistp521
+ AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHHOPBR9p9kq5Cqzpe4cr3jHnweaYrHPXv5sXNzt+sCXP54uc5rWUBhxW2OQVvQzJ47dEVhEKi4WSC7LcuKS2G5AQDzWXNiasHvYIYQU3F/EknVCZnsiXYqXphYkJA6rJCNRnISZCIC1mocq6PB7J08ONdRFCvjfUBuVRT8BNGXNmQ/zQ==
diff --git a/ansible/playbook_macos_dev_station.yml b/ansible/playbook_macos_dev_station.yml
new file mode 100644
index 0000000..7c2604f
--- /dev/null
+++ b/ansible/playbook_macos_dev_station.yml
@@ -0,0 +1,14 @@
+---
+- name: Configure macOS dev station
+ hosts: darwin_dev
+ gather_facts: true
+
+ pre_tasks:
+ - name: Ensure target is macOS
+ ansible.builtin.assert:
+ that:
+ - ansible_system == "Darwin"
+ fail_msg: "This playbook supports macOS hosts only"
+
+ roles:
+ - macos_dev_station
diff --git a/ansible/roles/macos_dev_station/defaults/main.yml b/ansible/roles/macos_dev_station/defaults/main.yml
new file mode 100644
index 0000000..c7ce942
--- /dev/null
+++ b/ansible/roles/macos_dev_station/defaults/main.yml
@@ -0,0 +1,86 @@
+---
+macos_dev_station_launchd_mode: "daemon" # daemon-only role
+macos_dev_station_service_user_enabled: true
+macos_dev_station_service_user: "devstation"
+macos_dev_station_service_group: "staff"
+macos_dev_station_service_shell: "/usr/bin/false"
+macos_dev_station_service_user_ssh_enabled: false
+macos_dev_station_service_user_ssh_shell: "/bin/zsh"
+macos_dev_station_service_user_ssh_authorized_keys: []
+macos_dev_station_service_home: "/Users/{{ macos_dev_station_service_user }}"
+
+macos_dev_station_workspace_dir: "{{ macos_dev_station_service_home }}/projects"
+macos_dev_station_scripts_dir: "{{ macos_dev_station_service_home }}/scripts"
+macos_dev_station_logs_dir: "{{ macos_dev_station_service_home }}/Library/Logs"
+macos_dev_station_launchd_plist_dir: "/Library/LaunchDaemons"
+macos_dev_station_path: >-
+ {{ macos_dev_station_service_home }}/.local/bin:{{ macos_dev_station_service_home }}/go/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+
+macos_dev_station_code_server_host: "0.0.0.0"
+macos_dev_station_code_server_port: 3000
+macos_dev_station_code_server_auth: "password"
+macos_dev_station_code_server_password: "{{ lookup('env', 'CODE_SERVER_PASSWORD', default='') }}"
+macos_dev_station_code_server_workspace: "{{ macos_dev_station_workspace_dir }}"
+macos_dev_station_code_server_extensions_enabled: true
+macos_dev_station_code_server_extensions_fail_on_error: false
+macos_dev_station_code_server_extensions:
+ - ms-python.python
+ - ms-python.vscode-pylance
+ - ms-azuretools.vscode-docker
+ - hashicorp.terraform
+ - redhat.ansible
+ - eamodio.gitlens
+
+macos_dev_station_oh_my_zsh_enabled: true
+macos_dev_station_oh_my_zsh_install_url: "https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh"
+
+macos_dev_station_colima_enabled: true
+macos_dev_station_colima_cpu: 4
+macos_dev_station_colima_memory: 8
+
+macos_dev_station_brew_packages:
+ - git
+ - tmux
+ - wget
+ - curl
+ - jq
+ - openssl
+ - uv
+ - node
+ - python
+ - go
+ - rust
+ - docker
+ - colima
+ - code-server
+
+macos_dev_station_npm_packages: []
+
+macos_dev_station_git_init_default_branch: "main"
+macos_dev_station_git_pull_rebase: false
+
+macos_dev_station_ssh_key_path: "{{ ansible_facts['env']['HOME'] }}/.ssh/id_ed25519"
+macos_dev_station_ssh_key_comment: "macmini-dev"
+
+macos_dev_station_code_server_launchd_enabled: true
+macos_dev_station_opencode_launchd_enabled: true
+macos_dev_station_opencode_server_host: "127.0.0.1"
+macos_dev_station_opencode_server_port: 4096
+macos_dev_station_opencode_server_username: "{{ lookup('env', 'OPENCODE_SERVER_USERNAME', default='') }}"
+macos_dev_station_opencode_server_password: "{{ lookup('env', 'OPENCODE_SERVER_PASSWORD', default='') }}"
+macos_dev_station_opencode_extra_environment: {}
+macos_dev_station_opencode_command: "opencode serve --hostname {{ macos_dev_station_opencode_server_host }} --port {{ macos_dev_station_opencode_server_port }}"
+macos_dev_station_opencode_config: {}
+macos_dev_station_opencode_config_path: "{{ macos_dev_station_service_home }}/.config/opencode/opencode.json"
+
+# MCP installer definitions
+# Supported kinds:
+# - npm: package + optional version
+# - uv: package + optional version + binary
+# - go: module + optional version + binary
+macos_dev_station_mcp_installers_enabled: true
+macos_dev_station_mcp_installers: []
+
+# Legacy ad-hoc hook kept for backward compatibility.
+macos_dev_station_mcp_bootstrap_commands: []
+macos_dev_station_manage_launchd: true
diff --git a/ansible/roles/macos_dev_station/handlers/main.yml b/ansible/roles/macos_dev_station/handlers/main.yml
new file mode 100644
index 0000000..536ad97
--- /dev/null
+++ b/ansible/roles/macos_dev_station/handlers/main.yml
@@ -0,0 +1,60 @@
+---
+- name: Unload code-server launch agent if already loaded
+ become: true
+ ansible.builtin.command: >-
+ launchctl bootout system {{ macos_dev_station_launchd_plist_dir }}/com.a13labs.code-server.plist
+ failed_when: false
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_code_server_launchd_enabled | bool
+ listen: Restart code-server launch agent
+
+- name: Load code-server launch agent
+ become: true
+ ansible.builtin.command: >-
+ launchctl bootstrap system {{ macos_dev_station_launchd_plist_dir }}/com.a13labs.code-server.plist
+ changed_when: true
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_code_server_launchd_enabled | bool
+ listen: Restart code-server launch agent
+
+- name: Enable code-server launch agent
+ become: true
+ ansible.builtin.command: launchctl enable system/com.a13labs.code-server
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_code_server_launchd_enabled | bool
+ listen: Restart code-server launch agent
+
+- name: Unload opencode launch agent if already loaded
+ become: true
+ ansible.builtin.command: >-
+ launchctl bootout system {{ macos_dev_station_launchd_plist_dir }}/com.a13labs.opencode.plist
+ failed_when: false
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_opencode_launchd_enabled | bool
+ listen: Restart opencode launch agent
+
+- name: Load opencode launch agent
+ become: true
+ ansible.builtin.command: >-
+ launchctl bootstrap system {{ macos_dev_station_launchd_plist_dir }}/com.a13labs.opencode.plist
+ changed_when: true
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_opencode_launchd_enabled | bool
+ listen: Restart opencode launch agent
+
+- name: Enable opencode launch agent
+ become: true
+ ansible.builtin.command: launchctl enable system/com.a13labs.opencode
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_opencode_launchd_enabled | bool
+ listen: Restart opencode launch agent
diff --git a/ansible/roles/macos_dev_station/tasks/main.yml b/ansible/roles/macos_dev_station/tasks/main.yml
new file mode 100644
index 0000000..e042cf9
--- /dev/null
+++ b/ansible/roles/macos_dev_station/tasks/main.yml
@@ -0,0 +1,445 @@
+---
+- name: Ensure host is macOS
+ ansible.builtin.assert:
+ that:
+ - ansible_system == "Darwin"
+ fail_msg: "Role macos_dev_station supports macOS hosts only"
+
+- name: Ensure launchd mode is valid
+ ansible.builtin.assert:
+ that:
+ - macos_dev_station_launchd_mode == "daemon"
+ fail_msg: "macos_dev_station currently supports daemon mode only. Set macos_dev_station_launchd_mode to 'daemon'."
+
+- name: Check passwordless sudo availability for daemon mode
+ ansible.builtin.command: sudo -n true
+ register: __macos_dev_station_sudo_check
+ changed_when: false
+ failed_when: false
+
+- name: Ensure sudo access is available for daemon mode
+ ansible.builtin.assert:
+ that:
+ - __macos_dev_station_sudo_check.rc == 0 or (ansible_become_password | default('') | length > 0)
+ fail_msg: "Daemon mode needs sudo. Configure passwordless sudo for ansible user or set ANSIBLE_BECOME_PASSWORD via sectool."
+
+- name: Ensure OpenCode server credentials are provided
+ ansible.builtin.assert:
+ that:
+ - macos_dev_station_opencode_server_username | length > 0
+ - macos_dev_station_opencode_server_password | length > 0
+ fail_msg: "Set OPENCODE_SERVER_USERNAME and OPENCODE_SERVER_PASSWORD in environment (recommended via sectool) before running the playbook."
+ when: macos_dev_station_opencode_launchd_enabled | bool
+
+- name: Ensure code-server password is provided
+ ansible.builtin.assert:
+ that:
+ - macos_dev_station_code_server_password | length > 0
+ fail_msg: "Set CODE_SERVER_PASSWORD in environment (recommended via sectool) before running the playbook."
+ when:
+ - macos_dev_station_code_server_launchd_enabled | bool
+ - macos_dev_station_code_server_auth == "password"
+
+- name: Install Homebrew packages
+ become: false
+ community.general.homebrew:
+ name: "{{ macos_dev_station_brew_packages }}"
+ state: present
+
+- name: Ensure dedicated service user exists
+ become: true
+ ansible.builtin.user:
+ name: "{{ macos_dev_station_service_user }}"
+ shell: "{{ (macos_dev_station_service_user_ssh_enabled | bool) | ternary(macos_dev_station_service_user_ssh_shell, macos_dev_station_service_shell) }}"
+ home: "{{ macos_dev_station_service_home }}"
+ create_home: true
+ state: present
+ when: macos_dev_station_service_user_enabled | bool
+
+- name: Discover npm executable path
+ ansible.builtin.command: /bin/sh -lc "command -v npm"
+ register: __macos_npm_path
+ changed_when: false
+ failed_when: __macos_npm_path.rc != 0
+ environment:
+ PATH: "{{ macos_dev_station_path }}"
+ when: macos_dev_station_npm_packages | length > 0
+
+- name: Install global npm packages
+ community.general.npm:
+ name: "{{ item.name }}"
+ version: "{{ item.version }}"
+ executable: "{{ __macos_npm_path.stdout | trim }}"
+ global: true
+ environment:
+ PATH: "{{ macos_dev_station_path }}"
+ loop: "{{ macos_dev_station_npm_packages }}"
+ when: macos_dev_station_npm_packages | length > 0
+
+- name: Ensure required user directories exist
+ become: true
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ mode: "0750"
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+ loop:
+ - "{{ macos_dev_station_service_home }}"
+ - "{{ macos_dev_station_workspace_dir }}"
+ - "{{ macos_dev_station_scripts_dir }}"
+ - "{{ macos_dev_station_logs_dir }}"
+
+- name: Ensure service home permissions are restrictive
+ become: true
+ ansible.builtin.file:
+ path: "{{ macos_dev_station_service_home }}"
+ state: directory
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+ mode: "0700"
+
+- name: Ensure service user config directories exist
+ become: true
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+ mode: "0750"
+ loop:
+ - "{{ macos_dev_station_service_home }}/.cache"
+ - "{{ macos_dev_station_service_home }}/.local"
+ - "{{ macos_dev_station_service_home }}/.local/bin"
+ - "{{ macos_dev_station_service_home }}/.local/lib"
+ - "{{ macos_dev_station_service_home }}/.local/lib/node_modules"
+ - "{{ macos_dev_station_service_home }}/.config"
+ - "{{ macos_dev_station_service_home }}/.config/opencode"
+ when: macos_dev_station_service_user_enabled | bool
+
+- name: Ensure OpenCode config object is valid when provided
+ ansible.builtin.assert:
+ that:
+ - macos_dev_station_opencode_config is mapping
+ fail_msg: "macos_dev_station_opencode_config must be a mapping (dictionary)."
+
+- name: Render OpenCode config from host vars
+ become: true
+ ansible.builtin.template:
+ src: opencode.json.j2
+ dest: "{{ macos_dev_station_opencode_config_path }}"
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+ mode: "0600"
+ notify: Restart opencode launch agent
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_opencode_config | length > 0
+
+- name: Download Oh My Zsh installer
+ become: true
+ ansible.builtin.get_url:
+ url: "{{ macos_dev_station_oh_my_zsh_install_url }}"
+ dest: "{{ macos_dev_station_service_home }}/.cache/ohmyzsh-install.sh"
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+ mode: "0755"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_oh_my_zsh_enabled | bool
+
+- name: Install Oh My Zsh for service user
+ become: true
+ become_user: "{{ macos_dev_station_service_user }}"
+ ansible.builtin.command: >-
+ /bin/sh {{ macos_dev_station_service_home }}/.cache/ohmyzsh-install.sh --unattended
+ args:
+ creates: "{{ macos_dev_station_service_home }}/.oh-my-zsh"
+ environment:
+ HOME: "{{ macos_dev_station_service_home }}"
+ USER: "{{ macos_dev_station_service_user }}"
+ RUNZSH: "no"
+ CHSH: "no"
+ KEEP_ZSHRC: "yes"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_oh_my_zsh_enabled | bool
+
+- name: Install managed MCP server runtimes
+ ansible.builtin.include_tasks: mcp_installers.yml
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_mcp_installers_enabled | bool
+ - macos_dev_station_mcp_installers | length > 0
+
+- name: Run MCP bootstrap commands
+ become: true
+ become_user: "{{ macos_dev_station_service_user }}"
+ ansible.builtin.shell: "{{ item }}"
+ register: __mcp_bootstrap_result
+ changed_when: __mcp_bootstrap_result.rc == 0
+ args:
+ executable: /bin/sh
+ environment:
+ HOME: "{{ macos_dev_station_service_home }}"
+ USER: "{{ macos_dev_station_service_user }}"
+ PATH: "{{ macos_dev_station_path }}"
+ loop: "{{ macos_dev_station_mcp_bootstrap_commands }}"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_mcp_bootstrap_commands | length > 0
+
+- name: List installed code-server extensions for service user
+ become: true
+ become_user: "{{ macos_dev_station_service_user }}"
+ ansible.builtin.command: code-server --list-extensions
+ register: __code_server_extensions_installed
+ changed_when: false
+ environment:
+ HOME: "{{ macos_dev_station_service_home }}"
+ USER: "{{ macos_dev_station_service_user }}"
+ PATH: "{{ macos_dev_station_path }}"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_code_server_extensions_enabled | bool
+ - macos_dev_station_code_server_extensions | length > 0
+
+- name: Install base code-server extensions for service user
+ become: true
+ become_user: "{{ macos_dev_station_service_user }}"
+ ansible.builtin.command: >-
+ code-server --install-extension {{ item }}
+ register: __code_server_extension_install
+ changed_when: __code_server_extension_install.rc == 0
+ failed_when: >-
+ (macos_dev_station_code_server_extensions_fail_on_error | bool)
+ and (__code_server_extension_install.rc != 0)
+ environment:
+ HOME: "{{ macos_dev_station_service_home }}"
+ USER: "{{ macos_dev_station_service_user }}"
+ PATH: "{{ macos_dev_station_path }}"
+ notify: Restart code-server launch agent
+ loop: "{{ macos_dev_station_code_server_extensions }}"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_code_server_extensions_enabled | bool
+ - item not in (__code_server_extensions_installed.stdout_lines | default([]))
+
+- name: Report skipped code-server extensions that are unavailable
+ ansible.builtin.debug:
+ msg: >-
+ Skipping unavailable code-server extension '{{ item.item }}':
+ {{ (item.stderr_lines | default([])) | join(' ') }}
+ loop: "{{ __code_server_extension_install.results | default([]) }}"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_code_server_extensions_enabled | bool
+ - not (macos_dev_station_code_server_extensions_fail_on_error | bool)
+ - item.rc is defined
+ - item.rc != 0
+
+- name: Ensure devstation SSH directory exists when SSH access is enabled
+ become: true
+ ansible.builtin.file:
+ path: "{{ macos_dev_station_service_home }}/.ssh"
+ state: directory
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+ mode: "0700"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_service_user_ssh_enabled | bool
+
+- name: Ensure devstation authorized keys are present when SSH access is enabled
+ become: true
+ ansible.posix.authorized_key:
+ user: "{{ macos_dev_station_service_user }}"
+ key: "{{ item }}"
+ state: present
+ loop: "{{ macos_dev_station_service_user_ssh_authorized_keys }}"
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_service_user_ssh_enabled | bool
+
+- name: Check whether macOS Remote Login allowlist group exists
+ become: true
+ ansible.builtin.command: dscl . -read /Groups/com.apple.access_ssh
+ register: __macos_access_ssh_group
+ changed_when: false
+ failed_when: false
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_service_user_ssh_enabled | bool
+
+- name: Check whether service user is in macOS Remote Login allowlist
+ become: true
+ ansible.builtin.command: >-
+ dseditgroup -o checkmember -m {{ macos_dev_station_service_user }} com.apple.access_ssh
+ register: __macos_access_ssh_member
+ changed_when: false
+ failed_when: false
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_service_user_ssh_enabled | bool
+ - __macos_access_ssh_group.rc == 0
+
+- name: Ensure service user is allowed by macOS Remote Login allowlist
+ become: true
+ ansible.builtin.command: >-
+ dseditgroup -o edit -a {{ macos_dev_station_service_user }} -t user com.apple.access_ssh
+ changed_when: true
+ when:
+ - macos_dev_station_service_user_enabled | bool
+ - macos_dev_station_service_user_ssh_enabled | bool
+ - __macos_access_ssh_group.rc == 0
+ - __macos_access_ssh_member.rc != 0
+
+- name: Remove legacy user launch agent plists for hard mode
+ ansible.builtin.file:
+ path: "{{ ansible_facts['env']['HOME'] }}/Library/LaunchAgents/{{ item }}"
+ state: absent
+ loop:
+ - com.a13labs.code-server.plist
+ - com.a13labs.opencode.plist
+
+- name: Ensure SSH directory exists
+ ansible.builtin.file:
+ path: "{{ ansible_facts['env']['HOME'] }}/.ssh"
+ state: directory
+ mode: "0700"
+
+- name: Generate SSH key if missing
+ ansible.builtin.command: >-
+ ssh-keygen -t ed25519 -C {{ macos_dev_station_ssh_key_comment }}
+ -f {{ macos_dev_station_ssh_key_path }} -N ''
+ args:
+ creates: "{{ macos_dev_station_ssh_key_path }}"
+
+- name: Configure Git init default branch
+ community.general.git_config:
+ name: init.defaultBranch
+ scope: global
+ value: "{{ macos_dev_station_git_init_default_branch }}"
+
+- name: Configure Git pull.rebase
+ community.general.git_config:
+ name: pull.rebase
+ scope: global
+ value: "{{ macos_dev_station_git_pull_rebase | ternary('true', 'false') }}"
+
+- name: Render code-server launcher script
+ become: true
+ ansible.builtin.template:
+ src: start-ide.sh.j2
+ dest: "{{ macos_dev_station_scripts_dir }}/start-ide.sh"
+ mode: "0755"
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+
+- name: Render opencode launcher script
+ become: true
+ ansible.builtin.template:
+ src: start-opencode.sh.j2
+ dest: "{{ macos_dev_station_scripts_dir }}/start-opencode.sh"
+ mode: "0755"
+ owner: "{{ macos_dev_station_service_user }}"
+ group: "{{ macos_dev_station_service_group }}"
+
+- name: Render tmux configuration
+ ansible.builtin.copy:
+ dest: "{{ ansible_facts['env']['HOME'] }}/.tmux.conf"
+ mode: "0644"
+ content: |
+ set -g mouse on
+ set -g history-limit 100000
+
+- name: Ensure Colima runtime is started
+ ansible.builtin.command: >-
+ colima start --cpu {{ macos_dev_station_colima_cpu }} --memory {{ macos_dev_station_colima_memory }}
+ register: __colima_start
+ changed_when: "'already running' not in ((__colima_start.stdout | default('')) + (__colima_start.stderr | default('')))"
+ when: macos_dev_station_colima_enabled | bool
+ environment:
+ PATH: "{{ macos_dev_station_path }}"
+
+- name: Render code-server launchd unit
+ become: true
+ ansible.builtin.template:
+ src: code-server.plist.j2
+ dest: "{{ macos_dev_station_launchd_plist_dir }}/com.a13labs.code-server.plist"
+ mode: "0644"
+ owner: root
+ group: wheel
+ when: macos_dev_station_code_server_launchd_enabled | bool
+ notify: Restart code-server launch agent
+
+- name: Render opencode launchd unit
+ become: true
+ ansible.builtin.template:
+ src: opencode.plist.j2
+ dest: "{{ macos_dev_station_launchd_plist_dir }}/com.a13labs.opencode.plist"
+ mode: "0644"
+ owner: root
+ group: wheel
+ when: macos_dev_station_opencode_launchd_enabled | bool
+ notify: Restart opencode launch agent
+
+- name: Check code-server daemon status
+ become: true
+ ansible.builtin.command: launchctl print system/com.a13labs.code-server
+ register: __code_server_daemon_status
+ failed_when: false
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_code_server_launchd_enabled | bool
+
+- name: Bootstrap code-server daemon if missing
+ become: true
+ ansible.builtin.command: >-
+ launchctl bootstrap system {{ macos_dev_station_launchd_plist_dir }}/com.a13labs.code-server.plist
+ changed_when: true
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_code_server_launchd_enabled | bool
+ - __code_server_daemon_status.rc != 0
+
+- name: Ensure code-server daemon is enabled
+ become: true
+ ansible.builtin.command: launchctl enable system/com.a13labs.code-server
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_code_server_launchd_enabled | bool
+
+- name: Check opencode daemon status
+ become: true
+ ansible.builtin.command: launchctl print system/com.a13labs.opencode
+ register: __opencode_daemon_status
+ failed_when: false
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_opencode_launchd_enabled | bool
+
+- name: Bootstrap opencode daemon if missing
+ become: true
+ ansible.builtin.command: >-
+ launchctl bootstrap system {{ macos_dev_station_launchd_plist_dir }}/com.a13labs.opencode.plist
+ changed_when: true
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_opencode_launchd_enabled | bool
+ - __opencode_daemon_status.rc != 0
+
+- name: Ensure opencode daemon is enabled
+ become: true
+ ansible.builtin.command: launchctl enable system/com.a13labs.opencode
+ changed_when: false
+ when:
+ - macos_dev_station_manage_launchd | bool
+ - macos_dev_station_opencode_launchd_enabled | bool
+
+- name: Ensure launch agents are loaded now
+ ansible.builtin.meta: flush_handlers
+ when: macos_dev_station_manage_launchd | bool
diff --git a/ansible/roles/macos_dev_station/tasks/mcp_installers.yml b/ansible/roles/macos_dev_station/tasks/mcp_installers.yml
new file mode 100644
index 0000000..8f35758
--- /dev/null
+++ b/ansible/roles/macos_dev_station/tasks/mcp_installers.yml
@@ -0,0 +1,132 @@
+---
+- name: Ensure MCP installer definitions list is valid
+ ansible.builtin.assert:
+ that:
+ - macos_dev_station_mcp_installers is iterable
+ fail_msg: "macos_dev_station_mcp_installers must be a list of installer definitions."
+
+- name: Validate MCP installer kind values
+ ansible.builtin.assert:
+ that:
+ - item.kind is defined
+ - item.kind in ['npm', 'uv', 'go']
+ fail_msg: "MCP installer '{{ item.name | default('unnamed') }}' has unsupported kind '{{ item.kind | default('undefined') }}'. Supported: npm, uv, go."
+ loop: "{{ macos_dev_station_mcp_installers }}"
+ when: item.enabled | default(true) | bool
+
+- name: Validate npm MCP installers
+ ansible.builtin.assert:
+ that:
+ - item.package is defined
+ - item.package | length > 0
+ fail_msg: "npm MCP installer '{{ item.name | default('unnamed') }}' requires 'package'."
+ loop: "{{ macos_dev_station_mcp_installers }}"
+ when:
+ - item.enabled | default(true) | bool
+ - item.kind == 'npm'
+
+- name: Validate uv MCP installers
+ ansible.builtin.assert:
+ that:
+ - item.package is defined
+ - item.package | length > 0
+ - item.binary is defined
+ - item.binary | length > 0
+ fail_msg: "uv MCP installer '{{ item.name | default('unnamed') }}' requires 'package' and 'binary'."
+ loop: "{{ macos_dev_station_mcp_installers }}"
+ when:
+ - item.enabled | default(true) | bool
+ - item.kind == 'uv'
+
+- name: Validate go MCP installers
+ ansible.builtin.assert:
+ that:
+ - item.module is defined
+ - item.module | length > 0
+ - item.binary is defined
+ - item.binary | length > 0
+ fail_msg: "go MCP installer '{{ item.name | default('unnamed') }}' requires 'module' and 'binary'."
+ loop: "{{ macos_dev_station_mcp_installers }}"
+ when:
+ - item.enabled | default(true) | bool
+ - item.kind == 'go'
+
+- name: Discover npm executable path for MCP installers
+ ansible.builtin.command: /bin/sh -lc "command -v npm"
+ register: __macos_mcp_npm_path
+ changed_when: false
+ failed_when: __macos_mcp_npm_path.rc != 0
+ environment:
+ PATH: "{{ macos_dev_station_path }}"
+ when: (macos_dev_station_mcp_installers | selectattr('kind', 'equalto', 'npm') | list | length) > 0
+
+- name: Install npm MCP packages for service user
+ become: true
+ become_user: "{{ macos_dev_station_service_user }}"
+ ansible.builtin.command:
+ argv:
+ - "{{ __macos_mcp_npm_path.stdout | trim }}"
+ - install
+ - --prefix
+ - "{{ macos_dev_station_service_home }}/.local"
+ - --global
+ - "{{ item.package }}@{{ item.version | default('latest') }}"
+ args:
+ creates: "{{ macos_dev_station_service_home }}/.local/lib/node_modules/{{ item.package }}"
+ register: __mcp_npm_install
+ changed_when: __mcp_npm_install.rc == 0
+ environment:
+ HOME: "{{ macos_dev_station_service_home }}"
+ USER: "{{ macos_dev_station_service_user }}"
+ PATH: "{{ macos_dev_station_path }}"
+ notify: Restart opencode launch agent
+ loop: "{{ macos_dev_station_mcp_installers }}"
+ when:
+ - item.enabled | default(true) | bool
+ - item.kind == 'npm'
+
+- name: Install uv MCP tools for service user
+ become: true
+ become_user: "{{ macos_dev_station_service_user }}"
+ ansible.builtin.command:
+ argv:
+ - uv
+ - tool
+ - install
+ - "{{ item.package }}@{{ item.version | default('latest') }}"
+ args:
+ creates: "{{ macos_dev_station_service_home }}/.local/bin/{{ item.binary }}"
+ register: __mcp_uv_install
+ changed_when: __mcp_uv_install.rc == 0
+ environment:
+ HOME: "{{ macos_dev_station_service_home }}"
+ USER: "{{ macos_dev_station_service_user }}"
+ PATH: "{{ macos_dev_station_path }}"
+ notify: Restart opencode launch agent
+ loop: "{{ macos_dev_station_mcp_installers }}"
+ when:
+ - item.enabled | default(true) | bool
+ - item.kind == 'uv'
+
+- name: Install go MCP binaries for service user
+ become: true
+ become_user: "{{ macos_dev_station_service_user }}"
+ ansible.builtin.command:
+ argv:
+ - go
+ - install
+ - "{{ item.module }}@{{ item.version | default('latest') }}"
+ args:
+ creates: "{{ macos_dev_station_service_home }}/.local/bin/{{ item.binary }}"
+ register: __mcp_go_install
+ changed_when: __mcp_go_install.rc == 0
+ environment:
+ HOME: "{{ macos_dev_station_service_home }}"
+ USER: "{{ macos_dev_station_service_user }}"
+ PATH: "{{ macos_dev_station_path }}"
+ GOBIN: "{{ macos_dev_station_service_home }}/.local/bin"
+ notify: Restart opencode launch agent
+ loop: "{{ macos_dev_station_mcp_installers }}"
+ when:
+ - item.enabled | default(true) | bool
+ - item.kind == 'go'
diff --git a/ansible/roles/macos_dev_station/templates/code-server.plist.j2 b/ansible/roles/macos_dev_station/templates/code-server.plist.j2
new file mode 100644
index 0000000..a01590f
--- /dev/null
+++ b/ansible/roles/macos_dev_station/templates/code-server.plist.j2
@@ -0,0 +1,40 @@
+
+
+
+
+ Label
+ com.a13labs.code-server
+
+ UserName
+ {{ macos_dev_station_service_user }}
+
+ WorkingDirectory
+ {{ macos_dev_station_workspace_dir }}
+
+ ProgramArguments
+
+ /bin/bash
+ {{ macos_dev_station_scripts_dir }}/start-ide.sh
+
+
+ RunAtLoad
+
+
+ KeepAlive
+
+
+ StandardOutPath
+ {{ macos_dev_station_logs_dir }}/code-server.log
+
+ StandardErrorPath
+ {{ macos_dev_station_logs_dir }}/code-server.err.log
+
+ EnvironmentVariables
+
+ PATH
+ {{ macos_dev_station_path }}
+ PASSWORD
+ {{ macos_dev_station_code_server_password }}
+
+
+
diff --git a/ansible/roles/macos_dev_station/templates/opencode.json.j2 b/ansible/roles/macos_dev_station/templates/opencode.json.j2
new file mode 100644
index 0000000..cf36003
--- /dev/null
+++ b/ansible/roles/macos_dev_station/templates/opencode.json.j2
@@ -0,0 +1 @@
+{{ macos_dev_station_opencode_config | to_nice_json }}
diff --git a/ansible/roles/macos_dev_station/templates/opencode.plist.j2 b/ansible/roles/macos_dev_station/templates/opencode.plist.j2
new file mode 100644
index 0000000..88f371c
--- /dev/null
+++ b/ansible/roles/macos_dev_station/templates/opencode.plist.j2
@@ -0,0 +1,46 @@
+
+
+
+
+ Label
+ com.a13labs.opencode
+
+ UserName
+ {{ macos_dev_station_service_user }}
+
+ WorkingDirectory
+ {{ macos_dev_station_workspace_dir }}
+
+ ProgramArguments
+
+ /bin/bash
+ {{ macos_dev_station_scripts_dir }}/start-opencode.sh
+
+
+ RunAtLoad
+
+
+ KeepAlive
+
+
+ StandardOutPath
+ {{ macos_dev_station_logs_dir }}/opencode.log
+
+ StandardErrorPath
+ {{ macos_dev_station_logs_dir }}/opencode.err.log
+
+ EnvironmentVariables
+
+ PATH
+ {{ macos_dev_station_path }}
+ OPENCODE_SERVER_USERNAME
+ {{ macos_dev_station_opencode_server_username }}
+ OPENCODE_SERVER_PASSWORD
+ {{ macos_dev_station_opencode_server_password }}
+{% for env_key, env_value in macos_dev_station_opencode_extra_environment.items() %}
+ {{ env_key }}
+ {{ env_value }}
+{% endfor %}
+
+
+
diff --git a/ansible/roles/macos_dev_station/templates/start-ide.sh.j2 b/ansible/roles/macos_dev_station/templates/start-ide.sh.j2
new file mode 100644
index 0000000..a108118
--- /dev/null
+++ b/ansible/roles/macos_dev_station/templates/start-ide.sh.j2
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+cd {{ macos_dev_station_workspace_dir }}
+
+exec code-server \
+ --bind-addr {{ macos_dev_station_code_server_host }}:{{ macos_dev_station_code_server_port }} \
+ --auth {{ macos_dev_station_code_server_auth }} \
+ --disable-telemetry \
+ {{ macos_dev_station_code_server_workspace }}
diff --git a/ansible/roles/macos_dev_station/templates/start-opencode.sh.j2 b/ansible/roles/macos_dev_station/templates/start-opencode.sh.j2
new file mode 100644
index 0000000..cd9f7eb
--- /dev/null
+++ b/ansible/roles/macos_dev_station/templates/start-opencode.sh.j2
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+cd {{ macos_dev_station_workspace_dir }}
+
+exec {{ macos_dev_station_opencode_command }}
diff --git a/ansible/sectool.env b/ansible/sectool.env
index 4eea01a..5d21237 100644
--- a/ansible/sectool.env
+++ b/ansible/sectool.env
@@ -16,4 +16,8 @@ CLOUDNS_AUTH_ID=$CLOUDNS_AUTH_ID
CLOUDNS_PASSWORD=$CLOUDNS_PASSWORD
WIN_PROVISION_PASSWORD=$WIN_PROVISION_PASSWORD
WIN_GAME_PASSWORD=$WIN_GAME_PASSWORD
-WIN_CI_01_PASSWORD=$WIN_CI_01_PASSWORD
\ No newline at end of file
+WIN_CI_01_PASSWORD=$WIN_CI_01_PASSWORD
+OPENCODE_SERVER_USERNAME=$OPENCODE_SERVER_USERNAME
+OPENCODE_SERVER_PASSWORD=$OPENCODE_SERVER_PASSWORD
+CODE_SERVER_PASSWORD=$CODE_SERVER_PASSWORD
+OPEN_WEBUI_API_KEY=$OPEN_WEBUI_API_KEY
diff --git a/inventory/hosts b/inventory/hosts
index 0f74d58..5a13f51 100644
--- a/inventory/hosts
+++ b/inventory/hosts
@@ -138,3 +138,6 @@ ci-01.lab.alexpires.me
[windows_gitea_runner_windows]
ci-01.lab.alexpires.me
+[darwin_dev]
+mac-01.lab.alexpires.me
+
diff --git a/scripts/setup-dev-station.sh b/scripts/setup-dev-station.sh
new file mode 100755
index 0000000..55d52eb
--- /dev/null
+++ b/scripts/setup-dev-station.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+SCRIPT_NAME="$(basename "$0")"
+LOG_FILE="${HOME}/.setup-dev-station.log"
+
+log() {
+ printf '[%s] %s\n' "${SCRIPT_NAME}" "$*" | tee -a "${LOG_FILE}"
+}
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ log "This script is for macOS only."
+ exit 1
+fi
+
+log "Starting macOS bootstrap for dev station prerequisites."
+
+if ! xcode-select -p >/dev/null 2>&1; then
+ log "Installing Xcode Command Line Tools (follow GUI prompt once)."
+ xcode-select --install || true
+else
+ log "Xcode Command Line Tools already installed."
+fi
+
+if ! command -v brew >/dev/null 2>&1; then
+ log "Installing Homebrew."
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+else
+ log "Homebrew already installed."
+fi
+
+if [[ -x /opt/homebrew/bin/brew ]]; then
+ eval "$(/opt/homebrew/bin/brew shellenv)"
+fi
+
+if ! grep -Fq '/opt/homebrew/bin/brew shellenv' "${HOME}/.zprofile" 2>/dev/null; then
+ log "Persisting Homebrew shellenv into ~/.zprofile."
+ printf '\n# Homebrew\neval "$(/opt/homebrew/bin/brew shellenv)"\n' >> "${HOME}/.zprofile"
+else
+ log "Homebrew shellenv already present in ~/.zprofile."
+fi
+
+log "Updating Homebrew metadata."
+brew update
+
+log "Installing bootstrap packages."
+brew install git curl jq python ansible
+
+log "Preparing workspace directories."
+mkdir -p "${HOME}/projects" "${HOME}/scripts"
+
+log "Bootstrap complete."
+log "Next step: run Ansible playbook for full dev-station configuration:"
+log " cd ~/projects/a13labs.infra/ansible"
+log " ansible-playbook playbook_macos_dev_station.yml --limit darwin_dev"
\ No newline at end of file
diff --git a/terraform/apps/dev-01/config.tf b/terraform/apps/dev-01/config.tf
index f5ac8d4..a270290 100644
--- a/terraform/apps/dev-01/config.tf
+++ b/terraform/apps/dev-01/config.tf
@@ -32,7 +32,6 @@ locals {
ip = "10.19.4.136"
},
]
-
zones = [
{
fqdn = "${local.lab_domain}"
@@ -52,31 +51,21 @@ locals {
type = "A"
content = "10.19.4.136"
},
+ {
+ name = "mac-01"
+ type = "A"
+ content = "10.19.4.203"
+ },
{
name = "ollama"
type = "CNAME"
content = "dev-01.${local.lab_domain}."
},
- {
- name = "kokoro"
- type = "CNAME"
- content = "gpu-01.${local.lab_domain}."
- },
{
name = "open-webui"
type = "CNAME"
content = "dev-01.${local.lab_domain}."
},
- {
- name = "comfyui-backend"
- type = "CNAME"
- content = "gpu-01.${local.lab_domain}."
- },
- {
- name = "comfyui"
- type = "CNAME"
- content = "dev-01.${local.lab_domain}."
- },
{
name = "smb"
type = "CNAME"
@@ -131,7 +120,17 @@ locals {
name = "terraform-mcp"
type = "CNAME"
content = "dev-01.${local.lab_domain}."
- }
+ },
+ {
+ name = "opencode"
+ type = "CNAME"
+ content = "dev-01.${local.lab_domain}."
+ },
+ {
+ name = "vscode"
+ type = "CNAME"
+ content = "dev-01.${local.lab_domain}."
+ },
]
}
]
@@ -153,6 +152,38 @@ locals {
# },
# ]
# }
+ "opencode" = {
+ http_port = 4096
+ tls = true
+ fqdn = ["opencode.lab.alexpires.me"]
+ upstream = "http://10.19.4.203:4096"
+ read_timeout = "3600s"
+
+ locations = [
+ {
+ path = "/"
+ headers = {
+ "Upgrade" = "$http_upgrade"
+ "Connection" = "upgrade"
+ }
+ },
+ ]
+ },
+ "code-server" = {
+ http_port = 3000
+ tls = true
+ fqdn = ["vscode.lab.alexpires.me"]
+ upstream = "http://10.19.4.203:3000"
+ locations = [
+ {
+ path = "/"
+ headers = {
+ "Upgrade" = "$http_upgrade"
+ "Connection" = "upgrade"
+ }
+ },
+ ]
+ }
}
streams = {