--- - 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 sets PATH for tool binaries become: true ansible.builtin.blockinfile: path: "{{ linux_dev_station_service_home }}/.zshrc" block: | # ansible: linux_dev_station path if ! [[ "$PATH" =~ "$HOME/.local/bin:" ]]; then PATH="$HOME/.local/bin:$PATH" fi if ! [[ "$PATH" =~ "$HOME/bin:" ]]; then PATH="$HOME/bin:$PATH" fi export PATH marker: "# {mark} ansible: linux_dev_station path" create: true mode: "0644" owner: "{{ linux_dev_station_service_user }}" group: "{{ linux_dev_station_service_user }}" when: - linux_dev_station_service_user_enabled | bool - name: Ensure .zshrc unsets history vars before Oh My Zsh become: true ansible.builtin.blockinfile: path: "{{ linux_dev_station_service_home }}/.zshrc" block: | # ansible: linux_dev_station unset history before oh-my-zsh unset HISTFILE HISTSIZE SAVEHIST marker: "# {mark} ansible: linux_dev_station unset history before oh-my-zsh" 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_oh_my_zsh_enabled | bool - name: Ensure .zshrc sources Oh My Zsh become: true ansible.builtin.blockinfile: path: "{{ linux_dev_station_service_home }}/.zshrc" block: | # ansible: linux_dev_station oh-my-zsh export ZSH="$HOME/.oh-my-zsh" ZSH_THEME="{{ linux_dev_station_oh_my_zsh_theme }}" plugins=({{ linux_dev_station_oh_my_zsh_plugins | join(' ') }}) source "$ZSH/oh-my-zsh.sh" marker: "# {mark} ansible: linux_dev_station oh-my-zsh" 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_oh_my_zsh_enabled | bool - name: Ensure .zshrc has extra environment variables become: true ansible.builtin.blockinfile: path: "{{ linux_dev_station_service_home }}/.zshrc" block: | # ansible: linux_dev_station extra env vars {% for env_key, env_value in linux_dev_station_zshrc_extra_env.items() %} export {{ env_key }}="{{ env_value }}" {% endfor %} marker: "# {mark} ansible: linux_dev_station extra env vars" 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_zshrc_extra_env | length > 0 - 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