This commit is contained in:
Pablo Martin 2025-07-01 16:14:44 +02:00
parent 5f06a966aa
commit 3343de2dc0
12 changed files with 286 additions and 57 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
inventory.ini inventory.ini
venv/*

View file

@ -5,7 +5,11 @@ This describes how to prepare each machine before deploying services on them.
## 01.01 First steps ## 01.01 First steps
* Create an ssh key or pick an existing one. We'll refer to it as the `personal_ssh_key`. * Create an ssh key or pick an existing one. We'll refer to it as the `personal_ssh_key`.
* The guide assumes the laptop (Lapy) has `ansible` installed. If not, do `sudo apt install -y ansible` and `ansible --version` to check. * Deploy ansible on the laptop (Lapy), which will act as the ansible control node. To do so:
* Create a `venv`: `python3 -m venv venv`
* Activate it: `source venv/bin/activate`
* Install the listed ansible requirements with `pip install -r requirements.txt`
* Keep in mind you should activate this `venv` from now on when running `ansible` commands.
## 01.02 Prepare the VPS (Vipy) ## 01.02 Prepare the VPS (Vipy)
@ -20,9 +24,13 @@ This describes how to prepare each machine before deploying services on them.
### 01.02.02 Prepare Ansible vars ### 01.02.02 Prepare Ansible vars
* You have an example `infra/example.inventory.ini`. Copy it with `cp example.inventory.ini inventory.ini` and fill in with the vars for your VPS. * You have an example `ansible/example.inventory.ini`. Copy it with `cp ansible/example.inventory.ini ansible/inventory.ini` and fill in with the values for your VPS.
### 01.02.03 First steps with Ansible ### 01.02.03 Create user and secure VPS access
* cd into `infra` * Ansible will create a user on the first playbook `01_basic_vps_setup_playbook.yml`. This is the user that will get used regularly. But, since this user doesn't exist, you obviosuly need to first run this playbook from some other user. We assume your VPS provider has given you a root user, which is what you need to define as the running user in the next command.
* Run `ansible-playbook playbook.yml` * cd into `ansible`
* Run `ansible-playbook -i inventory.ini infra/01_user_and_access_setup_playbook.yml -e 'ansible_user="your root user here"'
* Then, configure firewall access, fail2ban and auditd with `ansible-playbook -i inventory.ini infra/02_firewall_playbook.yml`
Note that both the root user and the `counterweight` user will use the same SSH pubkey for auth.

View file

@ -0,0 +1,18 @@
# 02. VPS Core Services Setup
Now that Vipy is ready, we need to deploy some basic services which are foundational for the apps we're actually interested in.
This assumes you've completed the markdown `01`.
## 02.01 Deploy Caddy
* Use Ansible to run the caddy playbook:
```
cd ansible
ansible-playbook -i inventory.ini services/caddy_playbook.yml
```
* Starting config will be empty. Modifying the caddy config file to add endpoints as we add services is covered by the instructions of each service.
## 02.02 Deploy Uptime Kuma

View file

@ -0,0 +1,2 @@
[vipy]
your.vps.ip.here ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/your-key

View file

@ -0,0 +1,65 @@
- name: Secure Debian VPS
hosts: vipy
vars_files:
- ../vars.yml
become: true
tasks:
- name: Update and upgrade apt packages
apt:
update_cache: yes
upgrade: full
autoremove: yes
- name: Create new user
user:
name: "{{ new_user }}"
groups: sudo
shell: /bin/bash
state: present
create_home: yes
- name: Set up SSH directory for new user
file:
path: "/home/{{ new_user }}/.ssh"
state: directory
mode: "0700"
owner: "{{ new_user }}"
group: "{{ new_user }}"
- name: Copy current user's authorized_keys to new user
copy:
src: "/home/{{ ansible_user }}/.ssh/authorized_keys"
dest: "/home/{{ new_user }}/.ssh/authorized_keys"
owner: "{{ new_user }}"
group: "{{ new_user }}"
mode: "0600"
remote_src: true
- name: Allow new user to run sudo without password
copy:
dest: "/etc/sudoers.d/{{ new_user }}"
content: "{{ new_user }} ALL=(ALL) NOPASSWD:ALL"
owner: root
group: root
mode: "0440"
- name: Disable root login
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
backrefs: yes
loop:
- { regexp: "^#?PermitRootLogin .*", line: "PermitRootLogin no" }
- {
regexp: "^#?PasswordAuthentication .*",
line: "PasswordAuthentication no",
}
- name: Restart SSH
service:
name: ssh
state: restarted

View file

@ -1,56 +1,10 @@
- name: Secure Debian VPS - name: Secure Debian VPS
hosts: vipy hosts: vipy
vars_files: vars_files:
- vars.yml - ../vars.yml
become: true become: true
tasks: tasks:
- name: Update and upgrade apt packages
apt:
update_cache: yes
upgrade: full
autoremove: yes
- name: Create new user
user:
name: "{{ new_user }}"
groups: sudo
shell: /bin/bash
state: present
create_home: yes
- name: Set up SSH directory for new user
file:
path: "/home/{{ new_user }}/.ssh"
state: directory
mode: "0700"
owner: "{{ new_user }}"
group: "{{ new_user }}"
- name: Change SSH port and disable root login
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
backrefs: yes
loop:
- { regexp: "^#?Port .*", line: "Port {{ ssh_port }}" }
- { regexp: "^#?PermitRootLogin .*", line: "PermitRootLogin no" }
- {
regexp: "^#?PasswordAuthentication .*",
line: "PasswordAuthentication no",
}
- name: Restart SSH
service:
name: ssh
state: restarted
- name: Set SSH port to new port
set_fact:
ansible_port: "{{ ssh_port }}"
- name: Install UFW - name: Install UFW
apt: apt:
name: ufw name: ufw
@ -68,11 +22,12 @@
- name: Allow outgoing traffic - name: Allow outgoing traffic
ufw: ufw:
rule: allow rule: allow
direction: outgoing direction: out
- name: Allow SSH port through UFW - name: Allow SSH port through UFW
ufw: ufw:
rule: allow rule: allow
direction: in
port: "{{ ssh_port }}" port: "{{ ssh_port }}"
proto: tcp proto: tcp
from_ip: "{{ allow_ssh_from if allow_ssh_from != 'any' else omit }}" from_ip: "{{ allow_ssh_from if allow_ssh_from != 'any' else omit }}"

View file

@ -0,0 +1,61 @@
- name: Install and configure Caddy on Debian 12
hosts: vipy
become: yes
tasks:
- name: Install required packages
apt:
name:
- debian-keyring
- debian-archive-keyring
- apt-transport-https
- curl
state: present
update_cache: yes
- name: Download Caddy GPG armored key
ansible.builtin.get_url:
url: https://dl.cloudsmith.io/public/caddy/stable/gpg.key
dest: /tmp/caddy-stable-archive-keyring.asc
mode: '0644'
- name: Convert ASCII armored key to binary keyring
ansible.builtin.command:
cmd: gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg /tmp/caddy-stable-archive-keyring.asc
args:
creates: /usr/share/keyrings/caddy-stable-archive-keyring.gpg
- name: Ensure permissions on keyring file
ansible.builtin.file:
path: /usr/share/keyrings/caddy-stable-archive-keyring.gpg
owner: root
group: root
mode: '0644'
- name: Add Caddy repository list file
ansible.builtin.get_url:
url: https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt
dest: /etc/apt/sources.list.d/caddy-stable.list
mode: '0644'
validate_certs: yes
- name: Update apt cache after adding repo
apt:
update_cache: yes
- name: Install Caddy
apt:
name: caddy
state: present
- name: Ensure Caddy service is enabled and started
systemd:
name: caddy
enabled: yes
state: started
- name: Allow HTTPS through UFW
ufw:
rule: allow
port: '443'
proto: tcp

View file

@ -0,0 +1,59 @@
- name: Install Docker and Docker Compose on Debian 12
hosts: all
become: yes
tasks:
- name: Ensure required packages are installed
apt:
name:
- ca-certificates
- curl
- gnupg
- lsb-release
state: present
update_cache: yes
- name: Add Docker GPG key
ansible.builtin.apt_key:
url: https://download.docker.com/linux/debian/gpg
state: present
- name: Add Docker repository
ansible.builtin.apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
state: present
filename: docker
- name: Update apt cache after adding Docker repo
apt:
update_cache: yes
- name: Install Docker Engine and CLI
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: latest
- name: Ensure Docker is started and enabled
systemd:
name: docker
enabled: yes
state: started
- name: Add user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
- name: Create symlink for docker-compose (optional CLI alias)
file:
src: /usr/libexec/docker/cli-plugins/docker-compose
dest: /usr/local/bin/docker-compose
state: link
when: ansible_facts['os_family'] == "Debian"
ignore_errors: true # In case the plugin path differs slightly

View file

@ -0,0 +1,51 @@
- name: Deploy Uptime Kuma with Docker Compose and configure Caddy reverse proxy
hosts: vipy
become: yes
vars:
uptime_kuma_dir: /opt/uptime-kuma
uptime_kuma_port: 3001
caddy_sites_dir: /etc/caddy/sites-enabled
uptime_kuma_domain: uptime.example.com # Change to your domain
tasks:
- name: Create uptime kuma directory
file:
path: "{{ uptime_kuma_dir }}"
state: directory
owner: {{ ansible_user }}
group: {{ ansible_user }}
mode: '0755'
- name: Create docker-compose.yml for uptime kuma
copy:
dest: "{{ uptime_kuma_dir }}/docker-compose.yml"
content: |
version: "3"
services:
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
ports:
- "{{ uptime_kuma_port }}:3001"
volumes:
- ./data:/app/data
- name: Deploy uptime kuma container with docker compose
command: docker-compose up -d
args:
chdir: "{{ uptime_kuma_dir }}"
- name: Create Caddy reverse proxy configuration for uptime kuma
copy:
dest: "{{ caddy_sites_dir }}/uptime-kuma.conf"
content: |
{{ uptime_kuma_domain }} {
reverse_proxy localhost:{{ uptime_kuma_port }}
}
owner: root
group: root
mode: '0644'
- name: Reload Caddy to apply new config
command: systemctl reload caddy

View file

@ -1,3 +1,4 @@
new_user: counterweight new_user: counterweight
ssh_port: 2222 ssh_port: 22
allow_ssh_from: "any" allow_ssh_from: "any"

View file

@ -1,2 +0,0 @@
[vipy]
your.vps.ip.here ansible_user=debian ansible_port=22

10
requirements.txt Normal file
View file

@ -0,0 +1,10 @@
ansible==10.7.0
ansible-core==2.17.12
cffi==1.17.1
cryptography==45.0.4
Jinja2==3.1.6
MarkupSafe==3.0.2
packaging==25.0
pycparser==2.22
PyYAML==6.0.2
resolvelib==1.0.1