Initialize newly refactored infra repo.
This commit is contained in:
commit
d867522fbe
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
.env
|
||||
ansible/collections
|
||||
ansible/roles
|
||||
archive
|
||||
dhparams.pem
|
||||
imap.passwd
|
||||
self-signed-ca
|
||||
external-requirements-only.yml
|
||||
ansible/playbooks/test.yml
|
||||
gabite.bitcoiner.social/files/scripts
|
||||
strfry-policies
|
15
ansible/README.md
Normal file
15
ansible/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Ansible Infrastructure
|
||||
|
||||
Remember to update collections and roles periodically.
|
||||
|
||||
```bash
|
||||
ansible-galaxy install -r requirements.yml --ignore-errors --force
|
||||
```
|
||||
|
||||
## NixOS note
|
||||
|
||||
Workaround to run with `jmespath` on NixOS (for caddy and grafana which need it):
|
||||
|
||||
```shell
|
||||
nix-shell -p python311Packages.jmespath --run "ansible-playbook playbooks/group_tasks/observability/main.yml"
|
||||
```
|
11
ansible/ansible.cfg
Normal file
11
ansible/ansible.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
[defaults]
|
||||
inventory = hosts.cfg
|
||||
verbose=1
|
||||
roles_path = roles
|
||||
collections_path = collections
|
||||
|
||||
[ssh_connection]
|
||||
pipelining=True
|
||||
|
||||
[inventory]
|
||||
enable_plugins = yaml, ini
|
8
ansible/group_vars/all/caddy.yml
Normal file
8
ansible/group_vars/all/caddy.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
caddy_home: /var/lib/caddy
|
||||
caddy_systemd_capabilities_enabled: yes
|
||||
caddy_config: |
|
||||
# node_exporter
|
||||
{{ inventory_hostname }}:4430 {
|
||||
reverse_proxy http://127.0.0.1:8030
|
||||
}
|
11
ansible/group_vars/all/node_exporter.yml
Normal file
11
ansible/group_vars/all/node_exporter.yml
Normal file
@ -0,0 +1,11 @@
|
||||
--- # prometheus.prometheus.node_exporter role variables
|
||||
# https://prometheus-community.github.io/ansible/branch/main/node_exporter_role.html
|
||||
|
||||
node_exporter_web_listen_address: "127.0.0.1:8030"
|
||||
node_exporter_enabled_collectors: # https://github.com/prometheus/node_exporter#collectors
|
||||
- cpu.info # CPU Model
|
||||
- interrupts
|
||||
- netstat
|
||||
- vmstat
|
||||
- systemd
|
||||
- processes
|
6
ansible/group_vars/all/sysadmin.yml
Normal file
6
ansible/group_vars/all/sysadmin.yml
Normal file
@ -0,0 +1,6 @@
|
||||
--- # This variable is used by:
|
||||
# playbooks/podman.yml
|
||||
# playbooks/ssh.yml
|
||||
# roles/bleetube.dotfiles
|
||||
|
||||
sysadmin_username: jim
|
32
ansible/group_vars/all/versions.yml
Normal file
32
ansible/group_vars/all/versions.yml
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
# https://github.com/hoytech/strfry/tags
|
||||
strfry_version: master
|
||||
|
||||
# https://git.v0l.io/Kieran/snort/releases
|
||||
#snort_version: v0.1.20 # We must merge our branch for updates, the role follows a branch
|
||||
|
||||
# https://github.com/prometheus/node_exporter/releases
|
||||
node_exporter_version: latest
|
||||
|
||||
# https://github.com/prometheus/prometheus/releases
|
||||
prometheus_version: latest
|
||||
|
||||
# https://github.com/prometheus/blackbox_exporter/releases
|
||||
blackbox_exporter_version: '0.25.0'
|
||||
|
||||
# https://github.com/prometheus/alertmanager/releases
|
||||
alertmanager_version: latest
|
||||
|
||||
# https://github.com/grafana/grafana/releases
|
||||
#grafana_version: latest # collection writes deprecated alerting config
|
||||
grafana_version: '10.4.3'
|
||||
|
||||
# https://git.xenrox.net/~xenrox/ntfy-alertmanager
|
||||
ntfy_alertmanager_version: v0.3.0
|
||||
|
||||
# https://go.dev/dl/
|
||||
# https://github.com/gantsign/ansible-role-golang/tree/master/vars/versions
|
||||
#golang_version: "1.22.2" # gantsign.golang, default should be latest
|
||||
|
||||
# https://grafana.com/grafana/dashboards/1860-node-exporter-full/?tab=revisions
|
||||
grafana_node_dashboard_version: 36
|
13
ansible/group_vars/mail/disposable-mail.yml
Normal file
13
ansible/group_vars/mail/disposable-mail.yml
Normal file
@ -0,0 +1,13 @@
|
||||
--- # bleetube.disposable-mail role variables
|
||||
dkim_selector: mail
|
||||
dkim_key_path: /etc/dkimkeys
|
||||
postfix_hostname: "{{ dkim_selector }}.{{ postfix_domain }}"
|
||||
imap_bind_address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}"
|
||||
postfix_smtpd_tls_cert_file: "/var/acme/certificates/{{ postfix_hostname }}.crt"
|
||||
postfix_smtpd_tls_key_file: "/var/acme/certificates/{{ postfix_hostname }}.key"
|
||||
postfix_smtpd_tls_dh1024_param_file: /etc/ssl/dhparams.pem
|
||||
postfix_maildir_user: maildir
|
||||
postfix_inet_interfaces: all
|
||||
postfix_virtual_mailbox_base: /var/vmail
|
||||
postfix_mynetworks:
|
||||
- 127.0.0.0/8
|
14
ansible/group_vars/observability/alertmanager.yml
Normal file
14
ansible/group_vars/observability/alertmanager.yml
Normal file
@ -0,0 +1,14 @@
|
||||
--- # prometheus.prometheus.alertmanager role variables
|
||||
alertmanager_web_listen_address: "127.0.0.1:9093"
|
||||
alertmanager_web_external_url: "https://{{ inventory_hostname }}/alertmanager/"
|
||||
|
||||
# The overlapping yaml templating requires us to use {% raw %} and {% endraw %}.
|
||||
alertmanager_receivers:
|
||||
- name: ntfy
|
||||
webhook_configs:
|
||||
- url: "{{ ntfy_alertmanager_base_url }}/{{ ntfy_alertmanager_topic_name }}"
|
||||
|
||||
# https://prometheus.io/docs/alerting/latest/configuration/#route
|
||||
alertmanager_route:
|
||||
group_by: [ 'instance']
|
||||
receiver: ntfy
|
32
ansible/group_vars/observability/blackbox_exporter.yml
Normal file
32
ansible/group_vars/observability/blackbox_exporter.yml
Normal file
@ -0,0 +1,32 @@
|
||||
--- # prometheus.prometheus.blacbox_exporter role variables
|
||||
# https://github.com/prometheus/blackbox_exporter
|
||||
blackbox_exporter_web_listen_address: "127.0.0.1:8031"
|
||||
|
||||
blackbox_exporter_configuration_modules:
|
||||
http_2xx:
|
||||
prober: http
|
||||
timeout: 5s
|
||||
http:
|
||||
valid_status_codes: []
|
||||
method: GET
|
||||
preferred_ip_protocol: ip4
|
||||
fail_if_not_ssl: true
|
||||
|
||||
tcp_probe:
|
||||
prober: tcp
|
||||
timeout: 5s
|
||||
|
||||
smtp_starttls:
|
||||
prober: tcp
|
||||
timeout: 5s
|
||||
tcp:
|
||||
query_response:
|
||||
- expect: "^220 ([^ ]+) ESMTP (.+)$"
|
||||
- send: "EHLO prober\r"
|
||||
- expect: "^250-STARTTLS"
|
||||
- send: "STARTTLS\r"
|
||||
- expect: "^220"
|
||||
- starttls: true
|
||||
- send: "EHLO prober\r"
|
||||
- expect: "^250-AUTH"
|
||||
- send: "QUIT\r"
|
46
ansible/group_vars/observability/grafana.yml
Normal file
46
ansible/group_vars/observability/grafana.yml
Normal file
@ -0,0 +1,46 @@
|
||||
--- # grafana.grafana.grafana role variables
|
||||
# https://github.com/grafana/grafana-ansible-collection
|
||||
# https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/
|
||||
|
||||
grafana_url: "https://{{ inventory_hostname }}" # required for oauth callbacks
|
||||
grafana_port: 8043
|
||||
#grafana_database:
|
||||
# type: postgres # This only really makes sense if postgres is already in use on this sytem. Otherwise, sqlite is fine.
|
||||
# host: /var/run/postgresql
|
||||
# name: grafana
|
||||
# user: grafana
|
||||
# password: "{{ lookup('ansible.builtin.env', 'GRAFANA_POSTGRES_PASSWORD') }}"
|
||||
# max_idle_conn: 2
|
||||
# max_open_conn: ""
|
||||
# log_queries: ""
|
||||
grafana_analytics:
|
||||
reporting_enabled: false
|
||||
grafana_snapshots:
|
||||
external_enabled: false
|
||||
grafana_users:
|
||||
allow_sign_up: false
|
||||
grafana_datasources:
|
||||
- name: "{{ inventory_hostname }}"
|
||||
type: "prometheus"
|
||||
# Note that this depends on the prometheus_web_external_url variable in ansible/sableye/install-prometheus.yml.
|
||||
url: "http://localhost:9090/prometheus"
|
||||
access: "proxy" # "server" in the UI
|
||||
# basicAuth: true
|
||||
# basicAuthUser: "admin"
|
||||
# basicAuthPassword: "password"
|
||||
isDefault: true
|
||||
jsonData:
|
||||
tlsAuth: false
|
||||
tlsAuthWithCACert: false
|
||||
tlsSkipVerify: true
|
||||
|
||||
grafana_dashboards:
|
||||
- dashboard_id: 1860 # https://grafana.com/grafana/dashboards/1860-node-exporter-full/
|
||||
revision_id: "{{ grafana_node_dashboard_version|default(31) }}"
|
||||
datasource: "{{ inventory_hostname }}"
|
||||
|
||||
# grafana_smtp:
|
||||
# host:
|
||||
# user:
|
||||
# password:
|
||||
# from_address:
|
7
ansible/group_vars/observability/ntfy.yml
Normal file
7
ansible/group_vars/observability/ntfy.yml
Normal file
@ -0,0 +1,7 @@
|
||||
--- # bleetube.ntfy and bleetube.ntfy-alertmanager role variables
|
||||
ntfy_alertmanager_base_url: "https://{{ inventory_hostname }}:4433"
|
||||
ntfy_alertmanager_http_address: "127.0.0.1:8033"
|
||||
#ntfy_web_root: /ntfy
|
||||
|
||||
# fix gantsign.golang bug
|
||||
golang_download_dir: "{{ x_ansible_download_dir | default(ansible_env.HOME + '/.ansible/tmp/downloads') }}"
|
18
ansible/group_vars/observability/prometheus.yml
Normal file
18
ansible/group_vars/observability/prometheus.yml
Normal file
@ -0,0 +1,18 @@
|
||||
--- # prometheus.prometheus.prometheus role variables
|
||||
prometheus_web_listen_address: "127.0.0.1:9090"
|
||||
# https://www.robustperception.io/using-external-urls-and-proxies-with-prometheus/
|
||||
# Note that the grafana_datasources variable also needs to reflect this path.
|
||||
prometheus_web_external_url: "https://{{ inventory_hostname }}/prometheus/"
|
||||
# https://prometheus.io/docs/prometheus/latest/storage/#operational-aspects
|
||||
prometheus_storage_retention: 365d
|
||||
prometheus_storage_retention_size: 10GB
|
||||
#prometheus_config_flags_extra:
|
||||
# storage.tsdb.wal-compression: # enabled by default
|
||||
|
||||
prometheus_alertmanager_config:
|
||||
- path_prefix: alertmanager/
|
||||
static_configs:
|
||||
- targets: [ "127.0.0.1:9093" ]
|
||||
|
||||
prometheus_alert_rules_files:
|
||||
- "/etc/prometheus/rules/*.rules"
|
12
ansible/host_vars/gabite.bitcoiner.social/certbot.yml
Normal file
12
ansible/host_vars/gabite.bitcoiner.social/certbot.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
certbot_certs:
|
||||
- domains:
|
||||
- "{{ inventory_hostname }}"
|
||||
# - bitcoiner.social
|
||||
# - nostr.bitcoiner.social
|
||||
|
||||
webroot: /var/www/html
|
||||
certbot_admin_email: sysadmin.aghast522@passinbox.com
|
||||
certbot_auto_renew_hour: "1"
|
||||
certbot_auto_renew_minute: "13"
|
||||
certbot_create_extra_args: ""
|
@ -0,0 +1,2 @@
|
||||
--- # bleetube.disposable-mail role variables
|
||||
postfix_domain: bitcoiner.social
|
4
ansible/host_vars/gabite.bitcoiner.social/linux.yml
Normal file
4
ansible/host_vars/gabite.bitcoiner.social/linux.yml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
sysadmin_packages_custom:
|
||||
- wireguard
|
||||
- wireguard-tools
|
50
ansible/host_vars/gabite.bitcoiner.social/strfry.yml
Normal file
50
ansible/host_vars/gabite.bitcoiner.social/strfry.yml
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
nginx_strfry_https_port: 443
|
||||
nginx_strfry_metrics_port: 4434
|
||||
strfry_metrics_plugin_port: 8034
|
||||
nginx_strfry_domain: bitcoiner.social
|
||||
strfry_policies_enabled: yes
|
||||
strfry_relay:
|
||||
bind: "127.0.0.1"
|
||||
port: 8000
|
||||
nofiles: 65536
|
||||
realIpHeader: x-forwarded-for
|
||||
info:
|
||||
name: bitcoiner.social
|
||||
description: A fast, reliable, and up-to-date nostr relay with monitored server availability and nightly off-site backups.
|
||||
pubkey: ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b
|
||||
contact: nostr@bitcoiner.social
|
||||
maxWebsocketPayloadSize: 131072
|
||||
autoPingSeconds: 55
|
||||
enableTcpKeepalive: no
|
||||
queryTimesliceBudgetMicroseconds: 10000
|
||||
maxFilterLimit: 500
|
||||
maxSubsPerConnection: 20
|
||||
writePolicy:
|
||||
plugin: /var/lib/strfry/complex-entrypoint.ts
|
||||
lookbackSeconds: 0
|
||||
compression:
|
||||
enabled: yes
|
||||
slidingWindow: yes
|
||||
logging:
|
||||
dumpInAll: no
|
||||
dumpInEvents: no
|
||||
dumpInReqs: no
|
||||
dbScanPerf: no
|
||||
invalidEvents: yes
|
||||
numThreads:
|
||||
ingester: 3
|
||||
reqWorker: 3
|
||||
reqMonitor: 3
|
||||
negentropy: 2
|
||||
negentropy:
|
||||
enabled: yes
|
||||
maxSyncEvents: 1000000
|
||||
strfry_events:
|
||||
maxEventSize: 65536
|
||||
rejectEventsNewerThanSeconds: 900
|
||||
rejectEventsOlderThanSeconds: 94608000
|
||||
rejectEphemeralEventsOlderThanSeconds: 60
|
||||
ephemeralEventsLifetimeSeconds: 300
|
||||
maxNumTags: 2000
|
||||
maxTagValSize: 1024
|
11
ansible/host_vars/garchomp.bitcoiner.social/acme-lego.yml
Normal file
11
ansible/host_vars/garchomp.bitcoiner.social/acme-lego.yml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
# prefer lego because synapse uses a version of python3-zope.interface that conflicts with certbot
|
||||
acme_email: acme@bitcoiner.social
|
||||
acme_domains:
|
||||
- { domain: "{{ inventory_hostname }}", provider: namecheap }
|
||||
- { domain: bitcoiner.social, provider: namecheap }
|
||||
- { domain: mail.bitcoiner.social, provider: namecheap }
|
||||
- { domain: nostr.bitcoiner.social, provider: namecheap }
|
||||
- { domain: snort.bitcoiner.social, provider: namecheap }
|
||||
- { domain: cast.bitcoiner.social, provider: namecheap }
|
||||
- { domain: news.bitcoiner.social, provider: namecheap }
|
42
ansible/host_vars/garchomp.bitcoiner.social/caddy.yml
Normal file
42
ansible/host_vars/garchomp.bitcoiner.social/caddy.yml
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
caddy_systemd_capabilities_enabled: yes
|
||||
caddy_config: |
|
||||
# node_exporter
|
||||
{{ inventory_hostname }}:4430 {
|
||||
reverse_proxy http://127.0.0.1:8030
|
||||
}
|
||||
|
||||
# observability
|
||||
garchomp.bitcoiner.social {
|
||||
|
||||
# grafana
|
||||
reverse_proxy http://127.0.0.1:8043
|
||||
handle /api/live {
|
||||
reverse_proxy http://127.0.0.1:8043
|
||||
}
|
||||
handle /public/* {
|
||||
root * /usr/share/grafana
|
||||
file_server
|
||||
}
|
||||
|
||||
# prometheus
|
||||
handle /prometheus/* {
|
||||
reverse_proxy http://127.0.0.1:9090
|
||||
#header_up Host {host}
|
||||
}
|
||||
|
||||
# alertmanager
|
||||
handle /alertmanager/* {
|
||||
reverse_proxy http://127.0.0.1:9093
|
||||
}
|
||||
}
|
||||
|
||||
# blackbox_exporter
|
||||
garchomp.bitcoiner.social:4431 {
|
||||
reverse_proxy http://127.0.0.1:8031
|
||||
}
|
||||
|
||||
# ntfy-alertmanager
|
||||
garchomp.bitcoiner.social:4433 {
|
||||
reverse_proxy http://127.0.0.1:8033
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
--- # bleetube.disposable-mail role variables
|
||||
postfix_domain: bitcoiner.social
|
5
ansible/host_vars/garchomp.bitcoiner.social/doas.yml
Normal file
5
ansible/host_vars/garchomp.bitcoiner.social/doas.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
# permit nopass blee as postgres
|
||||
doas_permit_list:
|
||||
- username: blee
|
||||
role: opendkim
|
3
ansible/host_vars/garchomp.bitcoiner.social/nodejs.yml
Normal file
3
ansible/host_vars/garchomp.bitcoiner.social/nodejs.yml
Normal file
@ -0,0 +1,3 @@
|
||||
# habla.news requirements
|
||||
npm_packages:
|
||||
- pnpm
|
6
ansible/host_vars/garchomp.bitcoiner.social/snort.yml
Normal file
6
ansible/host_vars/garchomp.bitcoiner.social/snort.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
snort_repository_url: https://github.com/bleetube/snort.git
|
||||
snort_version: bitcoiner.social
|
||||
nginx_snort_domain: snort.bitcoiner.social
|
||||
nginx_snort_port: 443
|
||||
#snort_always_build: yes
|
50
ansible/host_vars/garchomp.bitcoiner.social/strfry.yml
Normal file
50
ansible/host_vars/garchomp.bitcoiner.social/strfry.yml
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
nginx_strfry_https_port: 443
|
||||
nginx_strfry_metrics_port: 4434
|
||||
strfry_metrics_plugin_port: 8034
|
||||
nginx_strfry_domain: bitcoiner.social
|
||||
strfry_policies_enabled: no
|
||||
strfry_relay:
|
||||
bind: "127.0.0.1"
|
||||
port: 8030
|
||||
nofiles: 65536
|
||||
realIpHeader: x-forwarded-for
|
||||
info:
|
||||
name: bitcoiner.social
|
||||
description: A fast, reliable, and up-to-date nostr relay with monitored server availability and nightly off-site backups.
|
||||
pubkey: ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b
|
||||
contact: nostr@bitcoiner.social
|
||||
maxWebsocketPayloadSize: 131072
|
||||
autoPingSeconds: 55
|
||||
enableTcpKeepalive: no
|
||||
queryTimesliceBudgetMicroseconds: 10000
|
||||
maxFilterLimit: 500
|
||||
maxSubsPerConnection: 20
|
||||
writePolicy:
|
||||
plugin: /var/lib/strfry/copmlex-entrypoint.ts
|
||||
lookbackSeconds: 0
|
||||
compression:
|
||||
enabled: yes
|
||||
slidingWindow: yes
|
||||
logging:
|
||||
dumpInAll: no
|
||||
dumpInEvents: no
|
||||
dumpInReqs: no
|
||||
dbScanPerf: no
|
||||
invalidEvents: yes
|
||||
numThreads:
|
||||
ingester: 3
|
||||
reqWorker: 3
|
||||
reqMonitor: 3
|
||||
negentropy: 2
|
||||
negentropy:
|
||||
enabled: no
|
||||
maxSyncEvents: 1000000
|
||||
strfry_events:
|
||||
maxEventSize: 65536
|
||||
rejectEventsNewerThanSeconds: 900
|
||||
rejectEventsOlderThanSeconds: 94608000
|
||||
rejectEphemeralEventsOlderThanSeconds: 60
|
||||
ephemeralEventsLifetimeSeconds: 300
|
||||
maxNumTags: 2000
|
||||
maxTagValSize: 1024
|
16
ansible/hosts.cfg
Normal file
16
ansible/hosts.cfg
Normal file
@ -0,0 +1,16 @@
|
||||
[observability]
|
||||
garchomp.bitcoiner.social
|
||||
|
||||
[mail]
|
||||
garchomp.bitcoiner.social
|
||||
|
||||
[certbot]
|
||||
gabite.bitcoiner.social
|
||||
|
||||
[strfry]
|
||||
garchomp.bitcoiner.social
|
||||
gabite.bitcoiner.social
|
||||
|
||||
[all:vars]
|
||||
ansible_user=jim
|
||||
ansible_become_method=doas
|
5
ansible/playbooks/caddy.yml
Normal file
5
ansible/playbooks/caddy.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- hosts: all
|
||||
roles:
|
||||
- role: caddy
|
||||
become: yes
|
40
ansible/playbooks/group_tasks/certbot/main.yml
Normal file
40
ansible/playbooks/group_tasks/certbot/main.yml
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
- hosts: certbot
|
||||
|
||||
pre_tasks:
|
||||
- import_tasks: tasks/dhparams.yml
|
||||
tags: dhparams
|
||||
|
||||
- name: Loop through the Certbot certificate list to configure nginx for each ACME domain
|
||||
include_tasks: tasks/nginx_conf.yml
|
||||
loop: "{{ certbot_certs }}"
|
||||
loop_control:
|
||||
loop_var: acme_domain
|
||||
when: certbot_certs is defined
|
||||
tags: nginx
|
||||
|
||||
- name: Ensure html directory for certbot challenge
|
||||
ansible.builtin.file:
|
||||
path: /var/www/html
|
||||
state: directory
|
||||
mode: 0755
|
||||
become: yes
|
||||
|
||||
- name: Remove default nginx page so it doesn't interfere with certbot
|
||||
ansible.builtin.file:
|
||||
path: /etc/nginx/conf.d/default.conf
|
||||
state: absent
|
||||
become: yes
|
||||
|
||||
roles:
|
||||
- role: geerlingguy.certbot
|
||||
become: yes
|
||||
tags: certbot
|
||||
vars:
|
||||
certbot_auto_renew: true
|
||||
certbot_create_if_missing: true
|
||||
|
||||
tasks:
|
||||
- import_tasks: tasks/node_exporter.yml
|
||||
tags: node_exporter
|
||||
become: yes
|
22
ansible/playbooks/group_tasks/certbot/tasks/dhparams.yml
Normal file
22
ansible/playbooks/group_tasks/certbot/tasks/dhparams.yml
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
- name: Register pre-generated dhparams
|
||||
ansible.builtin.stat:
|
||||
path: files/dhparams.pem
|
||||
delegate_to: localhost
|
||||
register: dhparams
|
||||
tags: dhparams
|
||||
|
||||
- name: Use pre-generated dhparams to reduce deployment time by several minutes.
|
||||
ansible.builtin.copy:
|
||||
src: dhparams.pem
|
||||
dest: /etc/ssl/
|
||||
force: false
|
||||
when: dhparams.stat.exists
|
||||
become: yes
|
||||
tags: dhparams
|
||||
|
||||
- name: Generate Diffie-Hellman parameters with the default size (4096 bits)
|
||||
community.crypto.openssl_dhparam:
|
||||
path: /etc/ssl/dhparams.pem
|
||||
become: yes
|
||||
tags: dhparams
|
70
ansible/playbooks/group_tasks/certbot/tasks/nginx_conf.yml
Normal file
70
ansible/playbooks/group_tasks/certbot/tasks/nginx_conf.yml
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
- name: Configure nginx for certbot challenge and TLSv1.2 for {{ acme_domain.domains[0] }}
|
||||
ansible.builtin.import_role:
|
||||
name: nginx_core.nginx_config
|
||||
allow_duplicates: true
|
||||
tags: nginx
|
||||
become: yes
|
||||
vars:
|
||||
nginx_config_dhparam: /etc/ssl/dhparams.pem
|
||||
nginx_config_http_template_enable: true
|
||||
nginx_config_http_template:
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/acme_{{ acme_domain.domains[0] }}.conf"
|
||||
backup: false
|
||||
config:
|
||||
core:
|
||||
server_name: "{{ acme_domain.domains[0] }}"
|
||||
ssl:
|
||||
certificate: "/etc/letsencrypt/live/{{ acme_domain.domains[0] }}/fullchain.pem"
|
||||
certificate_key: "/etc/letsencrypt/live/{{ acme_domain.domains[0] }}/privkey.pem"
|
||||
trusted_certificate: "/etc/letsencrypt/live/{{ acme_domain.domains[0] }}/chain.pem"
|
||||
ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||
dhparam: "{{ nginx_config_dhparam }}"
|
||||
ecdh_curve: X25519:secp521r1:secp384r1
|
||||
prefer_server_ciphers: true
|
||||
protocols:
|
||||
- TLSv1.2
|
||||
- TLSv1.3
|
||||
session_cache:
|
||||
shared:
|
||||
name: "{{ acme_domain.domains[0] }}"
|
||||
size: 1M
|
||||
session_tickets: false
|
||||
session_timeout: 1d
|
||||
ocsp: true
|
||||
ocsp_cache:
|
||||
name: cache
|
||||
size: 64k
|
||||
stapling: true
|
||||
stapling_verify: true
|
||||
ocsp_responder: http://r3.o.lencr.org
|
||||
headers:
|
||||
add_headers:
|
||||
- name: Strict-Transport-Security
|
||||
value: '"max-age=7776000"'
|
||||
always: true
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/http_{{ acme_domain.domains[0] }}.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
server_name: "{{ acme_domain.domains[0] }}"
|
||||
listen:
|
||||
- address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}:80"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
rewrite: # https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite
|
||||
return:
|
||||
url: "https://{{ acme_domain.domains[0] }}$request_uri"
|
||||
code: 301
|
||||
- location: /.well-known/acme-challenge/
|
||||
core:
|
||||
alias: /var/www/html/.well-known/acme-challenge/
|
||||
try_files:
|
||||
files: $uri
|
||||
code: =404
|
@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Configure nginx
|
||||
ansible.builtin.import_role:
|
||||
name: nginx_core.nginx_config
|
||||
tags: nginx
|
||||
vars:
|
||||
nginx_config_http_template_enable: true
|
||||
nginx_config_http_template:
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/node-exp_{{ inventory_hostname }}.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
listen:
|
||||
# Use IP address in Ansible facts. https://stackoverflow.com/q/39819378/9290
|
||||
- address: "{{ default_interface_ipv4_address|default(ansible_default_ipv4.address) }}"
|
||||
ssl: true
|
||||
port: 4430
|
||||
include:
|
||||
- "/etc/nginx/acme_{{ inventory_hostname }}.conf"
|
||||
locations:
|
||||
- location: /
|
||||
proxy:
|
||||
pass: "http://127.0.0.1:8030"
|
||||
http_version: 1.1
|
28
ansible/playbooks/group_tasks/lego.yml
Normal file
28
ansible/playbooks/group_tasks/lego.yml
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
- hosts: all, !proxmox
|
||||
|
||||
pre_tasks:
|
||||
- name: Gather facts about the localhost
|
||||
ansible.builtin.setup:
|
||||
delegate_to: localhost
|
||||
|
||||
# - name: Print
|
||||
# debug:
|
||||
# var: ansible_facts
|
||||
# - name: Assert that the local machine is squirtle
|
||||
# ansible.builtin.assert:
|
||||
# that:
|
||||
# - ansible_facts['nodename'] == 'squirtle'
|
||||
# msg: "This playbook should only be run from squirtle."
|
||||
|
||||
roles:
|
||||
- role: bleetube.lego
|
||||
when: ansible_nodename == 'squirtle'
|
||||
|
||||
tasks:
|
||||
- name: Reload nginx
|
||||
ansible.builtin.service:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
tags: nginx
|
||||
become: yes
|
25
ansible/playbooks/group_tasks/mail.yml
Normal file
25
ansible/playbooks/group_tasks/mail.yml
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
- hosts: mail
|
||||
become: yes
|
||||
|
||||
pre_tasks:
|
||||
- name: Gather facts about the localhost
|
||||
ansible.builtin.setup:
|
||||
delegate_to: localhost
|
||||
tags: lego
|
||||
|
||||
roles:
|
||||
- role: bleetube.lego
|
||||
when: ansible_nodename == 'squirtle'
|
||||
tags: lego
|
||||
|
||||
- role: bleetube.disposable-mail
|
||||
tags: mail
|
||||
|
||||
tasks:
|
||||
- name: Reload dovecot
|
||||
ansible.builtin.service:
|
||||
name: dovecot
|
||||
state: reloaded
|
||||
tags: lego
|
||||
become: yes
|
7
ansible/playbooks/group_tasks/nginx.yml
Normal file
7
ansible/playbooks/group_tasks/nginx.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- hosts: nginx, certbot
|
||||
|
||||
roles:
|
||||
- role: nginxinc.nginx_core.nginx
|
||||
tags: nginx
|
||||
become: yes
|
24
ansible/playbooks/group_tasks/observability.yml
Normal file
24
ansible/playbooks/group_tasks/observability.yml
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
- hosts: observability
|
||||
|
||||
roles:
|
||||
- role: prometheus.prometheus.alertmanager
|
||||
become: true
|
||||
tags: alertmanager
|
||||
- role: prometheus.prometheus.blackbox_exporter
|
||||
become: true
|
||||
tags: blackbox_exporter
|
||||
- role: prometheus.prometheus.prometheus
|
||||
become: true
|
||||
tags: prometheus
|
||||
- role: grafana.grafana.grafana
|
||||
become: true
|
||||
tags: grafana
|
||||
- role: bleetube.ntfy
|
||||
tags: ntfy
|
||||
become: true
|
||||
- role: bleetube.ntfy-alertmanager
|
||||
tags: ntfy-alertmanager
|
||||
- role: caddy
|
||||
tags: caddy
|
||||
become: true
|
22
ansible/playbooks/group_tasks/postgresql.yml
Normal file
22
ansible/playbooks/group_tasks/postgresql.yml
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
- hosts: postgresql, matrix
|
||||
become: yes
|
||||
|
||||
vars:
|
||||
postgresql_apt_key_url: "https://www.postgresql.org/media/keys/ACCC4CF8.asc"
|
||||
|
||||
# https://github.com/ANXS/postgresql/issues/523
|
||||
ansible_python_interpreter: "/usr/bin/python3"
|
||||
|
||||
pre_tasks:
|
||||
- name: Assert all database passwords have been configured.
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.pass is defined
|
||||
- item.pass != ''
|
||||
fail_msg: "FAILED: Database password for {{ item.name }} is not configured."
|
||||
loop: "{{ postgresql_users }}"
|
||||
no_log: yes
|
||||
|
||||
roles:
|
||||
- role: anxs.postgresql
|
5
ansible/playbooks/group_tasks/strfry.yml
Normal file
5
ansible/playbooks/group_tasks/strfry.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- hosts: strfry
|
||||
roles:
|
||||
- role: bleetube.strfry
|
||||
tags: strfry
|
7
ansible/playbooks/group_tasks/wireguard.yml
Normal file
7
ansible/playbooks/group_tasks/wireguard.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- hosts: wireguard
|
||||
|
||||
roles:
|
||||
- role: bleetube.wireguard
|
||||
become: true
|
||||
tags: wireguard
|
@ -0,0 +1,9 @@
|
||||
# Manual steps
|
||||
|
||||
Not here:
|
||||
|
||||
* `/etc/wireguard/lanturn.conf`
|
||||
* `/var/lib/strfry/` stuff:
|
||||
* pubkeys.db
|
||||
* pubkeys.banned.ts
|
||||
* pubkeys.premium.json
|
@ -0,0 +1,94 @@
|
||||
#
|
||||
# Ansible managed
|
||||
#
|
||||
|
||||
upstream strfry {
|
||||
server 127.0.0.1:8000;
|
||||
}
|
||||
|
||||
map $http_accept $is_accept_nostr {
|
||||
default "0";
|
||||
"application/nostr+json" "1";
|
||||
}
|
||||
|
||||
# Combine the Upgrade and Accept headers
|
||||
map "$http_upgrade:$is_accept_nostr" $is_socket_or_nostr {
|
||||
default "0";
|
||||
"websocket:0" "1";
|
||||
"websocket:1" "1";
|
||||
":1" "1";
|
||||
}
|
||||
map "$http_upgrade:$is_accept_nostr" $is_nostr_and_not_socket {
|
||||
default "0";
|
||||
"websocket:0" "0";
|
||||
"websocket:1" "0";
|
||||
":1" "1";
|
||||
}
|
||||
|
||||
server {
|
||||
client_max_body_size 0;
|
||||
include /etc/nginx/acme_bitcoiner.social.conf;
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
access_log off;
|
||||
|
||||
location / {
|
||||
|
||||
# Intercept NIP-11
|
||||
if ($is_nostr_and_not_socket = 1) {
|
||||
return 200 '{"contact":"nostr@bitcoiner.social","description":"A fast, reliable, and up-to-date nostr relay with monitored server availability and nightly off-site backups.","name":"bitcoiner.social","pubkey":"ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b","software":"git+https://github.com/hoytech/strfry.git","supported_nips":[1,2,4,9,11,12,16,20,22,28,33,40],"version":"0.9.6","icon":"https://bitcoiner.social/favicon.ico"}';
|
||||
}
|
||||
|
||||
# Web Browsers
|
||||
# if ($is_socket_or_nostr = 0) {
|
||||
# return 301 https://www.offchain.pub;
|
||||
# }
|
||||
|
||||
|
||||
proxy_connect_timeout 3m;
|
||||
proxy_http_version 1.1;
|
||||
proxy_pass http://strfry;
|
||||
proxy_read_timeout 3m;
|
||||
proxy_send_timeout 3m;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
|
||||
}
|
||||
location /static {
|
||||
alias /var/www/static;
|
||||
}
|
||||
location /.well-known/nostr.json {
|
||||
alias /var/www/.well-known/nostr.json;
|
||||
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
|
||||
|
||||
}
|
||||
location /favicon.ico {
|
||||
alias /var/www/static/favicon96.png;
|
||||
|
||||
|
||||
}
|
||||
location = /.well-known/matrix/server {
|
||||
return 200 '{"m.server":"matrix.bitcoiner.social:443"}'
|
||||
;
|
||||
|
||||
|
||||
}
|
||||
location ~ ^(/_matrix|/_synapse/client) {
|
||||
return 301 https://matrix.bitcoiner.social$request_uri;
|
||||
|
||||
|
||||
}
|
||||
location /.well-known/lnurlp/ {
|
||||
proxy_pass http://10.19.21.42:8037;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
server {
|
||||
include /etc/nginx/acme_nostr.bitcoiner.social.conf;
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
access_log off;
|
||||
|
||||
location / {
|
||||
|
||||
# Intercept NIP-11
|
||||
if ($is_nostr_and_not_socket = 1) {
|
||||
return 200 '{"contact":"nostr@bitcoiner.social","description":"A fast, reliable, and up-to-date nostr relay with monitored server availability and nightly off-site backups.","name":"bitcoiner.social","pubkey":"ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b","software":"git+https://github.com/hoytech/strfry.git","supported_nips":[1,2,4,9,11,12,16,20,22,28,33,40],"version":"0.9.6","icon":"https://bitcoiner.social/favicon.ico"}';
|
||||
}
|
||||
|
||||
# Web Browsers
|
||||
#if ($is_socket_or_nostr = 0) {
|
||||
# return 301 https://www.offchain.pub;
|
||||
#}
|
||||
|
||||
|
||||
proxy_connect_timeout 3m;
|
||||
proxy_http_version 1.1;
|
||||
proxy_pass http://strfry;
|
||||
proxy_read_timeout 3m;
|
||||
proxy_send_timeout 3m;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
server {
|
||||
listen 127.0.0.1:9080;
|
||||
|
||||
access_log off;
|
||||
|
||||
location / {
|
||||
|
||||
# Intercept NIP-11
|
||||
if ($is_nostr_and_not_socket = 1) {
|
||||
return 200 '{"contact":"nostr@bitcoiner.social","description":"A fast, reliable, and up-to-date nostr relay with monitored server availability and nightly off-site backups.","name":"bitcoiner.social","pubkey":"ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b","software":"git+https://github.com/hoytech/strfry.git","supported_nips":[1,2,4,9,11,12,16,20,22,28,33,40],"version":"0.9.6","icon":"https://bitcoiner.social/favicon.ico"}';
|
||||
}
|
||||
|
||||
proxy_connect_timeout 3m;
|
||||
proxy_http_version 1.1;
|
||||
proxy_pass http://strfry;
|
||||
proxy_read_timeout 3m;
|
||||
proxy_send_timeout 3m;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
|
||||
}
|
||||
location /favicon.ico {
|
||||
alias /var/www/static/favicon96.png;
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"contact":"nostr@bitcoiner.social","description":"A fast, reliable, and up-to-date nostr relay with monitored server availability and nightly off-site backups.","name":"bitcoiner.social","pubkey": "ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b","software":"git+https://github.com/hoytech/strfry.git","supported_nips":[1,2,4,9,11,12,16,20,22,28,33,40],"version":"0.9.6","icon":"https://bitcoiner.social/favicon.ico"}
|
@ -0,0 +1 @@
|
||||
{"names": {"_": "ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b", "admin": "ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b", "blee": "69a0a0910b49a1dbfbc4e4f10df22b5806af5403a228267638f2e908c968228d", "heady_wook": "dee97518077808091745015441953ad814c22d4eaeaba6996385a3d7dc3d191e", "ToxiKat27": "12cfc2ec5a39a39d02f921f77e701dbc175b6287f22ddf0247af39706967f1d9", "ruti": "6d2a43408ee74c2e35fd7f4eb3288b73cfafd88cd2d059e6c05058e676fc5290", "harmony": "3c0a6d434e28f68a4b31d38ece15b33c1518aa58299b7d315426d716c5493ed4", "minnaar": "8f9efe5e5b5b13fa967568f037811e1d8fa8ece3101a8c65ad0a5d75665fe6da", "arra": "e8c7df1dfef1d97c1acb27e77ef3262faf349bfb333642b778fe15c48bebae52", "natoshi": "bc815d16028c8b012103018596386b77cbd6ef27f10f1992ada4986104df0cab", "testing": "83768054ef906ca493dcf703dd93b73fa71054ec80f83e71e2eb68eb5139e1d6", "w_s_bitcoin": "af2f682f512899852c6654ff065aa405a41d15e2b1cb6c9173123605744f06e6", "flippo": "76482d5e45bc5a595a1501ae4f603ab52191948ebd7899cee9e4ab9e4ecb9987", "bitcoinbabybee": "f6499fae7651521774864664c44dd97d10489d69b222ff2596614cbb15c39278", "OutlanderBC": "bc10bd1557c52725ab7a45e193b8db08fcd889c7b3c89294bbb8df1ad693c8ad", "oz": "f91f2529318cb52308613485d36328214946679c96458c9c8f966fec347ea758", "ZapCats": "c13b48ff647b105a5c41eb7cb48694f93ce903e873fab83c2b96efa7bfa1eb13", "GiveawayGames": "f717d66780a2393589d4e84bf3010d860790234549c2f5260706226905d8922a", "rakan": "b377757fa3efd9d4f56170bd08508872b13680a000be9b19f3c0f6fea3d861bc", "kbbq": "c03be92f51cc5ca98b26bb279a45d670d0ee3c8e0779fa791b22d33f88ed4c59"}, "relays": {"69a0a0910b49a1dbfbc4e4f10df22b5806af5403a228267638f2e908c968228d": ["wss://bitcoiner.social"], "ece3317bf8163930b5dafae50596b740b0608433b78568886a9a712a91a5d59b": ["wss://bitcoiner.social"]}}
|
@ -0,0 +1,181 @@
|
||||
import subprocess
|
||||
import json
|
||||
import csv
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s %(levelname)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
level=logging.INFO
|
||||
)
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(levelname)s: %(message)s')
|
||||
console.setFormatter(formatter)
|
||||
logging.getLogger('').addHandler(console)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_reports(days_since: int = 1):
|
||||
unhandled_reports = 0
|
||||
try:
|
||||
nostr_filter = json.dumps({
|
||||
"kinds":[1984],
|
||||
"since": get_timestamp(days_since)
|
||||
})
|
||||
logging.info(nostr_filter)
|
||||
result = subprocess.run(
|
||||
["strfry", "scan", nostr_filter],
|
||||
capture_output=True, text=True, check=True
|
||||
)
|
||||
strfry_reports = result.stdout.splitlines()
|
||||
logging.info(f"{len(strfry_reports)} reports have been received.")
|
||||
|
||||
report_data = {}
|
||||
for strfry_report in strfry_reports:
|
||||
report = json.loads(strfry_report)
|
||||
|
||||
pubkey = None
|
||||
event = None
|
||||
try:
|
||||
for tag in report.get('tags', []):
|
||||
if tag[0] == 'p':
|
||||
pubkey = tag[1]
|
||||
if tag[0] == 'e':
|
||||
event = (tag[1], tag[2])
|
||||
|
||||
if pubkey and event:
|
||||
if pubkey not in report_data:
|
||||
report_data[pubkey] = {'count': 0, 'events': list()}
|
||||
report_data[pubkey]['count'] += 1
|
||||
report_data[pubkey]['events'].append(event)
|
||||
elif pubkey:
|
||||
if pubkey not in report_data:
|
||||
report_data[pubkey] = {'count': 0, 'events': list()}
|
||||
report_data[pubkey]['count'] += 1
|
||||
else:
|
||||
# TODO: Handle NIP-69, maybe
|
||||
logging.warning(f"Un-handled report type: {report}")
|
||||
unhandled_reports += 1
|
||||
except:
|
||||
logging.error(f"Invalid report: {report}")
|
||||
unhandled_reports += 1
|
||||
|
||||
logging.warning(f"Unhandled reports: {unhandled_reports}")
|
||||
return report_data
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error Output:", e.stderr)
|
||||
|
||||
def display_user_posts(user_id):
|
||||
""" Display posts of a given user """
|
||||
|
||||
try:
|
||||
nostr_filter = json.dumps({"authors":[user_id],"limit":20})
|
||||
result = subprocess.run(
|
||||
["strfry", "scan", nostr_filter],
|
||||
capture_output=True, text=True, check=True
|
||||
)
|
||||
try:
|
||||
posts = json.loads(result.stdout)
|
||||
for post in posts:
|
||||
if post.get("kind") == 1:
|
||||
print(post.get("content", "")[:240])
|
||||
except:
|
||||
print(result.stdout)
|
||||
except subprocess.CalledProcessError as e:
|
||||
#print("Error occurred:", e)
|
||||
#print("Standard Output:", e.stdout)
|
||||
print("Error Output:", e.stderr)
|
||||
|
||||
def delete_user_content(user_id):
|
||||
""" Delete content of a given user """
|
||||
|
||||
try:
|
||||
nostr_filter = json.dumps({"authors":[user_id]})
|
||||
subprocess.run(
|
||||
["strfry", "delete", "--filter", nostr_filter],
|
||||
check=True
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error deleting content:", e)
|
||||
|
||||
def check_user(user_id: str, report_count):
|
||||
display_user_posts(user_id)
|
||||
print(f"Displaying posts for user: {user_id}")
|
||||
|
||||
# ask to ban the user
|
||||
answer = input(f"Do you want to ban this user that was reported {report_count} times? (y/N): ")
|
||||
if answer.lower() == 'y':
|
||||
print(f"Banning user: {user_id}")
|
||||
ban_user(user_id)
|
||||
else:
|
||||
print(f"Skipping ban for user: {user_id}")
|
||||
return
|
||||
|
||||
answer = input("Do you want to delete this user's content? (y/N): ")
|
||||
if answer.lower() == 'y':
|
||||
print(f"Deleting content for user: {user_id}")
|
||||
delete_user_content(user_id)
|
||||
else:
|
||||
print(f"Skipping deletion for user: {user_id}")
|
||||
|
||||
def ban_user(user_id):
|
||||
"""Ban the user by writing the user_id to a file"""
|
||||
|
||||
with open("banned_users.txt", "a") as f:
|
||||
f.write(f"{user_id}\n")
|
||||
|
||||
def is_user_banned(user_id):
|
||||
"""Check if the user is banned"""
|
||||
|
||||
with open("banned_users.txt", "r") as f:
|
||||
banned_users = f.read().splitlines()
|
||||
return user_id in banned_users
|
||||
|
||||
def read_csv_to_list(file_path) -> list:
|
||||
"""
|
||||
Reads a CSV file and returns a list of tuples.
|
||||
Each tuple contains the report count and reported public key from each row.
|
||||
|
||||
:param file_path: Path to the CSV file.
|
||||
:return: List of tuples (report_count, reported_pubkey).
|
||||
"""
|
||||
|
||||
data = []
|
||||
with open(file_path, 'r') as file:
|
||||
reader = csv.reader(file)
|
||||
for row in reader:
|
||||
# Convert the first element (count) to an integer
|
||||
report_count = int(row[0])
|
||||
reported_pubkey = row[1]
|
||||
data.append((report_count, reported_pubkey))
|
||||
logging.info(f"{len(data)} npubs have been reported during this period.")
|
||||
return data
|
||||
|
||||
def get_timestamp(days_ago: int) -> int:
|
||||
"""
|
||||
Returns a Unix timestamp for a given number of days ago.
|
||||
|
||||
:param days_ago: Number of days ago.
|
||||
:return: Unix timestamp.
|
||||
"""
|
||||
time_since = datetime.now() - timedelta(days=days_ago)
|
||||
return int(time.mktime(time_since.timetuple()))
|
||||
|
||||
|
||||
def main():
|
||||
unsorted_reports = get_reports(7)
|
||||
sorted_reports = sorted(unsorted_reports.items(), key=lambda x: x[1]['count'], reverse=True)
|
||||
reports = dict(sorted_reports)
|
||||
for reported_npub in reports:
|
||||
report = reports[reported_npub]
|
||||
if report['count'] > 5:
|
||||
for event in report['events']:
|
||||
print(event)
|
||||
check_user(reported_npub, report['count'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# compact
|
||||
doas -u strfry strfry compact strfry-db/compact.mdb
|
||||
systemctl stop strfry
|
||||
mv -v strfry-db/compact.mdb strfry-db/data.mdb
|
||||
systemctl start strfry
|
@ -0,0 +1,68 @@
|
||||
import { readLines } from 'https://deno.land/std@0.201.0/io/mod.ts';
|
||||
|
||||
import { DB } from "https://deno.land/x/sqlite@v3.8/mod.ts";
|
||||
|
||||
const db = new DB("pubkeys.db");
|
||||
const subscriberResults = db.query("SELECT pubkey FROM subscribers ORDER BY pubkey DESC") as string[][];
|
||||
const foafResults = db.query("SELECT DISTINCT foaf_pubkey FROM foaf ORDER BY foaf_pubkey DESC") as string[][];
|
||||
const subscriberSet = new Set(subscriberResults.map(subscriber => subscriber[0]));
|
||||
const foafSet = new Set(foafResults.map(foaf => foaf[0]));
|
||||
|
||||
const trustEventAgeLimit = 90;
|
||||
const foafEventAgeLimit = 30;
|
||||
const untrustEventAgeLimit = 7;
|
||||
|
||||
const importantKindAgeLimits: Record<number, number> = {
|
||||
0: 365, // NIP-01: profiles
|
||||
3: 365, // NIP-01: contacts
|
||||
9735: 180, // NIP-57: zap receipts
|
||||
24133: 365, // NIP-46: nostr connect
|
||||
13194: 365, // NIP-47: nostr wallet connect
|
||||
10002: 730, // NIP-65
|
||||
};
|
||||
|
||||
const exportEventsStdin = async (): Promise<void> => {
|
||||
if (Deno.isatty(Deno.stdin.rid)) {
|
||||
Deno.exit(1);
|
||||
}
|
||||
for await (const line of readLines(Deno.stdin)) {
|
||||
if (line.length === 0) {
|
||||
return;
|
||||
}
|
||||
exportLine(line);
|
||||
}
|
||||
};
|
||||
|
||||
const exportLine = (line: string): void => {
|
||||
const eventJson = JSON.parse(line);
|
||||
const created_at = new Date(eventJson.created_at * 1000); // convert seconds to ms
|
||||
|
||||
if (subscriberSet.has(eventJson.pubkey)) {
|
||||
|
||||
// Tier 1: subscribers
|
||||
const kindAgeLimit = (importantKindAgeLimits[eventJson.kind] || trustEventAgeLimit) * 24 * 60 * 60 * 1000; // convert days to ms
|
||||
const ageLimitDate = new Date(Date.now() - kindAgeLimit);
|
||||
if (created_at > ageLimitDate) {
|
||||
console.log(line); // keep events from the last year
|
||||
}
|
||||
} else if (foafSet.has(eventJson.pubkey)) {
|
||||
|
||||
// Tier 2: foaf
|
||||
const kindAgeLimit = (importantKindAgeLimits[eventJson.kind] || foafEventAgeLimit) * 24 * 60 * 60 * 1000; // convert days to ms
|
||||
const ageLimitDate = new Date(Date.now() - kindAgeLimit);
|
||||
if (created_at > ageLimitDate) {
|
||||
console.log(line); // keep events from the last year
|
||||
}
|
||||
} else {
|
||||
|
||||
// Tier 3: untrust
|
||||
const kindAgeLimit = (importantKindAgeLimits[eventJson.kind] || untrustEventAgeLimit) * 24 * 60 * 60 * 1000; // convert days to ms
|
||||
const ageLimitDate = new Date(Date.now() - kindAgeLimit);
|
||||
if (created_at > ageLimitDate) {
|
||||
console.log(line); // keep recent events
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
await exportEventsStdin();
|
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
set -e
|
||||
|
||||
echo Beginning pruning task.
|
||||
|
||||
# prune
|
||||
strfry export | deno run --allow-read=. --allow-write=pubkeys.db prune.ts > /tmp/pruning.jsonl
|
||||
mv -v strfry-db/data.mdb strfry-db/archive.mdb
|
||||
cat /tmp/pruning.jsonl | strfry import --no-verify
|
||||
|
||||
# compact
|
||||
strfry compact strfry-db/compact.mdb
|
||||
mv -v strfry-db/compact.mdb strfry-db/data.mdb
|
||||
|
||||
echo Pruning task completed.
|
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Compact and defragment strfry database
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/var/lib/strfry
|
||||
ExecStart=/var/lib/strfry/compact-strfry-database.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Compact and defragment strfry database
|
||||
|
||||
[Timer]
|
||||
# Every Sunday
|
||||
OnCalendar=Sun *-*-* 1:00:00
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
@ -0,0 +1,11 @@
|
||||
Unit]
|
||||
Description=Prune strfry database
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/var/lib/strfry
|
||||
ExecStartPre=systemctl stop strfry
|
||||
ExecStart=doas -u strfry /var/lib/strfry/pruning.sh
|
||||
ExecStartPost=systemctl start strfry
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Prune strfry database
|
||||
|
||||
[Timer]
|
||||
# Run every month on the second Sunday
|
||||
OnCalendar=Sun *-*-8..14 1:00:00
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
@ -0,0 +1,85 @@
|
||||
---
|
||||
- hosts: gabite.bitcoiner.social
|
||||
become: yes
|
||||
|
||||
handlers:
|
||||
- name: reload nginx
|
||||
ansible.builtin.service:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
- name: reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: yes
|
||||
tags: systemd
|
||||
|
||||
tasks:
|
||||
- name: Ensure well-known path
|
||||
ansible.builtin.file:
|
||||
path: /var/www/.well-known
|
||||
state: directory
|
||||
|
||||
- name: Restore nip-05 from backup
|
||||
ansible.builtin.copy:
|
||||
src: files/nip5.json
|
||||
dest: /var/www/.well-known/nostr.json
|
||||
force: no
|
||||
|
||||
- name: Copy manually defined nip-11 to well-known path
|
||||
ansible.builtin.copy:
|
||||
src: files/nip11.json
|
||||
dest: /var/www/nip11.json
|
||||
force: no
|
||||
|
||||
- name: Configure complex nginx proxy settings
|
||||
ansible.builtin.copy:
|
||||
src: "nginx/{{ item }}"
|
||||
dest: "/etc/nginx/conf.d/{{ item }}"
|
||||
loop:
|
||||
- bitcoiner.social.conf
|
||||
- nostr.bitcoiner.social.conf
|
||||
- tor_bitcoiner.social.conf
|
||||
notify: reload nginx
|
||||
tags: nginx
|
||||
|
||||
- include_tasks: nginx_conf.yml
|
||||
tags: nginx
|
||||
|
||||
- name: Set vm.swappiness in /etc/sysctl.conf
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/sysctl.conf
|
||||
regexp: '^vm.swappiness'
|
||||
line: 'vm.swappiness=10'
|
||||
state: present
|
||||
register: swappiness
|
||||
tags: swap
|
||||
|
||||
- name: Apply sysctl changes
|
||||
ansible.builtin.command:
|
||||
cmd: sysctl -p
|
||||
when: swappiness.changed
|
||||
tags: swap
|
||||
|
||||
- name: Configure custom strfry scripts
|
||||
ansible.builtin.copy:
|
||||
src: "files/scripts/{{ item }}"
|
||||
dest: "/var/lib/strfry/{{ item }}"
|
||||
owner: strfry
|
||||
group: strfry
|
||||
mode: '0755'
|
||||
loop:
|
||||
- compact-strfry-database.sh
|
||||
- pruning.sh
|
||||
- prune.ts
|
||||
tags: copy
|
||||
|
||||
- name: Copy systemd service files
|
||||
ansible.builtin.copy:
|
||||
src: "files/systemd/{{ item }}"
|
||||
dest: "/etc/systemd/system/{{ item }}"
|
||||
loop:
|
||||
- prune-strfry-database.service
|
||||
- prune-strfry-database.timer
|
||||
- compact-strfry-database.service
|
||||
- compact-strfry-database.timer
|
||||
tags: systemd
|
||||
notify: reload systemd
|
@ -0,0 +1,90 @@
|
||||
---
|
||||
- name: Configure nginx
|
||||
ansible.builtin.import_role:
|
||||
name: nginx_core.nginx_config
|
||||
vars:
|
||||
# overriding any numeric values in the main nginx config requires replacing the entire dictionary
|
||||
# See: https://github.com/nginxinc/ansible-role-nginx-config/issues/352
|
||||
nginx_config_main_template_enable: true
|
||||
nginx_config_main_template:
|
||||
template_file: nginx.conf.j2
|
||||
deployment_location: /etc/nginx/nginx.conf
|
||||
backup: false
|
||||
config: # https://nginx.org/en/docs/ngx_core_module.html
|
||||
main:
|
||||
user:
|
||||
username: nginx
|
||||
group: nginx
|
||||
worker_processes: auto
|
||||
error_log:
|
||||
file: /var/log/nginx/error.log
|
||||
level: notice
|
||||
#pid: /var/run/nginx.pid
|
||||
|
||||
# worker_rlimit_nofile changes the limit on the maximum number of open files (RLIMIT_NOFILE) for worker processes.
|
||||
# Used to increase the limit without restarting the main process.
|
||||
# The recommended value seems to be worker_connections * 2
|
||||
worker_rlimit_nofile: 12288
|
||||
|
||||
events:
|
||||
worker_connections: 4096
|
||||
|
||||
http: # https://nginx.org/en/docs/http/ngx_http_core_module.html
|
||||
default_type: application/octet-stream
|
||||
sendfile: true
|
||||
server_tokens: false
|
||||
tcp_nodelay: true
|
||||
tcp_nopush: true
|
||||
include:
|
||||
- /etc/nginx/mime.types
|
||||
- /etc/nginx/http.conf
|
||||
- /etc/nginx/conf.d/*.conf
|
||||
|
||||
nginx_config_http_template_enable: true
|
||||
nginx_config_http_template:
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: /etc/nginx/http.conf
|
||||
backup: false
|
||||
config:
|
||||
core:
|
||||
default_type: application/octet-stream
|
||||
sendfile: true
|
||||
server_tokens: false
|
||||
tcp_nodelay: true
|
||||
tcp_nopush: true
|
||||
resolver: # required for oscp stapling
|
||||
address:
|
||||
- '1.1.1.1'
|
||||
- '8.8.8.8'
|
||||
resolver_timeout: 10s
|
||||
log:
|
||||
format:
|
||||
- name: main
|
||||
format: |
|
||||
'$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for" "$realip_remote_addr"'
|
||||
gzip: # https://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
||||
enable: true
|
||||
comp_level: 9
|
||||
min_length: 100
|
||||
proxied: any
|
||||
types:
|
||||
- application/json
|
||||
- text/plain
|
||||
- text/css
|
||||
vary: true
|
||||
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/mappings.conf"
|
||||
backup: false
|
||||
config:
|
||||
map:
|
||||
mappings: # https://nginx.org/en/docs/http/websocket.html
|
||||
- string: $http_upgrade
|
||||
variable: $connection_upgrade
|
||||
content:
|
||||
- value: default
|
||||
new_value: upgrade
|
||||
- value: "''"
|
||||
new_value: close
|
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Habla News
|
||||
|
||||
[Service]
|
||||
User=news
|
||||
Group=news
|
||||
WorkingDirectory=/var/www/habla/news
|
||||
ExecStart=pnpm start
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -0,0 +1,79 @@
|
||||
---
|
||||
- hosts: garchomp.bitcoiner.social
|
||||
|
||||
handlers:
|
||||
- name: restart hablanews
|
||||
ansible.builtin.service:
|
||||
name: hablanews
|
||||
state: restarted
|
||||
become: yes
|
||||
|
||||
roles:
|
||||
- role: bleetube.nodejs
|
||||
become: yes
|
||||
tags: nodejs
|
||||
tasks:
|
||||
#- name: Create a hablanews group
|
||||
# ansible.builtin.group:
|
||||
# name: hablanews
|
||||
# state: present
|
||||
# become: yes
|
||||
# tags: group
|
||||
|
||||
- name: Create a news user
|
||||
ansible.builtin.user:
|
||||
shell: /bin/bash
|
||||
createhome: no
|
||||
home: /var/www/habla
|
||||
name: news
|
||||
group: news
|
||||
append: yes
|
||||
become: yes
|
||||
tags: user
|
||||
|
||||
- name: Create directory owned by news
|
||||
ansible.builtin.file:
|
||||
path: /var/www/habla/news
|
||||
state: directory
|
||||
owner: news
|
||||
group: news
|
||||
mode: '0755'
|
||||
become: yes
|
||||
tags: directory
|
||||
tags: git
|
||||
|
||||
- name: Clone git repository
|
||||
ansible.builtin.git:
|
||||
repo: https://github.com/bleetube/habla.news
|
||||
dest: /var/www/habla/news
|
||||
version: bitcoiner.social
|
||||
force: true
|
||||
become: yes
|
||||
become_user: news
|
||||
register: git_repository
|
||||
tags: git
|
||||
|
||||
- name: Build habla.news
|
||||
ansible.builtin.command:
|
||||
cmd: "{{ item }}"
|
||||
chdir: /var/www/habla/news
|
||||
become: yes
|
||||
become_user: news
|
||||
tags: build
|
||||
notify: restart hablanews
|
||||
loop:
|
||||
- pnpm install
|
||||
- pnpm build
|
||||
|
||||
- name: Install service unit
|
||||
ansible.builtin.copy:
|
||||
src: hablanews.service
|
||||
dest: /etc/systemd/system/hablanews.service
|
||||
become: yes
|
||||
tags: systemd
|
||||
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: yes
|
||||
become: yes
|
||||
tags: systemd
|
146
ansible/playbooks/host_tasks/garchomp.bitcoiner.social/nginx.yml
Normal file
146
ansible/playbooks/host_tasks/garchomp.bitcoiner.social/nginx.yml
Normal file
@ -0,0 +1,146 @@
|
||||
---
|
||||
- name: strfry | Configure nginx
|
||||
ansible.builtin.import_role:
|
||||
name: nginx_core.nginx_config
|
||||
vars:
|
||||
# afaict, overriding any numeric values in the main nginx config requires replacing the entire dictionary.
|
||||
# See: https://github.com/nginxinc/ansible-role-nginx-config/issues/352
|
||||
# The only difference between this and the nginx config used in playbooks/nginx/main.yml is the worker_rlimit_nofile value and worker_connections.
|
||||
nginx_config_main_template_enable: true
|
||||
nginx_config_main_template:
|
||||
template_file: nginx.conf.j2
|
||||
deployment_location: /etc/nginx/nginx.conf
|
||||
backup: false
|
||||
config: # https://nginx.org/en/docs/ngx_core_module.html
|
||||
main:
|
||||
user:
|
||||
username: nginx
|
||||
group: nginx
|
||||
worker_processes: auto
|
||||
error_log:
|
||||
file: /var/log/nginx/error.log
|
||||
level: notice
|
||||
#pid: /var/run/nginx.pid
|
||||
|
||||
# worker_rlimit_nofile changes the limit on the maximum number of open files (RLIMIT_NOFILE) for worker processes.
|
||||
# Used to increase the limit without restarting the main process.
|
||||
# The recomended value seems to be worker_connections * 2
|
||||
worker_rlimit_nofile: 12288
|
||||
|
||||
events:
|
||||
worker_connections: 4096
|
||||
|
||||
# include: # String or a list of strings
|
||||
# - /etc/nginx/modules.conf
|
||||
http: # https://nginx.org/en/docs/http/ngx_http_core_module.html
|
||||
default_type: application/octet-stream
|
||||
sendfile: true
|
||||
server_tokens: false
|
||||
tcp_nodelay: true
|
||||
tcp_nopush: true
|
||||
include:
|
||||
- /etc/nginx/mime.types
|
||||
- /etc/nginx/http.conf # These are shared http level configs that nginx_conf refuses to directly configure.
|
||||
- /etc/nginx/conf.d/*.conf
|
||||
|
||||
nginx_config_http_template_enable: true
|
||||
nginx_config_http_template:
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: /etc/nginx/http.conf
|
||||
backup: false
|
||||
config:
|
||||
core:
|
||||
default_type: application/octet-stream
|
||||
sendfile: true
|
||||
server_tokens: false
|
||||
tcp_nodelay: true
|
||||
tcp_nopush: true
|
||||
resolver: # required for oscp stapling
|
||||
address:
|
||||
- '1.1.1.1'
|
||||
- '8.8.8.8'
|
||||
resolver_timeout: 10s
|
||||
log:
|
||||
format:
|
||||
- name: main
|
||||
format: |
|
||||
'$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for" "$realip_remote_addr"'
|
||||
# - name: debugposts
|
||||
# format: |
|
||||
# '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
# '$status $body_bytes_sent "$http_referer" '
|
||||
# '"$http_user_agent" "$http_x_forwarded_for" "$realip_remote_addr"'
|
||||
# '"$request_data"'
|
||||
gzip: # https://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
||||
enable: true
|
||||
comp_level: 3
|
||||
disable: "msie6"
|
||||
min_length: 1100
|
||||
proxied: any
|
||||
types:
|
||||
- text/plain
|
||||
- text/css
|
||||
- application/x-javascript
|
||||
- text/xml
|
||||
- application/xml
|
||||
vary: true
|
||||
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/mappings.conf"
|
||||
backup: false
|
||||
config:
|
||||
map:
|
||||
mappings: # https://nginx.org/en/docs/http/websocket.html
|
||||
- string: $http_upgrade
|
||||
variable: $connection_upgrade
|
||||
content:
|
||||
- value: default
|
||||
new_value: upgrade
|
||||
- value: "''"
|
||||
new_value: close
|
||||
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/snort_{{ nginx_snort_domain|default(inventory_hostname) }}.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
listen:
|
||||
- address: "{{ default_interface_ipv4_address|default(ansible_default_ipv4.address) }}:{{ nginx_snort_port|default(4451) }} ssl"
|
||||
include:
|
||||
- "/etc/nginx/acme_{{ nginx_snort_domain|default(inventory_hostname) }}.conf"
|
||||
index: index.html
|
||||
#root: "{{ snort_install_path|default('/var/www/snort') }}"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
http2:
|
||||
enabled: yes
|
||||
locations:
|
||||
- location: /
|
||||
core:
|
||||
try_files:
|
||||
files: "{{ snort_install_path|default('/var/www/snort') }}/packages/app/public/ {{ snort_install_path|default('/var/www/snort') }}/packages/app/build/ @proxy"
|
||||
#files: $uri $uri/ /index.html
|
||||
- location: '@proxy'
|
||||
proxy:
|
||||
pass: http://localhost:8080 # 127.0.0.1 does not work.
|
||||
http_version: '1.1'
|
||||
#set_header:
|
||||
# - field: Host
|
||||
# value: $http_host
|
||||
- core:
|
||||
server_name: "{{ nginx_snort_domain|default(inventory_hostname) }}"
|
||||
listen:
|
||||
- address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}:80"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
rewrite:
|
||||
return:
|
||||
url: https://$server_name$request_uri
|
||||
code: 301
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
- hosts: garchomp.bitcoiner.social
|
||||
become: yes
|
||||
tasks:
|
||||
- import_tasks: strfry/nginx_conf.yml
|
||||
tags: nginx
|
||||
# - import_tasks: strfry/logrotate.yml
|
||||
# tags: logrotate
|
@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Install logrotate
|
||||
ansible.builtin.package:
|
||||
name: logrotate
|
||||
state: present
|
||||
|
||||
- name: Rotate strfry plugin logs
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/logrotate.d/strfry
|
||||
state: present
|
||||
create: true
|
||||
block: |
|
||||
/var/lib/strfry/plugin/plugin.log {
|
||||
daily
|
||||
rotate 3
|
||||
create
|
||||
truncate
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
---
|
||||
- name: Install redirect template for Snort
|
||||
ansible.builtin.template:
|
||||
src: templates/snort_redirect.conf.j2
|
||||
dest: /etc/nginx/snort_redirect.conf
|
||||
tags: nginx
|
||||
|
||||
- name: strfry | Configure nginx
|
||||
ansible.builtin.import_role:
|
||||
name: nginx_core.nginx_config
|
||||
become: true
|
||||
vars:
|
||||
nginx_config_http_template_enable: true
|
||||
nginx_config_http_template:
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/strfry_{{ nginx_strfry_domain }}.conf"
|
||||
backup: false
|
||||
config:
|
||||
upstreams:
|
||||
- name: strfry
|
||||
servers:
|
||||
- address: "127.0.0.1:{{ strfry_relay.port|default(7777) }}"
|
||||
#- address: unix:/var/lib/strfry/strfry.sock
|
||||
servers:
|
||||
- core:
|
||||
listen:
|
||||
- address: "{{ default_interface_ipv4_address|default(ansible_default_ipv4.address) }}:{{ nginx_strfry_https_port|default(443) }} ssl"
|
||||
# - address: "[2607:f130:0:105:216:3cff:fefb:92c2]:443 ssl"
|
||||
include:
|
||||
- "/etc/nginx/acme_{{ nginx_strfry_domain }}.conf"
|
||||
#- /etc/nginx/snort_redirect.conf # breaks amethyst relay profile
|
||||
client_max_body_size: 0 # Stream request body to backend
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
proxy:
|
||||
pass: http://strfry
|
||||
http_version: '1.1'
|
||||
set_header:
|
||||
- field: Host
|
||||
value: $http_host
|
||||
- field: Connection
|
||||
value: $connection_upgrade
|
||||
- field: Upgrade
|
||||
value: $http_upgrade
|
||||
- field: X-Forwarded-For
|
||||
value: $proxy_add_x_forwarded_for
|
||||
connect_timeout: 3m
|
||||
send_timeout: 3m
|
||||
read_timeout: 3m
|
||||
- location: /static
|
||||
core:
|
||||
alias: /var/www/static
|
||||
- location: /.well-known/nostr.json
|
||||
core:
|
||||
alias: /var/www/static/nostr.json
|
||||
headers:
|
||||
add_headers:
|
||||
- name: Access-Control-Allow-Origin
|
||||
value: '*'
|
||||
- location: /favicon.ico
|
||||
core:
|
||||
alias: /var/www/static/favicon96.png
|
||||
# https://matrix-org.github.io/synapse/latest/delegate.html
|
||||
- location: '= /.well-known/matrix/server'
|
||||
rewrite: # https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite
|
||||
return:
|
||||
code: 200
|
||||
text: >
|
||||
'{"m.server":"matrix.bitcoiner.social:443"}'
|
||||
- location: '~ ^(/_matrix|/_synapse/client)'
|
||||
rewrite:
|
||||
return:
|
||||
url: "https://matrix.bitcoiner.social$request_uri"
|
||||
code: 301
|
||||
- location: = /blee
|
||||
rewrite:
|
||||
return:
|
||||
url: https://snort.bitcoiner.social/p/npub1dxs2pygtfxsah77yuncsmu3ttqr274qr5g5zva3c7t5s3jtgy2xszsn4st
|
||||
code: 301
|
||||
|
||||
# nostr.bitcoiner.social
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/nostr.bitcoiner.social.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
listen:
|
||||
- address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}:443 ssl"
|
||||
include:
|
||||
- "/etc/nginx/acme_nostr.bitcoiner.social.conf"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
proxy:
|
||||
pass: http://strfry
|
||||
http_version: '1.1'
|
||||
set_header:
|
||||
- field: Host
|
||||
value: $http_host
|
||||
- field: Connection
|
||||
value: $connection_upgrade
|
||||
- field: Upgrade
|
||||
value: $http_upgrade
|
||||
- field: X-Forwarded-For
|
||||
value: $proxy_add_x_forwarded_for
|
||||
connect_timeout: 3m
|
||||
send_timeout: 3m
|
||||
read_timeout: 3m
|
||||
# headers:
|
||||
# add_headers:
|
||||
# - name: Access-Control-Allow-Origin
|
||||
# value: '*'
|
||||
# limit_req: # https://www.nginx.com/blog/rate-limiting-nginx/
|
||||
# limit_reqs: # see files/limits.conf
|
||||
# - zone: nostr
|
||||
# burst: 5
|
||||
# delay: false
|
||||
|
||||
# bitcoinr6de5lkvx4tpwdmzrdfdpla5sya2afwpcabjup2xpi5dulbad.onion
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/tor_{{ nginx_strfry_domain }}.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
listen:
|
||||
- address: "127.0.0.1:9080"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
proxy:
|
||||
pass: http://strfry
|
||||
http_version: '1.1'
|
||||
set_header:
|
||||
- field: Host
|
||||
value: $http_host
|
||||
- field: Connection
|
||||
value: $connection_upgrade
|
||||
- field: Upgrade
|
||||
value: $http_upgrade
|
||||
- field: X-Forwarded-For
|
||||
value: $proxy_add_x_forwarded_for
|
||||
connect_timeout: 3m
|
||||
send_timeout: 3m
|
||||
read_timeout: 3m
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/http_{{ nginx_strfry_domain }}.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
server_name: "{{ nginx_strfry_domain }}"
|
||||
listen:
|
||||
- address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}:80"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
rewrite:
|
||||
return:
|
||||
url: https://$server_name$request_uri
|
||||
code: 301
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/cast.bitcoiner.social.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
listen:
|
||||
- address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}:443 ssl"
|
||||
include:
|
||||
- "/etc/nginx/acme_cast.bitcoiner.social.conf"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
rewrite:
|
||||
return:
|
||||
url: "https://modusb.com$request_uri"
|
||||
code: 301
|
||||
- location: = /@lacosanostr/feed.xml
|
||||
rewrite:
|
||||
return:
|
||||
url: https://modusb.com/LCN.rss
|
||||
code: 301
|
||||
- template_file: http/default.conf.j2
|
||||
deployment_location: "/etc/nginx/conf.d/news.bitcoiner.social.conf"
|
||||
backup: false
|
||||
config:
|
||||
servers:
|
||||
- core:
|
||||
listen:
|
||||
- address: "{{ default_interface_ipv4_address|default(ansible_default_ipv4.address) }}:443 ssl"
|
||||
include:
|
||||
- "/etc/nginx/acme_news.bitcoiner.social.conf"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
proxy:
|
||||
pass: http://127.0.0.1:3000
|
||||
http_version: '1.1'
|
||||
set_header:
|
||||
- field: Host
|
||||
value: $http_host
|
||||
- core:
|
||||
listen:
|
||||
- address: "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}:80"
|
||||
log:
|
||||
access:
|
||||
- off
|
||||
locations:
|
||||
- location: /
|
||||
rewrite:
|
||||
return:
|
||||
url: https://$server_name$request_uri
|
||||
code: 301
|
@ -0,0 +1,14 @@
|
||||
location = / {
|
||||
if ($connection_upgrade = "close") {
|
||||
return 302 https://{{ nginx_snort_domain }}/global;
|
||||
}
|
||||
proxy_connect_timeout 3m;
|
||||
proxy_http_version 1.1;
|
||||
proxy_pass http://strfry;
|
||||
proxy_read_timeout 3m;
|
||||
proxy_send_timeout 3m;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
8
ansible/playbooks/linux.yml
Normal file
8
ansible/playbooks/linux.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
- hosts: all
|
||||
roles:
|
||||
- role: bleetube.linux
|
||||
tags: linux
|
||||
become: yes
|
||||
- role: bleetube.dotfiles
|
||||
tags: dotfiles
|
16
ansible/playbooks/main.yml
Normal file
16
ansible/playbooks/main.yml
Normal file
@ -0,0 +1,16 @@
|
||||
--- # Run playbooks in a specific order
|
||||
- import_playbook: linux.yml
|
||||
- import_playbook: nginx/main.yml
|
||||
- import_playbook: group_tasks/nginx.yml
|
||||
- import_playbook: group_tasks/certbot/main.yml
|
||||
#- import_playbook: group_tasks/lego.yml # squirtle only
|
||||
- import_playbook: node_exporter.yml
|
||||
#- import_playbook: group_tasks/mail.yml # gather_facts getent passwd is broken when importing from here
|
||||
#- import_playbook: group_tasks/wireguard.yml
|
||||
#- import_playbook: tor.yml
|
||||
#- import_playbook: group_tasks/observability/main.yml # Pending: https://stackoverflow.com/q/77318959/9290
|
||||
#- import_playbook: group_tasks/postgresql.yml
|
||||
#- import_playbook: group_tasks/strfry.yml # role always builds, even when we don't need it to. Haven't made a workaround yet.
|
||||
- import_playbook: host_tasks/gabite.bitcoiner.social/main.yml
|
||||
#- import_playbook: host_tasks/garchomp.bitcoiner.social/strfry.yml
|
||||
#- import_playbook: host_tasks/garchomp.bitcoiner.social/hablanews.yml
|
7
ansible/playbooks/node_exporter.yml
Normal file
7
ansible/playbooks/node_exporter.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- hosts: all
|
||||
become: yes
|
||||
|
||||
roles:
|
||||
- role: prometheus.prometheus.node_exporter
|
||||
tags: node_exporter
|
11
ansible/playbooks/oneshots/README.md
Normal file
11
ansible/playbooks/oneshots/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# oneshots
|
||||
|
||||
Quick and dirty setup scripts for bare metal machines and cheap VMs.
|
||||
|
||||
## doas
|
||||
|
||||
```bash
|
||||
ansible-playbook oneshots/doas/main.yml -e "ansible_user=root" --limit <host>
|
||||
```
|
||||
|
||||
* This is a oneshot because changing from sudo to doas midflight requires the inventory file to be changed.
|
40
ansible/playbooks/oneshots/doas/main.yml
Normal file
40
ansible/playbooks/oneshots/doas/main.yml
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
- hosts: all
|
||||
|
||||
tasks:
|
||||
- name: "Ensure normal user exists called: {{ sysadmin_username }}."
|
||||
ansible.builtin.user:
|
||||
name: "{{ sysadmin_username }}"
|
||||
shell: /bin/bash
|
||||
register: new_sysadmin
|
||||
|
||||
- name: Copy ssh directory
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: /root/.ssh
|
||||
dest: "/home/{{ sysadmin_username }}/"
|
||||
owner: "{{ sysadmin_username }}"
|
||||
group: "{{ sysadmin_username }}"
|
||||
when: new_sysadmin.changed
|
||||
|
||||
- name: Ensure doas is installed.
|
||||
ansible.builtin.package:
|
||||
name: doas
|
||||
state: present
|
||||
|
||||
- name: Configure doas.
|
||||
ansible.builtin.template:
|
||||
src: doas.conf.j2
|
||||
dest: /etc/doas.conf
|
||||
|
||||
# Ubuntu requires setting a root password before `sudo` can be removed.
|
||||
# - name: Try removing sudo.
|
||||
# ansible.builtin.package:
|
||||
# name: sudo
|
||||
# state: absent
|
||||
# tags: test
|
||||
# environment:
|
||||
# SUDO_FORCE_REMOVE: yes
|
||||
|
||||
- import_playbook: ../../ssh.yml
|
||||
- import_playbook: ../../linux.yml
|
5
ansible/playbooks/oneshots/doas/templates/doas.conf.j2
Normal file
5
ansible/playbooks/oneshots/doas/templates/doas.conf.j2
Normal file
@ -0,0 +1,5 @@
|
||||
permit :wheel
|
||||
permit nopass root
|
||||
{% if sysadmin_username is defined %}
|
||||
permit nopass {{ sysadmin_username }}
|
||||
{% endif %}
|
40
ansible/playbooks/ssh.yml
Normal file
40
ansible/playbooks/ssh.yml
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
- hosts: all
|
||||
become: true
|
||||
|
||||
handlers:
|
||||
- name: restart ssh
|
||||
service: name=sshd state=restarted
|
||||
|
||||
tasks:
|
||||
- name: Configure sshd to read from authorized_keys.d
|
||||
lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^AuthorizedKeysFile.*$'
|
||||
line: AuthorizedKeysFile %h/.ssh/authorized_keys /etc/ssh/authorized_keys.d/%u
|
||||
notify: restart ssh
|
||||
|
||||
- name: Ensure authorized_keys.d
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/authorized_keys.d
|
||||
state: directory
|
||||
|
||||
- name: Configure authorized keys
|
||||
ansible.builtin.copy:
|
||||
src: ~/.ssh/ansible_sysadmin_keys
|
||||
dest: "/etc/ssh/authorized_keys.d/{{ sysadmin_username }}"
|
||||
owner: "{{ sysadmin_username }}"
|
||||
group: "{{ sysadmin_username }}"
|
||||
|
||||
- name: Ensure root ssh directory
|
||||
ansible.builtin.file:
|
||||
path: /root/.ssh
|
||||
state: directory
|
||||
mode: '0700'
|
||||
|
||||
- name: Configure authorized keys for root
|
||||
ansible.builtin.copy:
|
||||
src: ~/.ssh/ansible_root_keys
|
||||
dest: /root/.ssh/authorized_keys
|
||||
owner: root
|
||||
group: root
|
8
ansible/playbooks/tor.yml
Normal file
8
ansible/playbooks/tor.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
- hosts: all
|
||||
roles:
|
||||
# - role: bleetube.tor
|
||||
# tags: onion
|
||||
- role: systemli.onion
|
||||
tags: onion
|
||||
become: yes
|
3
ansible/requirements.txt
Normal file
3
ansible/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://github.com/grafana/grafana-ansible-collection/blob/a2a8f423d64b0507d3d0bc7fb4dd3ee4628de1f7/roles/grafana/README.md?plain=1#L14
|
||||
# required by grafana.grafana.grafana, and caddy-ansible/caddy
|
||||
jmespath
|
78
ansible/requirements.yml
Normal file
78
ansible/requirements.yml
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
collections:
|
||||
|
||||
# Warning: does not consistently bring in the latest version of the nginx_conf role
|
||||
- name: nginxinc.nginx_core
|
||||
src: https://github.com/nginxinc/ansible-collection-nginx
|
||||
version: '>=0.6.0'
|
||||
|
||||
- name: prometheus.prometheus
|
||||
src: https://github.com/prometheus-community/ansible
|
||||
# version: '>=0.5.0'
|
||||
|
||||
- name: grafana.grafana
|
||||
src: https://github.com/grafana/grafana-ansible-collection
|
||||
# version: '>=2.1.4'
|
||||
|
||||
roles:
|
||||
|
||||
- name: bleetube.dotfiles
|
||||
src: https://git.satstack.dev/blee/ansible-role-dotfiles/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.linux
|
||||
src: https://git.satstack.dev/blee/ansible-role-linux/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.lego
|
||||
src: https://git.satstack.dev/blee/ansible-role-lego/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.wireguard
|
||||
src: https://git.satstack.dev/blee/ansible-role-wireguard/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.disposable-mail
|
||||
src: https://git.satstack.dev/blee/ansible-role-disposable-mail/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.ntfy
|
||||
src: https://git.satstack.dev/blee/ansible-role-ntfy/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.ntfy-alertmanager
|
||||
src: https://git.satstack.dev/blee/ansible-role-ntfy-alertmanager/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.nodejs
|
||||
src: https://git.satstack.dev/blee/ansible-role-nodejs/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.strfry
|
||||
src: https://git.satstack.dev/blee/ansible-role-strfry/archive/main.tar.gz
|
||||
|
||||
- name: bleetube.snort
|
||||
src: https://git.satstack.dev/blee/ansible-role-snort/archive/main.tar.gz
|
||||
|
||||
# includes a minor change that skips the outdated apt role dependency
|
||||
# - src: https://github.com/bleetube/ansible-role-onion
|
||||
# name: systemli.onion
|
||||
# version: skip_apt_key
|
||||
# version: '>=2.3.0'
|
||||
- src: https://github.com/systemli/ansible-role-onion
|
||||
name: systemli.onion
|
||||
|
||||
- name: geerlingguy.certbot
|
||||
src: https://github.com/geerlingguy/ansible-role-certbot
|
||||
|
||||
- src: https://github.com/gantsign/ansible-role-golang
|
||||
name: gantsign.golang
|
||||
|
||||
- src: https://github.com/ANXS/postgresql
|
||||
name: anxs.postgresql
|
||||
# version: v1.14.1
|
||||
|
||||
- src: https://github.com/nginxinc/ansible-role-nginx
|
||||
name: nginx_core.nginx
|
||||
|
||||
- src: https://github.com/nginxinc/ansible-role-nginx-config
|
||||
name: nginx_core.nginx_config
|
||||
#version: '>=0.7.0'
|
||||
|
||||
- name: caddy
|
||||
src: https://github.com/caddy-ansible/caddy-ansible
|
||||
|
||||
- name: robertdebock.dovecot
|
||||
src: https://github.com/robertdebock/ansible-role-dovecot
|
10
dnscontrol/creds.json
Normal file
10
dnscontrol/creds.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"bind": {
|
||||
"TYPE": "BIND"
|
||||
},
|
||||
"namecheap": {
|
||||
"TYPE": "NAMECHEAP",
|
||||
"apikey": "$NAMECHEAP_API_KEY",
|
||||
"apiuser": "$NAMECHEAP_API_USER"
|
||||
}
|
||||
}
|
24
dnscontrol/dnsconfig.js
Normal file
24
dnscontrol/dnsconfig.js
Normal file
@ -0,0 +1,24 @@
|
||||
// https://docs.dnscontrol.org/
|
||||
var REG_NAMECHEAP = NewRegistrar('namecheap');
|
||||
var DSP_NAMECHEAP = NewDnsProvider("namecheap");
|
||||
|
||||
D('bitcoiner.social', REG_NAMECHEAP, DnsProvider(DSP_NAMECHEAP),
|
||||
A('@', '23.137.254.12'), // gabite
|
||||
A('nostr', '23.137.254.12'), // gabite
|
||||
A('mail', '74.48.94.75'), // garchomp
|
||||
A('garchomp', '74.48.94.75'),
|
||||
A('gabite', '23.137.254.12'),
|
||||
AAAA('gabite', '2602:fc24:10:9::1'),
|
||||
// A('www', '23.95.61.214'), // porygon
|
||||
CNAME('www', 'bitcoiner.social.'),
|
||||
MX('@', 10, 'mail.bitcoiner.social.'),
|
||||
TXT('@', 'v=spf1 mx ~all'),
|
||||
TXT('mail._domainkey', 'v=DKIM1; h=sha256; k=rsa; s=email; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYynQ/ytpvm/JpR5G2Dr4Z8pE25x42tzwHflXgIHBwWT25tQQER9C/IKRa78fNQ1kkkhyzM1kT18m62rYJH1l1OGfKmC8gmfw8feIFggo4h6F2pUw8/6+4v5ZCs+heT7HNHonnOpSjvAAHv27W1PjMn+aoY9c49qQuxkSo9xBW4MA4wODUkWA2S4gmfDZwKmxZcgQBqvo+vSiY0Pv4eRe9yNKH4ADY13dYR6dJVRmWjX7yvvkOdQ3t/jChjYiu6dYkz3B/hj2Z5M/A0DGcgXuIo5m5QzI539kp878um2bB1gHICGVSB2zn+LvCzBm/Xp/jqEs3O/08HQls2BSbGHgwIDAQAB'),
|
||||
TXT('_dmarc', 'v=DMARC1; p=reject; rua=mailto:dmarc@bitcoiner.social')
|
||||
);
|
||||
|
||||
D('bitcoiner.wiki', REG_NAMECHEAP, DnsProvider(DSP_NAMECHEAP),
|
||||
A('@', '74.48.94.75'), // garchomp
|
||||
CNAME('www', 'bitcoiner.wiki.'),
|
||||
TXT('@', 'v=spf1 mx ~all')
|
||||
);
|
24
docs/decisions.md
Normal file
24
docs/decisions.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Architectural Decision Record
|
||||
|
||||
Servers are named after Pokemon because [names should be cute, not descriptive](https://news.ycombinator.com/item?id=34320517).
|
||||
|
||||
## DNS
|
||||
|
||||
DNS provider is Namecheap because they provide the following reasons:
|
||||
|
||||
* DNS can be managed via dnscontrol
|
||||
* Invoices can be paid to Namecheap directly without any intermediaries via their btcpayserver
|
||||
|
||||
I had also tried EasyDNS, but while their DNS could be managed via OctoDNS, I found it poorly supported. Their invoices could be paid in Bitcoin, but only through a third party.
|
||||
|
||||
## Web Proxy
|
||||
|
||||
Nginx for nostr relays because it provides peak performance and allows for complex configuration. Otherwise caddy is preferred for its configuration simplicity which reduces the total cost of ownership. The two web proxies cannot co-exist on the same server with a single public IP address, since both use http-01 validation with nginx and they cannot share port 80.
|
||||
|
||||
## TLS
|
||||
|
||||
* Certbot is used for nostr relay certificate renewal with http-01 validation because it is reliable and works in our nginx deployment.
|
||||
* ACME lego is used to renew mail server certificates with dns-01 validation because it doesn't need a webserver.
|
||||
* Caddy is used to run web services and automate certificate renewal on most other servers because of its configuration simplicity and reasonable performance.
|
||||
|
||||
Our nginx configuration continues to include TLS1.2 in addition to TLS1.3 to support legacy clients. It should probably be removed soon, as that would remove the need to generate dhparams.
|
16
scripts/backups/batch.sh
Executable file
16
scripts/backups/batch.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
set -e
|
||||
HOSTS=(
|
||||
garchomp.bitcoiner.social
|
||||
gabite.bitcoiner.social
|
||||
)
|
||||
|
||||
#source functions.sh
|
||||
for hostname in ${HOSTS[@]}; do
|
||||
echo "Running script for $hostname"
|
||||
(exec ./hosts/${hostname}.sh)
|
||||
echo "Finished running script for $hostname"
|
||||
done
|
||||
|
||||
du -sh $HOME/archive
|
45
scripts/backups/functions.sh
Normal file
45
scripts/backups/functions.sh
Normal file
@ -0,0 +1,45 @@
|
||||
TIMESTAMP=$(date +%m-%d-%Y)
|
||||
TIMESTAMP=$(date +%Y-%m-%d)
|
||||
BACKUP_PG_DB() {
|
||||
BACKUP_DIR=$HOME/archive/${TARGET}/postgresql
|
||||
DUMP_FILE=/var/lib/postgresql/${1}_${TIMESTAMP}.dump.bz2
|
||||
ssh root@${TARGET} "cd /var/lib/postgresql && doas -u postgres /usr/bin/pg_dump -Fc ${1} | /usr/bin/bzip2 > ${DUMP_FILE}"
|
||||
mkdir -p ${BACKUP_DIR}
|
||||
rsync -tav root@${TARGET}:${DUMP_FILE} ${BACKUP_DIR}/
|
||||
ssh root@${TARGET} rm -v ${DUMP_FILE}
|
||||
|
||||
# Only remove older backups if newer backups exist
|
||||
NEWER_BACKUPS=$(find $BACKUP_DIR -mtime -60 -type f -name "${1}_*.dump.bz2")
|
||||
if [[ -n $NEWER_BACKUPS ]]; then
|
||||
find $BACKUP_DIR -mtime +60 -type f -name "${1}_*.dump.bz2" -delete
|
||||
else
|
||||
echo "No newer backups found. Old backups not pruned."
|
||||
fi
|
||||
}
|
||||
BACKUP_MAIL() {
|
||||
mkdir -p $HOME/archive/${TARGET}/{dovecot,postfix}
|
||||
rsync -tav root@${TARGET}:/etc/dovecot/{imap.passwd,dovecot.conf} $HOME/archive/${TARGET}/dovecot/
|
||||
rsync -tav root@${TARGET}:/etc/postfix/virtual $HOME/archive/${TARGET}/postfix
|
||||
rsync -tav root@${TARGET}:/etc/dkimkeys $HOME/archive/${TARGET}/
|
||||
rsync -tav root@${TARGET}:/var/vmail $HOME/archive/${TARGET}/
|
||||
ssh root@${TARGET} find "/var/vmail/*/main/cur/" -type f -mtime +90 -delete
|
||||
}
|
||||
BACKUP_STRFRY_DB() {
|
||||
BACKUP_DIR=$HOME/archive/${TARGET}/strfry
|
||||
DUMP_FILE=/tmp/strfry_${TIMESTAMP}.jsonl.zst
|
||||
|
||||
# Only export data since the last backup, if any exist
|
||||
pushd ${BACKUP_DIR}
|
||||
LAST_BACKUP_DATE=$(ls strfry_*.jsonl.zst | sort -t_ -k2 | tail -n1 | sed -E 's/strfry_([0-9-]+).jsonl.zst/\1/')
|
||||
if [[ -n $LAST_BACKUP_DATE ]]; then
|
||||
LAST_BACKUP_TIMESTAMP=$(date -d "${LAST_BACKUP_DATE}" +%s)
|
||||
EXPORT_SINCE="--since ${LAST_BACKUP_TIMESTAMP}"
|
||||
fi
|
||||
popd
|
||||
|
||||
ssh root@${TARGET} "cd /var/lib/strfry && doas -u strfry strfry export ${EXPORT_SINCE} | zstd -c > ${DUMP_FILE}"
|
||||
|
||||
mkdir -p ${BACKUP_DIR}
|
||||
rsync -taP root@${TARGET}:${DUMP_FILE} ${BACKUP_DIR}
|
||||
ssh root@${TARGET} rm -v ${DUMP_FILE}
|
||||
}
|
23
scripts/backups/hosts/gabite.bitcoiner.social.sh
Executable file
23
scripts/backups/hosts/gabite.bitcoiner.social.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
TARGET=$(basename -- "$0" .sh)
|
||||
|
||||
source "$(dirname "$0")/../functions.sh"
|
||||
|
||||
BACKUP_MAIL
|
||||
BACKUP_STRFRY_DB
|
||||
|
||||
# handle the strfry database separately
|
||||
rsync -tav --exclude venv --exclude plugin.log --exclude data.mdb root@${TARGET}:/var/lib/strfry $HOME/archive/${TARGET}/
|
||||
rsync -tav root@${TARGET}:/var/lib/strfry/{pubkeys.*} $HOME/archive/${TARGET}/stfry/
|
||||
rsync -tav root@${TARGET}:/var/www/static/attachments $HOME/archive/${TARGET}/
|
||||
|
||||
# nostdress-wireguard
|
||||
mkdir -p $HOME/archive/${TARGET}/wireguard
|
||||
rsync -tav root@${TARGET}:/etc/wireguard/lanturn.conf $HOME/archive/${TARGET}/wireguard/
|
||||
|
||||
# tor
|
||||
rsync -tav root@${TARGET}:/var/lib/tor $HOME/archive/${TARGET}/
|
||||
rsync -tav root@${TARGET}:/etc/tor/torrc $HOME/archive/${TARGET}/
|
||||
|
||||
du -sh $HOME/archive/${TARGET}/
|
14
scripts/backups/hosts/garchomp.bitcoiner.social.sh
Executable file
14
scripts/backups/hosts/garchomp.bitcoiner.social.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
TARGET=$(basename -- "$0" .sh)
|
||||
|
||||
source "$(dirname "$0")/../functions.sh"
|
||||
|
||||
BACKUP_MAIL
|
||||
BACKUP_STRFRY_DB
|
||||
|
||||
# handle the strfry database separately
|
||||
rsync -tav --exclude venv --exclude plugin.log --exclude data.mdb root@${TARGET}:/var/lib/strfry $HOME/archive/${TARGET}/
|
||||
rsync -tav root@${TARGET}:/var/www/static/attachments $HOME/archive/${TARGET}/
|
||||
|
||||
du -sh $HOME/archive/${TARGET}/
|
Loading…
Reference in New Issue
Block a user