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