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:
@@ -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
|
||||
Reference in New Issue
Block a user