Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧱 Add Ansible playbook to manage cloud resources #1701

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ module.exports = {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"tsconfigRootDir": __dirname,
"project": "./packages/**/tsconfig.json"
// Avoid Out of Memory error
// https://github.com/typescript-eslint/typescript-eslint/issues/1192
"project": "./packages/tsconfig-eslint.json"
},
"plugins": [
"@typescript-eslint",
Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/ansible/ i@spgoding.com
3 changes: 2 additions & 1 deletion .packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,6 @@
"commit": "f222cd48f53a3dfb73506bba8ce259cf6ad3fc5f",
"version": "4.4.4"
}
}
},
"web-api-server": {}
}
3 changes: 3 additions & 0 deletions ansible/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
secrets.yml
inventory-*.yml
!inventory-prod.yml
11 changes: 11 additions & 0 deletions ansible/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
An Ansible playbook for managing cloud infrastructure used by Spyglass's various services.

Current managed services:

* API: https://api.spyglassmc.com
* Discord bot
* Weblate: https://weblate.spyglassmc.com

Ansible variables:

* See each role's default variables.
5 changes: 5 additions & 0 deletions ansible/inventory-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ungrouped:
hosts:
api.spyglassmc.com:
vars:
ansible_ssh_pipelining: true
6 changes: 6 additions & 0 deletions ansible/playbook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- name: Install spyglassmc.ai Cloud (TM)
hosts: all
roles:
- api-server
- discord-bot
- weblate
4 changes: 4 additions & 0 deletions ansible/roles/acmesh/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# (str) Dynamic DNS key for TXT record on _acme-challenge.spyglassmc.com on Hurricane Electric DNS
acmesh_dynamic_dns_key: null
# (str) Email used for generating certificates with Let's Encrypt
acmesh_email: null
23 changes: 23 additions & 0 deletions ansible/roles/acmesh/files/dns_hedyn.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/bash

# https://github.com/acmesh-official/acme.sh/issues/3512

dns_hedyn_add() {
fulldomain=$1
txtvalue=$2

HEdyn_key="${HEdyn_key:-$(_readdomainconf HEdyn_key)}"
if [ "$HEdyn_key" ]; then
_savedomainconf HEdyn_key "$HEdyn_key"
elif [ -z "$HEdyn_key" ]; then
_err "You didn't specify HEdyn_key environment variable."
return 1
fi

hostname_encoded="$(printf "%s" "${fulldomain}" | _url_encode)"
password_encoded="$(printf "%s" "${HEdyn_key}" | _url_encode)"
txt_encoded="$(printf "%s" "${txtvalue}" | _url_encode)"
body="hostname=${hostname_encoded}&password=${password_encoded}&txt=${txt_encoded}"
response=$(_post "$body" "https://dyn.dns.he.net/nic/update")
test $response == 'good'
}
40 changes: 40 additions & 0 deletions ansible/roles/acmesh/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
- name: Install acme.sh
become: true
ansible.builtin.shell:
cmd: |
set -o pipefail
curl https://get.acme.sh | sh -s email={{ acmesh_email }}
executable: /bin/bash
creates: /root/.acme.sh/acme.sh

- name: Create certificate directory
become: true
ansible.builtin.file:
path: /etc/nginx/ssl/spyglassmc.com/
state: directory
mode: '755'

- name: Add Hurricane Electric dynamic DNS integration
become: true
ansible.builtin.copy:
src: dns_hedyn.sh
dest: /root/.acme.sh/dnsapi/dns_hedyn.sh
mode: '755'

- name: Issue certificate
become: true
ansible.builtin.shell:
cmd: |
set -e
set -o pipefail
(umask 066; touch /etc/nginx/ssl/spyglassmc.com/key.pem)
(umask 022; touch /etc/nginx/ssl/spyglassmc.com/cert.pem)
export HEdyn_key={{ acmesh_dynamic_dns_key }}
/root/.acme.sh/acme.sh --issue --server letsencrypt \
-d *.spyglassmc.com --dns dns_hedyn
/root/.acme.sh/acme.sh --install-cert -d *.spyglassmc.com \
--key-file /etc/nginx/ssl/spyglassmc.com/key.pem \
--fullchain-file /etc/nginx/ssl/spyglassmc.com/cert.pem \
--reloadcmd "service nginx reload"
executable: /bin/bash
creates: /etc/nginx/ssl/spyglassmc.com/key.pem
2 changes: 2 additions & 0 deletions ansible/roles/api-server/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# (str)
api_server_webhook_secret: null
14 changes: 14 additions & 0 deletions ansible/roles/api-server/files/spyglassmc-api-server.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=SpyglassMC API server service
After=network.target

[Service]
User=api-server
Group=api-server
WorkingDirectory=/var/lib/api-server
ExecStart=/var/lib/api-server/start.sh
Restart=always

[Install]
WantedBy=multi-user.target

4 changes: 4 additions & 0 deletions ansible/roles/api-server/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: Reload systemctl daemon
become: true
ansible.builtin.systemd:
daemon_reload: true
3 changes: 3 additions & 0 deletions ansible/roles/api-server/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies:
- common
- nodejs
54 changes: 54 additions & 0 deletions ansible/roles/api-server/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
- name: Install API server
become: true
community.general.npm:
name: '@spyglassmc/web-api-server'
global: true
state: latest

- name: Create user
become: true
block:
- name: Create group
ansible.builtin.group:
name: api-server
system: true

- name: Create user
ansible.builtin.user:
name: api-server
group: api-server
create_home: true
home: /var/lib/api-server
system: true

- name: Create launcher script
become: true
ansible.builtin.template:
src: start.sh
dest: /var/lib/api-server/start.sh
owner: api-server
group: api-server
mode: '700'

- name: Create service
become: true
ansible.builtin.copy:
src: spyglassmc-api-server.service
dest: /lib/systemd/system/spyglassmc-api-server.service
mode: '644'
notify: Reload systemctl daemon

- name: Start service
become: true
ansible.builtin.service:
name: spyglassmc-api-server
enabled: true
state: started

- name: Create nginx reverse proxy
become: true
ansible.builtin.template:
src: weblate.conf
dest: /etc/nginx/conf.d/api-server.conf
mode: '644'
notify: Reload nginx
21 changes: 21 additions & 0 deletions ansible/roles/api-server/templates/api-server.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
server {
listen 80;
listen [::]:80;

server_name api.spyglassmc.com;

return 308 https://$host$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name api.spyglassmc.com;

location / {
include proxy_params;
proxy_pass http://127.0.0.1:{{ api_server_port }};
}
}

7 changes: 7 additions & 0 deletions ansible/roles/api-server/templates/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

export SPYGLASSMC_API_SERVER_DIR="/var/lib/api-server"
export SPYGLASSMC_API_SERVER_WEBHOOK_SECRET="{{ api_server_webhook_secret }}"
export SPYGLASSMC_API_SERVER_PORT="{{ api_server_port }}"

/usr/local/bin/spyglassmc-web-api-server
1 change: 1 addition & 0 deletions ansible/roles/api-server/vars/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
api_server_port: 3003
5 changes: 5 additions & 0 deletions ansible/roles/common/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- name: Update apt cache
become: true
ansible.builtin.apt:
update_cache: true
cache_valid_time: 86400 # 24 hours
6 changes: 6 additions & 0 deletions ansible/roles/discord-bot/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# (str)
discord_bot_client_id: null
# (str)
discord_bot_guild_id: null
# (str)
discord_bot_token: null
14 changes: 14 additions & 0 deletions ansible/roles/discord-bot/files/spyglassmc-discord-bot.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=SpyglassMC Discord bot service
After=network.target

[Service]
User=discord-bot
Group=discord-bot
WorkingDirectory=/var/lib/discord-bot
Environment="SPYGLASSMC_DISCORD_BOT_DIR=/var/lib/discord-bot"
ExecStart=/usr/local/bin/spyglassmc-discord-bot
Restart=always

[Install]
WantedBy=multi-user.target
4 changes: 4 additions & 0 deletions ansible/roles/discord-bot/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: Reload systemctl daemon
become: true
ansible.builtin.systemd:
daemon_reload: true
3 changes: 3 additions & 0 deletions ansible/roles/discord-bot/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies:
- common
- nodejs
45 changes: 45 additions & 0 deletions ansible/roles/discord-bot/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
- name: Install discord bot
become: true
community.general.npm:
name: '@spyglassmc/discord-bot'
global: true
state: latest

- name: Create user
become: true
block:
- name: Create group
ansible.builtin.group:
name: discord-bot
system: true

- name: Create user
ansible.builtin.user:
name: discord-bot
group: discord-bot
create_home: true
home: /var/lib/discord-bot
system: true

- name: Create config
become: true
become_user: discord-bot
ansible.builtin.template:
src: config.json
dest: /var/lib/discord-bot/config.json
mode: '600'

- name: Create service
become: true
ansible.builtin.copy:
src: spyglassmc-discord-bot.service
dest: /lib/systemd/system/spyglassmc-discord-bot.service
mode: '644'
notify: Reload systemctl daemon

- name: Start service
become: true
ansible.builtin.service:
name: spyglassmc-discord-bot
enabled: true
state: started
6 changes: 6 additions & 0 deletions ansible/roles/discord-bot/templates/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"clientId": "{{ discord_bot_client_id }}",
"guildId": "{{ discord_bot_guild_id }}",
"token": "{{ discord_bot_token }}"
}

2 changes: 2 additions & 0 deletions ansible/roles/docker/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dependencies:
- common
26 changes: 26 additions & 0 deletions ansible/roles/docker/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
- name: Add Docker apt repository
become: true
block:
- name: Add signing key
ansible.builtin.uri:
url: https://download.docker.com/linux/debian/gpg
dest: /etc/apt/keyrings/docker.asc
mode: '644'
status_code:
- 200 # OK
- 304 # Not Modified

- name: Add repository
ansible.builtin.apt_repository:
repo: 'deb [arch=arm64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable'
filename: docker

- name: Install Docker Engine
become: true
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
11 changes: 11 additions & 0 deletions ansible/roles/nginx/files/common.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ssl_certificate /etc/nginx/ssl/spyglassmc.com/cert.pem;
ssl_certificate_key /etc/nginx/ssl/spyglassmc.com/key.pem;

server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;

return 444;
}
Loading
Loading