diff --git a/ansible/group_vars/all/gitvana_bun.yml b/ansible/group_vars/all/gitvana_bun.yml new file mode 100644 index 0000000..f7b2794 --- /dev/null +++ b/ansible/group_vars/all/gitvana_bun.yml @@ -0,0 +1,11 @@ +--- +# Gitvana deployment defaults. Override these in host_vars or via -e as needed. +gitvana_repo_url: https://git.castaldifamily.com/nathan/gitvana +gitvana_repo_version: main + +# Runtime mode can be dev or preview. Preview is recommended for long-running service. +gitvana_run_mode: preview +gitvana_service_port: 3000 + +# Optional: If repository access requires a dedicated SSH key on target hosts, set this path. +# gitvana_git_key_file: /home/chester/.ssh/id_ed25519 diff --git a/ansible/playbooks/RUN-GITVANA-BUN.md b/ansible/playbooks/RUN-GITVANA-BUN.md new file mode 100644 index 0000000..1537e7d --- /dev/null +++ b/ansible/playbooks/RUN-GITVANA-BUN.md @@ -0,0 +1,87 @@ +# Run Gitvana Bun Deployment + +This runbook deploys Gitvana directly to Linux VM/LXC hosts using the Ansible role `gitvana_bun_host`. + +## Files added + +- `roles/gitvana_bun_host/*` +- `playbooks/deploy-gitvana-bun.yml` +- `group_vars/all/gitvana_bun.yml` + +## 1) Prepare control node + +From the repository root: + +```bash +cd ansible +ansible --version +ansible-galaxy collection install -r requirements.yml +``` + +## 2) Confirm target host access + +Use your existing inventory and test connectivity: + +```bash +ansible -i inventory/hosts.ini docker_nodes -m ping +``` + +If you want a single host, replace `docker_nodes` with a host like `heimdall`. + +## 3) Review or override deployment variables + +Default values are in `group_vars/all/gitvana_bun.yml` and role defaults. + +Common overrides: + +```bash +-e gitvana_target_hosts=heimdall +-e gitvana_repo_version=main +-e gitvana_service_port=3000 +-e gitvana_run_mode=preview +``` + +## 4) Run deployment + +Deploy to all `docker_nodes`: + +```bash +ansible-playbook -i inventory/hosts.ini playbooks/deploy-gitvana-bun.yml +``` + +Deploy to one host: + +```bash +ansible-playbook -i inventory/hosts.ini playbooks/deploy-gitvana-bun.yml -e gitvana_target_hosts=heimdall +``` + +## 5) Verify service + +Check status on target host: + +```bash +sudo systemctl status gitvana --no-pager +sudo journalctl -u gitvana -n 100 --no-pager +curl -I http://127.0.0.1:3000/ +``` + +## 6) Day-2 operations + +Redeploy after code updates: + +```bash +ansible-playbook -i inventory/hosts.ini playbooks/deploy-gitvana-bun.yml -e gitvana_target_hosts=heimdall +``` + +Restart service only: + +```bash +ansible -i inventory/hosts.ini heimdall -b -m ansible.builtin.systemd -a "name=gitvana state=restarted" +``` + +## Troubleshooting quick checks + +- Ensure Bun is present: `which bun && bun --version` +- Ensure app directory is owned by runtime user: `ls -la /opt/gitvana` +- Ensure service unit exists: `cat /etc/systemd/system/gitvana.service` +- Ensure selected host can access the git repository URL over network diff --git a/ansible/playbooks/deploy-gitvana-bun.yml b/ansible/playbooks/deploy-gitvana-bun.yml new file mode 100644 index 0000000..d3e8120 --- /dev/null +++ b/ansible/playbooks/deploy-gitvana-bun.yml @@ -0,0 +1,15 @@ +--- +- name: Deploy Gitvana on Linux VM/LXC hosts with Bun + hosts: "{{ gitvana_target_hosts | default('docker_nodes') }}" + gather_facts: true + become: true + + pre_tasks: + - name: Validate target host pattern + ansible.builtin.assert: + that: + - (gitvana_target_hosts | default('docker_nodes')) | length > 0 + fail_msg: "gitvana_target_hosts must not be empty." + + roles: + - role: gitvana_bun_host diff --git a/ansible/roles/gitvana_bun_host/README.md b/ansible/roles/gitvana_bun_host/README.md new file mode 100644 index 0000000..847c12f --- /dev/null +++ b/ansible/roles/gitvana_bun_host/README.md @@ -0,0 +1,40 @@ +# gitvana_bun_host + +Ansible role to deploy Gitvana directly on a Linux VM/LXC host using Bun and systemd. + +## What this role does + +- Installs required OS packages +- Installs Bun from official GitHub releases +- Clones/updates the Gitvana repository +- Installs dependencies with Bun +- Creates and manages a systemd unit +- Verifies service reachability over HTTP + +## Requirements + +- Target OS: Debian 12+ or Ubuntu 22.04+ +- Ansible Core 2.16+ +- SSH access with privilege escalation rights + +## Role variables + +See defaults in defaults/main.yml. + +Most commonly overridden: + +- gitvana_repo_url +- gitvana_repo_version +- gitvana_service_port +- gitvana_run_mode + +## Example playbook + +```yaml +--- +- name: Deploy Gitvana with Bun + hosts: docker_nodes + become: true + roles: + - role: gitvana_bun_host +``` diff --git a/ansible/roles/gitvana_bun_host/defaults/main.yml b/ansible/roles/gitvana_bun_host/defaults/main.yml new file mode 100644 index 0000000..80d4dae --- /dev/null +++ b/ansible/roles/gitvana_bun_host/defaults/main.yml @@ -0,0 +1,30 @@ +--- +gitvana_repo_url: https://git.castaldifamily.com/nathan/gitvana +gitvana_repo_version: main + +gitvana_app_user: gitvana +gitvana_app_group: gitvana +gitvana_app_root: /opt/gitvana + +gitvana_service_name: gitvana +gitvana_service_port: 3000 +gitvana_run_mode: preview + +gitvana_env: + NODE_ENV: production + PORT: "{{ gitvana_service_port }}" + +gitvana_healthcheck_path: / +gitvana_verify_status_codes: + - 200 + - 301 + - 302 + +bun_version: 1.2.15 +bun_install_root: /opt/bun +gitvana_bun_arch_map: + x86_64: linux-x64-baseline + aarch64: linux-aarch64 + +# Optional SSH deploy key for private repositories. +gitvana_git_key_file: "" diff --git a/ansible/roles/gitvana_bun_host/handlers/main.yml b/ansible/roles/gitvana_bun_host/handlers/main.yml new file mode 100644 index 0000000..440db74 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/handlers/main.yml @@ -0,0 +1,9 @@ +--- +- name: Reload systemd + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart Gitvana service + ansible.builtin.systemd: + name: "{{ gitvana_service_name }}" + state: restarted diff --git a/ansible/roles/gitvana_bun_host/meta/main.yml b/ansible/roles/gitvana_bun_host/meta/main.yml new file mode 100644 index 0000000..08d6f7a --- /dev/null +++ b/ansible/roles/gitvana_bun_host/meta/main.yml @@ -0,0 +1,26 @@ +--- +galaxy_info: + role_name: gitvana_bun_host + namespace: homelab + author: FrankGPT + description: Deploy Gitvana directly on Debian/Ubuntu hosts using Bun and systemd + license: MIT + min_ansible_version: "2.16" + + platforms: + - name: Ubuntu + versions: + - jammy + - noble + - name: Debian + versions: + - bookworm + + galaxy_tags: + - bun + - web + - application + - deployment + - lxc + +dependencies: [] diff --git a/ansible/roles/gitvana_bun_host/tasks/deploy_code.yml b/ansible/roles/gitvana_bun_host/tasks/deploy_code.yml new file mode 100644 index 0000000..37e7363 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/tasks/deploy_code.yml @@ -0,0 +1,35 @@ +--- +- name: Clone or update Gitvana repository + ansible.builtin.git: + repo: "{{ gitvana_repo_url }}" + dest: "{{ gitvana_app_root }}" + version: "{{ gitvana_repo_version }}" + update: true + key_file: "{{ gitvana_git_key_file | default(omit) }}" + accept_hostkey: true + become: true + become_user: "{{ gitvana_app_user }}" + register: gitvana_repo_sync + +- name: Ensure repository ownership is correct + ansible.builtin.file: + path: "{{ gitvana_app_root }}" + state: directory + recurse: true + owner: "{{ gitvana_app_user }}" + group: "{{ gitvana_app_group }}" + +- name: Check if dependencies directory exists + ansible.builtin.stat: + path: "{{ gitvana_app_root }}/node_modules" + register: gitvana_node_modules + +- name: Install application dependencies with Bun + ansible.builtin.command: + cmd: /usr/local/bin/bun install --frozen-lockfile + chdir: "{{ gitvana_app_root }}" + become: true + become_user: "{{ gitvana_app_user }}" + when: gitvana_repo_sync.changed or not gitvana_node_modules.stat.exists + changed_when: true + notify: Restart Gitvana service diff --git a/ansible/roles/gitvana_bun_host/tasks/install_bun.yml b/ansible/roles/gitvana_bun_host/tasks/install_bun.yml new file mode 100644 index 0000000..b2ecfa1 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/tasks/install_bun.yml @@ -0,0 +1,71 @@ +--- +- name: Ensure Bun install root exists + ansible.builtin.file: + path: "{{ bun_install_root }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Set Bun artifact values + ansible.builtin.set_fact: + bun_archive_name: "bun-{{ bun_arch_suffix }}.zip" + bun_download_url: "https://github.com/oven-sh/bun/releases/download/bun-v{{ bun_version }}/bun-{{ bun_arch_suffix }}.zip" + bun_archive_path: "/tmp/bun-v{{ bun_version }}-{{ bun_arch_suffix }}.zip" + bun_extract_path: "{{ bun_install_root }}/bun-v{{ bun_version }}-{{ bun_arch_suffix }}" + +- name: Ensure versioned Bun extract directory exists + ansible.builtin.file: + path: "{{ bun_extract_path }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Download Bun release archive + ansible.builtin.get_url: + url: "{{ bun_download_url }}" + dest: "{{ bun_archive_path }}" + mode: "0644" + +- name: Extract Bun archive + ansible.builtin.unarchive: + src: "{{ bun_archive_path }}" + dest: "{{ bun_extract_path }}" + remote_src: true + creates: "{{ bun_extract_path }}/.extracted" + +- name: Mark Bun extraction complete + ansible.builtin.file: + path: "{{ bun_extract_path }}/.extracted" + state: touch + mode: "0644" + +- name: Locate extracted Bun binary + ansible.builtin.find: + paths: "{{ bun_extract_path }}" + recurse: true + file_type: file + patterns: bun + register: bun_binary_find + +- name: Fail when Bun binary is missing + ansible.builtin.fail: + msg: "Bun binary was not found under {{ bun_extract_path }} after extraction." + when: bun_binary_find.matched | int == 0 + +- name: Select Bun binary path + ansible.builtin.set_fact: + bun_binary_path: "{{ (bun_binary_find.files | sort(attribute='path') | first).path }}" + +- name: Ensure Bun binary has execute permission + ansible.builtin.file: + path: "{{ bun_binary_path }}" + mode: "0755" + +- name: Link Bun binary into standard PATH + ansible.builtin.file: + src: "{{ bun_binary_path }}" + dest: /usr/local/bin/bun + state: link + force: true diff --git a/ansible/roles/gitvana_bun_host/tasks/main.yml b/ansible/roles/gitvana_bun_host/tasks/main.yml new file mode 100644 index 0000000..ffbea06 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Validate role input + ansible.builtin.import_tasks: validate.yml + +- name: Install base prerequisites + ansible.builtin.import_tasks: prereqs.yml + +- name: Install Bun runtime + ansible.builtin.import_tasks: install_bun.yml + +- name: Deploy application code + ansible.builtin.import_tasks: deploy_code.yml + +- name: Configure and start service + ansible.builtin.import_tasks: service.yml + +- name: Verify service reachability + ansible.builtin.import_tasks: verify.yml diff --git a/ansible/roles/gitvana_bun_host/tasks/prereqs.yml b/ansible/roles/gitvana_bun_host/tasks/prereqs.yml new file mode 100644 index 0000000..587e608 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/tasks/prereqs.yml @@ -0,0 +1,31 @@ +--- +- name: Install prerequisite packages + ansible.builtin.apt: + name: + - ca-certificates + - curl + - git + - unzip + state: present + update_cache: true + +- name: Ensure application group exists + ansible.builtin.group: + name: "{{ gitvana_app_group }}" + system: true + +- name: Ensure application user exists + ansible.builtin.user: + name: "{{ gitvana_app_user }}" + group: "{{ gitvana_app_group }}" + system: true + create_home: false + shell: /usr/sbin/nologin + +- name: Ensure application directory exists + ansible.builtin.file: + path: "{{ gitvana_app_root }}" + state: directory + owner: "{{ gitvana_app_user }}" + group: "{{ gitvana_app_group }}" + mode: "0755" diff --git a/ansible/roles/gitvana_bun_host/tasks/service.yml b/ansible/roles/gitvana_bun_host/tasks/service.yml new file mode 100644 index 0000000..08ebf65 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/tasks/service.yml @@ -0,0 +1,17 @@ +--- +- name: Render systemd unit file + ansible.builtin.template: + src: gitvana.service.j2 + dest: "/etc/systemd/system/{{ gitvana_service_name }}.service" + owner: root + group: root + mode: "0644" + notify: + - Reload systemd + - Restart Gitvana service + +- name: Ensure Gitvana service is enabled and running + ansible.builtin.systemd: + name: "{{ gitvana_service_name }}" + enabled: true + state: started diff --git a/ansible/roles/gitvana_bun_host/tasks/validate.yml b/ansible/roles/gitvana_bun_host/tasks/validate.yml new file mode 100644 index 0000000..0db29c0 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/tasks/validate.yml @@ -0,0 +1,31 @@ +--- +- name: Assert supported operating system + ansible.builtin.assert: + that: + - ansible_os_family == "Debian" + fail_msg: "This role supports Debian/Ubuntu targets only." + +- name: Assert required role variables + ansible.builtin.assert: + that: + - gitvana_repo_url | length > 0 + - gitvana_repo_version | length > 0 + - gitvana_app_user | length > 0 + - gitvana_app_group | length > 0 + - gitvana_app_root | length > 0 + - gitvana_service_name | length > 0 + - gitvana_service_port | int > 0 + - gitvana_service_port | int < 65536 + - bun_version | length > 0 + - bun_install_root | length > 0 + - gitvana_run_mode in ["dev", "preview"] + fail_msg: "One or more required variables are missing or invalid." + +- name: Map architecture for Bun release artifact + ansible.builtin.set_fact: + bun_arch_suffix: "{{ gitvana_bun_arch_map.get(ansible_architecture) }}" + +- name: Fail on unsupported architecture + ansible.builtin.fail: + msg: "Unsupported architecture for Bun install: {{ ansible_architecture }}" + when: bun_arch_suffix is not defined or bun_arch_suffix | length == 0 diff --git a/ansible/roles/gitvana_bun_host/tasks/verify.yml b/ansible/roles/gitvana_bun_host/tasks/verify.yml new file mode 100644 index 0000000..53e9dea --- /dev/null +++ b/ansible/roles/gitvana_bun_host/tasks/verify.yml @@ -0,0 +1,16 @@ +--- +- name: Wait for Gitvana service port + ansible.builtin.wait_for: + port: "{{ gitvana_service_port }}" + host: 127.0.0.1 + timeout: 60 + +- name: Verify local HTTP health endpoint + ansible.builtin.uri: + url: "http://127.0.0.1:{{ gitvana_service_port }}{{ gitvana_healthcheck_path }}" + method: GET + status_code: "{{ gitvana_verify_status_codes }}" + register: gitvana_health_result + retries: 3 + delay: 5 + until: gitvana_health_result.status in gitvana_verify_status_codes diff --git a/ansible/roles/gitvana_bun_host/templates/gitvana.service.j2 b/ansible/roles/gitvana_bun_host/templates/gitvana.service.j2 new file mode 100644 index 0000000..4ea1348 --- /dev/null +++ b/ansible/roles/gitvana_bun_host/templates/gitvana.service.j2 @@ -0,0 +1,21 @@ +[Unit] +Description=Gitvana Bun Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User={{ gitvana_app_user }} +Group={{ gitvana_app_group }} +WorkingDirectory={{ gitvana_app_root }} +{% for key, value in gitvana_env.items() %} +Environment={{ key }}={{ value }} +{% endfor %} +ExecStart=/usr/local/bin/bun run {{ gitvana_run_mode }} --host 0.0.0.0 --port {{ gitvana_service_port }} +Restart=on-failure +RestartSec=5 +TimeoutStartSec=120 +TimeoutStopSec=30 + +[Install] +WantedBy=multi-user.target