405 lines
11 KiB
YAML
405 lines
11 KiB
YAML
|
|
---
|
||
|
|
- name: Server Initialization Playbook
|
||
|
|
hosts: localhost
|
||
|
|
connection: local
|
||
|
|
become: yes
|
||
|
|
vars:
|
||
|
|
base_domain:
|
||
|
|
pangolin_domain: "pangolin.{{ base_domain }}"
|
||
|
|
forgejo_domain: "forgejo.{{ base_domain }}"
|
||
|
|
opencode_domain: "opencode.{{ base_domain }}"
|
||
|
|
acme_email: "admin@{{ base_domain }}"
|
||
|
|
docker_hosting_dir: "/home/{{ login_user }}/docker_hosting"
|
||
|
|
resources_dir: "{{ playbook_dir }}/resources"
|
||
|
|
login_user: "{{ ansible_env.SUDO_USER | default(ansible_user_id) }}"
|
||
|
|
deb_architecture: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' if ansible_architecture == 'aarch64' else ansible_architecture }}"
|
||
|
|
|
||
|
|
tasks:
|
||
|
|
- name: Update apt cache
|
||
|
|
ansible.builtin.apt:
|
||
|
|
update_cache: yes
|
||
|
|
cache_valid_time: 3600
|
||
|
|
|
||
|
|
- name: Install prerequisites for Docker repository
|
||
|
|
ansible.builtin.apt:
|
||
|
|
name:
|
||
|
|
- ca-certificates
|
||
|
|
- curl
|
||
|
|
- gnupg
|
||
|
|
- lsb-release
|
||
|
|
state: present
|
||
|
|
|
||
|
|
- name: Install common dev dependencies
|
||
|
|
ansible.builtin.apt:
|
||
|
|
name:
|
||
|
|
- git
|
||
|
|
- vim
|
||
|
|
- wget
|
||
|
|
- htop
|
||
|
|
- tmux
|
||
|
|
- unzip
|
||
|
|
- zip
|
||
|
|
- build-essential
|
||
|
|
- gcc
|
||
|
|
- make
|
||
|
|
- pkg-config
|
||
|
|
- libssl-dev
|
||
|
|
- libffi-dev
|
||
|
|
- python3
|
||
|
|
- python3-pip
|
||
|
|
- python3-venv
|
||
|
|
- nodejs
|
||
|
|
- npm
|
||
|
|
- jq
|
||
|
|
state: present
|
||
|
|
|
||
|
|
# --- Server hardening ---
|
||
|
|
|
||
|
|
- name: Install security packages
|
||
|
|
ansible.builtin.apt:
|
||
|
|
name:
|
||
|
|
- ufw
|
||
|
|
- fail2ban
|
||
|
|
- unattended-upgrades
|
||
|
|
- apt-listchanges
|
||
|
|
state: present
|
||
|
|
|
||
|
|
- name: Disable root SSH login
|
||
|
|
ansible.builtin.lineinfile:
|
||
|
|
path: /etc/ssh/sshd_config
|
||
|
|
regexp: '^#?PermitRootLogin'
|
||
|
|
line: 'PermitRootLogin no'
|
||
|
|
validate: 'sshd -t -f %s'
|
||
|
|
notify: restart sshd
|
||
|
|
|
||
|
|
- name: Disable SSH password authentication
|
||
|
|
ansible.builtin.lineinfile:
|
||
|
|
path: /etc/ssh/sshd_config
|
||
|
|
regexp: '^#?PasswordAuthentication'
|
||
|
|
line: 'PasswordAuthentication no'
|
||
|
|
validate: 'sshd -t -f %s'
|
||
|
|
notify: restart sshd
|
||
|
|
|
||
|
|
- name: Disable SSH challenge-response authentication
|
||
|
|
ansible.builtin.lineinfile:
|
||
|
|
path: /etc/ssh/sshd_config
|
||
|
|
regexp: '^#?KbdInteractiveAuthentication'
|
||
|
|
line: 'KbdInteractiveAuthentication no'
|
||
|
|
validate: 'sshd -t -f %s'
|
||
|
|
notify: restart sshd
|
||
|
|
|
||
|
|
- name: Set SSH MaxAuthTries
|
||
|
|
ansible.builtin.lineinfile:
|
||
|
|
path: /etc/ssh/sshd_config
|
||
|
|
regexp: '^#?MaxAuthTries'
|
||
|
|
line: 'MaxAuthTries 3'
|
||
|
|
validate: 'sshd -t -f %s'
|
||
|
|
notify: restart sshd
|
||
|
|
|
||
|
|
- name: UFW - Allow forwarding for Docker
|
||
|
|
ansible.builtin.lineinfile:
|
||
|
|
path: /etc/default/ufw
|
||
|
|
regexp: '^DEFAULT_FORWARD_POLICY='
|
||
|
|
line: 'DEFAULT_FORWARD_POLICY="ACCEPT"'
|
||
|
|
|
||
|
|
- name: UFW - Set default deny incoming
|
||
|
|
community.general.ufw:
|
||
|
|
direction: incoming
|
||
|
|
default: deny
|
||
|
|
|
||
|
|
- name: UFW - Set default allow outgoing
|
||
|
|
community.general.ufw:
|
||
|
|
direction: outgoing
|
||
|
|
default: allow
|
||
|
|
|
||
|
|
- name: UFW - Allow SSH
|
||
|
|
community.general.ufw:
|
||
|
|
rule: allow
|
||
|
|
port: '22'
|
||
|
|
proto: tcp
|
||
|
|
|
||
|
|
- name: UFW - Allow HTTP
|
||
|
|
community.general.ufw:
|
||
|
|
rule: allow
|
||
|
|
port: '80'
|
||
|
|
proto: tcp
|
||
|
|
|
||
|
|
- name: UFW - Allow HTTPS
|
||
|
|
community.general.ufw:
|
||
|
|
rule: allow
|
||
|
|
port: '443'
|
||
|
|
proto: tcp
|
||
|
|
|
||
|
|
- name: UFW - Allow WireGuard
|
||
|
|
community.general.ufw:
|
||
|
|
rule: allow
|
||
|
|
port: '51820'
|
||
|
|
proto: udp
|
||
|
|
|
||
|
|
- name: UFW - Allow WireGuard clients
|
||
|
|
community.general.ufw:
|
||
|
|
rule: allow
|
||
|
|
port: '21820'
|
||
|
|
proto: udp
|
||
|
|
|
||
|
|
- name: UFW - Allow Docker to reach opencode on host
|
||
|
|
community.general.ufw:
|
||
|
|
rule: allow
|
||
|
|
from_ip: 172.18.0.0/16
|
||
|
|
to_ip: any
|
||
|
|
port: '3000'
|
||
|
|
proto: tcp
|
||
|
|
|
||
|
|
- name: UFW - Enable firewall
|
||
|
|
community.general.ufw:
|
||
|
|
state: enabled
|
||
|
|
|
||
|
|
- name: Configure fail2ban for SSH
|
||
|
|
ansible.builtin.copy:
|
||
|
|
dest: /etc/fail2ban/jail.local
|
||
|
|
mode: '0644'
|
||
|
|
content: |
|
||
|
|
[DEFAULT]
|
||
|
|
bantime = 1h
|
||
|
|
findtime = 10m
|
||
|
|
maxretry = 5
|
||
|
|
|
||
|
|
[sshd]
|
||
|
|
enabled = true
|
||
|
|
port = ssh
|
||
|
|
filter = sshd
|
||
|
|
logpath = /var/log/auth.log
|
||
|
|
|
||
|
|
- name: Start and enable fail2ban
|
||
|
|
ansible.builtin.service:
|
||
|
|
name: fail2ban
|
||
|
|
state: started
|
||
|
|
enabled: yes
|
||
|
|
|
||
|
|
- name: Enable unattended security upgrades
|
||
|
|
ansible.builtin.copy:
|
||
|
|
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||
|
|
mode: '0644'
|
||
|
|
content: |
|
||
|
|
APT::Periodic::Update-Package-Lists "1";
|
||
|
|
APT::Periodic::Unattended-Upgrade "1";
|
||
|
|
APT::Periodic::AutocleanInterval "7";
|
||
|
|
|
||
|
|
- name: Create keyrings directory
|
||
|
|
ansible.builtin.file:
|
||
|
|
path: /etc/apt/keyrings
|
||
|
|
state: directory
|
||
|
|
mode: '0755'
|
||
|
|
|
||
|
|
- name: Add Docker GPG key
|
||
|
|
ansible.builtin.shell:
|
||
|
|
cmd: curl -fsSL https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg -o /etc/apt/keyrings/docker.asc
|
||
|
|
args:
|
||
|
|
creates: /etc/apt/keyrings/docker.asc
|
||
|
|
|
||
|
|
- name: Add Docker repository
|
||
|
|
ansible.builtin.apt_repository:
|
||
|
|
repo: "deb [arch={{ deb_architecture }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable"
|
||
|
|
state: present
|
||
|
|
|
||
|
|
- name: Install Docker
|
||
|
|
ansible.builtin.apt:
|
||
|
|
name:
|
||
|
|
- docker-ce
|
||
|
|
- docker-ce-cli
|
||
|
|
- containerd.io
|
||
|
|
- docker-compose-plugin
|
||
|
|
state: present
|
||
|
|
update_cache: yes
|
||
|
|
|
||
|
|
- name: Start and enable Docker
|
||
|
|
ansible.builtin.service:
|
||
|
|
name: docker
|
||
|
|
state: started
|
||
|
|
enabled: yes
|
||
|
|
|
||
|
|
- name: Add user to docker group
|
||
|
|
ansible.builtin.user:
|
||
|
|
name: "{{ login_user }}"
|
||
|
|
groups: docker
|
||
|
|
append: yes
|
||
|
|
|
||
|
|
- name: Install Rust via rustup for login user
|
||
|
|
ansible.builtin.shell:
|
||
|
|
cmd: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||
|
|
args:
|
||
|
|
creates: "/home/{{ login_user }}/.cargo/bin/rustup"
|
||
|
|
become_user: "{{ login_user }}"
|
||
|
|
|
||
|
|
- name: Install uv (Python package manager)
|
||
|
|
ansible.builtin.shell:
|
||
|
|
cmd: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||
|
|
args:
|
||
|
|
creates: "/home/{{ login_user }}/.local/bin/uv"
|
||
|
|
become_user: "{{ login_user }}"
|
||
|
|
|
||
|
|
# --- Forgejo SSH passthrough ---
|
||
|
|
|
||
|
|
- name: Create forgejo-shell directory
|
||
|
|
ansible.builtin.file:
|
||
|
|
path: /etc/forgejo/bin
|
||
|
|
state: directory
|
||
|
|
mode: '0755'
|
||
|
|
|
||
|
|
- name: Create forgejo-shell script
|
||
|
|
ansible.builtin.copy:
|
||
|
|
dest: /etc/forgejo/bin/forgejo-shell
|
||
|
|
mode: '0755'
|
||
|
|
content: |
|
||
|
|
#!/bin/sh
|
||
|
|
/usr/bin/docker exec -i --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" forgejo sh "$@"
|
||
|
|
|
||
|
|
- name: Create git user for Forgejo
|
||
|
|
ansible.builtin.user:
|
||
|
|
name: git
|
||
|
|
system: yes
|
||
|
|
shell: /etc/forgejo/bin/forgejo-shell
|
||
|
|
home: /home/git
|
||
|
|
create_home: yes
|
||
|
|
register: git_user
|
||
|
|
|
||
|
|
- name: Set git user UID/GID facts
|
||
|
|
ansible.builtin.set_fact:
|
||
|
|
git_uid: "{{ git_user.uid }}"
|
||
|
|
git_gid: "{{ git_user.group }}"
|
||
|
|
|
||
|
|
- name: Add git user to docker group
|
||
|
|
ansible.builtin.user:
|
||
|
|
name: git
|
||
|
|
groups: docker
|
||
|
|
append: yes
|
||
|
|
|
||
|
|
- name: Configure SSH passthrough for git user
|
||
|
|
ansible.builtin.copy:
|
||
|
|
dest: /etc/ssh/sshd_config.d/forgejo-ssh-passthrough.conf
|
||
|
|
mode: '0644'
|
||
|
|
content: |
|
||
|
|
Match User git
|
||
|
|
AuthorizedKeysCommandUser git
|
||
|
|
AuthorizedKeysCommand /usr/bin/docker exec -i forgejo /usr/local/bin/gitea keys -c /etc/gitea/app.ini -e git -u %u -t %t -k %k
|
||
|
|
notify: restart sshd
|
||
|
|
|
||
|
|
- name: Generate Pangolin secret
|
||
|
|
ansible.builtin.shell:
|
||
|
|
cmd: openssl rand -base64 48
|
||
|
|
register: pangolin_secret_result
|
||
|
|
changed_when: false
|
||
|
|
|
||
|
|
- name: Set Pangolin secret fact
|
||
|
|
ansible.builtin.set_fact:
|
||
|
|
pangolin_secret: "{{ pangolin_secret_result.stdout }}"
|
||
|
|
|
||
|
|
# --- Directory structure ---
|
||
|
|
|
||
|
|
- name: Create docker_hosting directory structure
|
||
|
|
ansible.builtin.file:
|
||
|
|
path: "{{ docker_hosting_dir }}/{{ item }}"
|
||
|
|
state: directory
|
||
|
|
owner: "{{ login_user }}"
|
||
|
|
group: "{{ login_user }}"
|
||
|
|
mode: '0755'
|
||
|
|
loop:
|
||
|
|
- docker_data/pangolin
|
||
|
|
- docker_data/pangolin/db
|
||
|
|
- docker_data/pangolin/letsencrypt
|
||
|
|
- docker_data/pangolin/logs
|
||
|
|
- docker_data/pangolin/traefik
|
||
|
|
- docker_data/pangolin/traefik/logs
|
||
|
|
|
||
|
|
- name: Create Forgejo data directory
|
||
|
|
ansible.builtin.file:
|
||
|
|
path: "{{ docker_hosting_dir }}/docker_data/forgejo/data"
|
||
|
|
state: directory
|
||
|
|
owner: git
|
||
|
|
group: git
|
||
|
|
mode: '0755'
|
||
|
|
|
||
|
|
# --- Pangolin config files ---
|
||
|
|
|
||
|
|
- name: Copy Pangolin config.yml
|
||
|
|
ansible.builtin.template:
|
||
|
|
src: "{{ resources_dir }}/pangolin/config.yml.j2"
|
||
|
|
dest: "{{ docker_hosting_dir }}/docker_data/pangolin/config.yml"
|
||
|
|
owner: "{{ login_user }}"
|
||
|
|
group: "{{ login_user }}"
|
||
|
|
mode: '0600'
|
||
|
|
force: no
|
||
|
|
|
||
|
|
- name: Copy Traefik static config
|
||
|
|
ansible.builtin.template:
|
||
|
|
src: "{{ resources_dir }}/pangolin/traefik_config.yml.j2"
|
||
|
|
dest: "{{ docker_hosting_dir }}/docker_data/pangolin/traefik/traefik_config.yml"
|
||
|
|
owner: "{{ login_user }}"
|
||
|
|
group: "{{ login_user }}"
|
||
|
|
mode: '0644'
|
||
|
|
|
||
|
|
- name: Copy Traefik dynamic config
|
||
|
|
ansible.builtin.template:
|
||
|
|
src: "{{ resources_dir }}/pangolin/dynamic_config.yml.j2"
|
||
|
|
dest: "{{ docker_hosting_dir }}/docker_data/pangolin/traefik/dynamic_config.yml"
|
||
|
|
owner: "{{ login_user }}"
|
||
|
|
group: "{{ login_user }}"
|
||
|
|
mode: '0644'
|
||
|
|
|
||
|
|
- name: Copy compose.yml
|
||
|
|
ansible.builtin.template:
|
||
|
|
src: "{{ resources_dir }}/compose.yml.j2"
|
||
|
|
dest: "{{ docker_hosting_dir }}/compose.yml"
|
||
|
|
owner: "{{ login_user }}"
|
||
|
|
group: "{{ login_user }}"
|
||
|
|
mode: '0644'
|
||
|
|
|
||
|
|
# --- OpenCode ---
|
||
|
|
|
||
|
|
- name: Install opencode
|
||
|
|
ansible.builtin.shell:
|
||
|
|
cmd: curl -fsSL https://opencode.ai/install | bash
|
||
|
|
args:
|
||
|
|
creates: /home/{{ login_user }}/.opencode/bin/opencode
|
||
|
|
become_user: "{{ login_user }}"
|
||
|
|
|
||
|
|
- name: Create opencode systemd service
|
||
|
|
ansible.builtin.copy:
|
||
|
|
dest: /etc/systemd/system/opencode.service
|
||
|
|
mode: '0644'
|
||
|
|
content: |
|
||
|
|
[Unit]
|
||
|
|
Description=OpenCode Server
|
||
|
|
After=network.target
|
||
|
|
|
||
|
|
[Service]
|
||
|
|
Type=simple
|
||
|
|
User={{ login_user }}
|
||
|
|
WorkingDirectory=/home/{{ login_user }}
|
||
|
|
ExecStart=/home/{{ login_user }}/.opencode/bin/opencode serve --port 3000 --hostname 0.0.0.0
|
||
|
|
Restart=on-failure
|
||
|
|
RestartSec=5
|
||
|
|
|
||
|
|
[Install]
|
||
|
|
WantedBy=multi-user.target
|
||
|
|
|
||
|
|
- name: Start and enable opencode service
|
||
|
|
ansible.builtin.systemd:
|
||
|
|
name: opencode
|
||
|
|
state: started
|
||
|
|
enabled: yes
|
||
|
|
daemon_reload: yes
|
||
|
|
|
||
|
|
# --- Start services ---
|
||
|
|
|
||
|
|
- name: Start all services via docker compose
|
||
|
|
ansible.builtin.shell:
|
||
|
|
cmd: docker compose up -d
|
||
|
|
chdir: "{{ docker_hosting_dir }}"
|
||
|
|
|
||
|
|
handlers:
|
||
|
|
- name: restart sshd
|
||
|
|
ansible.builtin.service:
|
||
|
|
name: ssh
|
||
|
|
state: restarted
|