~comcloudway/ansible-srht

0a9f436300e3e257fb50b718349d41d10d079342 — Jakob Meier 10 months ago
Initiali commit
Added ability to setup the meta module and some core database services
A  => .gitignore +2 -0
@@ 1,2 @@
group_vars/all/secrets.yml
hosts.yml

A  => ansible.cfg +8 -0
@@ 1,8 @@
[defaults]
inventory = hosts.yml
# disable cowsay - yes it looks beautiful, but is bloats the output
nocows = True

[ssh_connections]
# significantly speed up ssh
pipelining = true

A  => group_vars/all/default.yml +38 -0
@@ 1,38 @@
---
# has to be one of https://mirror.sr.ht/alpine/
# NOTE: As of 29.10.2023 the edge version is outdated
alpine_host_version: "v3.17"

srht_site_name: "sourcehut"
srht_site_info: "https://sourcehut.org"
srht_site_blurb: "the hacker forge"
srht_owner_name: "Drew DeVault"
srht_owner_email: "sir@cmpwn.com"
srht_domain: "example.com"
srht_protocol: "https"
srht_smtp_host: ""
srht_smtp_port: ""
srht_smtp_from: ""
srht_smtp_encryption: "starttls"
srht_smtp_auth: "plain"
srht_smtp_user: ""
srht_smtp_password: ""
srht_smtp_error_to: ""
srht_smtp_error_from: ""
srht_enable_registration: "yes"


# before running, you have to copy the pgp private and public key
# put the key id here
srht_pgp_key_id: ""
# the public key itself (without begin/end blocks) here
srht_email_pubkey: ""
# the private key itself (without begin/end blocks) here
srht_email_privkey: ""

# the following values have to be generated in advance
# to do so have a look at:
# https://git.sr.ht/~sircmpwn/core.sr.ht/tree/master/item/srht-keygen
srht_private_key: ""
srht_service_key: ""
srht_network_key: ""

A  => roles/meta.sr.ht/tasks/config.yml +135 -0
@@ 1,135 @@
---
- name: Ensure the meta.sr.ht config is injected
  ansible.builtin.blockinfile:
    path: /etc/sr.ht/config.ini
    marker: "#-- {mark} ANSIBLE meta.sr.ht --#"
    block: |
      [meta.sr.ht]
      #
      # URL meta.sr.ht is being served at (protocol://domain)
      origin={{ srht_protocol }}://meta.{{ srht_domain }}
      #
      # Address and port to bind the debug server to
      debug-host=0.0.0.0
      debug-port=5000
      #
      # Configures the SQLAlchemy connection string for the database.
      connection-string=postgresql://postgres@127.0.0.1/metasrht?sslmode=disable
      #
      # Set to "yes" to automatically run migrations on package upgrade.
      migrate-on-upgrade=yes
      #
      # The redis connection used for the webhooks worker
      webhooks=redis://127.0.0.1:6379/1
      #
      # If "yes", the user will be sent the stock sourcehut welcome emails after
      # signup (requires cron to be configured properly). These are specific to the
      # sr.ht instance so you probably want to patch these before enabling this.
      welcome-emails=no

      #
      # Origin URL for the API
      # By default, the API port is 100 more than the web port
      #api-origin=http://127.0.0.1:5100

      [meta.sr.ht::api]
      #
      # Maximum complexity of GraphQL queries. The higher this number, the more work
      # that API clients can burden the API backend with. Complexity is equal to the
      # number of discrete fields which would be returned to the user. 200 is a good
      # default.
      max-complexity=200

      #
      # The maximum time the API backend will spend processing a single API request.
      #
      # See https://golang.org/pkg/time/#ParseDuration
      max-duration=3s

      #
      # Set of IP subnets which are permitted to utilize internal API
      # authentication. This should be limited to the subnets from which your
      # *.sr.ht services are running.
      #
      # Comma-separated, CIDR notation.
      internal-ipnet=127.0.0.0/8,::1/128,192.168.0.0/16,10.0.0.0/8

      [meta.sr.ht::settings]
      #
      # If "no", public registration will not be permitted.
      registration={{ srht_enable_registration }}
      #
      # Where to redirect new users upon registration
      onboarding-redirect={{ srht_protocol }}://{{ srht_domain }}

      [meta.sr.ht::aliases]
      #
      # You can add aliases for the client IDs of commonly used OAuth clients here.
      #
      # Example:
      # git.sr.ht=12345

      [meta.sr.ht::billing]
      #
      # "yes" to enable the billing system
      enabled=no
      #
      # Get your keys at https://dashboard.stripe.com/account/apikeys
      stripe-public-key=
      stripe-secret-key=

      [meta.sr.ht::auth]
      #
      # What authentication method to use.
      #   builtin:  use sr.ht builtin authentication
      #   unix-pam: use Unix PAM authentication
      #auth-method=builtin

      [meta.sr.ht::auth::unix-pam]
      #
      # The default email domain to assign to newly created users when they first log
      # in.
      # User's email will be set to <username>@<email-default-domain>
      email-default-domain=example.com
      #
      # The PAM service to use for logging in.
      #service=sshd
      #
      # Whether to automatically create new users when authentication succeeds but the
      # user is not in the database.
      create-users=yes
      #
      # The UNIX group users need to belong to to have access to sourcehut.
      # If set,
      # only users belonging to this group will be able to log into the site.
      # If unset, any user on the system is able to log in if PAM authentication
      # succeeds.
      user-group=
      #
      # The UNIX group users need to belong to to have administrator permissions.
      # If set, administrator status on the site will be synced with group
      # association. Additionally, any user of this group will also be able to access
      # sourcehut even if they are not in the group specified in user-group.
      # If unset, administrator status can be manually assigned from the web
      # interface.
      admin-group=wheel
  register: conf

- name: Enable & start meta.sr.ht service
  ansible.builtin.service:
    name: meta.sr.ht
    state: restarted
    enabled: true
  when: conf.changed
- name: Enable & start meta.sr.ht api service
  ansible.builtin.service:
    name: meta.sr.ht-api
    state: restarted
    enabled: true
  when: conf.changed
- name: Enable & start meta.sr.ht webhooks service
  ansible.builtin.service:
    name: meta.sr.ht-webhooks
    state: restarted
    enabled: true
  when: conf.changed

A  => roles/meta.sr.ht/tasks/db.yml +15 -0
@@ 1,15 @@
---
- name: Copy database schema to host
  ansible.builtin.template:
    src: schema.psql
    dest: /tmp/metasrht.psql

- name: Create database
  community.postgresql.postgresql_db:
    name: metasrht

- name: Ensure database layout
  community.postgresql.postgresql_db:
    name: metasrht
    state: restore
    target: /tmp/metasrht.psql

A  => roles/meta.sr.ht/tasks/main.yml +15 -0
@@ 1,15 @@
---
- name: Install meta.sr.ht packages
  community.general.apk:
    name:
      - meta.sr.ht
    state: latest

- name: Setup Database
  ansible.builtin.import_tasks: db.yml

- name: Setup config & services
  ansible.builtin.import_tasks: config.yml

- name: Setup nginx
  ansible.builtin.import_tasks: nginx.yml

A  => roles/meta.sr.ht/tasks/nginx.yml +13 -0
@@ 1,13 @@
---
- name: Copy nginx config file
  ansible.builtin.template:
    src: nginx.conf
    dest: /etc/nginx/http.d/meta.sr.ht.conf
  register: nginxconf

- name: Start & enable nginx
  ansible.builtin.service:
    name: nginx
    state: restarted
    enabled: true
  when: nginxconf.changed

A  => roles/meta.sr.ht/templates/nginx.conf +38 -0
@@ 1,38 @@
server {
    include sourcehut.conf;
    server_name meta.{{ srht_domain }};

    location ^~ /.well-known {
        root /var/www;
    }

    location = /robots.txt {
        root /var/www;
    }

    location / {
        proxy_pass http://127.0.0.1:5000;
        include headers.conf;
        add_header Content-Security-Policy "default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline' *.stripe.com *.stripe.network; frame-src *.stripe.com *.stripe.network always; frame-ancestors 'none'" always;
        include web.conf;
    }

    location /register {
        proxy_pass http://127.0.0.1:5000;
        add_header Content-Security-Policy "default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline' *.stripe.com *.stripe.network; frame-src *.stripe.com *.stripe.network; frame-ancestors 'none'" always;
    }

    location /.well-known/oauth-authorization-server {
        proxy_pass http://127.0.0.1:5000;
    }

    location /query {
        proxy_pass http://127.0.0.1:5100;
        include graphql.conf;
    }

    location /static {
        root /usr/lib/$python/site-packages/metasrht;
        expires 30d;
    }
}

A  => roles/meta.sr.ht/templates/schema.psql +265 -0
@@ 1,265 @@
CREATE TYPE auth_method AS ENUM (
	'OAUTH_LEGACY',
	'OAUTH2',
	'COOKIE',
	'INTERNAL',
	'WEBHOOK'
);

CREATE TYPE webhook_event AS ENUM (
	'PROFILE_UPDATE',
	'PGP_KEY_ADDED',
	'PGP_KEY_REMOVED',
	'SSH_KEY_ADDED',
	'SSH_KEY_REMOVED'
);

CREATE TABLE "user" (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	username character varying(256) NOT NULL UNIQUE,
	password character varying(256) NOT NULL,
	email character varying(256) NOT NULL UNIQUE,
	new_email character varying(256),
	user_type character varying NOT NULL,
	confirmation_hash character varying(128),
	url character varying(256),
	location character varying(256),
	bio character varying(4096),
	pgp_key_id integer,
	reset_hash character varying(128),
	reset_expiry timestamp without time zone,
	stripe_customer character varying(256),
	payment_cents integer DEFAULT 0 NOT NULL,
	payment_interval character varying DEFAULT 'monthly'::character varying,
	payment_due timestamp without time zone,
	welcome_emails integer DEFAULT 0 NOT NULL,
	oauth_revocation_token character varying(256),
	suspension_notice character varying(4096)
);

CREATE TABLE audit_log_entry (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	ip_address character varying(50) NOT NULL,
	event_type character varying(256) NOT NULL,
	details character varying(512)
);

CREATE TABLE invoice (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	cents integer NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	valid_thru timestamp without time zone NOT NULL,
	source character varying(256) NOT NULL
);

CREATE TABLE pgpkey (
	id serial PRIMARY KEY,
	created timestamp without time zone,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	key character varying(32768) NOT NULL,
	fingerprint bytea NOT NULL UNIQUE,
	expiration timestamp without time zone
);

ALTER TABLE "user"
	ADD CONSTRAINT user_pgp_key_id_fkey
	FOREIGN KEY (pgp_key_id) REFERENCES pgpkey(id) ON DELETE SET NULL;

CREATE TABLE sshkey (
	id serial PRIMARY KEY,
	created timestamp without time zone,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	key character varying(4096) NOT NULL,
	fingerprint character varying(512) NOT NULL UNIQUE,
	comment character varying(256),
	last_used timestamp without time zone,
	b64_key character varying(4096),
	key_type character varying(256)
);

CREATE INDEX sshkey_md5_idx ON sshkey USING btree (md5((key)::text));

CREATE TABLE user_auth_factor (
	id serial PRIMARY KEY,
	user_id integer NOT NULL UNIQUE REFERENCES "user"(id) ON DELETE CASCADE,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	factor_type character varying NOT NULL,
	secret bytea,
	extra json
);

CREATE TABLE user_notes (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	note character varying
);

CREATE TABLE reserved_usernames (
    username varchar NOT NULL
);

CREATE INDEX reserved_usernames_ix ON reserved_usernames(username);

-- OAuth 2.0
CREATE TABLE oauth2_client (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	client_uuid uuid NOT NULL,
	client_secret_hash character varying(128) NOT NULL,
	client_secret_partial character varying(8) NOT NULL,
	redirect_url character varying,
	client_name character varying(256) NOT NULL,
	client_description character varying,
	client_url character varying,
	revoked boolean DEFAULT false NOT NULL
);

CREATE TABLE oauth2_grant (
	id serial PRIMARY KEY,
	issued timestamp without time zone NOT NULL,
	expires timestamp without time zone NOT NULL,
	comment character varying,
	token_hash character varying(128) NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	client_id integer REFERENCES oauth2_client(id) ON DELETE CASCADE
);

-- GraphQL webhooks
CREATE TABLE gql_profile_wh_sub (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	events webhook_event[] NOT NULL,
	url character varying NOT NULL,
	query character varying NOT NULL,
	auth_method auth_method NOT NULL,
	token_hash character varying(128),
	grants character varying,
	client_id uuid,
	expires timestamp without time zone,
	node_id character varying,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	CONSTRAINT gql_profile_wh_sub_auth_method_check
		CHECK ((auth_method = ANY(ARRAY['OAUTH2'::auth_method, 'INTERNAL'::auth_method]))),
	CONSTRAINT gql_profile_wh_sub_check
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (token_hash IS NOT NULL))),
	CONSTRAINT gql_profile_wh_sub_check1
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (expires IS NOT NULL))),
	CONSTRAINT gql_profile_wh_sub_check2
		CHECK (((auth_method = 'INTERNAL'::auth_method) = (node_id IS NOT NULL))),
	CONSTRAINT gql_profile_wh_sub_events_check
		CHECK ((array_length(events, 1) > 0))
);

CREATE INDEX gql_profile_wh_sub_token_hash_idx ON gql_profile_wh_sub USING btree (token_hash);

CREATE TABLE gql_profile_wh_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	date timestamp without time zone NOT NULL,
	event webhook_event NOT NULL,
	subscription_id integer NOT NULL
		REFERENCES gql_profile_wh_sub(id) ON DELETE CASCADE,
	request_body character varying NOT NULL,
	response_body character varying,
	response_headers character varying,
	response_status integer
);

-- Legacy OAuth (TODO: Remove these)
CREATE TABLE oauthclient (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	client_name character varying(256) NOT NULL,
	client_id character varying(16) NOT NULL,
	client_secret_hash character varying(128) NOT NULL,
	client_secret_partial character varying(8) NOT NULL,
	redirect_uri character varying(256),
	preauthorized boolean DEFAULT false NOT NULL
);

CREATE TABLE oauthscope (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	client_id integer NOT NULL REFERENCES oauthclient(id) ON DELETE CASCADE,
	name character varying(256) NOT NULL,
	description character varying(512) NOT NULL,
	write boolean NOT NULL
);

CREATE TABLE oauthtoken (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	expires timestamp without time zone NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	client_id integer REFERENCES oauthclient(id) ON DELETE CASCADE,
	token_hash character varying(128) NOT NULL,
	token_partial character varying(8) NOT NULL,
	scopes character varying(512) NOT NULL,
	comment character varying(128)
);

CREATE TABLE revocationurl (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	token_id integer NOT NULL REFERENCES oauthtoken(id),
	client_id integer NOT NULL REFERENCES oauthclient(id),
	url character varying(2048) NOT NULL
);

CREATE TABLE delegatedscope (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	client_id integer NOT NULL REFERENCES oauthclient(id) ON DELETE CASCADE,
	name character varying(256) NOT NULL,
	description character varying(512) NOT NULL,
	write boolean NOT NULL
);

-- Legacy webhooks (TODO: Remove these)
CREATE TABLE user_webhook_subscription (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	url character varying(2048) NOT NULL,
	events character varying NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	token_id integer REFERENCES oauthtoken(id) ON DELETE CASCADE
);

CREATE TABLE user_webhook_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	created timestamp without time zone NOT NULL,
	event character varying(256) NOT NULL,
	url character varying(2048) NOT NULL,
	payload character varying(16384) NOT NULL,
	payload_headers character varying(16384) NOT NULL,
	response character varying(16384),
	response_status integer NOT NULL,
	response_headers character varying(16384),
	subscription_id integer REFERENCES user_webhook_subscription(id) ON DELETE CASCADE
);

CREATE TABLE webhook_subscription (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	url character varying(2048) NOT NULL,
	events character varying NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	client_id integer REFERENCES oauthclient(id) ON DELETE CASCADE
);

A  => roles/sr.ht-core/tasks/config.yml +20 -0
@@ 1,20 @@
---
- name: Ensure the sr.ht config directory exists
  ansible.builtin.file:
    path: /etc/sr.ht
    state: directory
    recurse: true

- name: Create the email public key file
  ansible.builtin.template:
    src: email.pub
    dest: /etc/sr.ht/email.pub
- name: Create the email private key file
  ansible.builtin.template:
    src: email.priv
    dest: /etc/sr.ht/email.priv

- name: Copy sr.ht config file (rerun if changed)
  ansible.builtin.template:
    src: config.ini
    dest: /etc/sr.ht/config.ini

A  => roles/sr.ht-core/tasks/db.yml +12 -0
@@ 1,12 @@
---
- name: Start & enbale postgres database
  ansible.builtin.service:
    name: postgresql
    state: started
    enabled: true

- name: Start & enbale redis cache
  ansible.builtin.service:
    name: redis
    state: started
    enabled: true

A  => roles/sr.ht-core/tasks/main.yml +7 -0
@@ 1,7 @@
---
- name: Install base packages
  ansible.builtin.import_tasks: packages.yml
- name: Setup databases
  ansible.builtin.import_tasks: db.yml
- name: Copy config file
  ansible.builtin.import_tasks: config.yml

A  => roles/sr.ht-core/tasks/packages.yml +35 -0
@@ 1,35 @@
---
- name: Install Database
  community.general.apk:
    name:
      - postgresql14
      - redis
    state: latest

- name: Install chron daemon
  community.general.apk:
    name:
      - chrony
    state: latest

- name: Add sourcehut repos
  ansible.builtin.lineinfile:
    path: /etc/apk/repositories
    line: "https://mirror.sr.ht/alpine/{{ alpine_host_version }}/sr.ht/"
    insertbefore: "BOF"
- name: Fetch sourcehut signing key
  ansible.builtin.get_url:
    url: https://mirror.sr.ht/alpine/alpine@sr.ht.rsa.pub
    dest: /etc/apk/keys/alpine@sr.ht.rsa.pub
- name: Update & Upgrade repositories
  community.general.apk:
    update_cache: true
    upgrade: true

- name: Install base sourcehut services
  community.general.apk:
    name:
      - py3-srht
      - sr.ht-nginx
      - nginx
    state: latest

A  => roles/sr.ht-core/templates/config.ini +100 -0
@@ 1,100 @@
[sr.ht]
#
# The name of your network of sr.ht-based sites
site-name={{ srht_site_name }}
#
# The top-level info page for your site
site-info={{ srht_site_info }}
#
site-blurb={{ srht_site_blurb }}
#
# If this != production, we add a banner to each page
environment=production
#
# Contact information for the site owners
owner-name={{ srht_owner_name }}
owner-email={{ srht_owner_email }}
#
# The source code for your fork of sr.ht
source-url=https://git.sr.ht/~sircmpwn/srht
#
# Link to your instance's privacy policy. Uses the sr.ht privacy policy as the
# default, which describes the information collected by the upstream SourceHut
# code.
privacy-policy=https://man.sr.ht/privacy.md
#
# A key used for encrypting session cookies. Use `srht-keygen service` to
# generate the service key. This must be shared between each node of the same
# service (e.g. git1.sr.ht and git2.sr.ht), but different services may use
# different keys. If you configure all of your services with the same
# config.ini, you may use the same service-key for all of them.
service-key={{ srht_service_key }}
#
# A secret key to encrypt internal messages with. Use `srht-keygen network` to
# generate this key. It must be consistent between all services and nodes.
network-key={{ srht_network_key }}
#
# The redis host URL. This is used for caching and temporary storage, and must
# be shared between nodes (e.g. git1.sr.ht and git2.sr.ht), but need not be
# shared between services. It may be shared between services, however, with no
# ill effect, if this better suits your infrastructure.
redis-host=redis://127.0.0.1:6379/1
#
# Optional email address for reporting security-related issues.
security-address=
#
# The global domain of the site. If unset, the global domain will be determined
# from the service URL: each service is assumed to be a sub-domain of the global
# domain, i.e. of the form `meta.globaldomain.com`.
global-domain={{ srht_domain }}

[objects]
#
# Configure S3-compatible object storage for services. Optional.
#
# Minio is recommended as a FOSS solution over AWS: https://min.io
s3-upstream=
s3-access-key=
s3-secret-key=

[mail]
#
# Outgoing SMTP settings
smtp-host={{ srht_smtp_host }}
smtp-port={{ srht_smtp_port }}
smtp-from={{ srht_smtp_from }}
#
# Default: starttls
# Options: starttls, tls, insecure
smtp-encryption={{ srht_smtp_encryption }}
#
# Default: plain
# Options: plain, none
smtp-auth={{ srht_smtp_auth }}
# user / password are required if smtp-auth is plain
smtp-user={{ srht_smtp_user }}
smtp-password={{ srht_smtp_password }}
#
# Application exceptions are emailed to this address
error-to={{ srht_smtp_error_to }}
error-from={{ srht_smtp_error_from }}
#
# You should generate a PGP key to allow users to authenticate emails received
# from your services. Use `gpg --edit-key [key id]` to remove the password from
# your private key, then export it to a file and set pgp-privkey to the path to
# that file. pgp-pubkey should be set to the path to your public key, and
# pgp-key-id should be set to the key ID string. Outgoing emails are signed with
# this PGP key.
pgp-privkey=/etc/sr.ht/email.priv
pgp-pubkey=/etc/sr.ht/email.pub
pgp-key-id={{ srht_pgp_key_id }}

[webhooks]
#
# base64-encoded Ed25519 key for signing webhook payloads. This should be
# consistent between all services.
#
# Use the `srht-keygen webhook` command to generate this key. Put the private
# key here and distribute the public key to anyone who would want to verify
# webhook payloads from your service.
private-key={{ srht_private_key }}

A  => roles/sr.ht-core/templates/email.priv +3 -0
@@ 1,3 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----

{{ srht_email_privkey }}-----END PGP PRIVATE KEY BLOCK-----

A  => roles/sr.ht-core/templates/email.pub +3 -0
@@ 1,3 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

{{ srht_email_pubkey }}-----END PGP PUBLIC KEY BLOCK-----

A  => run.yml +13 -0
@@ 1,13 @@
---
############################################
# SETUP BASE SOURCEHUT REQUIREMENTS
# including databases and the auth (meta) backend
############################################
- name: Setup core sr.ht packages (required)
  hosts: all
  roles:
    - role: sr.ht-core
- name: Setup meta.sr.ht (required)
  hosts: all
  roles:
    - role: meta.sr.ht