Compare commits
2 commits
03f84d7b6a
...
5406f8c991
| Author | SHA1 | Date | |
|---|---|---|---|
| 5406f8c991 | |||
| 344d496a9b |
11 changed files with 59 additions and 419 deletions
|
|
@ -1,139 +0,0 @@
|
||||||
# Discourse State and Database Recovery
|
|
||||||
|
|
||||||
This restores Discourse from the BorgBase backup configured in `discourse.nix`.
|
|
||||||
It replaces the current Discourse state directory and PostgreSQL database with
|
|
||||||
the selected Borg archive.
|
|
||||||
|
|
||||||
The backup contains:
|
|
||||||
|
|
||||||
- `/var/lib/discourse`
|
|
||||||
- `/var/backup/discourse/discourse.dump`
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- BorgBase repo URL:
|
|
||||||
`ssh://oas17j8p@oas17j8p.repo.borgbase.com/./repo`
|
|
||||||
- Decrypted Borg SSH private key from the flash drive
|
|
||||||
- Borg repository passphrase from the flash drive
|
|
||||||
- A target server that has already been switched to this NixOS config
|
|
||||||
- A shell with `borg`, `openssh`, `postgresql`, and `rsync`
|
|
||||||
|
|
||||||
On NixOS or another machine with Nix:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
nix-shell -p borgbackup openssh postgresql rsync
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prepare Borg Access
|
|
||||||
|
|
||||||
Copy the Borg SSH key into a local recovery directory:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
mkdir -p ~/borg-recovery/discourse
|
|
||||||
cp /path/to/flash/borg-private-key ~/borg-recovery/discourse/borg-private-key
|
|
||||||
chmod 600 ~/borg-recovery/discourse/borg-private-key
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the Borg connection environment:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export BORG_REPO='ssh://oas17j8p@oas17j8p.repo.borgbase.com/./repo'
|
|
||||||
export BORG_RSH='ssh -i ~/borg-recovery/discourse/borg-private-key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new'
|
|
||||||
```
|
|
||||||
|
|
||||||
Read the Borg passphrase without showing it on screen:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
read -rsp 'Borg passphrase: ' BORG_PASSPHRASE
|
|
||||||
export BORG_PASSPHRASE
|
|
||||||
echo
|
|
||||||
```
|
|
||||||
|
|
||||||
## Extract a Backup
|
|
||||||
|
|
||||||
List available archives:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
borg list
|
|
||||||
```
|
|
||||||
|
|
||||||
Choose an archive name from the list, then extract only the Discourse state and
|
|
||||||
database dump into a temporary directory. Do not extract directly into `/`.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export ARCHIVE='ARCHIVE_NAME_FROM_BORG_LIST'
|
|
||||||
|
|
||||||
mkdir -p ~/borg-recovery/discourse/extract
|
|
||||||
cd ~/borg-recovery/discourse/extract
|
|
||||||
|
|
||||||
borg extract ::$ARCHIVE var/lib/discourse var/backup/discourse/discourse.dump
|
|
||||||
```
|
|
||||||
|
|
||||||
## Restore on the Server
|
|
||||||
|
|
||||||
Run these commands from `~/borg-recovery/discourse/extract` on the target
|
|
||||||
server. They assume the NixOS config has already created the `discourse` user,
|
|
||||||
`postgresql.service`, and `discourse-postgresql.service`.
|
|
||||||
|
|
||||||
Capture absolute tool paths so they still work through `sudo`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
RSYNC="$(command -v rsync)"
|
|
||||||
PSQL="$(command -v psql)"
|
|
||||||
DROPDB="$(command -v dropdb)"
|
|
||||||
PG_RESTORE="$(command -v pg_restore)"
|
|
||||||
```
|
|
||||||
|
|
||||||
Stop Discourse and make sure PostgreSQL is running:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo systemctl stop discourse.service
|
|
||||||
sudo systemctl start postgresql.service
|
|
||||||
```
|
|
||||||
|
|
||||||
Restore `/var/lib/discourse`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo "$RSYNC" -a --delete ./var/lib/discourse/ /var/lib/discourse/
|
|
||||||
sudo chown -R discourse:discourse /var/lib/discourse
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the `discourse` PostgreSQL database:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo -u postgres "$PSQL" -d postgres -v ON_ERROR_STOP=1 \
|
|
||||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'discourse';"
|
|
||||||
|
|
||||||
sudo -u postgres "$DROPDB" --if-exists discourse
|
|
||||||
sudo systemctl restart discourse-postgresql.service
|
|
||||||
|
|
||||||
sudo -u postgres "$PG_RESTORE" \
|
|
||||||
--exit-on-error \
|
|
||||||
--no-owner \
|
|
||||||
--role=discourse \
|
|
||||||
--dbname=discourse \
|
|
||||||
./var/backup/discourse/discourse.dump
|
|
||||||
|
|
||||||
sudo systemctl restart discourse-postgresql.service
|
|
||||||
```
|
|
||||||
|
|
||||||
Start Discourse:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo systemctl start discourse.service
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verify
|
|
||||||
|
|
||||||
Check the service and recent logs:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo systemctl status discourse.service --no-pager
|
|
||||||
sudo journalctl -u discourse.service -b --no-pager -n 100
|
|
||||||
```
|
|
||||||
|
|
||||||
If networking and DNS are already restored, open:
|
|
||||||
|
|
||||||
```text
|
|
||||||
https://discourse.torahimderecheretz.com
|
|
||||||
```
|
|
||||||
131
discourse.nix
131
discourse.nix
|
|
@ -1,131 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
services.discourse = {
|
|
||||||
enable = true;
|
|
||||||
admin = {
|
|
||||||
email = "yisroel.d.baum@gmail.com";
|
|
||||||
fullName = "Yisroel Baum";
|
|
||||||
passwordFile = config.sops.secrets."discourse-admin-password".path;
|
|
||||||
username = "yisroeldbaum";
|
|
||||||
};
|
|
||||||
database.ignorePostgresqlVersion = true;
|
|
||||||
hostname = "discourse.torahimderecheretz.com";
|
|
||||||
mail = {
|
|
||||||
notificationEmailAddress = "system@torahimderecheretz.com";
|
|
||||||
contactEmailAddress = "system@torahimderecheretz.com";
|
|
||||||
|
|
||||||
outgoing = {
|
|
||||||
serverAddress = "in-v3.mailjet.com";
|
|
||||||
port = 587;
|
|
||||||
username = "1ebdd73d944240c1a9c3201e52a1e8c6";
|
|
||||||
passwordFile = config.sops.secrets."discourse-mail-key".path;
|
|
||||||
authentication = "login";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
secretKeyBaseFile = config.sops.secrets."discourse-secret-key".path;
|
|
||||||
siteSettings = {
|
|
||||||
required = {
|
|
||||||
title = "Torah Im Derech Eretz";
|
|
||||||
site_description = "A forum to discuss the ideas of Rav Shamshon Refael Hirsch";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.borgbackup.jobs.discourse = {
|
|
||||||
paths = [
|
|
||||||
"/var/lib/discourse"
|
|
||||||
"/var/backup/discourse/discourse.dump"
|
|
||||||
];
|
|
||||||
repo = "ssh://oas17j8p@oas17j8p.repo.borgbase.com/./repo";
|
|
||||||
user = "root";
|
|
||||||
group = "root";
|
|
||||||
encryption = {
|
|
||||||
mode = "repokey-blake2";
|
|
||||||
passCommand = "${pkgs.coreutils}/bin/cat ${config.sops.secrets."borg-passphrase".path}";
|
|
||||||
};
|
|
||||||
doInit = true;
|
|
||||||
compression = "auto,zstd";
|
|
||||||
startAt = "*-*-* 07:15:00";
|
|
||||||
persistentTimer = true;
|
|
||||||
prune.keep = {
|
|
||||||
daily = 7;
|
|
||||||
weekly = 4;
|
|
||||||
monthly = 6;
|
|
||||||
};
|
|
||||||
readWritePaths = [ "/var/backup/discourse" ];
|
|
||||||
environment.BORG_RSH = "ssh -i ${config.sops.secrets."borg-private-key".path} -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.config/borg/known_hosts";
|
|
||||||
preHook = ''
|
|
||||||
discourseWasActive=/root/.cache/borg/discourse-was-active
|
|
||||||
dumpFile=/var/backup/discourse/discourse.dump
|
|
||||||
|
|
||||||
if ${pkgs.systemd}/bin/systemctl is-active --quiet discourse.service; then
|
|
||||||
touch "$discourseWasActive"
|
|
||||||
${pkgs.systemd}/bin/systemctl stop discourse.service
|
|
||||||
else
|
|
||||||
rm -f "$discourseWasActive"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$dumpFile"
|
|
||||||
umask 077
|
|
||||||
${lib.getExe' pkgs.su "su"} -s ${pkgs.runtimeShell} ${config.services.postgresql.superUser} -c '${config.services.postgresql.package}/bin/pg_dump --format=custom --dbname=${config.services.discourse.database.name}' > "$dumpFile"
|
|
||||||
'';
|
|
||||||
postHook = ''
|
|
||||||
discourseWasActive=/root/.cache/borg/discourse-was-active
|
|
||||||
dumpFile=/var/backup/discourse/discourse.dump
|
|
||||||
|
|
||||||
rm -f "$dumpFile"
|
|
||||||
|
|
||||||
if [ -e "$discourseWasActive" ]; then
|
|
||||||
${pkgs.systemd}/bin/systemctl start discourse.service
|
|
||||||
rm -f "$discourseWasActive"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d /var/backup/discourse 0700 root root - -"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services.borgbackup-job-discourse = {
|
|
||||||
after = [
|
|
||||||
"sops-install-secrets.service"
|
|
||||||
"network-online.target"
|
|
||||||
"postgresql.target"
|
|
||||||
"discourse-postgresql.service"
|
|
||||||
];
|
|
||||||
wants = [
|
|
||||||
"network-online.target"
|
|
||||||
"postgresql.target"
|
|
||||||
"discourse-postgresql.service"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
"discourse-admin-password" = {
|
|
||||||
sopsFile = ./secrets/discourseAdminPassword.yaml;
|
|
||||||
mode = "0400";
|
|
||||||
key = "password";
|
|
||||||
owner = "discourse";
|
|
||||||
group = "discourse";
|
|
||||||
};
|
|
||||||
"discourse-secret-key" = {
|
|
||||||
sopsFile = ./secrets/discourseSecretKey.yaml;
|
|
||||||
mode = "0400";
|
|
||||||
key = "key";
|
|
||||||
owner = "discourse";
|
|
||||||
group = "discourse";
|
|
||||||
};
|
|
||||||
"discourse-mail-key" = {
|
|
||||||
sopsFile = ./secrets/discourseMailKey.yaml;
|
|
||||||
mode = "0400";
|
|
||||||
key = "key";
|
|
||||||
owner = "discourse";
|
|
||||||
group = "discourse";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
44
flake.lock
generated
44
flake.lock
generated
|
|
@ -122,34 +122,18 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tide": {
|
"tide": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
]
|
||||||
"utils": "utils_2"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1778485449,
|
"lastModified": 1784446675,
|
||||||
"narHash": "sha256-Y8p1JKfU7I1T33qI7F6zXobtQqLqry4NVBb2eAlnBqc=",
|
"narHash": "sha256-r7X6qZeRLdOskfw3Ktz9VbatkHu/4Tb0wjpYx8AVZdc=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "ae9dd787a6ac64c300251cf047274dd76af6d2d6",
|
"rev": "21cca99e9e06f7aec0d14ba782cecad5e56ab098",
|
||||||
"revCount": 137,
|
"revCount": 140,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.yisroelbaum.com/yisroelbaum/TIDE"
|
"url": "https://git.yisroelbaum.com/yisroelbaum/TIDE"
|
||||||
},
|
},
|
||||||
|
|
@ -175,24 +159,6 @@
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@
|
||||||
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
|
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
|
||||||
modules = [
|
modules = [
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
./discourse.nix
|
|
||||||
./tide.nix
|
./tide.nix
|
||||||
./rabbi-gerzi.nix
|
./rabbi-gerzi.nix
|
||||||
home-manager.nixosModules.home-manager
|
home-manager.nixosModules.home-manager
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ host's age key.
|
||||||
```
|
```
|
||||||
sudo grep "public key" /var/lib/sops-nix/key.txt
|
sudo grep "public key" /var/lib/sops-nix/key.txt
|
||||||
```
|
```
|
||||||
3. On a workstation, put that public key into `.sops.yaml` at
|
3. On a workstation, put that public key into `.sops.yaml` at the repository
|
||||||
the repo root and encrypt `tide.yaml.example` into
|
root and encrypt the required secret files.
|
||||||
`tide.yaml`.
|
|
||||||
|
|
||||||
`tide.yaml` is encrypted and committed. `tide.yaml.example` is
|
## TIDE
|
||||||
the plaintext template.
|
|
||||||
|
Create `tide.yaml` from `tide.yaml.example` and encrypt it with sops. It must
|
||||||
|
contain `admin-password`, `mail-key`, and `secret-key`. The encrypted file is
|
||||||
|
committed; the example contains placeholders only.
|
||||||
|
|
||||||
## Rabbi Gerzi
|
## Rabbi Gerzi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
password: ENC[AES256_GCM,data:6/a9U7I2joMSkfiysYgMrBN3AE3DRUjQsZw=,iv:EDeN/IClY/yNkVi/FLiTeeOjkj5Df/1l4XXzf5STot0=,tag:8uoXPXJuL4JlMGzgtK6D+w==,type:str]
|
|
||||||
sops:
|
|
||||||
age:
|
|
||||||
- recipient: age1haj8v88kjna6ttkdufjpyjcf478kyvclnpdc8jwh97ewhqcc9eqsgrku4v
|
|
||||||
enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvYWtUZ2JYU3VNaHQ1bXVL
|
|
||||||
V0ZmRVIxVDg2SE5GcU5UMTZjcnVOUlBZOHlzCjc5elV3bk54ak05WStvNGUvNCtP
|
|
||||||
aVNJT0tFTVFMb2NKTlpValJ6YWFVcEEKLS0tIDlEUjRncWFsUGptZTBmSm1idm0z
|
|
||||||
ZUgxUHhvejMreHUrbDVwTkxwOE1DSkUK62qiTlgQvKruDj3Bh/8nJH+5CBl+fzbJ
|
|
||||||
q5ryzSfTpEcSfBwRENqIOYMOTjzr+y+JGymjokc9rCYb0NPQUFpHzg==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
lastmodified: "2026-06-02T18:00:59Z"
|
|
||||||
mac: ENC[AES256_GCM,data:H/+voyLLpsGxgTNjN96PL+I1tpW8Ylo+mWVTbsaUMnnOZIkfA81MzEjtdWOs0Pg4AysAeMbmJUob+gTtSfcqnVvhv/FBVHuPji+Vz0cj0TM8k7oLlaIgeL/MAZ1mhrxDIUK5pQYSVnaaWrQ43Z4DeqaboDY7feUOp8n6emICo5I=,iv:dCPOGBC80ZtM0ObeRRMAGH2f4K5zFtckaxKAJZll8yI=,tag:xwBJzExJXAw0T39hPdmgRw==,type:str]
|
|
||||||
unencrypted_suffix: _unencrypted
|
|
||||||
version: 3.12.2
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
key: ENC[AES256_GCM,data:Flvbs7sRCQ3BtSJXWurmVA+QQmvSo/fxrOVJNalBjj8=,iv:ddmk1XCjvNRmmqGhZwOWDlYSkdKz3ryyQwrav/T6Oz8=,tag:DvSjwdK01UAvBL1mYCe7kA==,type:str]
|
|
||||||
sops:
|
|
||||||
age:
|
|
||||||
- recipient: age1haj8v88kjna6ttkdufjpyjcf478kyvclnpdc8jwh97ewhqcc9eqsgrku4v
|
|
||||||
enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVUGFmM010b3NhdCtPME5O
|
|
||||||
RWRkUjF0akVEZVFtNkhZeUVSeWpWL3pKNG1nCllObGlPOTcrSyt1a2NCc0U4TVZM
|
|
||||||
cWVQbjJocEUySHFYaDFmNzllOGhuQWcKLS0tIHFJeFdUWENxK1RRSlhvSEI4NXlO
|
|
||||||
TG1wM1FTWjBBWHdHNnBUUE1kNWE4akUK1Ehv5kYNZrbf3m3vH5Oyy/g2mz8kIX8T
|
|
||||||
l2k1LbhUo3cNzIjkNM05RSrLZEBPItoo2nc9oTAPZiF1U/K76SMP6g==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
lastmodified: "2026-06-03T07:59:50Z"
|
|
||||||
mac: ENC[AES256_GCM,data:E4WwhOqUCmOBXwYUR5nFzth59F50KE7k88NkOBc6ERnX4b5joy0lmExenprH019UVJ/HSXOCWphUMwTat70uzwhaKBtlfa7AWmWyMa6zhIzxTsFagI4h1nWQP00uGb0MM3ULR3mcLtxXyTtZFWuY/d8ttdYBuv2oe29ikJTX610=,iv:gAZPLiYcNGq8LSEpIxDzDqdan+CJKyQ+SnQXHmponns=,tag:3tI0it3h6foT9W7zK3rmxg==,type:str]
|
|
||||||
unencrypted_suffix: _unencrypted
|
|
||||||
version: 3.12.2
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
key: ENC[AES256_GCM,data:lZXbXrorH/GGGg93WgnG6GlzNs0NEy0BlForhgKlwllsf2uJWnBukQDDxxI6gUWMp+Uzc3e9plrmGbM7HQfsccDoQux7kJjGdt5CJgou0eh4J8WGw51M+beixVdF6UuCFFiuLERstzpIP1jhmyDfLZjMVTwfoRe0/YP52Ljgl2Y=,iv:pShAMIXtbEuV7Y2o+3NgTIbPPaoZvx9qF7lj60LMabw=,tag:P6vnrqWzJXXhUl+sJrY2gw==,type:str]
|
|
||||||
sops:
|
|
||||||
age:
|
|
||||||
- recipient: age1haj8v88kjna6ttkdufjpyjcf478kyvclnpdc8jwh97ewhqcc9eqsgrku4v
|
|
||||||
enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlTHF2cGFIQXE2UDJhaVZX
|
|
||||||
KzhXcWNPbW0zYjBPbTVReUJKOW1ReEt5ZFFNCjFnU25UVVY5eXFyaFFMbkI1NHZ5
|
|
||||||
WHRIZ1pYT1ZVWUFJc2hrcGtYTndPdVEKLS0tIHZUSzkxUjEvU0kzRG93Vnc1ZGQz
|
|
||||||
WGZ3U2dvYmFIVlYvZ3c1Q2JsQkNGek0KGYlQEPskcDN2VxkHskctCTIjPjse1wLL
|
|
||||||
6VcEuidu69gkMqvDXR4bmnt8hxSqdH6OS3qEuDfLpqndjfCh//4CDw==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
lastmodified: "2026-06-02T18:08:12Z"
|
|
||||||
mac: ENC[AES256_GCM,data:jYec4f5lr8jYCVvzpM2a3lpIILOTKauz1DNuDIclL7kGA2QXJhW1njXKhmp/VCVbZLfoHCJk6SHyMcxUcx6HXtmc7FAVpu5+Vc/9N+D3gKlyXewDeC41CFdZ7GU0vtoVQK3KQe82rWl+6SRCWXeqLfmRlh7/pLwZy+bj20kG0XY=,iv:sIYEumkyDVrWZRVZpFPtrM96p82rtxW870LRkpHFsPY=,tag:Ey9L21LN+2HjCReuoqJtKQ==,type:str]
|
|
||||||
unencrypted_suffix: _unencrypted
|
|
||||||
version: 3.12.2
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
tide-env: ENC[AES256_GCM,data:O4B6jatKVbbVoO53QNgh1ra0Ij9cxo64j+wmmeTZWgX7Us3z0FT3hsOsmYWBsCrKFIGpA2meO4QXlowbJOw43sO7CiCFKuRN7rv58XbGk/hevNhUUTrqU8Seha18ZeoOfmUhD1ScJtyC5RkJb1xFnsFWmFtofebyabhcSLXmJz4UC7Vl2BsVZWULjp9LIkAIOcr2Y6Dm6SoXQa3n6p5yt5rKlY/Xfg/zQoczfi0TG+x+P4SzPPv2U8PJQGG1OqQ0bA82wFNcna+mIb6QIvWz27PeBK4pDfDYnA0SqzKEZsol3WgF5LWPXL5zQpsh11oy7YBWdx8omo3KNBx3+tl2/ACYLuTKRgrxP3UIE38hAm4j95IrL88jFWeNtfZ/fMdTckuQKR2vgbN6Q5HYqnYW72uxCYGZGWD+Jq7MAUduAbvzSlurLoHnKCrA+4Pf8Tu/yJBVDN+urhxnWJy8VpGDXFAGA+reXLtNjZXg1y2KNJOijmzEYfwZ8t53MvCdsJnF562mvCag+DXGex6DZ2qqKeXGa3Lw/YTPBXCHjHJfr+0l91NoOjelA33wS+h+RisHw/iCPlt3NHQgJtCXN7JRWM1Hi3p1ADgovvqJ7yBsT3OXD91QIy5NPSergGsj2vvj+9VBEzSTUuHA4KzYgxrjLUgLeohypr4veFs3qnC3/iTx9gkRfcFX0SMPgj1DWy9+pS42Wi+YDgILAQVSSOgrjhnMv2E0nYNEgZcTPS6SKj+Li4V3/OUVjbMj4N6BLsXxbhyFrKH6MF7ZOXU=,iv:yj0aUCReke2gI3uk/Qer6QBSCQgTwZmCaK7aPBtUwDo=,tag:e6NcaEL3f4EFsaGQKIH+BA==,type:str]
|
admin-password: ENC[AES256_GCM,data:yPOLYZ9oMvZrXcfgJQkGL83qPpHrJe7i4Gw=,iv:WmPO6hDNkB7nckFuJjnZX5/8xXpZkUxP8FkjmYmQxzs=,tag:g1dLh009OOBYYL9UkPz3uw==,type:str]
|
||||||
|
mail-key: ENC[AES256_GCM,data:/9bT8jECSyMqz45LLbWSV15OAJ4wA/ZiCvi9uYrSZK8=,iv:ZN9taTVcIHxqz63pxir7RWTp4CpjVxUBTH+HO92CvPU=,tag:ufVCA36rVBE2ghbI63QXfA==,type:str]
|
||||||
|
secret-key: ENC[AES256_GCM,data:pFsKF3A9RZSxeV9FwNKIzp+Avu6YWCCHj9zIeex7nHqhuFNuL8D4blQWPujms3wRTUj/OdCbgh56ZXdnEJ6l0vYBKBnP7tS7v7jBhZWtF03SqukCpu+c2HCO6wNvP0tw9zVVSVp0RRmvLq7KhmL6Y9748/TA08MPCVNVP46Xa2E=,iv:yH4vU6kjM+49ssWVAULJcZ9ny264zWsixGs/et6G1Lk=,tag:UWH3Epqn4yGqeTYeBcrDPg==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age1haj8v88kjna6ttkdufjpyjcf478kyvclnpdc8jwh97ewhqcc9eqsgrku4v
|
- enc: |
|
||||||
enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnQ2RzU0NBUTBhQTBERnBm
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOZEtYbEhoNW5FVnRlQXIr
|
||||||
RFRPdWlKRWtWMW8zaktGVyt5L3VPTENMakhVCjFsdStSbHFzdmFYcWxuU294VW5Q
|
TWJaVk5RUmdaUnNKcHRwbElWWCtLQ3l6cm00ClVzNWJZYzRpS21Wckd0dFlEano2
|
||||||
MWVQMzlJdmdqTW93Q1MwaVRFaVIvY2sKLS0tIHJxUk96VjRLeWpIanBlNktESDhI
|
c1k3TlNZNjU2WGVqUkExblRUZmJpbHcKLS0tIGsvWWhrREoxV2FEREFWQUNpMEZR
|
||||||
dS9VREordndFSVhnbXFCdUVFN0Z3aG8KN4h2ptJxttY/02FcmRqZa+ujom0LIS11
|
MlRTRk13YTljdi9LOW1talBpeDBQdmMKeDvwaLwwYjw1lLFf+/ZRmVGP+chYxa1Y
|
||||||
cS+qkrk7FnrTbSCNETtjc/FApLTxphptd93zCPJkQtulmo2d9soJlA==
|
d/NjatKGm8M/hEKw/MpEKSAMMNAJPUHVwDAeaQTro19TzYerV8e9BQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2026-05-11T07:36:34Z"
|
recipient: age1haj8v88kjna6ttkdufjpyjcf478kyvclnpdc8jwh97ewhqcc9eqsgrku4v
|
||||||
mac: ENC[AES256_GCM,data:VDSB1iKcP9AY4HGHPlQxINlc2wB03EkCd8TcHLfFqE0zNBPYlX0Buf6kqy89/0M9UmRYGS7IlYMEDlFGJ61cidD0Kxk2n9R7P32S75nS//lorX/28Slnwlfe9K4w7eS5p+zwO0QiPY3X87or6KTyi95QBYz6ALp9cpetrRn3nJk=,iv:PPqdmFbXCFmZgfXsEUvuQy1PDde2dD/EnHAFE2TGZIk=,tag:Hv+QMEM8wkjXPUt4TGhvCQ==,type:str]
|
lastmodified: "2026-07-19T07:04:49Z"
|
||||||
|
mac: ENC[AES256_GCM,data:UvfEnQuGSrXNe9Zyel2UEyfcAgh03UgBu0VzSg0lzOgYuK0qPwvezZNXRyfFkHo0AdpIJAmN8pwtavExUWqL8OOUgIcooK7hZJ9S5aXFfLeZ0YhpAZ1TpUmHTNRL7VMOJtLU/phvObggYTHJfLLE92RkwnKxqJ8hIli8VVIa59w=,iv:NYcAEyoNC8cygEczLGUXzuhY/OXTXCpP3BDQJJKQwQU=,tag:PgkGZwmkiCAkO/WPZJyDYQ==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.12.2
|
version: 3.13.2
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,5 @@
|
||||||
# Encrypt this with sops to produce ./tide.yaml:
|
# Encrypt this file with sops to produce ./tide.yaml.
|
||||||
#
|
|
||||||
# sops --encrypt --age $(cat ~/.config/sops/age/keys.txt | grep public | cut -d: -f2 | tr -d ' ') \
|
|
||||||
# secrets/tide.yaml.example > secrets/tide.yaml
|
|
||||||
#
|
|
||||||
# Or set up .sops.yaml with the host's age public key and run
|
|
||||||
# `sops secrets/tide.yaml`.
|
|
||||||
#
|
|
||||||
# Generate APP_KEY with:
|
|
||||||
# php -r "echo 'base64:'.base64_encode(random_bytes(32)).PHP_EOL;"
|
|
||||||
|
|
||||||
tide-env: |
|
admin-password: REPLACE_ME
|
||||||
APP_KEY=base64:REPLACE_ME
|
mail-key: REPLACE_ME
|
||||||
DB_PASSWORD=REPLACE_ME
|
secret-key: REPLACE_ME
|
||||||
MAIL_HOST=127.0.0.1
|
|
||||||
MAIL_PORT=1025
|
|
||||||
MAIL_USERNAME=
|
|
||||||
MAIL_PASSWORD=
|
|
||||||
MAIL_FROM_ADDRESS=noreply@tide.yisroelbaum.com
|
|
||||||
MAIL_FROM_NAME=TIDE
|
|
||||||
|
|
|
||||||
56
tide.nix
56
tide.nix
|
|
@ -1,37 +1,41 @@
|
||||||
{
|
{ config, ... }:
|
||||||
domainName,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
{
|
||||||
services.tide = {
|
services.tide = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = "tide.${domainName}";
|
secretFiles = {
|
||||||
apiDomain = "apitide.${domainName}";
|
adminPassword = config.sops.secrets."tide-admin-password".path;
|
||||||
secretsFile = "/run/secrets/tide-env";
|
mailPassword = config.sops.secrets."tide-mail-password".path;
|
||||||
# Reuse the wildcard cert already issued for *.${domainName}
|
secretKeyBase = config.sops.secrets."tide-secret-key-base".path;
|
||||||
# in configuration.nix instead of requesting a new one per
|
borgPassphrase = config.sops.secrets."borg-passphrase".path;
|
||||||
# subdomain.
|
borgPrivateKey = config.sops.secrets."borg-private-key".path;
|
||||||
nginx.useACMEHost = null;
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Don't fail evaluation when secrets/tide.yaml is missing (e.g.
|
|
||||||
# before the operator has encrypted it on a fresh checkout).
|
|
||||||
# sops-install-secrets will still error at activation time if
|
|
||||||
# the file is absent, which is the right place for that failure.
|
|
||||||
sops.validateSopsFiles = false;
|
|
||||||
|
|
||||||
# Use the host's age key generated with age-keygen, not the
|
|
||||||
# SSH-host-key-derived identity sops-nix falls back to by default.
|
|
||||||
# The encrypted file's recipient is the public key paired with
|
|
||||||
# this private key.
|
|
||||||
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||||
sops.age.sshKeyPaths = [ ];
|
sops.age.sshKeyPaths = [ ];
|
||||||
sops.gnupg.sshKeyPaths = [ ];
|
sops.gnupg.sshKeyPaths = [ ];
|
||||||
|
|
||||||
sops.secrets."tide-env" = {
|
sops.secrets = {
|
||||||
sopsFile = ./secrets/tide.yaml;
|
"tide-admin-password" = {
|
||||||
# phpfpm reads this via EnvironmentFile, which runs as root
|
sopsFile = ./secrets/tide.yaml;
|
||||||
# before dropping to the tide user, so root readable is enough.
|
key = "admin-password";
|
||||||
mode = "0400";
|
mode = "0400";
|
||||||
|
owner = "discourse";
|
||||||
|
group = "discourse";
|
||||||
|
};
|
||||||
|
"tide-mail-password" = {
|
||||||
|
sopsFile = ./secrets/tide.yaml;
|
||||||
|
key = "mail-key";
|
||||||
|
mode = "0400";
|
||||||
|
owner = "discourse";
|
||||||
|
group = "discourse";
|
||||||
|
};
|
||||||
|
"tide-secret-key-base" = {
|
||||||
|
sopsFile = ./secrets/tide.yaml;
|
||||||
|
key = "secret-key";
|
||||||
|
mode = "0400";
|
||||||
|
owner = "discourse";
|
||||||
|
group = "discourse";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue