add ipfs playbook
Some checks failed
ci / test (push) Failing after 6m3s
fp/our CI/CD / build (push) Successful in 1m27s

This commit is contained in:
CJ_Clippy 2025-10-04 08:45:34 -08:00
parent 40ee0589b7
commit 325fe576e2
75 changed files with 2175 additions and 434 deletions

View File

@ -8,5 +8,6 @@
}, },
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"git.ignoreLimitWarning": true "git.ignoreLimitWarning": true,
"ansible.python.interpreterPath": "/home/cj/Documents/futureporn-monorepo/venv/bin/python"
} }

View File

@ -2,25 +2,28 @@
Here we have playbooks to help provision infrastructure to do specific tasks. Here we have playbooks to help provision infrastructure to do specific tasks.
## Inventory ## Getting started
Terraform handles spinning up Vultr instances. See `../terraform` for that. Also, Terraform is where we get our Ansible inventory, so make sure to run `tofu apply` in `../terraform` before working with Ansible. ### Dependencies
## Available plays Install the necessary ansible galaxy roles & collections
ansible-galaxy role install -r requirements.yml
ansible-galaxy collection install -r requirements.yml
Generate an inventory file, via opentofu.
tofu handles spinning up Vultr instances. See `../terraform` for that. Also, Terraform is where we get our Ansible inventory, so make sure to run `tofu apply` in `../terraform` before working with Ansible.
### Run playbook
ansible-playbook ./site.yml
## Available playbooks
### bootstrap.yml ### bootstrap.yml
Prepare the instances for working with Ansible. This sets up SSH and ensures python is installed. Prepare the instances for working with Ansible. This sets up SSH and ensures python is installed.
### k3s-ansible
Provision the instances to act as a k3s cluster.
@see https://github.com/k3s-io/k3s-ansible/tree/master
tl;dr: `ansible-playbook k3s.orchestration.site`
### site.yml
Sets up all the instances to perform bright.futureporn.net workloads. Includes setting up Dokku to work with k3s-scheduler.

View File

@ -0,0 +1 @@
---

View File

@ -0,0 +1,9 @@
---
backblaze_bucket_name: fp-usc
backblaze_allowed_origins:
- https://futureporn.net
backblaze_enable_b2_cli: true
backblaze_enable_cors: false
backblaze_auth_key_id: "{{ lookup('dotenv', 'BACKBLAZE_AUTH_KEY_ID', file='../../../../.env.production') }}"
backblaze_auth_application_key: "{{ lookup('dotenv', 'BACKBLAZE_AUTH_APPLICATION_KEY', file='../../../../.env.production') }}"

View File

@ -0,0 +1,18 @@
---
- name: Download b2-cli
ansible.builtin.get_url:
url: https://github.com/Backblaze/B2_Command_Line_Tool/releases/download/v4.4.2/b2v4-linux
dest: /usr/local/bin/b2
mode: "0755"
- name: Authenticate b2-cli
ansible.builtin.expect:
command: /usr/local/bin/b2 account authorize
responses:
"Backblaze application key ID:":
- "{{ backblaze_auth_key_id }}"
"Backblaze application key:":
- "{{ backblaze_auth_application_key }}"
creates: /home/ipfs/.config/b2/account_info
become: true
become_user: ipfs

View File

@ -0,0 +1,65 @@
---
# - name: Get current CORS rules
# ansible.builtin.command: b2 bucket get "{{ backblaze_bucket_name }}"
# register: b2_get_result
# changed_when: false
# - name: Extract current CORS rules JSON
# ansible.builtin.set_fact:
# current_cors: "{{ (b2_get_result.stdout | from_json).corsRules | default([]) }}"
# - name: Load desired CORS rules
# ansible.builtin.slurp:
# src: "{{ cors_file }}"
# register: desired_cors_raw
# - name: Decode and parse desired CORS rules
# ansible.builtin.set_fact:
# desired_cors: "{{ desired_cors_raw.content | b64decode | from_json }}"
# - name: Compare and set CORS if different
# ansible.builtin.command: >
# b2 bucket update --cors-rules "{{ desired_cors_raw.content | b64decode }}" "{{ bucket_name }}"
# when: current_cors | to_json != desired_cors | to_json
# changed_when: true
# #
- name: Render cors-rules.json from template
ansible.builtin.template:
mode: "0755"
src: cors-rules.json.j2
dest: /tmp/cors-rules.json
- name: Get current CORS rules
ansible.builtin.command: b2 bucket get "{{ backblaze_bucket_name }}"
register: b2_get_result
changed_when: false
- name: Extract current CORS rules JSON
ansible.builtin.set_fact:
current_cors: "{{ (b2_get_result.stdout | from_json).corsRules | default([]) }}"
- name: Load desired CORS rules
ansible.builtin.slurp:
src: /tmp/cors-rules.json
register: desired_cors_raw
- name: Decode and parse desired CORS rules
ansible.builtin.set_fact:
desired_cors: "{{ desired_cors_raw.content | b64decode | from_json }}"
- name: Debug desired_cors
ansible.builtin.debug:
var: desired_cors
- name: Debug current_cors
ansible.builtin.debug:
var: current_cors
- name: Compare and set CORS if different
ansible.builtin.command: >
b2 bucket update --cors-rules "{{ desired_cors_raw.content | b64decode }}" "{{ backblaze_bucket_name }}"
when: current_cors | to_json != desired_cors | to_json
changed_when: true

View File

@ -0,0 +1,11 @@
---
- name: Configure b2-cli
ansible.builtin.include_tasks:
file: b2-cli.yml
when: backblaze_enable_b2_cli
- name: Configure CORS
ansible.builtin.include_tasks:
file: cors.yml
when: backblaze_enable_cors

View File

@ -0,0 +1,14 @@
[
{
"allowedHeaders": ["*"],
"allowedOperations": ["s3_head", "s3_put", "s3_get"],
"allowedOrigins": [
{% for origin in backblaze_allowed_origins %}
"{{ origin }}"{% if not loop.last %},{% endif %}
{% endfor %}
],
"corsRuleName": "allowUploads",
"exposeHeaders": ["etag"],
"maxAgeSeconds": 3600
}
]

View File

@ -1,7 +1,7 @@
--- ---
## @warning Do not edit this file! It's probably right, and you're probably doing something wrong! ## @warning Do not edit this file! It's probably right, and you're probably doing something wrong!
## If you're seeing interactive host auth checks prompts while running ansible-playbook, ## If you're seeing interactive host auth checks prompts while running ansible-playbook,
## you're probably skipping over this role! Don't skip it! ## you're probably skipping over this role! Don't skip it!
## @see https://gist.github.com/shirou/6928012 ## @see https://gist.github.com/shirou/6928012
@ -9,21 +9,22 @@
- name: Scan for SSH host keys. - name: Scan for SSH host keys.
delegate_to: localhost delegate_to: localhost
ansible.builtin.shell: ansible.builtin.shell:
cmd: ssh-keyscan -q -p 22 {{ ansible_host }} 2>/dev/null cmd: ssh-keyscan -p 22 {{ ansible_host }} 2>/dev/null
changed_when: false changed_when: false
register: ssh_scan register: bootstrap_ssh_scan
retries: 2 # it always fails the first time retries: 2 # it always fails the first time
until: ssh_scan.rc == 0 until: bootstrap_ssh_scan.rc == 0
- debug: - name: Debug bootstrap_ssh_scan variable
var: ssh_scan ansible.builtin.debug:
var: bootstrap_ssh_scan
- name: Update known_hosts. - name: Update known_hosts.
ansible.builtin.known_hosts: ansible.builtin.known_hosts:
key: "{{ item }}" key: "{{ item }}"
name: "{{ ansible_host }}" name: "{{ ansible_host }}"
with_items: "{{ ssh_scan.stdout_lines }}" with_items: "{{ bootstrap_ssh_scan.stdout_lines }}"
delegate_to: localhost delegate_to: localhost
- name: Install python3 - name: Install python3

View File

@ -11,7 +11,7 @@
- ncdu - ncdu
- pipx - pipx
- fd-find - fd-find
update_cache: yes update_cache: true
state: present state: present
- name: Download Docker installer - name: Download Docker installer

View File

@ -0,0 +1,3 @@
---
coolify_dir: /opt/coolify

View File

@ -0,0 +1,25 @@
---
- name: Install Coolify using official installer
ansible.builtin.shell: |
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash
args:
creates: /coolify/docker-compose.yml # adjust if needed to prevent reruns
# @note securely connect to coolify webui using SSH tunneling.
# ssh -L 8000:localhost:8000 root@our
# @see https://coolify.io/docs/knowledge-base/server/firewall
- name: Allow UFW ports
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 8000 # coolify UI
- 6001 # real-time comms
- 6002 # terminal
- 80
- 443

View File

@ -0,0 +1,52 @@
---
- name: Ensure prerequisites are installed
become: true
ansible.builtin.apt:
name:
- ca-certificates
- curl
state: present
update_cache: true
- name: Ensure /etc/apt/keyrings directory exists
become: true
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: "0755"
- name: Download Docker GPG key
become: true
ansible.builtin.get_url:
url: https://download.docker.com/linux/ubuntu/gpg
dest: /etc/apt/keyrings/docker.asc
mode: "0644"
- name: Add Docker APT repository
become: true
ansible.builtin.apt_repository:
repo: "deb [arch={{ ansible_architecture }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} stable"
state: present
filename: docker
update_cache: true
- name: Install docker
become: true
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: true
- name: Install docker ansible module dependencies
ansible.builtin.pip:
name: "{{ item }}"
state: present
loop:
- jsondiff
- pyyaml
- docker

View File

@ -0,0 +1,5 @@
---
- name: Install docker
ansible.builtin.include_tasks:
file: docker.yml

View File

@ -0,0 +1,3 @@
---
infisical_caddy_image: caddy:2
infisical_docker_tag: latest-postgres

View File

@ -0,0 +1,13 @@
---
- name: Restart caddy
community.docker.docker_container:
name: infisical-caddy-1
image: "{{ infisical_caddy_image }}"
state: started
restart: true
- name: Restart infisical
community.docker.docker_compose_v2:
project_src: /opt/infisical
state: restarted

View File

@ -0,0 +1,45 @@
---
- name: Ensure infisical directory exists
ansible.builtin.file:
path: /opt/infisical
state: directory
mode: "0755"
- name: Generate .env file
ansible.builtin.template:
src: env.j2
dest: /opt/infisical/.env
mode: "0600"
- name: Install passlib
ansible.builtin.pip:
name: passlib # dependency of Ansible's passwordhash
state: present
- name: Template Caddyfile
ansible.builtin.template:
src: Caddyfile.j2
dest: /opt/infisical/Caddyfile
mode: "0600"
notify:
- Restart caddy
- name: Template Docker Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: /opt/infisical/docker-compose.yml
mode: "0644"
- name: Start up docker-compose.yml
community.docker.docker_compose_v2:
project_src: /opt/infisical
state: present
- name: Configure firewall
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 443

View File

@ -0,0 +1,13 @@
infisical.futureporn.net {
# basic_auth {
# {{ lookup('dotenv', 'INFISICAL_BASIC_AUTH_USERNAME', file='../../../../.env')}} {{ lookup('dotenv', 'INFISICAL_BASIC_AUTH_PASSWORD', file='../../../../.env') | password_hash('bcrypt') }}
# }
reverse_proxy infisical-backend:8080 {
health_uri /
health_interval 10s
health_timeout 5s
}
}

View File

@ -0,0 +1,86 @@
x-logging: &default-logging
driver: "json-file"
options:
max-size: "${LOG_MAX_SIZE:-20m}"
max-file: "${LOG_MAX_FILE:-10}"
compress: "true"
services:
caddy:
image: {{ infisical_caddy_image }}
restart: unless-stopped
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
ports:
- 443:443
environment:
- BASE_URL=infisical.futureporn.net
logging: *default-logging
networks:
- infisical
backend:
container_name: infisical-backend
image: infisical/infisical:{{ infisical_docker_tag }}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db-migration:
condition: service_completed_successfully
pull_policy: always
env_file: .env
environment:
- NODE_ENV=production
ports:
- 80:8080
networks:
- infisical
redis:
container_name: infisical-redis
image: redis
restart: unless-stopped
env_file: .env
environment:
- ALLOW_EMPTY_PASSWORD=yes
volumes:
- ./volumes/redis:/data
networks:
- infisical
db:
container_name: infisical-db
image: postgres:14-alpine
restart: unless-stopped
env_file: .env
volumes:
- ./volumes/postgres:/var/lib/postgresql/data
networks:
- infisical
healthcheck:
test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
interval: 5s
timeout: 10s
retries: 10
db-migration:
container_name: infisical-db-migration
depends_on:
db:
condition: service_healthy
image: infisical/infisical:{{ infisical_docker_tag }}
env_file: .env
command: npm run migration:latest
pull_policy: always
networks:
- infisical
networks:
infisical:
volumes:
caddy_data: null

View File

@ -0,0 +1,23 @@
# Website URL
SITE_URL=https://infisical.futureporn.net
# Keys
# Required key for platform encryption/decryption ops
# GENERATE YOUR OWN KEY WITH `openssl rand -hex 16`
ENCRYPTION_KEY={{ lookup('dotenv', 'INFISICAL_ENCRYPTION_KEY', file='../../../../.env') }}
# JWT
# Required secrets to sign JWT tokens
# GENERATE YOUR OWN KEY WITH `openssl rand -base64 32`
AUTH_SECRET={{ lookup('dotenv', 'INFISICAL_AUTH_SECRET', file='../../../../.env') }}
# Postgres
POSTGRES_PASSWORD={{ lookup('dotenv', 'INFISICAL_POSTGRES_PASSWORD', file='../../../../.env') }}
POSTGRES_USER=infisical
POSTGRES_DB=infisical
# Do not change the next line
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
# Redis
# Do not change the next line
REDIS_URL=redis://redis:6379

View File

@ -1,3 +1,11 @@
--- ---
ipfs_kubo_version: v0.34.1 ipfs_kubo_version: v0.38.0
ipfs_cluster_follow_version: v1.1.2 ipfs_migrations_version: v2.0.2
ipfs_cluster_follow_version: v1.1.4
ipfs_cluster_service_version: v1.1.4
ipfs_storage_max: 5TB
ipfs_path: /mnt/blockstorage/ipfs
ipfs_enable_blockstorage: false
ipfs_enable_kubo: true
ipfs_enable_ipfs_cluster_service: false
ipfs_enable_ipfs_cluster_follow: false

View File

@ -0,0 +1,72 @@
#!/bin/bash
set -euo pipefail
############################################################
# Script: pinall.sh
#
# Description:
# This script reads a list of S3 object keys (filenames)
# from a file, downloads each file from Backblaze B2,
# adds it to a local IPFS node, and optionally cleans up
# the temporary downloaded file to save disk space.
#
# Usage:
# ./pinall.sh <file-with-s3-keys>
# Example:
# # sudo -u ipfs env IPFS_PATH=/mnt/blockstorage/ipfs pinall.sh /home/ipfs/filenames.txt
#
# - files.txt should contain one S3 key per line.
# - Lines starting with '#' or empty lines are ignored.
#
# Environment:
# - Requires `b2` CLI configured with B2 credentials.
# - Requires an IPFS node installed and accessible at
# $IPFS_PATH (set in script).
#
# Behavior:
# 1. Reads each key from the input file.
# 2. Downloads the file from B2 to /tmp/<key>.
# 3. Adds the downloaded file to IPFS (CID version 1).
# 4. Deletes the temporary file after adding to IPFS.
# 5. Logs progress with timestamps to stdout.
#
# Exit Codes:
# - 0: All files processed successfully.
# - 1: Incorrect usage or missing input file.
#
############################################################
# Usage check
if [ $# -ne 1 ]; then
echo "Usage: $0 <file-with-s3-keys>"
exit 1
fi
ipfs id
echo "Using IPFS_PATH=$IPFS_PATH"
FILELIST=$1
while IFS= read -r KEY; do
[[ -z "$KEY" || "$KEY" =~ ^# ]] && continue
echo "[$(date +"%Y-%m-%d %H:%M:%S")] Downloading $KEY from B2..."
TMPFILE="/tmp/$KEY"
if b2 file download "b2://futureporn/$KEY" "$TMPFILE"; then
echo "[$(date +"%Y-%m-%d %H:%M:%S")] Download complete: $KEY"
else
echo "[$(date +"%Y-%m-%d %H:%M:%S")] Download failed: $KEY"
rm -f "$TMPFILE"
continue
fi
echo "[$(date +"%Y-%m-%d %H:%M:%S")] Adding $KEY to IPFS..."
ipfs add --cid-version=1 "$TMPFILE"
# optional cleanup to save space
rm -f "$TMPFILE"
done < "$FILELIST"
echo "[$(date +"%Y-%m-%d %H:%M:%S")] All tasks complete."

View File

@ -14,3 +14,17 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
- name: Migrate ipfs
ansible.builtin.shell:
creates: "{{ ipfs_path }}/version"
cmd: "env IPFS_PATH={{ ipfs_path }} /usr/local/bin/fs-repo-migrations" # noqa command-instead-of-shell
become: true
become_user: ipfs
- name: Initialize ipfs
ansible.builtin.shell:
creates: "{{ ipfs_path }}/datastore_spec"
cmd: "env IPFS_PATH={{ ipfs_path }} /usr/local/bin/ipfs init --profile pebbleds" # noqa command-instead-of-shell
become: true
become_user: ipfs

View File

@ -0,0 +1,28 @@
---
- name: Create gpt table on /dev/vdb
community.general.parted:
device: /dev/vdb
number: 1
part_type: primary
label: gpt
fs_type: ext4
state: present
- name: Create an ext4 filesystem on /dev/vdb1
community.general.filesystem:
fstype: ext4
dev: /dev/vdb1
- name: Get UUID of /dev/vdb1
ansible.builtin.command:
cmd: blkid -s UUID -o value /dev/vdb1
register: ipfs_vdb1_uuid
changed_when: false
- name: Mount /dev/vdb1 to /mnt/blockstorage
ansible.posix.mount:
path: /mnt/blockstorage
src: /dev/disk/by-uuid/{{ ipfs_vdb1_uuid.stdout }}
fstype: ext4
opts: defaults,noatime,nofail
state: mounted

View File

@ -0,0 +1,56 @@
---
- name: Load IPFS config from remote
ansible.builtin.slurp:
src: "{{ ipfs_path }}/config"
register: ipfs_config_raw
- name: Parse IPFS config JSON
ansible.builtin.set_fact:
ipfs_config: "{{ ipfs_config_raw.content | b64decode | from_json }}"
# - name: Debug IPFS config
# ansible.builtin.debug:
# var: ipfs_config
- name: Show storagemax variable
ansible.builtin.debug:
msg: " here is what we ahvea configured for ipfs_storage_max:{{ ipfs_storage_max }}"
- name: Show storagemax before
ansible.builtin.debug:
msg: "here is the storagemax before: {{ ipfs_config.Datastore.StorageMax }}"
# noqa command-instead-of-shell
- name: Configure IPFS Provide.DHT.SweepEnabled
ansible.builtin.shell:
cmd: "env IPFS_PATH={{ ipfs_path }} /usr/local/bin/ipfs config --json Provide.DHT.SweepEnabled true"
when: ipfs_config.Provide.DHT.SweepEnabled is not defined or not true
changed_when: true # explicitly mark it as a change when it runs
notify:
- Restart ipfs
become: true
become_user: ipfs
# noqa command-instead-of-shell
- name: Configure IPFS Datastore.StorageMax
ansible.builtin.shell:
cmd: "env IPFS_PATH={{ ipfs_path }} /usr/local/bin/ipfs config Datastore.StorageMax {{ ipfs_storage_max }}"
when: ipfs_config.Datastore.StorageMax is not defined or ipfs_config.Datastore.StorageMax != ipfs_storage_max
changed_when: true
notify:
- Restart ipfs
become: true
become_user: ipfs
- name: Load IPFS config from remote
ansible.builtin.slurp:
src: "{{ ipfs_path }}/config"
register: ipfs_config_raw_after
- name: Parse IPFS config JSON after making changes
ansible.builtin.set_fact:
ipfs_config_after: "{{ ipfs_config_raw_after.content | b64decode | from_json }}"
- name: Blah
ansible.builtin.debug:
msg: "here is the storagemax after: {{ ipfs_config_after.Datastore.StorageMax }}"

View File

@ -0,0 +1,13 @@
---
- name: Download and extract IPFS fs-repo-migrations
ansible.builtin.unarchive:
src: "https://dist.ipfs.tech/fs-repo-migrations/{{ ipfs_migrations_version }}/fs-repo-migrations_{{ ipfs_migrations_version }}_linux-amd64.tar.gz"
dest: /tmp
remote_src: true
- name: Install IPFS fs-repo-migrations
ansible.builtin.copy:
src: /tmp/fs-repo-migrations/fs-repo-migrations
dest: /usr/local/bin/fs-repo-migrations
mode: "0755"
remote_src: true

View File

@ -0,0 +1,7 @@
---
- name: Create pinall.sh
ansible.builtin.copy:
src: pinall.sh
dest: /usr/local/bin/pinall.sh
remote_src: false
mode: "0755"

View File

@ -0,0 +1,29 @@
---
- name: Download and extract ipfs-cluster-follow
ansible.builtin.unarchive:
src: "https://dist.ipfs.tech/ipfs-cluster-follow/{{ ipfs_cluster_follow_version }}/ipfs-cluster-follow_{{ ipfs_cluster_follow_version }}_linux-amd64.tar.gz"
dest: /tmp
remote_src: true
notify:
- Restart ipfs-cluster-follow
- name: Install ipfs-cluster-follow
ansible.builtin.copy:
src: /tmp/ipfs-cluster-follow/ipfs-cluster-follow
dest: /usr/local/bin/ipfs-cluster-follow
mode: "0755"
remote_src: true
notify:
- Restart ipfs-cluster-follow
- name: Generate random peername
ansible.builtin.set_fact:
ipfs_cluster_peername: "{{ lookup('password', '/dev/null length=8 chars=hexdigits') }}"
- name: Create ipfs-cluster-follow service
ansible.builtin.template:
src: ipfs-cluster-follow.service.j2
dest: /etc/systemd/system/ipfs-cluster-follow.service
mode: "0644"
notify:
- Restart ipfs-cluster-follow

View File

@ -0,0 +1,25 @@
---
- name: Download and extract ipfs-cluster-service
ansible.builtin.unarchive:
src: "https://dist.ipfs.tech/ipfs-cluster-service/{{ ipfs_cluster_service_version }}/ipfs-cluster-service_{{ ipfs_cluster_service_version }}_linux-amd64.tar.gz"
dest: /tmp
remote_src: true
notify:
- Restart ipfs-cluster-service
- name: Install ipfs-cluster-service
ansible.builtin.copy:
src: /tmp/ipfs-cluster-service/ipfs-cluster-service
dest: /usr/local/bin/ipfs-cluster-service
mode: "0755"
remote_src: true
notify:
- Restart ipfs-cluster-service
- name: Create ipfs-cluster-service service
ansible.builtin.template:
src: ipfs-cluster-service.service.j2
dest: /etc/systemd/system/ipfs-cluster-service.service
mode: "0644"
notify:
- Restart ipfs-cluster-service

View File

@ -0,0 +1,45 @@
---
- name: Download and extract IPFS Kubo
ansible.builtin.unarchive:
src: "https://dist.ipfs.tech/kubo/{{ ipfs_kubo_version }}/kubo_{{ ipfs_kubo_version }}_linux-amd64.tar.gz"
dest: /tmp
remote_src: true
notify:
- Initialize ipfs
- Restart ipfs
- name: Install IPFS Kubo
ansible.builtin.copy:
src: /tmp/kubo/ipfs
dest: /usr/local/bin/ipfs
mode: "0755"
remote_src: true
notify:
- Initialize ipfs
- Restart ipfs
- name: Create IPFS directory
ansible.builtin.file:
dest: "{{ ipfs_path }}"
owner: ipfs
group: ipfs
state: directory
mode: "0755"
notify:
- Initialize ipfs
- Configure ipfs
- Restart ipfs
- name: Create IPFS service
ansible.builtin.template:
src: ipfs.service.j2
dest: /etc/systemd/system/ipfs.service
mode: "0644"
notify:
- Initialize ipfs
- Configure ipfs
- Restart ipfs
- name: Install fs-repo-migrations
ansible.builtin.include_tasks:
file: fs-repo-migrations.yml

View File

@ -1,175 +1,34 @@
--- ---
- name: Configure firewall (UDP & TCP)
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: any
loop:
- 4001
- 24007
- 24008
- name: Configure firewall (TCP) - name: Setup Firewall and system misc
community.general.ufw: ansible.builtin.include_tasks:
rule: allow file: system.yml
port: "{{ item }}"
proto: tcp
loop:
- 29152:65535
- name: Configure Blockstorage
ansible.builtin.include_tasks:
file: blockstorage.yml
when: ipfs_enable_blockstorage
- name: Install glusterfs - name: Configure kubo
ansible.builtin.apt: ansible.builtin.include_tasks:
name: file: kubo.yml
- glusterfs-server when: ipfs_enable_kubo
state: present
- name: Start & enable glusterd service - name: Configure ipfs-cluster-follow
ansible.builtin.systemd_service: ansible.builtin.include_tasks:
name: glusterd.service file: ipfs-cluster-follow.yml
state: started when: ipfs_enable_ipfs_cluster_follow
enabled: true
# - name: Create gluster volume - name: Configure ipfs-cluster-service
# gluster.gluster.gluster_volume: ansible.builtin.include_tasks:
# state: present file: ipfs-cluster-service.yml
# name: ipfs-datastore when: ipfs_enable_ipfs_cluster_service
# bricks: /bricks/brick1/g1
# rebalance: true
# cluster: "{{ groups['ipfs'] }}"
# run_once: true
- name: Configure helpers
ansible.builtin.include_tasks:
file: helpers.yml
# - name: Start gluster volume # This should always be last
# gluster.gluster.gluster_volume: - name: Configure ipfs
# state: started ansible.builtin.include_tasks:
# name: ipfs-datastore file: config.yml
# - name: Limit volume usage
# gluster.gluster.gluster_volume:
# state: present
# name: ipfs-datastore
# directory: /
# quota: 6.0TB
## Example: mount -t glusterfs fp-bright-0:/gv0 /mountme
# - name: Mount gluster volume
# ansible.posix.mount:
# src: "{{ ansible_hostname }}:/g1"
# path: /mnt/g1
# fstype: glusterfs
# state: mounted
- name: Create ipfs group
ansible.builtin.group:
name: ipfs
state: present
- name: Create ipfs user
ansible.builtin.user:
name: ipfs
group: ipfs
create_home: true
home: /home/ipfs
system: true
- name: Download and extract IPFS Kubo
ansible.builtin.unarchive:
src: "https://dist.ipfs.tech/kubo/{{ ipfs_kubo_version }}/kubo_{{ ipfs_kubo_version }}_linux-amd64.tar.gz"
dest: /tmp
remote_src: true
notify:
- Restart ipfs
- name: Install IPFS Kubo
ansible.builtin.copy:
src: /tmp/kubo/ipfs
dest: /usr/local/bin/ipfs
mode: "0755"
remote_src: true
notify:
- Restart ipfs
- name: Download and extract ipfs-cluster-follow
ansible.builtin.unarchive:
src: "https://dist.ipfs.tech/ipfs-cluster-follow/{{ ipfs_cluster_follow_version }}/ipfs-cluster-follow_{{ ipfs_cluster_follow_version }}_linux-amd64.tar.gz"
dest: /tmp
remote_src: true
notify:
- Restart ipfs-cluster-follow
- name: Install ipfs-cluster-follow
ansible.builtin.copy:
src: /tmp/ipfs-cluster-follow/ipfs-cluster-follow
dest: /usr/local/bin/ipfs-cluster-follow
mode: "0755"
remote_src: true
notify:
- Restart ipfs-cluster-follow
- name: Generate random peername
ansible.builtin.set_fact:
cluster_peername: "{{ lookup('password', '/dev/null length=8 chars=hexdigits') }}"
- name: Create ipfs-cluster-follow service
ansible.builtin.template:
src: ipfs-cluster-follow.service.j2
dest: /etc/systemd/system/ipfs-cluster-follow.service
mode: "0644"
notify:
- Restart ipfs-cluster-follow
- name: Create ipfs service
ansible.builtin.template:
src: ipfs.service.j2
dest: /etc/systemd/system/ipfs.service
mode: "0644"
notify:
- Restart ipfs
- name: Check current value of Routing.AcceleratedDHTClient
ansible.builtin.command: ipfs config Routing.AcceleratedDHTClient
register: ipfs_dht_config
changed_when: false # this never changes things, it only gathers data
- name: Enable IPFS Routing.AcceleratedDHTClient
ansible.builtin.command: ipfs config --json Routing.AcceleratedDHTClient true
notify:
- Restart ipfs
when: ipfs_dht_config.stdout != "true"
changed_when: true
- name: Create IPFS directory
ansible.builtin.file:
dest: /home/ipfs/.ipfs
owner: ipfs
group: ipfs
state: directory
mode: "0755"
- name: Check if IPFS config exists
ansible.builtin.stat:
path: /home/ipfs/.ipfs/config
register: ipfs_config
- name: Initialize IPFS
ansible.builtin.command: /usr/local/bin/ipfs init
become: true
become_user: ipfs
args:
chdir: /home/ipfs
when: not ipfs_config.stat.exists
changed_when: true # Explicitly mark this as a change when it runs
notify:
- Restart ipfs
## @todo enable once we get gluster working
# - name: Symlink IPFS blocks directory to gluster brick
# ansible.builtin.file:
# src: /home/ipfs/.ipfs/blocks
# dest: /mnt/g1/.ipfs/blocks
# owner: ipfs
# group: ipfs
# state: link
# notify:
# - Restart ipfs

View File

@ -0,0 +1,44 @@
---
- name: Configure firewall (UDP & TCP)
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: any
loop:
- 4001
- 24007
- 24008
- name: Configure firewall (TCP)
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 29152:65535
- name: Create ipfs group
ansible.builtin.group:
name: ipfs
state: present
- name: Create ipfs user
ansible.builtin.user:
name: ipfs
group: ipfs
create_home: true
home: /home/ipfs
system: true
# @see https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes
- name: Set sysctl values for net.core.rmem_max and wmem_max
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: true
loop:
- { name: "net.core.rmem_max", value: "7500000" }
- { name: "net.core.wmem_max", value: "7500000" }
notify:
- Restart ipfs

View File

@ -3,7 +3,7 @@ Description=ipfs-cluster-follow
[Service] [Service]
Type=simple Type=simple
Environment=CLUSTER_PEERNAME="{{cluster_peername}}" Environment=CLUSTER_PEERNAME="{{ ipfs_cluster_peername }}"
ExecStart=/usr/local/bin/ipfs-cluster-follow futureporn.net run --init https://futureporn.net/api/service.json ExecStart=/usr/local/bin/ipfs-cluster-follow futureporn.net run --init https://futureporn.net/api/service.json
User=ipfs User=ipfs
Restart=always Restart=always

View File

@ -0,0 +1,12 @@
[Unit]
Description=IPFS Repo Migrations
[Service]
Type=oneshot
Environment=IPFS_PATH={{ ipfs_path }}
ExecStart=/usr/local/bin/fs-repo-migrations -y
User=ipfs
[Install]
WantedBy=multi-user.target

View File

@ -3,7 +3,8 @@ Description=IPFS Daemon
[Service] [Service]
Type=simple Type=simple
Environment=IPFS_PATH=/home/ipfs/.ipfs Environment=IPFS_PATH={{ ipfs_path }}
Environment=IPFS_TELEMETRY=off
ExecStart=/usr/local/bin/ipfs daemon ExecStart=/usr/local/bin/ipfs daemon
User=ipfs User=ipfs
Restart=always Restart=always
@ -11,4 +12,3 @@ RestartSec=10
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

View File

@ -0,0 +1,29 @@
---
- name: Ensure komodo directory exists
ansible.builtin.file:
path: /opt/komodo
state: directory
mode: "0755"
- name: Get docker compose file
ansible.builtin.get_url:
url: komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/ferretdb.compose.yaml
dest: /opt/komodo
mode: "0755"
- name: Get .env file
ansible.builtin.get_url:
url: https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env
dest: /opt/komodo
mode: "0755"
# we need to use lineinfile to set the following
- name: set config
ansible.builtin.lineinfile:
- name: Run Komodo core

View File

@ -0,0 +1,119 @@
# KOMODO_DISABLE_USER_REGISTRATION=true
# KOMODO_ENABLE_NEW_USERS=false
# KOMODO_DISABLE_NON_ADMIN_CREATE=true
# KOMODO_HOST=https://komodo.future.porn
# KOMODO_DB_USERNAME=admin
# KOMODO_DB_PASSWORD=admin
# KOMODO_PASSKEY=a_random_passkey
####################################
# 🦎 KOMODO COMPOSE - VARIABLES 🦎 #
####################################
## These compose variables can be used with all Komodo deployment options.
## Pass these variables to the compose up command using `--env-file komodo/compose.env`.
## Additionally, they are passed to both Komodo Core and Komodo Periphery with `env_file: ./compose.env`,
## so you can pass any additional environment variables to Core / Periphery directly in this file as well.
## Stick to a specific version, or use `latest`
COMPOSE_KOMODO_IMAGE_TAG=latest
## DB credentials
KOMODO_DB_USERNAME=admin
KOMODO_DB_PASSWORD=admin
## Configure a secure passkey to authenticate between Core / Periphery.
KOMODO_PASSKEY=a_random_passkey
## Set your time zone for schedules
## https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TZ=Etc/UTC
#=-------------------------=#
#= Komodo Core Environment =#
#=-------------------------=#
## Full variable list + descriptions are available here:
## 🦎 https://github.com/moghtech/komodo/blob/main/config/core.config.toml 🦎
## Note. Secret variables also support `${VARIABLE}_FILE` syntax to pass docker compose secrets.
## Docs: https://docs.docker.com/compose/how-tos/use-secrets/#examples
## Used for Oauth / Webhook url suggestion / Caddy reverse proxy.
KOMODO_HOST="{{ komodo_host }}"
## Displayed in the browser tab.
KOMODO_TITLE=fp Komodo
## Create a server matching this address as the "first server".
## Use `https://host.docker.internal:8120` when using systemd-managed Periphery.
KOMODO_FIRST_SERVER=https://periphery:8120
## Make all buttons just double-click, rather than the full confirmation dialog.
KOMODO_DISABLE_CONFIRM_DIALOG=false
## Rate Komodo polls your servers for
## status / container status / system stats / alerting.
## Options: 1-sec, 5-sec, 15-sec, 1-min, 5-min, 15-min
## Default: 15-sec
KOMODO_MONITORING_INTERVAL="15-sec"
## Interval at which to poll Resources for any updates / automated actions.
## Options: 15-min, 1-hr, 2-hr, 6-hr, 12-hr, 1-day
## Default: 1-hr
KOMODO_RESOURCE_POLL_INTERVAL="1-hr"
## Used to auth incoming webhooks. Alt: KOMODO_WEBHOOK_SECRET_FILE
KOMODO_WEBHOOK_SECRET=a_random_secret
## Used to generate jwt. Alt: KOMODO_JWT_SECRET_FILE
KOMODO_JWT_SECRET=a_random_jwt_secret
## Time to live for jwt tokens.
## Options: 1-hr, 12-hr, 1-day, 3-day, 1-wk, 2-wk
KOMODO_JWT_TTL="1-day"
## Enable login with username + password.
KOMODO_LOCAL_AUTH=true
## Disable new user signups.
KOMODO_DISABLE_USER_REGISTRATION=false
## All new logins are auto enabled
KOMODO_ENABLE_NEW_USERS=false
## Disable non-admins from creating new resources.
KOMODO_DISABLE_NON_ADMIN_CREATE=false
## Allows all users to have Read level access to all resources.
KOMODO_TRANSPARENT_MODE=false
## Prettier logging with empty lines between logs
KOMODO_LOGGING_PRETTY=false
## More human readable logging of startup config (multi-line)
KOMODO_PRETTY_STARTUP_CONFIG=false
## OIDC Login
KOMODO_OIDC_ENABLED=false
## Must reachable from Komodo Core container
# KOMODO_OIDC_PROVIDER=https://oidc.provider.internal/application/o/komodo
## Change the host to one reachable be reachable by users (optional if it is the same as above).
## DO NOT include the `path` part of the URL.
# KOMODO_OIDC_REDIRECT_HOST=https://oidc.provider.external
## Your OIDC client id
# KOMODO_OIDC_CLIENT_ID= # Alt: KOMODO_OIDC_CLIENT_ID_FILE
## Your OIDC client secret.
## If your provider supports PKCE flow, this can be ommitted.
# KOMODO_OIDC_CLIENT_SECRET= # Alt: KOMODO_OIDC_CLIENT_SECRET_FILE
## Make usernames the full email.
## Note. This does not work for all OIDC providers.
# KOMODO_OIDC_USE_FULL_EMAIL=true
## Add additional trusted audiences for token claims verification.
## Supports comma separated list, and passing with _FILE (for compose secrets).
# KOMODO_OIDC_ADDITIONAL_AUDIENCES=abc,123 # Alt: KOMODO_OIDC_ADDITIONAL_AUDIENCES_FILE
## Github Oauth
KOMODO_GITHUB_OAUTH_ENABLED=false
# KOMODO_GITHUB_OAUTH_ID= # Alt: KOMODO_GITHUB_OAUTH_ID_FILE
# KOMODO_GITHUB_OAUTH_SECRET= # Alt: KOMODO_GITHUB_OAUTH_SECRET_FILE
## Google Oauth
KOMODO_GOOGLE_OAUTH_ENABLED=false
# KOMODO_GOOGLE_OAUTH_ID= # Alt: KOMODO_GOOGLE_OAUTH_ID_FILE
# KOMODO_GOOGLE_OAUTH_SECRET= # Alt: KOMODO_GOOGLE_OAUTH_SECRET_FILE
## Aws - Used to launch Builder instances.
KOMODO_AWS_ACCESS_KEY_ID= # Alt: KOMODO_AWS_ACCESS_KEY_ID_FILE
KOMODO_AWS_SECRET_ACCESS_KEY= # Alt: KOMODO_AWS_SECRET_ACCESS_KEY_FILE

View File

@ -0,0 +1,95 @@
###################################
# 🦎 KOMODO COMPOSE - FERRETDB 🦎 #
###################################
## This compose file will deploy:
## 1. Postgres + FerretDB Mongo adapter (https://www.ferretdb.com)
## 2. Komodo Core
## 3. Komodo Periphery
services:
postgres:
# Recommended: Pin to a specific version
# https://github.com/FerretDB/documentdb/pkgs/container/postgres-documentdb
image: ghcr.io/ferretdb/postgres-documentdb
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
# ports:
# - 5432:5432
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${KOMODO_DB_USERNAME}
POSTGRES_PASSWORD: ${KOMODO_DB_PASSWORD}
POSTGRES_DB: postgres
ferretdb:
# Recommended: Pin to a specific version
# https://github.com/FerretDB/FerretDB/pkgs/container/ferretdb
image: ghcr.io/ferretdb/ferretdb
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
depends_on:
- postgres
# ports:
# - 27017:27017
volumes:
- ferretdb-state:/state
environment:
FERRETDB_POSTGRESQL_URL: postgres://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@postgres:5432/postgres
core:
image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
depends_on:
- ferretdb
ports:
- 9120:9120
env_file: ./compose.env
environment:
KOMODO_DATABASE_ADDRESS: ferretdb:27017
KOMODO_DATABASE_USERNAME: ${KOMODO_DB_USERNAME}
KOMODO_DATABASE_PASSWORD: ${KOMODO_DB_PASSWORD}
volumes:
## Core cache for repos for latest commit hash / contents
- repo-cache:/repo-cache
## Store sync files on server
# - /path/to/syncs:/syncs
## Optionally mount a custom core.config.toml
# - /path/to/core.config.toml:/config/config.toml
## Allows for systemd Periphery connection at
## "http://host.docker.internal:8120"
# extra_hosts:
# - host.docker.internal:host-gateway
## Deploy Periphery container using this block,
## or deploy the Periphery binary with systemd using
## https://github.com/moghtech/komodo/tree/main/scripts
periphery:
image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
env_file: ./compose.env
volumes:
## Mount external docker socket
- /var/run/docker.sock:/var/run/docker.sock
## Allow Periphery to see processes outside of container
- /proc:/proc
## Specify the Periphery agent root directory.
## Must be the same inside and outside the container,
## or docker will get confused. See https://github.com/moghtech/komodo/discussions/180.
## Default: /etc/komodo.
- ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}
volumes:
# Postgres
postgres-data:
# FerretDB
ferretdb-state:
# Core
repo-cache:

View File

@ -1,23 +0,0 @@
future.porn {
root * /usr/share/futureporn
file_server
# Define the upstream servers for load balancing
reverse_proxy {% for host in groups['our'] %}{{ hostvars[host]['internal_ip'] }}:{{our_server_port}} {% endfor %} {
# Load balancing policy (optional, defaults to "random")
lb_policy round_robin
# Health checks (optional)
health_uri /health
health_interval 10s
health_timeout 5s
}
handle_errors {
respond "💥 Error ~ {err.status_code} {err.status_text}"
}
}

View File

@ -0,0 +1,21 @@
{% set sites = ['future.porn', 'pgadmin.sbtp.xyz', 'rssapp.sbtp.xyz'] %}
{% for site in sites %}
{{ site }} {
# Define the upstream servers (docker swarm nodes) for load balancing
reverse_proxy {% for host in groups['our'] %}{{ hostvars[host]['internal_ip'] }}:{{ our_server_port }} {% endfor %} {
# Load balancing policy (optional, defaults to "random")
lb_policy least_connections
# Health checks
health_uri /health
health_interval 10s
health_timeout 5s
}
handle_errors {
respond "💥 Error; Please try again later. Code {err.status_code} | {err.status_text}."
}
}
{% endfor %}

View File

@ -0,0 +1,3 @@
---
our_caddy_image: caddy:2

View File

@ -0,0 +1,8 @@
---
- name: Restart app
ansible.builtin.systemd_service:
name: our-server
state: restarted
enabled: true
daemon_reload: true

View File

@ -0,0 +1,198 @@
---
- name: Create futureporn group
ansible.builtin.group:
name: futureporn
state: present
- name: Create futureporn user
ansible.builtin.user:
name: futureporn
group: futureporn
create_home: true
home: /home/futureporn
system: true
- name: Ensure futureporn directory exists
ansible.builtin.file:
path: /opt/futureporn
state: directory
mode: "0755"
notify:
- Restart app
- name: Ensure config directory exists
ansible.builtin.file:
path: /usr/local/etc/futureporn/our
state: directory
mode: "0755"
notify:
- Restart app
- name: Generate .env file
ansible.builtin.template:
src: env.j2
dest: "{{ env_file }}"
mode: "0600"
notify:
- Restart app
- name: Download Futureporn source code
ansible.builtin.git:
repo: https://gitea.futureporn.net/futureporn/fp
dest: /opt/futureporn
version: "{{ our_commit }}"
update: true
tags:
- our
notify:
- Restart app
- name: Install Our packages based on package.json
community.general.npm:
path: "{{ app_dir }}"
- name: Install passlib
ansible.builtin.pip:
name: passlib # dependency of Ansible's passwordhash
state: present
- name: Create our-server service
ansible.builtin.template:
src: our-server.service.j2
dest: /etc/systemd/system/our-server.service
mode: "0644"
notify:
- Restart app
# - name: Template Caddyfile
# ansible.builtin.template:
# src: Caddyfile.j2
# dest: /opt/our/Caddyfile
# mode: "0600"
# notify:
# - Restart caddy
# - name: Template Docker Compose file
# ansible.builtin.template:
# src: docker-compose.yml.j2
# dest: /opt/our/docker-compose.yml
# mode: "0644"
# notify:
# - Restart app
- name: Set default UFW policy to deny incoming
community.general.ufw:
state: enabled
policy: deny
direction: incoming
- name: Configure firewall
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 443
- 80
- name: Allow /20 subnet access
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
from: 10.2.112.0/20
loop:
- 3000
# Bright app Reference
# ---
# # Terraform Vultr provider doesn't have a VFS resource/datasource yet.
# # This is a workaround for that missing feature.
# #
# # @see https://github.com/vultr/terraform-provider-vultr/issues/560
# - name: Get the VFS id
# ansible.builtin.uri:
# url: https://api.vultr.com/v2/vfs
# method: GET
# status_code: 200
# headers:
# Authorization: "Bearer {{ lookup('dotenv', 'VULTR_API_KEY', file='../.env') }}"
# register: vfs_list
# - name: Get VFS variables
# ansible.builtin.set_fact:
# our_vfs_id: "{{ vfs_list.json.vfs | selectattr('label', 'equalto', 'our') | map(attribute='id') | first }}"
# - name: Debug the our VFS id
# ansible.builtin.debug:
# msg: "The VFS ID for 'our' is {{ our_vfs_id }}"
# - name: Attach VFS to Vultr instance
# ansible.builtin.uri:
# url: https://api.vultr.com/v2/vfs/{{ our_vfs_id }}/attachments/{{ hostvars[inventory_hostname]['vultr_instance_id'] }}
# method: PUT
# status_code:
# - 200
# - 201
# - 409
# headers:
# Authorization: "Bearer {{ lookup('dotenv', 'VULTR_API_KEY', file='../.env') }}"
# register: vfs_attach
# changed_when:
# - vfs_attach.json is defined
# - "'state' in vfs_attach.json"
# - vfs_attach.json.state == "ATTACHED"
# notify:
# - Mount vfs
# - Restart our
# - name: Debug vfs_attach
# ansible.builtin.debug:
# var: vfs_attach
# - name: Get the VFS mount_tag
# ansible.builtin.set_fact:
# vfs_mount_tag: "{{ vfs_attach.json.mount_tag | default('') }}"
# - name: Setup docker container
# community.docker.docker_container:
# name: our
# image: gitea.futureporn.net/futureporn/our:latest
# pull: always
# state: started
# ports:
# - "4000:4000"
# volumes:
# - "/mnt/vfs/futureporn:/mnt/vfs/futureporn"
# env:
# DB_HOST: "{{ hostvars['fp-db-0']['internal_ip'] }}"
# DB_USER: "{{ lookup('dotenv', 'DB_USER', file='../.env') }}"
# DB_NAME: "our"
# DB_PORT: "5432"
# DB_PASS: "{{ lookup('dotenv', 'DB_PASS', file='../.env') }}"
# MIX_ENV: prod
# PUBLIC_S3_ENDPOINT: https://futureporn-b2.b-cdn.net
# PATREON_REDIRECT_URI: https://our.futureporn.net/auth/patreon/callback
# SITE_URL: https://our.futureporn.net
# PHX_HOST: our.futureporn.net
# AWS_BUCKET: futureporn
# AWS_REGION: us-west-000
# AWS_HOST: s3.us-west-000.backblazeb2.com
# SECRET_KEY_BASE: "{{ lookup('dotenv', 'SECRET_KEY_BASE', file='../.env') }}"
# PATREON_CLIENT_SECRET: "{{ lookup('dotenv', 'PATREON_CLIENT_SECRET', file='../.env') }}"
# PATREON_CLIENT_ID: "{{ lookup('dotenv', 'PATREON_CLIENT_ID', file='../.env') }}"
# AWS_ACCESS_KEY_ID: "{{ lookup('dotenv', 'AWS_ACCESS_KEY_ID', file='../.env') }}"
# AWS_SECRET_ACCESS_KEY: "{{ lookup('dotenv', 'AWS_SECRET_ACCESS_KEY', file='../.env') }}"
# TRACKER_HELPER_ACCESSLIST_URL: https://tracker.futureporn.net/accesslist
# TRACKER_HELPER_USERNAME: "{{ lookup('dotenv', 'TRACKER_HELPER_USERNAME', file='../.env') }}"
# TRACKER_HELPER_PASSWORD: "{{ lookup('dotenv', 'TRACKER_HELPER_PASSWORD', file='../.env') }}"
# TRACKER_URL: https://tracker.futureporn.net:6969
# CACHE_DIR: /mnt/vfs/futureporn # we use Vultr File System to share cache among all Phoenix instances

View File

@ -0,0 +1,6 @@
---
- name: Setup fastify app
ansible.builtin.include_tasks: fastify.yml
tags:
- fastify

View File

@ -0,0 +1,33 @@
ORIGIN=https://future.porn
COOKIE_SECRET={{ lookup('dotenv', 'COOKIE_SECRET', file='../../../../.env.production')}}
DB_USER={{ lookup('dotenv', 'DB_USER', file='../../../../.env.production')}}
DB_PASSWORD={{ lookup('dotenv', 'DB_PASSWORD', file='../../../../.env.production')}}
DB_NAME=future_porn
CDN_ORIGIN=https://fp-usc.b-cdn.net
CDN_TOKEN_SECRET={{ lookup('dotenv', 'CDN_TOKEN_SECRET', file='../../../../.env.production')}}
NODE_ENV=production
DATABASE_URL={{ lookup('dotenv', 'DATABASE_URL', file='../../../../.env.production')}}
PGADMIN_DEFAULT_EMAIL={{ lookup('dotenv', 'PGADMIN_DEFAULT_EMAIL', file='../../../../.env.production')}}
PGADMIN_DEFAULT_PASSWORD={{ lookup('dotenv', 'PGADMIN_DEFAULT_PASSWORD', file='../../../../.env.production')}}
PATREON_CLIENT_ID={{ lookup('dotenv', 'PATREON_CLIENT_ID', file='../../../../.env.production')}}
PATREON_CLIENT_SECRET={{ lookup('dotenv', 'PATREON_CLIENT_SECRET', file='../../../../.env.production')}}
PATREON_API_ORIGIN=https://www.patreon.com
PATREON_AUTHORIZE_PATH=/oauth2/authorize
PATREON_TOKEN_PATH=/api/oauth2/token
S3_BUCKET=fp-usc
S3_REGION=us-west-000
S3_KEY_ID={{ lookup('dotenv', 'S3_KEY_ID', file='../../../../.env.production')}}
S3_APPLICATION_KEY={{ lookup('dotenv', 'S3_APPLICATION_KEY', file='../../../../.env.production')}}
S3_ENDPOINT=https://s3.us-west-000.backblazeb2.com
CACHE_ROOT='/mnt/vfs/futureporn/our'

View File

@ -0,0 +1,18 @@
[Unit]
Description=FuturePorn Our Server
After=network.target
[Service]
Type=simple
WorkingDirectory={{ app_dir }}
ExecStart=/usr/bin/env /usr/bin/npx tsx src/index.ts
#ExecStart=/usr/bin/env /usr/bin/npx dotenvx run -f {{ env_file }} -- npx tsx src/index.ts
Restart=always
RestartSec=5
User={{ app_user }}
EnvironmentFile={{ env_file }}
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,16 @@
[Unit]
Description=FuturePorn Our Worker
After=network.target
[Service]
Type=simple
WorkingDirectory={{ app_dir }}
ExecStart=/usr/bin/env NODE_ENV=production /usr/bin/node dist/worker.js
Restart=on-failure
User={{ app_user }}
EnvironmentFile={{ env_file }}
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,7 @@
---
app_user: futureporn
app_dir: /opt/futureporn/services/our
app_entry: dist/main.js
env_file: /usr/local/etc/futureporn/our/env
nodejs_version: "20.x"
our_commit: main

View File

@ -0,0 +1,2 @@
---

View File

@ -0,0 +1,8 @@
---
- name: Restart worker
ansible.builtin.systemd_service:
name: our-worker
state: restarted
enabled: true
daemon_reload: true

View File

@ -0,0 +1,75 @@
---
- name: Create futureporn group
ansible.builtin.group:
name: futureporn
state: present
- name: Create futureporn user
ansible.builtin.user:
name: futureporn
group: futureporn
create_home: true
home: /home/futureporn
system: true
- name: Ensure futureporn directory exists
ansible.builtin.file:
path: /opt/futureporn
state: directory
mode: "0755"
notify:
- restart worker
- name: Ensure config directory exists
ansible.builtin.file:
path: /usr/local/etc/futureporn/our
state: directory
mode: "0755"
notify:
- restart worker
- name: Generate .env file
ansible.builtin.template:
src: env.j2
dest: "{{ env_file }}"
mode: "0600"
notify:
- restart worker
- name: Download Futureporn source code
ansible.builtin.git:
repo: https://gitea.futureporn.net/futureporn/fp
dest: /opt/futureporn
version: "{{ our_commit }}"
update: true
tags:
- our
notify:
- Restart worker
- name: Install Our packages based on package.json
community.general.npm:
path: "{{ app_dir }}"
- name: Install passlib
ansible.builtin.pip:
name: passlib # dependency of Ansible's passwordhash
state: present
- name: Create our-worker service
ansible.builtin.template:
src: our-worker.service.j2
dest: /etc/systemd/system/our-worker.service
mode: "0644"
notify:
- restart worker
- name: Set default UFW policy to deny incoming
community.general.ufw:
state: enabled
policy: deny
direction: incoming

View File

@ -0,0 +1,30 @@
ORIGIN=https://future.porn
COOKIE_SECRET={{ lookup('dotenv', 'COOKIE_SECRET', file='../../../../.env.production')}}
DB_USER={{ lookup('dotenv', 'DB_USER', file='../../../../.env.production')}}
DB_PASSWORD={{ lookup('dotenv', 'DB_PASSWORD', file='../../../../.env.production')}}
DB_NAME=future_porn
CDN_ORIGIN=https://fp-usc.b-cdn.net
CDN_TOKEN_SECRET={{ lookup('dotenv', 'CDN_TOKEN_SECRET', file='../../../../.env.production')}}
NODE_ENV=production
DATABASE_URL={{ lookup('dotenv', 'DATABASE_URL', file='../../../../.env.production')}}
PGADMIN_DEFAULT_EMAIL={{ lookup('dotenv', 'PGADMIN_DEFAULT_EMAIL', file='../../../../.env.production')}}
PGADMIN_DEFAULT_PASSWORD={{ lookup('dotenv', 'PGADMIN_DEFAULT_PASSWORD', file='../../../../.env.production')}}
PATREON_CLIENT_ID={{ lookup('dotenv', 'PATREON_CLIENT_ID', file='../../../../.env.production')}}
PATREON_CLIENT_SECRET={{ lookup('dotenv', 'PATREON_CLIENT_SECRET', file='../../../../.env.production')}}
PATREON_API_ORIGIN=https://www.patreon.com
PATREON_AUTHORIZE_PATH=/oauth2/authorize
PATREON_TOKEN_PATH=/api/oauth2/token
S3_BUCKET=fp-usc
S3_REGION=us-west-000
S3_KEY_ID={{ lookup('dotenv', 'S3_KEY_ID', file='../../../../.env.production') }}
S3_APPLICATION_KEY={{ lookup('dotenv', 'S3_APPLICATION_KEY', file='../../../../.env.production')}}
S3_ENDPOINT=https://s3.us-west-000.backblazeb2.com
CACHE_ROOT='/mnt/vfs/futureporn/our'

View File

@ -0,0 +1,14 @@
[Unit]
Description=FuturePorn Our Worker
After=network.target
[Service]
Type=simple
WorkingDirectory={{ app_dir }}
ExecStart=/usr/bin/env /usr/bin/npx tsx src/worker.ts
Restart=on-failure
User={{ app_user }}
EnvironmentFile={{ env_file }}
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,7 @@
---
app_user: futureporn
app_dir: /opt/futureporn/services/our
app_entry: src/worker.ts
env_file: /usr/local/etc/futureporn/our/env
nodejs_version: "20.x"
our_commit: main

View File

@ -0,0 +1,218 @@
#!/bin/bash
set -e # Stop script execution on error
NGINX_CONF_PATH="./docker/nginx/active_backend.conf"
NGINX_CONTAINER="app"
ENV_FILE=".env"
build_containers() {
echo "📦 Building Docker containers..."
docker compose build
echo "✅ Docker containers built successfully."
}
prepare_nginx_config() {
if [ ! -d "./docker/nginx" ]; then
echo "📂 Nginx directory not found. Creating it..."
mkdir -p ./docker/nginx
echo "✅ Nginx directory created."
fi
}
update_nginx_config() {
local active_color=$1
echo "🔄 Updating Nginx configuration to route traffic to '$active_color' containers..."
cat > "$NGINX_CONF_PATH" <<EOL
upstream app_backend {
server $active_color:9000 max_fails=3 fail_timeout=30s;
}
EOL
echo "📋 Copying Nginx configuration to the container..."
docker cp "$NGINX_CONF_PATH" "$NGINX_CONTAINER:/etc/nginx/conf.d/active_backend.conf"
echo "🔁 Reloading Nginx to apply the new configuration..."
docker exec "$NGINX_CONTAINER" nginx -s reload >/dev/null 2>&1
echo "✅ Nginx configuration updated and reloaded successfully."
}
wait_for_health() {
local container_prefix=$1
local retries=5
local unhealthy_found
echo "⏳ Waiting for containers with prefix '$container_prefix' to become healthy..."
while (( retries > 0 )); do
unhealthy_found=false
for container_name in $(docker ps --filter "name=$container_prefix" --format "{{.Names}}"); do
health_status=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}unknown{{end}}' "$container_name" || echo "unknown")
if [[ "$health_status" != "healthy" ]]; then
unhealthy_found=true
echo "🚧 Container '$container_name' is not ready. Current status: $health_status."
fi
done
if ! $unhealthy_found; then
echo "✅ All containers with prefix '$container_prefix' are healthy."
return 0
fi
echo "⏳ Retrying... ($retries retries left)"
((retries--))
sleep 5
done
echo "❌ Error: Some containers with prefix '$container_prefix' are not healthy. Aborting deployment."
rollback
exit 0
}
rollback() {
echo "🛑 Rolling back deployment. Ensuring the active environment remains intact."
if [ -n "$PREVIOUS_COLOR" ]; then
echo "🔄 Restoring CONTAINER_COLOR=$PREVIOUS_COLOR in .env."
sed -i.bak "s/^CONTAINER_COLOR=.*/CONTAINER_COLOR=$PREVIOUS_COLOR/" "$ENV_FILE"
rm -f "$ENV_FILE.bak"
echo "✅ Restored CONTAINER_COLOR=$PREVIOUS_COLOR in .env."
else
echo "🚧 No previous CONTAINER_COLOR found to restore."
fi
if docker ps --filter "name=green" --format "{{.Names}}" | grep -q "green"; then
echo "✅ Active environment 'green' remains intact."
echo "🛑 Stopping and removing 'blue' containers..."
docker compose stop "blue" >/dev/null 2>&1 || true
docker compose rm -f "blue" >/dev/null 2>&1 || true
elif docker ps --filter "name=blue" --format "{{.Names}}" | grep -q "blue"; then
echo "✅ Active environment 'blue' remains intact."
echo "🛑 Stopping and removing 'green' containers..."
docker compose stop "green" >/dev/null 2>&1 || true
docker compose rm -f "green" >/dev/null 2>&1 || true
else
echo "❌ No active environment detected after rollback. Manual intervention might be needed."
fi
echo "🔄 Rollback completed."
}
update_env_file() {
local active_color=$1
# check if .env file exists
if [ ! -f "$ENV_FILE" ]; then
echo "❌ .env file not found. Creating a new one..."
echo "CONTAINER_COLOR=$active_color" > "$ENV_FILE"
echo "✅ Created .env file with CONTAINER_COLOR=$active_color."
return
fi
# backup previous CONTAINER_COLOR value
if grep -q "^CONTAINER_COLOR=" "$ENV_FILE"; then
PREVIOUS_COLOR=$(grep "^CONTAINER_COLOR=" "$ENV_FILE" | cut -d '=' -f 2)
echo "♻️ Backing up previous CONTAINER_COLOR=$PREVIOUS_COLOR."
else
PREVIOUS_COLOR=""
fi
# update CONTAINER_COLOR value in .env
if grep -q "^CONTAINER_COLOR=" "$ENV_FILE"; then
sed -i.bak "s/^CONTAINER_COLOR=.*/CONTAINER_COLOR=$active_color/" "$ENV_FILE"
echo "🔄 Updated CONTAINER_COLOR=$active_color in .env"
else
echo "CONTAINER_COLOR=$active_color" >> "$ENV_FILE"
echo "🖋️ Added CONTAINER_COLOR=$active_color to .env"
fi
# remove backup file
if [ -f "$ENV_FILE.bak" ]; then
rm "$ENV_FILE.bak"
fi
}
install_dependencies() {
local container=$1
echo "📥 Installing dependencies in container '$container'..."
# Install Laravel dependencies
docker exec -u root -it "$container" bash -c "composer install --no-dev --optimize-autoloader"
docker exec -u root -it "$container" bash -c "mkdir -p database && touch database/database.sqlite"
# Permissions setup
docker exec -u root -it "$container" bash -c "chown www-data:www-data -R ./storage ./bootstrap ./database"
docker exec -u root -it "$container" bash -c "chmod -R 775 ./storage ./bootstrap/cache"
# Clear caches and run migrations
docker exec -u root -it "$container" bash -c "php artisan cache:clear"
docker exec -u root -it "$container" bash -c "php artisan config:clear"
docker exec -u root -it "$container" bash -c "php artisan route:clear"
docker exec -u root -it "$container" bash -c "php artisan view:clear"
docker exec -u root -it "$container" bash -c "php artisan migrate --force"
echo "✅ Dependencies installed and database initialized successfully in container '$container'."
}
deploy() {
local active=$1
local new=$2
# Update .env before deploying
update_env_file "$new"
echo "🚀 Starting deployment. Current active environment: '$active'. Deploying to '$new'..."
docker compose --profile "$new" up -d
wait_for_health "$new"
install_dependencies "$new"
update_nginx_config "$new"
echo "🗑️ Removing old environment: '$active'..."
echo "🛑 Stopping '$active' containers..."
docker compose stop $active >/dev/null 2>&1 || true
echo "🗑️ Removing '$active' containers..."
docker compose rm -f $active >/dev/null 2>&1 || true
update_env_file "$new"
echo "✅ Deployment to '$new' completed successfully."
}
get_active_container() {
if [ -f "$ENV_FILE" ] && grep -q "CONTAINER_COLOR" "$ENV_FILE"; then
grep "CONTAINER_COLOR" "$ENV_FILE" | cut -d '=' -f 2
else
echo ""
fi
}
# Main script logic
prepare_nginx_config
build_containers
ACTIVE_COLOR=$(get_active_container)
if [ -z "$ACTIVE_COLOR" ]; then
# if no active container found, deploy 'blue'
echo "🟦 Initial setup. Bringing up 'blue' containers..."
docker compose --profile blue up -d
wait_for_health "blue"
install_dependencies "blue"
update_nginx_config "blue"
update_env_file "blue"
elif [ "$ACTIVE_COLOR" == "green" ]; then
# if the active is 'green', deploy 'blue'
PREVIOUS_COLOR="green"
deploy "green" "blue"
elif [ "$ACTIVE_COLOR" == "blue" ]; then
# if the active is 'blue', deploy 'green'
PREVIOUS_COLOR="blue"
deploy "blue" "green"
else
# if the active is neither 'green' nor 'blue', reset to 'blue'
echo "🚧 Unexpected CONTAINER_COLOR value. Resetting to 'blue'..."
PREVIOUS_COLOR=""
docker compose --profile blue up -d
wait_for_health "blue"
install_dependencies "blue"
update_nginx_config "blue"
update_env_file "blue"
fi
echo "🎉 Deployment successful!"

View File

@ -0,0 +1,8 @@
---
- name: Mount vfs
ansible.posix.mount:
src: "{{ vfs_mount_tag }}"
path: /mnt/vfs
fstype: virtiofs
state: mounted

View File

@ -0,0 +1,60 @@
---
- name: Create directory
ansible.builtin.file:
path: /etc/futureporn/our
state: directory
mode: "0755"
- name: Copy env file
ansible.builtin.copy:
src: ../../../../.env.production
dest: /etc/futureporn/our/.env
mode: "0600"
- name: Clone the latest code
ansible.builtin.git:
repo: https://gitea.futureporn.net/futureporn/fp
dest: /tmp/checkout
single_branch: true
version: main
clone: true
force: true
- name: Copy the compose file
ansible.builtin.copy:
remote_src: true
src: /tmp/checkout/services/our/compose.production.yaml
dest: /etc/futureporn/our/compose.production.yaml
mode: "0744"
- name: Deploy stack to green
community.docker.docker_stack:
state: present
name: our-green
compose:
- /etc/futureporn/our/compose.production.yaml
- services:
server:
ports:
- target: 5000 # container port
published: 8084 # Swarm ingress port
protocol: tcp
mode: ingress
- name: Deploy stack to blue
community.docker.docker_stack:
state: present
name: our-blue
compose:
- /etc/futureporn/our/compose.production.yaml
- services:
server:
ports:
- target: 5000 # container port
published: 8085 # Swarm ingress port
protocol: tcp
mode: ingress
# - name: Remove stack
# community.docker.docker_stack:
# name: mystack
# state: absent

View File

@ -0,0 +1,11 @@
---
- name: Get VFS mount tag
ansible.builtin.set_fact:
our_vfs_mount_tag: "{{ lookup('dotenv', 'VULTR_VFS_MOUNT_TAG', file='../../../../.env.production') }}"
- name: Mount VFS
ansible.posix.mount:
src: "{{ our_vfs_mount_tag }}"
path: /mnt/vfs
fstype: virtiofs
state: mounted

View File

@ -0,0 +1,13 @@
---
- name: Configure filesystem
ansible.builtin.include_tasks:
file: filesystem.yml
- name: Configure docker stack
ansible.builtin.include_tasks:
file: stack.yml
- name: Deploy our
ansible.builtin.include_tasks:
file: deploy.yml

View File

@ -0,0 +1,46 @@
---
# Terraform Vultr provider doesn't expose the mount_tag.
# it does however expose the vfs id, which we save to ansible host vars at time of `tofu apply`.
# As a workaround, we use Vultr api to fetch the mount_tag, and mount the vfs to the instance.
- name: Get the VFS data
ansible.builtin.uri:
url: https://api.vultr.com/v2/vfs
method: GET
status_code: 200
headers:
Authorization: "Bearer {{ lookup('dotenv', 'VULTR_API_KEY', file='../.env') }}"
register: vfs_list
- name: Get VFS variables
ansible.builtin.set_fact:
our_vfs_id: "{{ vfs_list.json.vfs | selectattr('tags', 'contains', 'our') | map(attribute='id') | first }}"
- name: Debug the our VFS id
ansible.builtin.debug:
msg: "The VFS ID for 'our' is {{ our_vfs_id }}"
- name: Attach VFS to Vultr instance
ansible.builtin.uri:
url: https://api.vultr.com/v2/vfs/{{ vultr_vfs_storage_id }}/attachments/{{ hostvars[inventory_hostname]['vultr_instance_id'] }}
method: PUT
status_code:
- 200
- 201
- 409
headers:
Authorization: "Bearer {{ lookup('dotenv', 'VULTR_API_KEY', file='../.env') }}"
register: vfs_attach
changed_when:
- vfs_attach.json is defined
- "'state' in vfs_attach.json"
- vfs_attach.json.state == "ATTACHED"
notify:
- Mount vfs
- name: Debug vfs_attach
ansible.builtin.debug:
var: vfs_attach
- name: Get the VFS mount_tag
ansible.builtin.set_fact:
vfs_mount_tag: "{{ vfs_attach.json.mount_tag | default('') }}"

View File

@ -0,0 +1 @@
---

View File

@ -0,0 +1,3 @@
---
swarm_enable_manager: false
swarm_enable_worker: false

View File

@ -0,0 +1,11 @@
---
- name: Configure swarm manager
ansible.builtin.include_tasks:
file: manager.yml
when: swarm_enable_manager
- name: Configure swarm worker
ansible.builtin.include_tasks:
file: worker.yml
when: swarm_enable_worker

View File

@ -0,0 +1,24 @@
---
- name: Init a new swarm with default parameters
community.docker.docker_swarm:
state: present
listen_addr: "{{ internal_ip }}:2377"
advertise_addr: "{{ internal_ip }}:4567"
register: swarm_create
- name: Set join tokens as host facts (manager only)
set_fact:
swarm_worker_join_token: "{{ swarm_create.swarm_facts.JoinTokens.Worker }}"
swarm_manager_join_token: "{{ swarm_create.swarm_facts.JoinTokens.Manager }}"
- name: Debug
ansible.builtin.debug:
var: swarm_create
- name: Get worker join token
ansible.builtin.set_fact:
swarm_worker_join_token: "{{ swarm_create.swarm_facts.JoinTokens.Worker }}"
- name: Get manager join token
ansible.builtin.set_fact:
swarm_manager_join_token: "{{ swarm_create.swarm_facts.JoinTokens.Manager }}"

View File

@ -0,0 +1,19 @@
---
- debug:
var: groups['swarm-node']
- name: Get all swarm nodes except the first one
set_fact:
swarm_worker_ips: "{{ groups['swarm-node'][1:] }}"
- name: Join worker nodes
community.docker.docker_swarm:
state: join
advertise_addr: "{{ internal_ip }}:4567"
join_token: "{{ hostvars[groups['swarm-node'] | first]['swarm_worker_join_token'] }}"
remote_addrs: swarm_worker_ips
# - name: Join swarm as worker
# community.docker.docker_swarm:
# state: joined
# join_token: "{{ hostvars[groups['swarm-node'] | first].swarm_worker_join_token }}"
# remote_addrs: ["{{ hostvars[groups['swarm-node'] | first].internal_ip }}:2377"]

View File

@ -0,0 +1,21 @@
- name: Add Tailscale's GPG key
ansible.builtin.get_url:
url: https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg
dest: /usr/share/keyrings/tailscale-archive-keyring.gpg
mode: '0644'
- name: Add Tailscale apt repository
ansible.builtin.get_url:
url: https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list
dest: /etc/apt/sources.list.d/tailscale.list
mode: '0644'
- name: Update apt cache
ansible.builtin.apt:
update_cache: yes
- name: Install tailscale
ansible.builtin.apt:
name: tailscale
state: present

View File

@ -0,0 +1,20 @@
---
- name: Configure firewall
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 80
- 443
- name: Install Caddy
ansible.builtin.import_role:
name: nvjacobo.caddy
- name: Configure Caddyfile
ansible.builtin.template:
src: "templates/Caddyfile.j2"
dest: /etc/caddy/Caddyfile
mode: "0644"
notify: restart caddy # nvjacobo.caddy handles this

View File

@ -0,0 +1,44 @@
---
- name: Setup volume
community.docker.docker_volume:
name: pg_data
- name: Setup docker container
community.docker.docker_container:
name: uppy-companion
image: transloadit/companion
pull: missing
state: started
ports:
- "3020:3020"
env:
NODE_ENV: prod
COMPANION_PORT: "{{ lookup('dotenv', 'COMPANION_PORT', file='../.env') }}"
COMPANION_DOMAIN: "{{ lookup('dotenv', 'COMPANION_DOMAIN', file='../.env') }}"
COMPANION_SELF_ENDPOINT: "{{ lookup('dotenv', 'COMPANION_SELF_ENDPOINT', file='../.env') }}"
COMPANION_HIDE_METRICS: "{{ lookup('dotenv', 'COMPANION_HIDE_METRICS', file='../.env') }}"
COMPANION_HIDE_WELCOME: "{{ lookup('dotenv', 'COMPANION_HIDE_WELCOME', file='../.env') }}"
COMPANION_STREAMING_UPLOAD: "{{ lookup('dotenv', 'COMPANION_STREAMING_UPLOAD', file='../.env') }}"
COMPANION_TUS_DEFERRED_UPLOAD_LENGTH: "{{ lookup('dotenv', 'COMPANION_TUS_DEFERRED_UPLOAD_LENGTH', file='../.env') }}"
COMPANION_CLIENT_ORIGINS: "{{ lookup('dotenv', 'COMPANION_CLIENT_ORIGINS', file='../.env') }}"
COMPANION_PROTOCOL: "{{ lookup('dotenv', 'COMPANION_PROTOCOL', file='../.env') }}"
COMPANION_DATADIR: /mnt/uppy-server-data
COMPANION_SECRET: "{{ lookup('dotenv', 'COMPANION_SECRET', file='../.env') }}"
COMPANION_PREAUTH_SECRET: "{{ lookup('dotenv', 'COMPANION_PREAUTH_SECRET', file='../.env') }}"
COMPANION_AWS_KEY: "{{ lookup('dotenv', 'COMPANION_AWS_KEY', file='../.env') }}"
COMPANION_AWS_SECRET: "{{ lookup('dotenv', 'COMPANION_AWS_SECRET', file='../.env') }}"
COMPANION_AWS_BUCKET: "{{ lookup('dotenv', 'COMPANION_AWS_BUCKET', file='../.env') }}"
COMPANION_AWS_ENDPOINT: "{{ lookup('dotenv', 'COMPANION_AWS_ENDPOINT', file='../.env') }}"
COMPANION_AWS_REGION: "{{ lookup('dotenv', 'COMPANION_AWS_REGION', file='../.env') }}"
COMPANION_AWS_FORCE_PATH_STYLE: "false"
COMPANION_AWS_PREFIX: usc/
mounts:
- type: volume
target: "/mnt/uppy-server-data"
source: "uppy_data"
# - name: Allow VPC2.0 network access
# community.general.ufw:
# rule: allow
# port: '5432'
# proto: tcp
# from: 10.2.128.0/20

View File

@ -0,0 +1,15 @@
uppy.futureporn.net {
# Define the upstream servers for load balancing
reverse_proxy :3020 {
# Health checks (optional)
health_uri /metrics
health_interval 10s
health_timeout 5s
}
handle_errors {
respond "💥 Error ~ {err.status_code} {err.status_text}"
}
}

View File

@ -1,7 +1,7 @@
--- ---
- name: Bootstrap - name: Bootstrap
hosts: our hosts: all
gather_facts: false ## required because ansible_host may not have python gather_facts: false ## required because ansible_host may not have python
check_mode: false check_mode: false
become: false become: false
@ -9,114 +9,82 @@
- bootstrap - bootstrap
- name: Assert common dependencies - name: Assert common dependencies
hosts: our hosts: swarm-node
gather_facts: true gather_facts: true
check_mode: false check_mode: false
become: true become: true
roles: roles:
- common - common
- docker
# the decision of worker vs manager is set in ansible inventory by opentofu
- name: Set up docker swarm
hosts: swarm-node
gather_facts: true
roles:
- swarm
- name: Assert our dependencies - name: Assert our dependencies
hosts: our hosts: swarm-node
gather_facts: true gather_facts: true
check_mode: false check_mode: false
become: true become: true
roles: roles:
- our - our
- name: Install Komodo Service
hosts: our
vars_files:
- vars/main.yml
vars:
komodo_allowed_ips: "[\"::ffff:{{ lookup('dotenv', 'KOMODO_ALLOWED_IPS', file='../.env.production') }}\"]"
roles:
- role: bpbradley.komodo
komodo_action: "install"
komodo_version: "latest"
komodo_passkeys: "{{ lookup('dotenv', 'KOMODO_PASSKEYS', file='../.env.production') }}"
komodo_core_url: "{{ lookup('dotenv', 'KOMODO_CORE_URL', file='../.env.production') }}"
komodo_core_api_key: "{{ lookup('dotenv', 'KOMODO_CORE_API_KEY', file='../.env.production') }}"
komodo_core_api_secret: "{{ lookup('dotenv', 'KOMODO_CORE_API_SECRET', file='../.env.production') }}"
enable_server_management: true
tasks:
- name: debug
ansible.builtin.debug:
msg:
komodo_allowed_ips: "{{ komodo_allowed_ips }}"
- name: Allow port 8120 TCP from Komodo core
community.general.ufw:
rule: allow
port: "8120"
proto: tcp
from_ip: "{{ lookup('dotenv', 'KOMODO_ALLOWED_IPS', file='../.env.production') }}"
- name: Configure database
hosts: database
gather_facts: true
check_mode: false
become: false
roles:
- database
tags:
- database
- name: Configure Our Server
hosts: our-server
gather_facts: true
check_mode: false
become: false
tags:
- our-server
roles:
- geerlingguy.nodejs
- our-server
- name: Configure Our Worker
hosts: our-worker
gather_facts: true
check_mode: false
become: false
tags:
- our-worker
roles:
- geerlingguy.nodejs
- our-worker
- name: Configure load balancer - name: Configure load balancer
hosts: load_balancer hosts: loadbalancer
gather_facts: true gather_facts: true
check_mode: false check_mode: false
become: false become: false
roles: roles:
- load_balancer - loadbalancer
vars_files: vars_files:
- vars/main.yml - vars/main.yml
tags: tags:
- lb - lb
- name: Set up b2-cli
hosts: ipfs
gather_facts: true
roles:
- backblaze
tags:
- b2
- name: Set up IPFS - name: Set up IPFS
hosts: ipfs hosts: ipfs
gather_facts: true gather_facts: true
vars:
ipfs_enable_blockstorage: true
ipfs_enable_kubo: true
ipfs_enable_ipfs_cluster_service: false
ipfs_enable_ipfs_cluster_follow: true
ipfs_enable_b2_cli: trure
roles: roles:
- ipfs - ipfs
tags: tags:
- capture
- ipfs - ipfs
- name: Install Capture instance
hosts: capture # - name: Set up our app
gather_facts: true # hosts: swarm-node
check_mode: false # gather_facts: true
become: false # roles:
roles: # - our
- capture # tags:
tags: # - our
- capture
# - name: Install Capture instance
# hosts: capture
# gather_facts: true
# check_mode: false
# become: false
# roles:
# - capture
# tags:
# - capture
# - name: Configure tracker # - name: Configure tracker
# hosts: tracker # hosts: tracker

28
ansible/vars/main.yml Normal file
View File

@ -0,0 +1,28 @@
---
# infisical_project_id: "{{ lookup('dotenv', 'INFISICAL_PROJECT_ID', file='../.env') }}"
# infisical_client_id: "{{ lookup('dotenv', 'INFISICAL_CLIENT_ID', file='../.env') }}"
# infisical_client_secret: "{{ lookup('dotenv', 'INFISICAL_CLIENT_SECRET', file='../.env') }}"
# infisical_url: "{{ lookup('dotenv', 'INFISICAL_URL', file='../.env' )}}"
# infisical_env_slug: prod
# infisical_secrets: >-
# {{
# lookup(
# 'infisical.vault.read_secrets',
# universal_auth_client_id=infisical_client_id,
# universal_auth_client_secret=infisical_client_secret,
# project_id=infisical_project_id,
# env_slug=infisical_env_slug,
# path='/',
# url=infisical_url,
# wantlist=true
# )
# | ansible.builtin.items2dict
# }}
s3_region: us-west-000
s3_endpoint: https://s3.us-west-000.backblazeb2.com
kubo_version: v0.34.1
our_server_port: 3000

View File

@ -9,7 +9,7 @@ locals {
variable "ipfs_hosts" { variable "ipfs_hosts" {
description = "List of IP addresses for IPFS nodes" description = "List of IP addresses for IPFS nodes"
type = list(string) type = list(string)
default = ["161.97.186.203", "38.242.193.246"] default = ["38.242.193.246"]
} }
@ -17,9 +17,6 @@ variable "our_port" {
default = "5000" default = "5000"
} }
variable "database_host" {
default = "10.2.128.4"
}
variable "public_s3_endpoint" { variable "public_s3_endpoint" {
default = "https://fp-usc.b-cdn.net" default = "https://fp-usc.b-cdn.net"
@ -124,33 +121,52 @@ resource "bunnynet_dns_zone" "future_porn" {
# load balancing instance # load balancing instance
resource "vultr_instance" "load_balancer" { # resource "vultr_instance" "loadbalancer" {
# count = 1
# hostname = "fp-lb-${count.index}"
# plan = "vc2-1c-2gb"
# region = "ord"
# backups = "disabled"
# ddos_protection = "false"
# os_id = 1743
# enable_ipv6 = true
# label = "fp lb ${count.index}"
# tags = ["futureporn", "loadbalancer", "our", "tofu"]
# ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID]
# user_data = base64encode(var.vps_user_data)
# vpc_ids = [
# vultr_vpc.futureporn_vpc.id
# ]
# reserved_ip_id = vultr_reserved_ip.futureporn_v2_ip.id
# }
# our0
resource "vultr_instance" "our_vps" {
count = 1 count = 1
hostname = "fp-lb-${count.index}" hostname = "fp-our-${count.index}"
plan = "vc2-1c-2gb" plan = "vc2-4c-8gb"
region = "ord" region = "ord"
backups = "disabled" backups = "disabled"
ddos_protection = "false" ddos_protection = "false"
os_id = 1743 os_id = 1743
enable_ipv6 = true enable_ipv6 = true
label = "fp lb ${count.index}" label = "fp our ${count.index}"
tags = ["futureporn", "load_balancer", "our"] tags = ["futureporn", "our", "tofu"]
ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID] ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID]
user_data = base64encode(var.vps_user_data) user_data = base64encode(var.vps_user_data)
vpc_ids = [ vpc_ids = [
vultr_vpc.futureporn_vpc.id vultr_vpc.futureporn_vpc.id
] ]
reserved_ip_id = vultr_reserved_ip.futureporn_v2_ip.id
} }
resource "bunnynet_dns_record" "future_porn_apex" { # resource "bunnynet_dns_record" "future_porn_apex" {
zone = bunnynet_dns_zone.future_porn.id # zone = bunnynet_dns_zone.future_porn.id
name = "" # name = ""
type = "A" # type = "A"
value = vultr_instance.our_loadbalancer[0].main_ip # value = vultr_instance.loadbalancer[0].main_ip
ttl = 3600 # ttl = 3600
} # }
resource "bunnynet_dns_record" "www_future_porn" { resource "bunnynet_dns_record" "www_future_porn" {
@ -166,17 +182,17 @@ resource "bunnynet_dns_record" "www_future_porn" {
# vultr instance for running our app # vultr instance for running our app
resource "vultr_instance" "our_server" { resource "vultr_instance" "swarm_node" {
count = 1 count = 2
hostname = "fp-our-server-${count.index}" hostname = "swarm-node-${count.index}"
plan = "vc2-2c-4gb" plan = "vc2-2c-4gb"
region = "ord" region = "ord"
backups = "disabled" backups = "disabled"
ddos_protection = "false" ddos_protection = "false"
os_id = 1743 os_id = 1743
enable_ipv6 = true enable_ipv6 = true
label = "fp our server ${count.index}" label = "swarm node ${count.index}"
tags = ["futureporn", "our", "server"] tags = ["fp", "our", "server", "tofu"]
ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID] ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID]
vpc_ids = [ vpc_ids = [
vultr_vpc.futureporn_vpc.id vultr_vpc.futureporn_vpc.id
@ -184,24 +200,7 @@ resource "vultr_instance" "our_server" {
user_data = base64encode(var.vps_user_data) user_data = base64encode(var.vps_user_data)
} }
# vultr instance for running our app's background task runners
resource "vultr_instance" "our_worker" {
count = 2
hostname = "fp-our-worker-${count.index}"
plan = "vc2-2c-4gb"
region = "ord"
backups = "disabled"
ddos_protection = "false"
os_id = 1743
enable_ipv6 = true
label = "fp our worker ${count.index}"
tags = ["futureporn", "our", "worker"]
ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID]
vpc_ids = [
vultr_vpc.futureporn_vpc.id
]
user_data = base64encode(var.vps_user_data)
}
# # vultr instance meant for capturing VODs # # vultr instance meant for capturing VODs
@ -245,62 +244,42 @@ resource "vultr_instance" "our_worker" {
# } # }
resource "vultr_instance" "database" { # This is our ipfs node with a really big dick, I mean disk
count = 1 resource "vultr_instance" "ipfs_vps" {
hostname = "fp-db-${count.index}" count = 1
plan = "vc2-1c-2gb" hostname = "fp-ipfs-${count.index}"
region = "ord" plan = "vc2-2c-2gb"
backups = "enabled" region = "ord"
backups_schedule { backups = "disabled"
hour = "2"
type = "daily"
}
ddos_protection = "false" ddos_protection = "false"
os_id = 1743 os_id = 1743
enable_ipv6 = true enable_ipv6 = true
vpc_ids = [vultr_vpc.futureporn_vpc.id] label = "fp ipfs ${count.index}"
label = "fp database ${count.index}" tags = ["futureporn", "ipfs", "tofu"]
tags = ["futureporn", "database"]
ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID] ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID]
user_data = base64encode(var.vps_user_data) user_data = base64encode(var.vps_user_data)
} }
# backups = "enabled"
# backups_schedule {
# hour = "2"
# type = "daily"
# }
# resource "vultr_instance" "tracker" {
# count = 0
# hostname = "fp-tracker-${count.index}"
# plan = "vc2-1c-2gb"
# region = "ord"
# backups = "disabled"
# ddos_protection = "false"
# os_id = 1743
# enable_ipv6 = true
# vpc_ids = [vultr_vpc.futureporn_vpc.id]
# label = "fp tracker ${count.index}"
# tags = ["futureporn", "tracker"]
# ssh_key_ids = [local.envs.VULTR_SSH_KEY_ID]
# user_data = base64encode(var.vps_user_data)
# reserved_ip_id = vultr_reserved_ip.futureporn_tracker_ip.id
# }
resource "ansible_host" "ipfs_vps" { resource "ansible_host" "ipfs_vps" {
for_each = { for idx, host in var.ipfs_hosts : idx => host } for_each = { for idx, host in vultr_instance.ipfs_vps : idx => host }
name = each.value name = each.value.main_ip # <-- pick the IP or hostname here
groups = ["ipfs"] groups = ["ipfs"]
variables = { variables = {
ansible_user = "root" ansible_user = "root"
ansible_host = each.value ansible_host = each.value.main_ip # <-- pick the IP here too
} }
} }
resource "vultr_block_storage" "ipfs_blockstorage" {
label = "fp-ipfs"
size_gb = 5000
region = "ord"
attached_to_instance = vultr_instance.ipfs_vps[0].id
}
# resource "ansible_host" "capture_vps" { # resource "ansible_host" "capture_vps" {
# for_each = { for idx, host in vultr_instance.capture_vps : idx => host } # for_each = { for idx, host in vultr_instance.capture_vps : idx => host }
# name = each.value.hostname # name = each.value.hostname
@ -317,48 +296,34 @@ resource "ansible_host" "ipfs_vps" {
# } # }
# } # }
resource "ansible_host" "load_balancer" { # resource "ansible_host" "loadbalancer" {
for_each = { for idx, host in vultr_instance.load_balancer : idx => host } # count = length(vultr_instance.loadbalancer)
name = each.value.hostname
groups = ["load_balancer"]
variables = {
ansible_host = each.value.main_ip
internal_ip = each.value.internal_ip
}
}
resource "ansible_host" "database" { # name = vultr_instance.loadbalancer[count.index].hostname
for_each = { for idx, host in vultr_instance.database : idx => host } # groups = ["loadbalancer"]
name = each.value.hostname # variables = {
groups = ["database"] # ansible_host = vultr_instance.loadbalancer[count.index].main_ip
variables = { # internal_ip = vultr_instance.loadbalancer[count.index].internal_ip
ansible_host = each.value.main_ip # }
internal_ip = each.value.internal_ip # }
}
}
resource "ansible_host" "our_server" {
for_each = { for idx, host in vultr_instance.our_server : idx => host } resource "ansible_host" "swarm_node" {
for_each = { for idx, host in vultr_instance.swarm_node : idx => host }
name = each.value.hostname name = each.value.hostname
groups = ["our-server"] groups = ["swarm-node"]
variables = { variables = {
ansible_host = each.value.main_ip ansible_host = each.value.main_ip
internal_ip = each.value.internal_ip internal_ip = each.value.internal_ip
vultr_instance_id = each.value.id vultr_instance_id = each.value.id
vultr_vfs_storage_id = vultr_virtual_file_system_storage.vfs.id
# Set swarm manager only on the 0th host
swarm_enable_manager = each.key == 0 ? true : false
swarm_enable_worker = each.key == 0 ? false : true
} }
} }
resource "ansible_host" "our_worker" {
for_each = { for idx, host in vultr_instance.our_worker : idx => host }
name = each.value.hostname
groups = ["our-worker"]
variables = {
ansible_host = each.value.main_ip
internal_ip = each.value.internal_ip
vultr_instance_id = each.value.id
}
}
# resource "ansible_host" "tracker" { # resource "ansible_host" "tracker" {
@ -372,24 +337,26 @@ resource "ansible_host" "our_worker" {
# } # }
# } # }
resource "ansible_host" "our" { # resource "ansible_host" "our" {
for_each = { for idx, host in vultr_instance.our_vps : idx => host } # for_each = { for idx, host in vultr_instance.our_vps : idx => host }
name = each.value.hostname # name = each.value.hostname
groups = ["our"] # groups = ["our"]
variables = { # variables = {
ansible_host = each.value.main_ip # ansible_host = each.value.main_ip
internal_ip = each.value.internal_ip # internal_ip = each.value.internal_ip
vultr_instance_id = each.value.id # vultr_instance_id = each.value.id
vultr_vfs_storage_id = vultr_virtual_file_system_storage.vfs.id # vultr_vfs_storage_id = vultr_virtual_file_system_storage.vfs.id
} # }
} # }
resource "vultr_virtual_file_system_storage" "vfs" { resource "vultr_virtual_file_system_storage" "vfs" {
label = "fp-vfs-cache" label = "fp-vfs-cache"
size_gb = 200 size_gb = 200
region = "ord" region = "ord"
tags = ["our", "vfs"] tags = ["our", "vfs"]
attached_instances = vultr_instance.swarm_node[*].id
} }
@ -399,12 +366,8 @@ resource "vultr_virtual_file_system_storage" "vfs" {
# } # }
resource "ansible_group" "our-server" { resource "ansible_group" "swarm-node" {
name = "our-server" name = "swarm-node"
}
resource "ansible_group" "our-worker" {
name = "our-worker"
} }
@ -413,23 +376,22 @@ resource "ansible_group" "our" {
} }
resource "ansible_group" "load_balancer" { # resource "ansible_group" "loadbalancer" {
name = "load_balancer" # name = "loadbalancer"
} # }
resource "ansible_group" "database" { resource "ansible_group" "ipfs" {
name = "database" name = "ipfs"
} }
resource "ansible_group" "futureporn" { resource "ansible_group" "futureporn" {
name = "futureporn" name = "futureporn"
children = [ children = [
"load_balancer", # "loadbalancer",
"database",
"capture", "capture",
"our-server", "swarm-node",
"our-worker", "our",
"our" "ipfs"
] ]
} }