Add Linux development station role with systemd services for code-server and OpenCode
- Implemented handlers for restarting code-server and OpenCode services. - Created main tasks for setting up a Linux development station, including user management, package installation, and configuration. - Added validation for OpenCode server credentials and code-server password. - Introduced tool package management for npm, uv, and go installers. - Developed templates for systemd service units for code-server and OpenCode. - Included scripts for starting code-server and OpenCode with SSH agent support. - Updated inventory and Terraform configuration to include new development host.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
[defaults]
|
||||
inventory = ../inventory/hosts
|
||||
allow_world_readable_tmpfiles = True
|
||||
|
||||
[ssh_connection]
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
hostname: dev-02.lab.alexpires.me
|
||||
ansible_user: "{{ lookup('env', 'ANSIBLE_USER') }}"
|
||||
ansible_host: "10.19.4.210"
|
||||
|
||||
linux_dev_station_service_user_enabled: true
|
||||
linux_dev_station_service_user: "devstation"
|
||||
linux_dev_station_service_home: "/home/devstation"
|
||||
linux_dev_station_workspace_dir: "/home/devstation/projects"
|
||||
linux_dev_station_scripts_dir: "/home/devstation/scripts"
|
||||
linux_dev_station_logs_dir: "/home/devstation/.local/state/logs"
|
||||
|
||||
linux_dev_station_code_server_host: "0.0.0.0"
|
||||
linux_dev_station_code_server_port: 3000
|
||||
linux_dev_station_code_server_auth: "password"
|
||||
|
||||
linux_dev_station_opencode_server_host: "0.0.0.0"
|
||||
linux_dev_station_opencode_server_port: 4096
|
||||
linux_dev_station_manage_systemd: true
|
||||
linux_dev_station_oh_my_zsh_enabled: true
|
||||
|
||||
linux_dev_station_code_server_extensions_enabled: true
|
||||
linux_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
|
||||
|
||||
linux_dev_station_ssh_agent_enabled: true
|
||||
linux_dev_station_mcp_bootstrap_commands: []
|
||||
|
||||
linux_dev_station_mcp_installers_enabled: true
|
||||
linux_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"
|
||||
|
||||
linux_dev_station_opencode_extra_environment:
|
||||
GITEA_MCP_HOST: "https://code.alexpires.me"
|
||||
GITEA_APP_TOKEN: "{{ lookup('env', 'GITEA_APP_TOKEN', default='') }}"
|
||||
|
||||
linux_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='') }}"
|
||||
timeout: 3000000
|
||||
chunkTimeout: 3000000
|
||||
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"
|
||||
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
|
||||
|
||||
linux_dev_station_service_user_ssh_enabled: true
|
||||
linux_dev_station_service_user_ssh_authorized_keys: []
|
||||
|
||||
# Firewall ports
|
||||
ufw_enable: true
|
||||
fw_allowed_ports:
|
||||
- { rule: "allow", port: "22", proto: "tcp", from: "10.5.5.5/32" } # SSH Access (VPN device)
|
||||
- { rule: "allow", port: "22", proto: "tcp", from: "10.19.4.0/24" } # SSH Access (local network)
|
||||
- { rule: "allow", port: "3000", proto: "tcp", from: "10.19.4.0/24" } # code-server (local network only)
|
||||
- { rule: "allow", port: "4096", proto: "tcp", from: "10.19.4.0/24" } # opencode (local network only)
|
||||
- { rule: "allow", port: "9090", proto: "tcp", from: "10.19.4.0/24" } # opencode (local network only)
|
||||
@@ -118,25 +118,25 @@ llama_server_devices:
|
||||
- "/dev/dri:/dev/dri:rw"
|
||||
|
||||
llama_server_models:
|
||||
# TODO: replace revision values with pinned commit SHAs for strict reproducibility.
|
||||
# # TODO: replace revision values with pinned commit SHAs for strict reproducibility.
|
||||
- name: "gemma-4-12b-q8-0"
|
||||
model_id: "unsloth/gemma-4-12b-it-GGUF"
|
||||
quant: "Q8_0"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "gemma-4-26b-a4b-ud-iq4-xs"
|
||||
model_id: "unsloth/gemma-4-26B-A4B-it-GGUF"
|
||||
quant: "UD-IQ4_XS"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "gemma-4-31b-iq4-xs"
|
||||
model_id: "unsloth/gemma-4-31B-it-GGUF"
|
||||
quant: "IQ4_XS"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "gpt-oss-20b-q4-0"
|
||||
model_id: "unsloth/gpt-oss-20b-GGUF"
|
||||
quant: "Q4_0"
|
||||
@@ -149,44 +149,56 @@ llama_server_models:
|
||||
model_id: "unsloth/Qwen3.5-27B-GGUF"
|
||||
quant: "Q4_K_M"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "qwen3-5-35b-a3b-q4-k-m"
|
||||
model_id: "unsloth/Qwen3.5-35B-A3B-GGUF"
|
||||
quant: "Q4_K_M"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "qwen3-5-9b-q8-0"
|
||||
model_id: "unsloth/Qwen3.5-9B-GGUF"
|
||||
quant: "Q8_0"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "qwen3-6-27b-q4-k-m"
|
||||
model_id: "unsloth/Qwen3.6-27B-GGUF"
|
||||
quant: "Q4_K_M"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "qwen3-6-35b-a3b-ud-q4-k-m"
|
||||
model_id: "unsloth/Qwen3.6-35B-A3B-GGUF"
|
||||
quant: "UD-Q4_K_M"
|
||||
revision: "main"
|
||||
# preset:
|
||||
# ctx-size: 98304
|
||||
- name: "qwen3-6-27b-mtp-q4-0"
|
||||
model_id: "unsloth/Qwen3.6-27B-MTP-GGUF"
|
||||
quant: "Q4_0"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
temperature: 0.6
|
||||
top_p: 0.95
|
||||
top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
repeat_penalty: 1.0
|
||||
# ctx-size: 98304
|
||||
- name: "qwen3-6-35b-a3b-mtp-ud-q4-m"
|
||||
model_id: "unsloth/Qwen3.6-35B-A3B-MTP-GGUF"
|
||||
quant: "UD-Q4_K_M"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
- name: "qwen3-6-35b-a3b-ud-q4-k-m"
|
||||
model_id: "unsloth/Qwen3.6-35B-A3B-GGUF"
|
||||
quant: "UD-Q4_K_M"
|
||||
revision: "main"
|
||||
preset:
|
||||
ctx-size: 98304
|
||||
temperature: 0.6
|
||||
top_p: 0.95
|
||||
top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
repeat_penalty: 1.0
|
||||
# ctx-size: 98304
|
||||
|
||||
llama_server_argv_extra:
|
||||
[
|
||||
@@ -195,15 +207,15 @@ llama_server_argv_extra:
|
||||
"-np",
|
||||
-1,
|
||||
"-b",
|
||||
1024,
|
||||
2048,
|
||||
"-ub",
|
||||
512,
|
||||
"-fa",
|
||||
"on",
|
||||
"-ctk",
|
||||
"q4_0",
|
||||
"-ctv",
|
||||
"q8_0",
|
||||
"-ctv",
|
||||
"q4_0",
|
||||
"-cram",
|
||||
-1,
|
||||
"-sm",
|
||||
@@ -216,11 +228,14 @@ llama_server_argv_extra:
|
||||
"off",
|
||||
"-mg",
|
||||
1,
|
||||
"--timeout",
|
||||
1800,
|
||||
"--mmap",
|
||||
"--spec-type",
|
||||
"draft-mtp",
|
||||
"--spec-draft-n-max",
|
||||
2,
|
||||
"--swa-full",
|
||||
]
|
||||
|
||||
auto_suspend_enabled: true
|
||||
|
||||
@@ -76,6 +76,8 @@ macos_dev_station_opencode_config:
|
||||
options:
|
||||
baseURL: "https://open-webui.lab.alexpires.me/api"
|
||||
apiKey: "{{ lookup('env', 'OPEN_WEBUI_API_KEY', default='') }}"
|
||||
timeout: 3000000
|
||||
chunkTimeout: 3000000
|
||||
models:
|
||||
gemma-4-12b-q8-0:
|
||||
name: "gemma-4-12b-q8-0"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
- name: Configure Linux dev station
|
||||
hosts: linux_dev
|
||||
gather_facts: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Ensure target is Fedora
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['distribution'] == "Fedora"
|
||||
fail_msg: "This playbook supports Fedora hosts only"
|
||||
|
||||
roles:
|
||||
- linux_dev_station
|
||||
@@ -0,0 +1 @@
|
||||
cis_logind_conf: {}
|
||||
@@ -11,5 +11,4 @@
|
||||
notify:
|
||||
- Reload systemd
|
||||
tags:
|
||||
- systemd
|
||||
- logind
|
||||
- roles::cis::logind
|
||||
|
||||
@@ -6,3 +6,6 @@ KillExcludeUsers=root
|
||||
IdleAction=lock
|
||||
IdleActionSec=15min
|
||||
RemoveIPC=yes
|
||||
{% for key, value in cis_logind_conf.items() %}
|
||||
{{ key }}={{ value }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
linux_dev_station_service_user_enabled: true
|
||||
linux_dev_station_service_user: "devstation"
|
||||
linux_dev_station_service_shell: "/usr/sbin/nologin"
|
||||
linux_dev_station_service_user_ssh_enabled: false
|
||||
linux_dev_station_service_user_ssh_shell: "/bin/zsh"
|
||||
linux_dev_station_service_user_ssh_authorized_keys: []
|
||||
linux_dev_station_service_home: "/home/{{ linux_dev_station_service_user }}"
|
||||
|
||||
linux_dev_station_workspace_dir: "{{ linux_dev_station_service_home }}/projects"
|
||||
linux_dev_station_scripts_dir: "{{ linux_dev_station_service_home }}/scripts"
|
||||
linux_dev_station_logs_dir: "{{ linux_dev_station_service_home }}/.local/state/logs"
|
||||
linux_dev_station_systemd_unit_dir: "/etc/systemd/system"
|
||||
linux_dev_station_path: >-
|
||||
{{ linux_dev_station_service_home }}/.local/bin:{{ linux_dev_station_service_home }}/go/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/usr/local/bin
|
||||
|
||||
linux_dev_station_dnf_packages:
|
||||
- git
|
||||
- tmux
|
||||
- wget
|
||||
- curl
|
||||
- jq
|
||||
- openssl
|
||||
- zsh
|
||||
- golang
|
||||
- ripgrep
|
||||
- fzf
|
||||
|
||||
linux_dev_station_tool_packages:
|
||||
- name: uv
|
||||
install_script_url: "https://astral.sh/uv/install.sh"
|
||||
- name: node
|
||||
manager: fnm
|
||||
- name: rust
|
||||
manager: rustup
|
||||
- name: code-server
|
||||
version: "4.127.0"
|
||||
sha256: ""
|
||||
- name: opencode
|
||||
version: "1.17.18"
|
||||
sha256: ""
|
||||
|
||||
linux_dev_station_code_server_host: "0.0.0.0"
|
||||
linux_dev_station_code_server_port: 3000
|
||||
linux_dev_station_code_server_auth: "password"
|
||||
linux_dev_station_code_server_password: "{{ lookup('env', 'CODE_SERVER_PASSWORD', default='') }}"
|
||||
linux_dev_station_code_server_workspace: "{{ linux_dev_station_workspace_dir }}"
|
||||
linux_dev_station_code_server_extensions_enabled: true
|
||||
linux_dev_station_code_server_extensions_fail_on_error: false
|
||||
linux_dev_station_code_server_extensions:
|
||||
- ms-python.python
|
||||
- ms-python.vscode-pylance
|
||||
- ms-azuretools.vscode-docker
|
||||
- hashicorp.terraform
|
||||
- redhat.ansible
|
||||
- eamodio.gitlens
|
||||
|
||||
linux_dev_station_copilot_enabled: false
|
||||
linux_dev_station_copilot_extensions:
|
||||
- GitHub.copilot
|
||||
- GitHub.copilot-chat
|
||||
linux_dev_station_copilot_fail_on_error: false
|
||||
|
||||
linux_dev_station_oh_my_zsh_enabled: true
|
||||
linux_dev_station_oh_my_zsh_install_url: "https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh"
|
||||
|
||||
linux_dev_station_npm_packages: []
|
||||
|
||||
linux_dev_station_git_init_default_branch: "main"
|
||||
linux_dev_station_git_pull_rebase: false
|
||||
|
||||
linux_dev_station_ssh_key_path: "{{ ansible_facts['env']['HOME'] }}/.ssh/id_ed25519"
|
||||
linux_dev_station_ssh_key_comment: "dev-02"
|
||||
|
||||
linux_dev_station_code_server_systemd_enabled: true
|
||||
linux_dev_station_opencode_systemd_enabled: true
|
||||
linux_dev_station_opencode_server_host: "0.0.0.0"
|
||||
linux_dev_station_opencode_server_port: 4096
|
||||
linux_dev_station_opencode_server_username: "{{ lookup('env', 'OPENCODE_SERVER_USERNAME', default='') }}"
|
||||
linux_dev_station_opencode_server_password: "{{ lookup('env', 'OPENCODE_SERVER_PASSWORD', default='') }}"
|
||||
linux_dev_station_opencode_extra_environment: {}
|
||||
linux_dev_station_opencode_command: "opencode serve --hostname {{ linux_dev_station_opencode_server_host }} --port {{ linux_dev_station_opencode_server_port }}"
|
||||
linux_dev_station_opencode_config: {}
|
||||
linux_dev_station_opencode_config_path: "{{ linux_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
|
||||
linux_dev_station_mcp_installers_enabled: true
|
||||
linux_dev_station_mcp_installers: []
|
||||
|
||||
# Legacy ad-hoc hook kept for backward compatibility.
|
||||
linux_dev_station_mcp_bootstrap_commands: []
|
||||
linux_dev_station_manage_systemd: true
|
||||
|
||||
# SSH agent
|
||||
linux_dev_station_ssh_agent_enabled: false
|
||||
linux_dev_station_ssh_agent_keys:
|
||||
- "{{ linux_dev_station_service_home }}/.ssh/id_ed25519"
|
||||
linux_dev_station_ssh_agent_passphrase: "{{ lookup('env', 'SSH_KEY_PASSPHRASE', default='') }}"
|
||||
linux_dev_station_ssh_agent_socket: "{{ linux_dev_station_service_home }}/.ssh/ssh-agent.sock"
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
# ansible: linux_dev_station install-copilot.sh
|
||||
# Installs GitHub Copilot and Copilot Chat extensions into code-server
|
||||
# with engine-compatible versions resolved from the VS Marketplace API.
|
||||
set -euo pipefail
|
||||
|
||||
get_engine_version() {
|
||||
local version_output
|
||||
version_output=$(code-server --version | head -n1)
|
||||
if echo "$version_output" | grep -q "with Code"; then
|
||||
echo "$version_output" | sed -n 's/.*with Code \([0-9.]*\).*/\1/p'
|
||||
else
|
||||
echo "$version_output" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
get_user_data_dir() {
|
||||
local process_info
|
||||
process_info=$(ps aux 2>/dev/null | grep -v grep | grep "code-server" | head -n 1) ||
|
||||
process_info=$(ps -ef 2>/dev/null | grep -v grep | grep "code-server" | head -n 1)
|
||||
if [ -n "$process_info" ]; then
|
||||
local dir
|
||||
dir=$(echo "$process_info" | grep -o -- '--user-data-dir=[^ ]*' | sed 's/--user-data-dir=//')
|
||||
if [ -n "$dir" ]; then
|
||||
echo "$dir"; return
|
||||
fi
|
||||
dir=$(echo "$process_info" | grep -o -- '--user-data-dir [^ ]*' | sed 's/--user-data-dir //')
|
||||
if [ -n "$dir" ]; then
|
||||
echo "$dir"; return
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
find_compatible_version() {
|
||||
local extension_id="$1"
|
||||
local engine_version="$2"
|
||||
local response
|
||||
response=$(curl -s -X POST "https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json;api-version=3.0-preview.1" \
|
||||
-d "{
|
||||
\"filters\": [{
|
||||
\"criteria\": [
|
||||
{\"filterType\": 7, \"value\": \"$extension_id\"},
|
||||
{\"filterType\": 12, \"value\": \"4096\"}
|
||||
],
|
||||
\"pageSize\": 50
|
||||
}],
|
||||
\"flags\": 4112
|
||||
}")
|
||||
echo "$response" | jq -r --arg engine_version "$engine_version" '
|
||||
.results[0].extensions[0].versions[] |
|
||||
select(.version | test("^[0-9]+\\.[0-9]+\\.[0-9]*$")) |
|
||||
select(all(.properties[]; .key != "Microsoft.VisualStudio.Code.PreRelease" or .value != "true")) |
|
||||
{
|
||||
version: .version,
|
||||
engine: (.properties[] | select(.key == "Microsoft.VisualStudio.Code.Engine") | .value)
|
||||
} |
|
||||
select(.engine | ltrimstr("^") | split(".") |
|
||||
map(split("-")[0] | tonumber? // 0) as $engine_parts |
|
||||
($engine_version | split(".") | map(split("-")[0] | tonumber? // 0)) as $server_parts |
|
||||
(
|
||||
($engine_parts[0] // 0) < $server_parts[0] or
|
||||
(($engine_parts[0] // 0) == $server_parts[0] and ($engine_parts[1] // 0) < $server_parts[1]) or
|
||||
(($engine_parts[0] // 0) == $server_parts[0] and ($engine_parts[1] // 0) == $server_parts[1] and ($engine_parts[2] // 0) <= $server_parts[2])
|
||||
)
|
||||
) |
|
||||
.version' | head -n 1
|
||||
}
|
||||
|
||||
install_extension() {
|
||||
local extension_id="$1"
|
||||
local version="$2"
|
||||
local user_data_dir="$3"
|
||||
local extension_name
|
||||
extension_name=$(echo "$extension_id" | cut -d'.' -f2)
|
||||
local temp_dir="/tmp/code-extensions"
|
||||
echo "Installing $extension_id v$version..."
|
||||
mkdir -p "$temp_dir"
|
||||
echo " Downloading..."
|
||||
curl -L "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/$extension_name/$version/vspackage" \
|
||||
-o "$temp_dir/$extension_name.vsix.gz"
|
||||
if [ ! -f "$temp_dir/$extension_name.vsix.gz" ]; then
|
||||
echo " [SKIP] Download failed for $extension_id"
|
||||
return 1
|
||||
fi
|
||||
if command -v gunzip >/dev/null 2>&1; then
|
||||
gunzip -f "$temp_dir/$extension_name.vsix.gz"
|
||||
else
|
||||
gzip -df "$temp_dir/$extension_name.vsix.gz"
|
||||
fi
|
||||
if [ -n "$user_data_dir" ]; then
|
||||
code-server --user-data-dir="$user_data_dir" --force --install-extension "$temp_dir/$extension_name.vsix"
|
||||
else
|
||||
code-server --force --install-extension "$temp_dir/$extension_name.vsix"
|
||||
fi
|
||||
rm -f "$temp_dir/$extension_name.vsix"
|
||||
echo " [OK] $extension_id installed successfully!"
|
||||
return 0
|
||||
}
|
||||
|
||||
check_dependencies() {
|
||||
local missing_deps=()
|
||||
for cmd in curl jq code-server; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
missing_deps+=("$cmd")
|
||||
fi
|
||||
done
|
||||
if ! command -v gunzip >/dev/null 2>&1 && ! command -v gzip >/dev/null 2>&1; then
|
||||
missing_deps+=("gunzip/gzip")
|
||||
fi
|
||||
if [ "${#missing_deps[@]}" -gt 0 ]; then
|
||||
echo "Error: Missing required dependencies: ${missing_deps[*]}"
|
||||
echo "Please install the missing dependencies and try again."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "========================================"
|
||||
echo " GitHub Copilot Extensions Installer"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
check_dependencies
|
||||
|
||||
ENGINE_VERSION="$(get_engine_version)"
|
||||
if [ -z "$ENGINE_VERSION" ]; then
|
||||
echo "Error: Could not extract engine version from code-server"
|
||||
exit 1
|
||||
fi
|
||||
echo "Detected code-server engine version: $ENGINE_VERSION"
|
||||
|
||||
USER_DATA_DIR="$(get_user_data_dir)"
|
||||
if [ -n "$USER_DATA_DIR" ]; then
|
||||
echo "Detected user-data-dir: $USER_DATA_DIR"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
EXTENSIONS="GitHub.copilot GitHub.copilot-chat"
|
||||
FAILED=0
|
||||
|
||||
for ext in $EXTENSIONS; do
|
||||
echo "Processing $ext..."
|
||||
version="$(find_compatible_version "$ext" "$ENGINE_VERSION")"
|
||||
if [ -z "$version" ]; then
|
||||
echo " [SKIP] No compatible version found for $ext"
|
||||
FAILED="$((FAILED + 1))"
|
||||
else
|
||||
echo " Found compatible version: $version"
|
||||
if ! install_extension "$ext" "$version" "$USER_DATA_DIR"; then
|
||||
FAILED="$((FAILED + 1))"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo "[OK] All extensions installed successfully!"
|
||||
rm -rf /tmp/code-extensions
|
||||
else
|
||||
echo "[WARN] Completed with $FAILED error(s)"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
- name: Reload and restart code-server
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: code-server
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
when:
|
||||
- linux_dev_station_manage_systemd | bool
|
||||
- linux_dev_station_code_server_systemd_enabled | bool
|
||||
listen: Restart code-server
|
||||
|
||||
- name: Reload and restart opencode
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: opencode
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
when:
|
||||
- linux_dev_station_manage_systemd | bool
|
||||
- linux_dev_station_opencode_systemd_enabled | bool
|
||||
listen: Restart opencode
|
||||
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
---
|
||||
- name: Ensure host is Fedora
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['distribution'] == "Fedora"
|
||||
fail_msg: "Role linux_dev_station supports Fedora hosts only"
|
||||
|
||||
- name: Ensure OpenCode server credentials are provided
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- linux_dev_station_opencode_server_username | length > 0
|
||||
- linux_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: linux_dev_station_opencode_systemd_enabled | bool
|
||||
|
||||
- name: Ensure code-server password is provided
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- linux_dev_station_code_server_password | length > 0
|
||||
fail_msg: >-
|
||||
Set CODE_SERVER_PASSWORD in environment
|
||||
(recommended via sectool) before running the playbook.
|
||||
when:
|
||||
- linux_dev_station_code_server_systemd_enabled | bool
|
||||
- linux_dev_station_code_server_auth == "password"
|
||||
|
||||
- name: Install dnf packages
|
||||
become: true
|
||||
ansible.builtin.dnf:
|
||||
name: "{{ linux_dev_station_dnf_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Ensure dedicated service user exists
|
||||
become: true
|
||||
ansible.builtin.user:
|
||||
name: "{{ linux_dev_station_service_user }}"
|
||||
shell: >-
|
||||
{{ (linux_dev_station_service_user_ssh_enabled | bool) |
|
||||
ternary(linux_dev_station_service_user_ssh_shell, linux_dev_station_service_shell) }}
|
||||
home: "{{ linux_dev_station_service_home }}"
|
||||
create_home: true
|
||||
state: present
|
||||
when: linux_dev_station_service_user_enabled | bool
|
||||
|
||||
- name: Ensure service user cache directory exists for tool installs
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ linux_dev_station_service_home }}/.cache"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
when: linux_dev_station_service_user_enabled | bool
|
||||
|
||||
- name: Install tool-managed packages
|
||||
ansible.builtin.include_tasks: tool_packages.yml
|
||||
|
||||
- name: Discover npm executable path
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: /bin/sh -lc "command -v npm"
|
||||
register: __linux_dev_station_npm_path
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
environment:
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_npm_packages | length > 0
|
||||
|
||||
- name: Install global npm packages
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
community.general.npm:
|
||||
name: "{{ item.name }}"
|
||||
version: "{{ item.version }}"
|
||||
executable: "{{ __linux_dev_station_npm_path.stdout | trim }}"
|
||||
global: true
|
||||
environment:
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
loop: "{{ linux_dev_station_npm_packages }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_npm_packages | length > 0
|
||||
- __linux_dev_station_npm_path.rc | default(1) == 0
|
||||
|
||||
- name: Ensure required user directories exist
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
loop:
|
||||
- "{{ linux_dev_station_service_home }}"
|
||||
- "{{ linux_dev_station_workspace_dir }}"
|
||||
- "{{ linux_dev_station_scripts_dir }}"
|
||||
- "{{ linux_dev_station_logs_dir }}"
|
||||
|
||||
- name: Ensure service home permissions are restrictive
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ linux_dev_station_service_home }}"
|
||||
state: directory
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: Ensure service user config directories exist
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
loop:
|
||||
- "{{ linux_dev_station_service_home }}/.local"
|
||||
- "{{ linux_dev_station_service_home }}/.local/bin"
|
||||
- "{{ linux_dev_station_service_home }}/.local/lib"
|
||||
- "{{ linux_dev_station_service_home }}/.local/lib/node_modules"
|
||||
- "{{ linux_dev_station_service_home }}/.config"
|
||||
- "{{ linux_dev_station_service_home }}/.config/opencode"
|
||||
when: linux_dev_station_service_user_enabled | bool
|
||||
|
||||
- name: Ensure OpenCode config object is valid when provided
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- linux_dev_station_opencode_config is mapping
|
||||
fail_msg: "linux_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: "{{ linux_dev_station_opencode_config_path }}"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0600"
|
||||
notify: Restart opencode
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_opencode_config | length > 0
|
||||
|
||||
- name: Download Oh My Zsh installer
|
||||
become: true
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ linux_dev_station_oh_my_zsh_install_url }}"
|
||||
dest: "{{ linux_dev_station_service_home }}/.cache/ohmyzsh-install.sh"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0755"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_oh_my_zsh_enabled | bool
|
||||
|
||||
- name: Install Oh My Zsh for service user
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: >-
|
||||
/bin/sh {{ linux_dev_station_service_home }}/.cache/ohmyzsh-install.sh --unattended
|
||||
args:
|
||||
creates: "{{ linux_dev_station_service_home }}/.oh-my-zsh"
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
RUNZSH: "no"
|
||||
CHSH: "no"
|
||||
KEEP_ZSHRC: "yes"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_oh_my_zsh_enabled | bool
|
||||
|
||||
- name: Ensure .zshrc has ssh-agent startup for devstation
|
||||
become: true
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ linux_dev_station_service_home }}/.zshrc"
|
||||
block: |
|
||||
# ansible: linux_dev_station ssh-agent
|
||||
export SSH_AUTH_SOCK="{{ linux_dev_station_ssh_agent_socket }}"
|
||||
if [ -S "$SSH_AUTH_SOCK" ] && ssh-add -l &>/dev/null; then
|
||||
# Agent socket exists and has keys — nothing to do
|
||||
true
|
||||
else
|
||||
if [ -S "$SSH_AUTH_SOCK" ]; then
|
||||
# Socket exists but agent is dead or has no keys — restart
|
||||
rm -f "$SSH_AUTH_SOCK"
|
||||
eval "$(ssh-agent -a "$SSH_AUTH_SOCK" 2>/dev/null)" || true
|
||||
else
|
||||
# No socket — start fresh agent
|
||||
eval "$(ssh-agent -a "$SSH_AUTH_SOCK" 2>/dev/null)" || true
|
||||
fi
|
||||
if [ -f "{{ linux_dev_station_scripts_dir }}/ssh-agent-start.sh" ]; then
|
||||
source "{{ linux_dev_station_scripts_dir }}/ssh-agent-start.sh"
|
||||
fi
|
||||
fi
|
||||
marker: "# {mark} ansible: linux_dev_station ssh-agent"
|
||||
create: true
|
||||
mode: "0644"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_ssh_agent_enabled | bool
|
||||
|
||||
- name: Install managed MCP server runtimes
|
||||
ansible.builtin.include_tasks: mcp_installers.yml
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_mcp_installers_enabled | bool
|
||||
- linux_dev_station_mcp_installers | length > 0
|
||||
|
||||
- name: Run MCP bootstrap commands
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.shell: "{{ item }}"
|
||||
register: __linux_dev_station_mcp_bootstrap_result
|
||||
changed_when: __linux_dev_station_mcp_bootstrap_result.rc == 0
|
||||
args:
|
||||
executable: /bin/sh
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
loop: "{{ linux_dev_station_mcp_bootstrap_commands }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_mcp_bootstrap_commands | length > 0
|
||||
|
||||
- name: List installed code-server extensions for service user
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: code-server --list-extensions
|
||||
register: __linux_dev_station_code_server_extensions_installed
|
||||
changed_when: false
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_code_server_extensions_enabled | bool
|
||||
- linux_dev_station_code_server_extensions | length > 0
|
||||
|
||||
- name: Install base code-server extensions for service user
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: >-
|
||||
code-server --install-extension {{ item }}
|
||||
register: __linux_dev_station_code_server_extension_install
|
||||
changed_when: __linux_dev_station_code_server_extension_install.rc == 0
|
||||
failed_when: >-
|
||||
(linux_dev_station_code_server_extensions_fail_on_error | bool)
|
||||
and (__linux_dev_station_code_server_extension_install.rc != 0)
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
notify: Restart code-server
|
||||
loop: "{{ linux_dev_station_code_server_extensions }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_code_server_extensions_enabled | bool
|
||||
- item not in (__linux_dev_station_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: "{{ __linux_dev_station_code_server_extension_install.results | default([]) }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_code_server_extensions_enabled | bool
|
||||
- not (linux_dev_station_code_server_extensions_fail_on_error | bool)
|
||||
- item.rc is defined
|
||||
- item.rc != 0
|
||||
|
||||
- name: Copy Copilot installer script
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
src: install-copilot.sh
|
||||
dest: "{{ linux_dev_station_service_home }}/.cache/install-copilot.sh"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0755"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_copilot_enabled | bool
|
||||
|
||||
- name: Install GitHub Copilot extensions via compatible VSIX
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: "{{ linux_dev_station_service_home }}/.cache/install-copilot.sh"
|
||||
register: __linux_dev_station_copilot_install_result
|
||||
changed_when: __linux_dev_station_copilot_install_result.rc == 0
|
||||
failed_when: >-
|
||||
(linux_dev_station_copilot_fail_on_error | bool)
|
||||
and (__linux_dev_station_copilot_install_result.rc != 0)
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_copilot_enabled | bool
|
||||
|
||||
- name: Ensure devstation SSH directory exists when SSH access is enabled
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ linux_dev_station_service_home }}/.ssh"
|
||||
state: directory
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0700"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_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: "{{ linux_dev_station_service_user }}"
|
||||
key: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ linux_dev_station_service_user_ssh_authorized_keys }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_service_user_ssh_enabled | bool
|
||||
|
||||
- name: Render ssh-agent starter script
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
src: ssh-agent-start.sh.j2
|
||||
dest: "{{ linux_dev_station_scripts_dir }}/ssh-agent-start.sh"
|
||||
mode: "0755"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_ssh_agent_enabled | bool
|
||||
|
||||
- name: Ensure ssh_config has ssh-agent settings for devstation
|
||||
become: true
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ linux_dev_station_service_home }}/.ssh/config"
|
||||
block: |
|
||||
Host *
|
||||
AddKeysToAgent yes
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
marker: "# {mark} ansible: linux_dev_station ssh-agent"
|
||||
create: true
|
||||
mode: "0600"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_ssh_agent_enabled | bool
|
||||
|
||||
- name: Generate SSH key if missing
|
||||
ansible.builtin.command: >-
|
||||
ssh-keygen -t ed25519 -C {{ linux_dev_station_ssh_key_comment }}
|
||||
-f {{ linux_dev_station_ssh_key_path }} -N ''
|
||||
args:
|
||||
creates: "{{ linux_dev_station_ssh_key_path }}"
|
||||
|
||||
- name: Configure Git init default branch
|
||||
community.general.git_config:
|
||||
name: init.defaultBranch
|
||||
scope: global
|
||||
value: "{{ linux_dev_station_git_init_default_branch }}"
|
||||
|
||||
- name: Configure Git pull.rebase
|
||||
community.general.git_config:
|
||||
name: pull.rebase
|
||||
scope: global
|
||||
value: "{{ linux_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: "{{ linux_dev_station_scripts_dir }}/start-ide.sh"
|
||||
mode: "0755"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
|
||||
- name: Render opencode launcher script
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
src: start-opencode.sh.j2
|
||||
dest: "{{ linux_dev_station_scripts_dir }}/start-opencode.sh"
|
||||
mode: "0755"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
|
||||
- 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: Render code-server systemd unit
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
src: code-server.service.j2
|
||||
dest: "{{ linux_dev_station_systemd_unit_dir }}/code-server.service"
|
||||
mode: "0644"
|
||||
when: linux_dev_station_code_server_systemd_enabled | bool
|
||||
notify: Restart code-server
|
||||
|
||||
- name: Render opencode systemd unit
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
src: opencode.service.j2
|
||||
dest: "{{ linux_dev_station_systemd_unit_dir }}/opencode.service"
|
||||
mode: "0644"
|
||||
when: linux_dev_station_opencode_systemd_enabled | bool
|
||||
notify: Restart opencode
|
||||
|
||||
- name: Ensure code-server service is started and enabled
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: code-server
|
||||
state: started
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
when:
|
||||
- linux_dev_station_manage_systemd | bool
|
||||
- linux_dev_station_code_server_systemd_enabled | bool
|
||||
|
||||
- name: Ensure opencode service is started and enabled
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: opencode
|
||||
state: started
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
when:
|
||||
- linux_dev_station_manage_systemd | bool
|
||||
- linux_dev_station_opencode_systemd_enabled | bool
|
||||
|
||||
- name: Ensure systemd services are started
|
||||
ansible.builtin.meta: flush_handlers
|
||||
when: linux_dev_station_manage_systemd | bool
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
- name: Ensure MCP installer definitions list is valid
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- linux_dev_station_mcp_installers is iterable
|
||||
fail_msg: "linux_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: "{{ linux_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: "{{ linux_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: "{{ linux_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: "{{ linux_dev_station_mcp_installers }}"
|
||||
when:
|
||||
- item.enabled | default(true) | bool
|
||||
- item.kind == 'go'
|
||||
|
||||
- name: Discover npm executable path for MCP installers
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: /bin/sh -lc "command -v npm"
|
||||
register: __linux_dev_station_mcp_npm_path
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
environment:
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
when: (linux_dev_station_mcp_installers | selectattr('kind', 'equalto', 'npm') | list | length) > 0
|
||||
|
||||
- name: Install npm MCP packages for service user
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- "{{ __linux_dev_station_mcp_npm_path.stdout | trim }}"
|
||||
- install
|
||||
- --prefix
|
||||
- "{{ linux_dev_station_service_home }}/.local"
|
||||
- --global
|
||||
- "{{ item.package }}@{{ item.version | default('latest') }}"
|
||||
args:
|
||||
creates: "{{ linux_dev_station_service_home }}/.local/lib/node_modules/{{ item.package }}"
|
||||
register: __linux_dev_station_mcp_npm_install
|
||||
changed_when: __linux_dev_station_mcp_npm_install.rc == 0
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
notify: Restart opencode
|
||||
loop: "{{ linux_dev_station_mcp_installers }}"
|
||||
when:
|
||||
- item.enabled | default(true) | bool
|
||||
- item.kind == 'npm'
|
||||
- __linux_dev_station_mcp_npm_path.rc | default(1) == 0
|
||||
|
||||
- name: Install uv MCP tools for service user
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- uv
|
||||
- tool
|
||||
- install
|
||||
- "{{ item.package }}@{{ item.version | default('latest') }}"
|
||||
args:
|
||||
creates: "{{ linux_dev_station_service_home }}/.local/bin/{{ item.binary }}"
|
||||
register: __linux_dev_station_mcp_uv_install
|
||||
changed_when: __linux_dev_station_mcp_uv_install.rc == 0
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
notify: Restart opencode
|
||||
loop: "{{ linux_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: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- go
|
||||
- install
|
||||
- "{{ item.module }}@{{ item.version | default('latest') }}"
|
||||
args:
|
||||
creates: "{{ linux_dev_station_service_home }}/.local/bin/{{ item.binary }}"
|
||||
register: __linux_dev_station_mcp_go_install
|
||||
changed_when: __linux_dev_station_mcp_go_install.rc == 0
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
GOBIN: "{{ linux_dev_station_service_home }}/.local/bin"
|
||||
notify: Restart opencode
|
||||
loop: "{{ linux_dev_station_mcp_installers }}"
|
||||
when:
|
||||
- item.enabled | default(true) | bool
|
||||
- item.kind == 'go'
|
||||
@@ -0,0 +1,268 @@
|
||||
---
|
||||
- name: Ensure tool packages list is valid
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- linux_dev_station_tool_packages is iterable
|
||||
fail_msg: "linux_dev_station_tool_packages must be a list of tool definitions."
|
||||
|
||||
- name: Download uv installer
|
||||
become: true
|
||||
ansible.builtin.get_url:
|
||||
url: https://astral.sh/uv/install.sh
|
||||
dest: "{{ linux_dev_station_service_home }}/.cache/uv-install.sh"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0755"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'uv') | list | length > 0
|
||||
|
||||
- name: Install uv
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: >-
|
||||
/bin/sh {{ linux_dev_station_service_home }}/.cache/uv-install.sh
|
||||
args:
|
||||
creates: "{{ linux_dev_station_service_home }}/.local/bin/uv"
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}"
|
||||
UV_UNINSTALL_MISSING_PACKAGES: "1"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'uv') | list | length > 0
|
||||
|
||||
- name: Download rustup installer
|
||||
become: true
|
||||
ansible.builtin.get_url:
|
||||
url: https://sh.rustup.rs
|
||||
dest: "{{ linux_dev_station_service_home }}/.cache/rustup-install.sh"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0755"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'rust') | list | length > 0
|
||||
|
||||
- name: Install rustup
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: >-
|
||||
/bin/sh {{ linux_dev_station_service_home }}/.cache/rustup-install.sh -y --default-toolchain stable
|
||||
args:
|
||||
creates: "{{ linux_dev_station_service_home }}/.cargo/bin/rustc"
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}:{{ linux_dev_station_service_home }}/.cargo/bin"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'rust') | list | length > 0
|
||||
|
||||
- name: Download fnm (Fast Node Manager) binary
|
||||
become: true
|
||||
ansible.builtin.get_url:
|
||||
url: "https://github.com/Schniz/fnm/releases/download/{{ fnm_version }}/fnm-linux.zip"
|
||||
dest: "{{ linux_dev_station_service_home }}/.cache/fnm.zip"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0644"
|
||||
vars:
|
||||
fnm_version: v1.38.1
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'node') | list | length > 0
|
||||
|
||||
- name: Create .fnm directory
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.file:
|
||||
path: "{{ linux_dev_station_service_home }}/.fnm"
|
||||
state: directory
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0755"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'node') | list | length > 0
|
||||
|
||||
- name: Extract fnm to .fnm directory
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.unarchive:
|
||||
src: "{{ linux_dev_station_service_home }}/.cache/fnm.zip"
|
||||
dest: "{{ linux_dev_station_service_home }}/.fnm"
|
||||
remote_src: true
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'node') | list | length > 0
|
||||
|
||||
- name: Ensure fnm binary is executable
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ linux_dev_station_service_home }}/.fnm/fnm"
|
||||
mode: "0755"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'node') | list | length > 0
|
||||
|
||||
- name: Install LTS Node.js version via fnm
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: >-
|
||||
{{ linux_dev_station_service_home }}/.fnm/fnm install --lts
|
||||
args:
|
||||
creates: "{{ linux_dev_station_service_home }}/.fnm/node-versions"
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}:{{ linux_dev_station_service_home }}/.fnm"
|
||||
FNM_DIR: "{{ linux_dev_station_service_home }}/.fnm"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'node') | list | length > 0
|
||||
|
||||
- name: Get installed Node.js version from fnm node-versions directory
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: ls "{{ linux_dev_station_service_home }}/.fnm/node-versions"
|
||||
register: fnm_versions_list
|
||||
changed_when: false
|
||||
environment:
|
||||
FNM_DIR: "{{ linux_dev_station_service_home }}/.fnm"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'node') | list | length > 0
|
||||
|
||||
- name: Set default Node.js version via fnm
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.command: >-
|
||||
{{ linux_dev_station_service_home }}/.fnm/fnm default {{ node_version }}
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
PATH: "{{ linux_dev_station_path }}:{{ linux_dev_station_service_home }}/.fnm"
|
||||
FNM_DIR: "{{ linux_dev_station_service_home }}/.fnm"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'node') | list | length > 0
|
||||
- fnm_versions_list is defined
|
||||
- fnm_versions_list.stdout | trim | length > 0
|
||||
vars:
|
||||
node_version: "{{ fnm_versions_list.stdout | trim }}"
|
||||
|
||||
- name: Download code-server tarball
|
||||
become: true
|
||||
ansible.builtin.get_url:
|
||||
url: "https://github.com/coder/code-server/releases/download/v{{ tool.version }}/code-server-{{ tool.version }}-linux-amd64.tar.gz"
|
||||
dest: "{{ linux_dev_station_service_home }}/.cache/code-server.tar.gz"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0644"
|
||||
vars:
|
||||
tool: "{{ linux_dev_station_tool_packages | selectattr('name', 'equalto', 'code-server') | first }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'code-server') | list | length > 0
|
||||
|
||||
- name: Create code-server installation directories
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ linux_dev_station_service_home }}/.local/lib"
|
||||
- "{{ linux_dev_station_service_home }}/.local/bin"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'code-server') | list | length > 0
|
||||
|
||||
- name: Extract code-server tarball to lib directory
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.unarchive:
|
||||
src: "{{ linux_dev_station_service_home }}/.cache/code-server.tar.gz"
|
||||
dest: "{{ linux_dev_station_service_home }}/.local/lib"
|
||||
remote_src: true
|
||||
environment:
|
||||
HOME: "{{ linux_dev_station_service_home }}"
|
||||
USER: "{{ linux_dev_station_service_user }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'code-server') | list | length > 0
|
||||
|
||||
- name: Create symlink for code-server binary
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.file:
|
||||
src: "{{ linux_dev_station_service_home }}/.local/lib/code-server-{{ tool.version }}-linux-amd64/bin/code-server"
|
||||
dest: "{{ linux_dev_station_service_home }}/.local/bin/code-server"
|
||||
state: link
|
||||
vars:
|
||||
tool: "{{ linux_dev_station_tool_packages | selectattr('name', 'equalto', 'code-server') | first }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'code-server') | list | length > 0
|
||||
|
||||
- name: Create opencode installation directories
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ linux_dev_station_service_home }}/.local/lib"
|
||||
- "{{ linux_dev_station_service_home }}/.local/lib/opencode-{{ tool.version }}"
|
||||
- "{{ linux_dev_station_service_home }}/.local/bin"
|
||||
vars:
|
||||
tool: "{{ linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | first }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | list | length > 0
|
||||
|
||||
- name: Download opencode tarball
|
||||
become: true
|
||||
ansible.builtin.get_url:
|
||||
url: "https://github.com/anomalyco/opencode/releases/download/v{{ tool.version }}/opencode-linux-x64.tar.gz"
|
||||
dest: "{{ linux_dev_station_service_home }}/.cache/opencode-{{ tool.version }}.tar.gz"
|
||||
owner: "{{ linux_dev_station_service_user }}"
|
||||
group: "{{ linux_dev_station_service_user }}"
|
||||
mode: "0644"
|
||||
vars:
|
||||
tool: "{{ linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | first }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | list | length > 0
|
||||
|
||||
- name: Extract opencode tarball to versioned directory
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.unarchive:
|
||||
src: "{{ linux_dev_station_service_home }}/.cache/opencode-{{ tool.version }}.tar.gz"
|
||||
dest: "{{ linux_dev_station_service_home }}/.local/lib/opencode-{{ tool.version }}"
|
||||
remote_src: true
|
||||
vars:
|
||||
tool: "{{ linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | first }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | list | length > 0
|
||||
|
||||
- name: Create symlink for opencode binary
|
||||
become: true
|
||||
become_user: "{{ linux_dev_station_service_user }}"
|
||||
ansible.builtin.file:
|
||||
src: "{{ linux_dev_station_service_home }}/.local/lib/opencode-{{ tool.version }}/opencode"
|
||||
dest: "{{ linux_dev_station_service_home }}/.local/bin/opencode"
|
||||
state: link
|
||||
vars:
|
||||
tool: "{{ linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | first }}"
|
||||
when:
|
||||
- linux_dev_station_service_user_enabled | bool
|
||||
- linux_dev_station_tool_packages | selectattr('name', 'equalto', 'opencode') | list | length > 0
|
||||
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=code-server - VS Code on remote Linux
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ linux_dev_station_service_user }}
|
||||
Group={{ linux_dev_station_service_user }}
|
||||
WorkingDirectory={{ linux_dev_station_workspace_dir }}
|
||||
ExecStart=/bin/bash {{ linux_dev_station_scripts_dir }}/start-ide.sh
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=append:{{ linux_dev_station_logs_dir }}/code-server.log
|
||||
StandardError=append:{{ linux_dev_station_logs_dir }}/code-server.err.log
|
||||
|
||||
Environment="HOME={{ linux_dev_station_service_home }}"
|
||||
Environment="PATH={{ linux_dev_station_path }}"
|
||||
Environment="PASSWORD={{ linux_dev_station_code_server_password }}"
|
||||
{% if linux_dev_station_ssh_agent_enabled | bool %}
|
||||
Environment="SSH_AUTH_SOCK={{ linux_dev_station_ssh_agent_socket }}"
|
||||
{% endif %}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1 @@
|
||||
{{ linux_dev_station_opencode_config | to_nice_json }}
|
||||
@@ -0,0 +1,28 @@
|
||||
[Unit]
|
||||
Description=OpenCode - AI-powered terminal IDE
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ linux_dev_station_service_user }}
|
||||
Group={{ linux_dev_station_service_user }}
|
||||
WorkingDirectory={{ linux_dev_station_workspace_dir }}
|
||||
ExecStart=/bin/bash {{ linux_dev_station_scripts_dir }}/start-opencode.sh
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=append:{{ linux_dev_station_logs_dir }}/opencode.log
|
||||
StandardError=append:{{ linux_dev_station_logs_dir }}/opencode.err.log
|
||||
|
||||
Environment="HOME={{ linux_dev_station_service_home }}"
|
||||
Environment="PATH={{ linux_dev_station_path }}"
|
||||
Environment="OPENCODE_SERVER_USERNAME={{ linux_dev_station_opencode_server_username }}"
|
||||
Environment="OPENCODE_SERVER_PASSWORD={{ linux_dev_station_opencode_server_password }}"
|
||||
{% for env_key, env_value in linux_dev_station_opencode_extra_environment.items() %}
|
||||
Environment="{{ env_key }}={{ env_value }}"
|
||||
{% endfor %}
|
||||
{% if linux_dev_station_ssh_agent_enabled | bool %}
|
||||
Environment="SSH_AUTH_SOCK={{ linux_dev_station_ssh_agent_socket }}"
|
||||
{% endif %}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# SSH agent starter — idempotent, safe to source from concurrent sessions.
|
||||
# Starts ssh-agent if not already running, then adds keys.
|
||||
|
||||
export SSH_AUTH_SOCK="{{ linux_dev_station_ssh_agent_socket }}"
|
||||
|
||||
# Start ssh-agent if socket doesn't exist
|
||||
if [ ! -S "$SSH_AUTH_SOCK" ]; then
|
||||
eval "$(/usr/bin/ssh-agent -a "{{ linux_dev_station_ssh_agent_socket }}" -s)" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Wait for the agent socket to appear
|
||||
__wait=0
|
||||
while [ ! -S "$SSH_AUTH_SOCK" ] && [ "$__wait" -lt 15 ]; do
|
||||
sleep 1
|
||||
__wait=$(( __wait + 1 ))
|
||||
done
|
||||
|
||||
# Add keys if the agent is running but has none loaded
|
||||
if [ -S "$SSH_AUTH_SOCK" ]; then
|
||||
if ssh-add -l 2>/dev/null | grep -q "The agent has no identities"; then
|
||||
for _key in {{ linux_dev_station_ssh_agent_keys | map('quote') | join(' ') }}; do
|
||||
if [ -f "$_key" ]; then
|
||||
if [ -n "{{ linux_dev_station_ssh_agent_passphrase }}" ]; then
|
||||
printf '%s\n' "{{ linux_dev_station_ssh_agent_passphrase }}" | ssh-add "$_key" 2>/dev/null || true
|
||||
else
|
||||
ssh-add "$_key" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=SSH Agent for devstation
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ linux_dev_station_service_user }}
|
||||
Group={{ linux_dev_station_service_user }}
|
||||
ExecStart=/usr/bin/ssh-agent -a {{ linux_dev_station_ssh_agent_socket }}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=append:{{ linux_dev_station_logs_dir }}/ssh-agent.log
|
||||
StandardError=append:{{ linux_dev_station_logs_dir }}/ssh-agent.err.log
|
||||
|
||||
Environment="HOME={{ linux_dev_station_service_home }}"
|
||||
Environment="PATH={{ linux_dev_station_path }}"
|
||||
Environment="SSH_AUTH_SOCK={{ linux_dev_station_ssh_agent_socket }}"
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
{% if linux_dev_station_ssh_agent_enabled | bool %}
|
||||
source "{{ linux_dev_station_scripts_dir }}/ssh-agent-start.sh"
|
||||
{% endif %}
|
||||
|
||||
cd {{ linux_dev_station_workspace_dir }}
|
||||
|
||||
exec code-server \
|
||||
--bind-addr {{ linux_dev_station_code_server_host }}:{{ linux_dev_station_code_server_port }} \
|
||||
--auth {{ linux_dev_station_code_server_auth }} \
|
||||
--disable-telemetry \
|
||||
{{ linux_dev_station_code_server_workspace }}
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
{% if linux_dev_station_ssh_agent_enabled | bool %}
|
||||
source "{{ linux_dev_station_scripts_dir }}/ssh-agent-start.sh"
|
||||
{% endif %}
|
||||
|
||||
cd {{ linux_dev_station_workspace_dir }}
|
||||
|
||||
exec {{ linux_dev_station_opencode_command }}
|
||||
@@ -120,6 +120,8 @@ def _normalize_preset_options(options: dict[str, Any], scope: str) -> dict[str,
|
||||
normalized: dict[str, str] = {}
|
||||
for key, value in options.items():
|
||||
key_str = str(key).strip()
|
||||
# convert _ to - for compatibility with llama.cpp router preset format
|
||||
key_str = key_str.replace("_", "-")
|
||||
if not key_str:
|
||||
raise RuntimeError(f"{scope} preset option keys must be non-empty")
|
||||
if value is None:
|
||||
|
||||
@@ -3,12 +3,14 @@ prod-01.alexpires.me
|
||||
dev-01.lab.alexpires.me
|
||||
vh-01.lab.alexpires.me
|
||||
gpu-01.lab.alexpires.me
|
||||
dev-02.lab.alexpires.me
|
||||
|
||||
[hardening]
|
||||
prod-01.alexpires.me
|
||||
dev-01.lab.alexpires.me
|
||||
vh-01.lab.alexpires.me
|
||||
gpu-01.lab.alexpires.me
|
||||
dev-02.lab.alexpires.me
|
||||
|
||||
[ubuntu]
|
||||
prod-01.alexpires.me
|
||||
@@ -17,6 +19,7 @@ dev-01.lab.alexpires.me
|
||||
[fedora]
|
||||
vh-01.lab.alexpires.me
|
||||
gpu-01.lab.alexpires.me
|
||||
dev-02.lab.alexpires.me
|
||||
|
||||
[certbot]
|
||||
vh-01.lab.alexpires.me
|
||||
@@ -29,6 +32,7 @@ prod-01.alexpires.me
|
||||
dev-01.lab.alexpires.me
|
||||
vh-01.lab.alexpires.me
|
||||
gpu-01.lab.alexpires.me
|
||||
dev-02.lab.alexpires.me
|
||||
|
||||
[gitea]
|
||||
prod-01.alexpires.me
|
||||
@@ -141,3 +145,7 @@ ci-01.lab.alexpires.me
|
||||
[darwin_dev]
|
||||
mac-01.lab.alexpires.me
|
||||
|
||||
[linux_dev]
|
||||
dev-02.lab.alexpires.me
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +111,11 @@ locals {
|
||||
type = "A"
|
||||
content = "10.19.4.207"
|
||||
},
|
||||
{
|
||||
name = "dev-02"
|
||||
type = "A"
|
||||
content = "10.19.4.210"
|
||||
},
|
||||
{
|
||||
name = "vaultwarden"
|
||||
type = "CNAME"
|
||||
|
||||
Reference in New Issue
Block a user