Compare commits
5 commits
642869ce9b
...
01c0fa76cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 01c0fa76cb | |||
| b00bd87046 | |||
| 98d5197056 | |||
| f9afc7285f | |||
| 3850948f71 |
28 changed files with 472 additions and 890 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
|
@ -38,13 +38,6 @@ jobs:
|
||||||
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
|
||||||
- name: Connect to Tailscale
|
|
||||||
uses: tailscale/github-action@v2
|
|
||||||
with:
|
|
||||||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
|
||||||
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
|
||||||
tags: tag:ci
|
|
||||||
|
|
||||||
- name: Configure SSH
|
- name: Configure SSH
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
.bootstrap/
|
.bootstrap/
|
||||||
.direnv/
|
.direnv/
|
||||||
|
dump/
|
||||||
|
|
|
||||||
202
README.md
202
README.md
|
|
@ -1,31 +1,184 @@
|
||||||
# Noisebridge Wiki Infra
|
# Noisebridge Wiki *2.0 Prototype*
|
||||||
|
|
||||||
This repo manages the Noisebridge MediaWiki primary and replica on NixOS.
|
This repo manages the Noisebridge wiki. It is currently for the Noisebridge Wiki 2.0 Prototype that is planned to eventually replace the current Noisebridge wiki infrastructure.
|
||||||
|
|
||||||
|
## Development Hosts
|
||||||
|
|
||||||
|
- primary wiki: `main-wiki.extremist.software`
|
||||||
|
- read-only replica: `replica-wiki.extremist.software`
|
||||||
|
- deployment/admin SSH user: `jet` *this is hoped to expand soon!*
|
||||||
|
|
||||||
|
A note here, once this project is underway, CI/CD should only allow changes to come through reviewed PRs into the main branch. These changes would then be built and deployed from an automated Github action (could be forgejo actions in the future)
|
||||||
|
|
||||||
|
The current repo is the deployment foundation for a two-machine MediaWiki stack:
|
||||||
|
|
||||||
|
- primary host: MediaWiki, MariaDB primary, Caddy, agenix-managed secrets
|
||||||
|
- replica host: MediaWiki, MariaDB read-only replica, Caddy, agenix-managed secrets
|
||||||
|
|
||||||
|
We haven't fully implemented all the features, but the remaining work is tracked here so this README can act as the main working checklist.
|
||||||
|
|
||||||
|
## Implementation Checklist
|
||||||
|
|
||||||
|
### Core rollout
|
||||||
|
|
||||||
|
- [ ] Finish `hosts/main-wiki.nix` with MediaWiki, MariaDB primary, Caddy, and agenix-managed secrets
|
||||||
|
- [ ] Finish `hosts/replica-wiki.nix` with MediaWiki, MariaDB replica, Caddy, and agenix-managed secrets
|
||||||
|
- [ ] Make the flake configuration fully express hostnames, domains, shared secrets, and host-only secrets
|
||||||
|
- [ ] Wire all required agenix secrets into services on both hosts
|
||||||
|
- [ ] Keep both machine closures building cleanly from the flake
|
||||||
|
- [ ] Keep `deploy-rs` as the standard deployment path for both machines
|
||||||
|
|
||||||
|
### Database and replication
|
||||||
|
|
||||||
|
- [ ] Create the MediaWiki MariaDB database and application user on the primary
|
||||||
|
- [ ] Configure MariaDB replication user and secure replication credentials
|
||||||
|
- [ ] Configure the replica host as a real read-only MariaDB replica
|
||||||
|
- [ ] Verify replication from primary to replica under normal wiki writes
|
||||||
|
- [ ] Document promotion and rebuild expectations for the replica
|
||||||
|
|
||||||
|
### MediaWiki application
|
||||||
|
|
||||||
|
- [ ] Choose and pin the exact MediaWiki version for the new stack
|
||||||
|
- [ ] Recreate the current wiki configuration from `LocalSettings.php` in a maintainable Nix-managed form
|
||||||
|
- [ ] Configure uploads, logo, favicon, and local asset paths
|
||||||
|
- [ ] Reinstall and validate required extensions
|
||||||
|
- [ ] Reinstall and validate any non-default skins
|
||||||
|
- [ ] Recreate job queue or maintenance task execution needed by the wiki
|
||||||
|
- [ ] Confirm the primary wiki is writable
|
||||||
|
- [ ] Confirm the replica wiki is publicly readable and actually read-only
|
||||||
|
|
||||||
|
### Web serving and TLS
|
||||||
|
|
||||||
|
- [ ] Configure Caddy virtual hosts for `main-wiki.extremist.software`
|
||||||
|
- [ ] Configure Caddy virtual hosts for `replica-wiki.extremist.software`
|
||||||
|
- [ ] Configure TLS and renewal behavior for both public hosts
|
||||||
|
- [ ] Recreate any needed redirects, asset routes, and static file handling
|
||||||
|
- [ ] Verify PHP-FPM and web serving behavior under the chosen runtime
|
||||||
|
|
||||||
|
### Migration and cutover
|
||||||
|
|
||||||
|
- [ ] Dump the current MediaWiki database
|
||||||
|
- [ ] Verify the actual table prefix used in production
|
||||||
|
- [ ] Copy uploaded files from `/srv/mediawiki/noisebridge.net/images/`
|
||||||
|
- [ ] Copy local static assets from `/srv/mediawiki/noisebridge.net/img/`
|
||||||
|
- [ ] Copy and review the current `LocalSettings.php`
|
||||||
|
- [ ] Inventory current secrets including DB credentials, MediaWiki secret keys, upgrade key, and ReCaptcha keys
|
||||||
|
- [ ] Inventory the exact live MediaWiki version, extensions, skins, and Composer-managed dependencies
|
||||||
|
- [ ] Inventory current Caddy and PHP-FPM runtime configuration
|
||||||
|
- [ ] Inventory cron or systemd jobs related to MediaWiki maintenance, backups, or queues
|
||||||
|
- [ ] Measure current database and upload sizes for migration planning
|
||||||
|
- [ ] Produce rollback notes for the final cutover
|
||||||
|
- [ ] Import production data into the new primary host
|
||||||
|
- [ ] Verify the replica catches up from the imported primary state
|
||||||
|
- [ ] Smoke test reading, editing, login, uploads, search, history, and diff behavior before cutover
|
||||||
|
|
||||||
|
### CI/CD and repository workflow
|
||||||
|
|
||||||
|
- [ ] Require reviewed PRs before merge to `main`
|
||||||
|
- [ ] Block direct pushes to `main`
|
||||||
|
- [ ] Keep `nix flake check` required in CI
|
||||||
|
- [ ] Keep both host builds required in CI
|
||||||
|
- [ ] Keep automatic deploys on pushes to `main`
|
||||||
|
- [ ] Add post-deploy smoke checks if needed
|
||||||
|
- [ ] Optionally move from GitHub Actions to Forgejo Actions later
|
||||||
|
|
||||||
|
### Security and access policy
|
||||||
|
|
||||||
|
- [ ] Explicitly define anonymous user permissions in MediaWiki
|
||||||
|
- [ ] Keep account creation invite-only
|
||||||
|
- [ ] Allow read, search, history, and diff access where desired
|
||||||
|
- [ ] Restrict broader special-page use for anonymous traffic
|
||||||
|
- [ ] Review SSH/admin access model beyond the initial `jet` user
|
||||||
|
|
||||||
|
## Future Features Checklist
|
||||||
|
|
||||||
|
### Edge and public access
|
||||||
|
|
||||||
|
- [ ] Use the final apex domain for the primary wiki
|
||||||
|
- [ ] Serve the final replica from `replica.<domain>`
|
||||||
|
- [ ] Support a direct-to-origin non-Cloudflare deployment mode
|
||||||
|
- [ ] Add a separate Cloudflare-proxied deployment mode later
|
||||||
|
|
||||||
|
### Performance and abuse controls
|
||||||
|
|
||||||
|
- [ ] Add aggressive anonymous rate limiting in Caddy
|
||||||
|
- [ ] Add cache policy that favors anonymous page views
|
||||||
|
- [ ] Reduce or bypass caching for logged-in and dynamic traffic
|
||||||
|
- [ ] Preserve good behavior for logged-in editors while limiting abuse
|
||||||
|
- [ ] Add stronger service and access logging for tuning
|
||||||
|
|
||||||
|
### Database evolution
|
||||||
|
|
||||||
|
- [ ] Revisit the long-term database backend after the baseline is stable
|
||||||
|
- [ ] Evaluate migration away from MariaDB if a better fit emerges
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
|
||||||
|
- [ ] Add public read-only Grafana
|
||||||
|
- [ ] Add a public status page
|
||||||
|
- [ ] Add email alerts
|
||||||
|
- [ ] Add Discord webhook alerts
|
||||||
|
- [ ] Add more detailed dashboards
|
||||||
|
|
||||||
|
### Backups and exports
|
||||||
|
|
||||||
|
- [ ] Add encrypted client-side backups to Backblaze B2
|
||||||
|
- [ ] Define a retention policy
|
||||||
|
- [ ] Write a restore runbook
|
||||||
|
- [ ] Support volunteer-hosted backup targets later
|
||||||
|
- [ ] Provide a scraper-friendly export API instead of forcing heavy live-site scraping
|
||||||
|
- [ ] Publish a sanitized public SQL subset rather than a raw production dump
|
||||||
|
- [ ] Generate daily export snapshots
|
||||||
|
- [ ] Host downloads away from the primary, ideally from the replica side
|
||||||
|
- [ ] Publish stable JSON metadata for latest export, export history, checksums, and download URLs
|
||||||
|
- [ ] Add additional public export formats beyond SQL subset dumps
|
||||||
|
|
||||||
|
### Tor and alternative access
|
||||||
|
|
||||||
|
- [ ] Add a stable onion service for the primary host
|
||||||
|
- [ ] Add a stable onion service for the replica host
|
||||||
|
- [ ] Manage onion private keys with agenix so addresses survive rebuilds
|
||||||
|
|
||||||
|
### Longer-term operations
|
||||||
|
|
||||||
|
- [ ] Write a formal failover and promotion runbook
|
||||||
|
- [ ] Add stronger deployment protections
|
||||||
|
- [ ] Add a scheduled flake lock update workflow or an admin-run update script with PR review before merge
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
Bootstrap a brand new VPS into NixOS and seed its stable agenix host key:
|
Bootstrap a brand new Ubuntu 22.04 DigitalOcean VPS into NixOS:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix run .#bootstrap-host -- <main-wiki|replica-wiki> <target-host> [ssh-identity-file]
|
nix run .#bootstrap-host -- --admin <name> <main-wiki|replica-wiki> <target-host> [ssh-identity-file]
|
||||||
nix run .#bootstrap-host -- <main-target-host> <replica-target-host> [ssh-identity-file]
|
nix run .#bootstrap-host -- --admin <name> <main-target-host> <replica-target-host> [ssh-identity-file]
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix run .#bootstrap-host -- main-wiki root@203.0.113.10 ~/.ssh/do-bootstrap
|
nix run .#bootstrap-host -- --admin jet main-wiki root@203.0.113.10 ~/.ssh/do-bootstrap
|
||||||
nix run .#bootstrap-host -- root@203.0.113.10 root@203.0.113.11 ~/.ssh/do-bootstrap
|
nix run .#bootstrap-host -- --admin jet root@203.0.113.10 root@203.0.113.11 ~/.ssh/do-bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`--admin <name>` is required. The admin must exist in `siteConfig.adminUsers` in `flake.nix`.
|
||||||
|
|
||||||
What bootstrap does:
|
What bootstrap does:
|
||||||
|
|
||||||
- generates or reuses `.bootstrap/<host>/host.age`
|
- copies a first-boot module to the host
|
||||||
- writes the matching public recipient to `secrets/hosts/<host>.age.pub`
|
- runs `nixos-infect` on the Ubuntu VPS
|
||||||
- rekeys the agenix secrets with `agenix -r`
|
- converts the machine to NixOS with the requested admin user
|
||||||
- runs `nixos-anywhere` against one or both raw VPS targets
|
- disables direct root SSH
|
||||||
- installs `/var/lib/agenix/host.age` onto the new machine
|
- fixes the known bad IPv6 routes generated by `nixos-infect`
|
||||||
- lets the machine decrypt its Tailscale auth secret and come up on Tailscale with its configured hostname
|
- verifies that the requested admin login and `sudo` work and that the host reaches `running`
|
||||||
|
|
||||||
|
What bootstrap is not:
|
||||||
|
|
||||||
|
- it is not the normal long-term deploy path
|
||||||
|
- it is not the full application rollout
|
||||||
|
- it is only the one-off Ubuntu-to-NixOS installer step
|
||||||
|
|
||||||
|
> This is made to only be run once and to potentially prop up new servers if needed
|
||||||
|
|
||||||
Deploy all already-bootstrapped hosts:
|
Deploy all already-bootstrapped hosts:
|
||||||
|
|
||||||
|
|
@ -58,24 +211,5 @@ nix flake check 'path:.' --accept-flake-config
|
||||||
|
|
||||||
1. Create a raw VPS.
|
1. Create a raw VPS.
|
||||||
2. Run `nix run .#bootstrap-host -- ...` from the repo root on an admin laptop.
|
2. Run `nix run .#bootstrap-host -- ...` from the repo root on an admin laptop.
|
||||||
3. The machine installs NixOS, gets its host agenix key, and joins Tailscale.
|
3. The machine installs NixOS and comes up over public SSH.
|
||||||
4. Future changes use `nix run .#deploy`.
|
4. Future configuration changes would be made through CI/CD.
|
||||||
|
|
||||||
## GitHub Settings
|
|
||||||
|
|
||||||
To require pull requests and auto-deploy only from `main`, set branch protection or a ruleset on `main` with:
|
|
||||||
|
|
||||||
- require a pull request before merging
|
|
||||||
- do not allow direct pushes to `main`
|
|
||||||
- require status checks to pass before merging
|
|
||||||
- select the CI check job from this repo
|
|
||||||
- optionally require approvals before merging
|
|
||||||
|
|
||||||
This repo already deploys on pushes to `main` in `.github/workflows/ci.yml`.
|
|
||||||
|
|
||||||
That means the intended flow is:
|
|
||||||
|
|
||||||
1. open a PR
|
|
||||||
2. CI passes
|
|
||||||
3. merge into `main`
|
|
||||||
4. GitHub Actions runs `nix run .#deploy`
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
disko.devices = {
|
|
||||||
disk.main = {
|
|
||||||
type = "disk";
|
|
||||||
device = "/dev/vda";
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
bios = {
|
|
||||||
size = "1M";
|
|
||||||
type = "EF02";
|
|
||||||
};
|
|
||||||
esp = {
|
|
||||||
size = "512M";
|
|
||||||
type = "EF00";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
root = {
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "ext4";
|
|
||||||
mountpoint = "/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
175
flake.lock
generated
175
flake.lock
generated
|
|
@ -67,48 +67,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disko": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773889306,
|
|
||||||
"narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"disko_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixos-anywhere",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1769524058,
|
|
||||||
"narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "master",
|
|
||||||
"repo": "disko",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
|
@ -125,27 +83,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": [
|
|
||||||
"nixos-anywhere",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1768135262,
|
|
||||||
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|
@ -167,95 +104,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-vm-test": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixos-anywhere",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1769079217,
|
|
||||||
"narHash": "sha256-R6qzhu+YJolxE2vUsPQWWwUKMbAG5nXX3pBtg8BNX38=",
|
|
||||||
"owner": "Enzime",
|
|
||||||
"repo": "nix-vm-test",
|
|
||||||
"rev": "58c15f78947b431d6c206e0966500c7e9139bd2f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "Enzime",
|
|
||||||
"ref": "pr-105-latest",
|
|
||||||
"repo": "nix-vm-test",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixos-anywhere": {
|
|
||||||
"inputs": {
|
|
||||||
"disko": "disko_2",
|
|
||||||
"flake-parts": "flake-parts",
|
|
||||||
"nix-vm-test": "nix-vm-test",
|
|
||||||
"nixos-images": "nixos-images",
|
|
||||||
"nixos-stable": "nixos-stable",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"treefmt-nix": "treefmt-nix"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1769956140,
|
|
||||||
"narHash": "sha256-D+RQ+DaIC/GVwv5lUs7e8jSmh8aPc77Kg/gRjaS25Zk=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixos-anywhere",
|
|
||||||
"rev": "92f82c5196a5f8588be4967e535c4cfd35e85902",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixos-anywhere",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixos-images": {
|
|
||||||
"inputs": {
|
|
||||||
"nixos-stable": [
|
|
||||||
"nixos-anywhere",
|
|
||||||
"nixos-stable"
|
|
||||||
],
|
|
||||||
"nixos-unstable": [
|
|
||||||
"nixos-anywhere",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1766770015,
|
|
||||||
"narHash": "sha256-kUmVBU+uBUPl/v3biPiWrk680b8N9rRMhtY97wsxiJc=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixos-images",
|
|
||||||
"rev": "e4dba54ddb6b2ad9c6550e5baaed2fa27938a5d2",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixos-images",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixos-stable": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1769598131,
|
|
||||||
"narHash": "sha256-e7VO/kGLgRMbWtpBqdWl0uFg8Y2XWFMdz0uUJvlML8o=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "fa83fd837f3098e3e678e6cf017b2b36102c7211",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-25.11",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773821835,
|
"lastModified": 1773821835,
|
||||||
|
|
@ -276,8 +124,6 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"agenix": "agenix",
|
"agenix": "agenix",
|
||||||
"deploy-rs": "deploy-rs",
|
"deploy-rs": "deploy-rs",
|
||||||
"disko": "disko",
|
|
||||||
"nixos-anywhere": "nixos-anywhere",
|
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -311,27 +157,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"treefmt-nix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixos-anywhere",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1769691507,
|
|
||||||
"narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"utils": {
|
"utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems_2"
|
"systems": "systems_2"
|
||||||
|
|
|
||||||
211
flake.nix
211
flake.nix
|
|
@ -16,14 +16,6 @@
|
||||||
url = "github:serokell/deploy-rs";
|
url = "github:serokell/deploy-rs";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
disko = {
|
|
||||||
url = "github:nix-community/disko";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
nixos-anywhere = {
|
|
||||||
url = "github:nix-community/nixos-anywhere";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|
@ -32,8 +24,6 @@
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
agenix,
|
agenix,
|
||||||
deploy-rs,
|
deploy-rs,
|
||||||
disko,
|
|
||||||
nixos-anywhere,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
|
@ -45,7 +35,14 @@
|
||||||
wikiName = "Noisebridge";
|
wikiName = "Noisebridge";
|
||||||
baseDomain = "noisebridge.net";
|
baseDomain = "noisebridge.net";
|
||||||
replicaSubdomain = "replica";
|
replicaSubdomain = "replica";
|
||||||
sshUser = "root";
|
sshUser = "jet";
|
||||||
|
adminUsers = {
|
||||||
|
jet = {
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu jetthomaspham@gmail.com"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
primaryHostName = "main-wiki";
|
primaryHostName = "main-wiki";
|
||||||
replicaHostName = "replica-wiki";
|
replicaHostName = "replica-wiki";
|
||||||
|
|
||||||
|
|
@ -58,11 +55,11 @@
|
||||||
hosts = {
|
hosts = {
|
||||||
primary = {
|
primary = {
|
||||||
nixosName = primaryHostName;
|
nixosName = primaryHostName;
|
||||||
tailscaleName = primaryHostName;
|
publicIpv4 = "134.199.221.52";
|
||||||
};
|
};
|
||||||
replica = {
|
replica = {
|
||||||
nixosName = replicaHostName;
|
nixosName = replicaHostName;
|
||||||
tailscaleName = replicaHostName;
|
publicIpv4 = "167.99.174.109";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -82,12 +79,8 @@
|
||||||
publicDomain = mkPublicDomain role;
|
publicDomain = mkPublicDomain role;
|
||||||
};
|
};
|
||||||
|
|
||||||
mkHost =
|
mkDeployHost =
|
||||||
{
|
hostModule: hostMeta:
|
||||||
hostMeta,
|
|
||||||
hostModule,
|
|
||||||
roleModules,
|
|
||||||
}:
|
|
||||||
lib.nixosSystem {
|
lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
|
|
@ -95,16 +88,11 @@
|
||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
agenix.nixosModules.default
|
agenix.nixosModules.default
|
||||||
disko.nixosModules.disko
|
|
||||||
hostModule
|
hostModule
|
||||||
./disk-config.nix
|
|
||||||
./modules/common.nix
|
./modules/common.nix
|
||||||
./modules/security.nix
|
./modules/admin-users.nix
|
||||||
./modules/tailscale.nix
|
./modules/deploy-ssh.nix
|
||||||
./modules/caddy.nix
|
];
|
||||||
./modules/mediawiki-base.nix
|
|
||||||
]
|
|
||||||
++ roleModules;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
primaryMeta = mkHostMeta "primary";
|
primaryMeta = mkHostMeta "primary";
|
||||||
|
|
@ -112,40 +100,28 @@
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
nixosConfigurations = {
|
nixosConfigurations = {
|
||||||
main-wiki = mkHost {
|
main-wiki = mkDeployHost ./hosts/main-wiki.nix primaryMeta;
|
||||||
hostMeta = primaryMeta;
|
|
||||||
hostModule = ./hosts/main-wiki;
|
|
||||||
roleModules = [
|
|
||||||
./modules/wiki-primary/mysql.nix
|
|
||||||
./modules/wiki-primary/mediawiki.nix
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
replica-wiki = mkHost {
|
replica-wiki = mkDeployHost ./hosts/replica-wiki.nix replicaMeta;
|
||||||
hostMeta = replicaMeta;
|
|
||||||
hostModule = ./hosts/replica-wiki;
|
|
||||||
roleModules = [
|
|
||||||
./modules/wiki-replica/mysql.nix
|
|
||||||
./modules/wiki-replica/mediawiki.nix
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
deploy.nodes = {
|
deploy.nodes = {
|
||||||
main-wiki = {
|
main-wiki = {
|
||||||
hostname = primaryMeta.tailscaleName;
|
hostname = primaryMeta.publicIpv4;
|
||||||
|
remoteBuild = true;
|
||||||
|
sshUser = siteConfig.sshUser;
|
||||||
profiles.system = {
|
profiles.system = {
|
||||||
user = siteConfig.sshUser;
|
user = "root";
|
||||||
sshUser = siteConfig.sshUser;
|
|
||||||
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.main-wiki;
|
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.main-wiki;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
replica-wiki = {
|
replica-wiki = {
|
||||||
hostname = replicaMeta.tailscaleName;
|
hostname = replicaMeta.publicIpv4;
|
||||||
|
remoteBuild = true;
|
||||||
|
sshUser = siteConfig.sshUser;
|
||||||
profiles.system = {
|
profiles.system = {
|
||||||
user = siteConfig.sshUser;
|
user = "root";
|
||||||
sshUser = siteConfig.sshUser;
|
|
||||||
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.replica-wiki;
|
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.replica-wiki;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -175,129 +151,19 @@
|
||||||
|
|
||||||
bootstrap-host = {
|
bootstrap-host = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${pkgs.writeShellScript "bootstrap-host" ''
|
program = "${pkgs.writeShellScript "bootstrap-host" (
|
||||||
set -euo pipefail
|
builtins.replaceStrings
|
||||||
|
[
|
||||||
usage() {
|
"@ADMIN_USERS_JSON@"
|
||||||
cat <<'EOF'
|
"@JQ@"
|
||||||
Usage:
|
]
|
||||||
nix run .#bootstrap-host -- <main-wiki|replica-wiki> <target-host> [ssh-identity-file]
|
[
|
||||||
nix run .#bootstrap-host -- <main-target-host> <replica-target-host> [ssh-identity-file]
|
(builtins.toJSON siteConfig.adminUsers)
|
||||||
|
"${pkgs.jq}/bin/jq"
|
||||||
Examples:
|
]
|
||||||
nix run .#bootstrap-host -- main-wiki root@203.0.113.10 ~/.ssh/do-bootstrap
|
(builtins.readFile ./scripts/bootstrap-host.sh)
|
||||||
nix run .#bootstrap-host -- root@203.0.113.10 root@203.0.113.11 ~/.ssh/do-bootstrap
|
)}";
|
||||||
EOF
|
meta.description = "Convert one or both Ubuntu DigitalOcean hosts into a minimal NixOS bootstrap config with nixos-infect";
|
||||||
}
|
|
||||||
|
|
||||||
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo_root="$(pwd)"
|
|
||||||
if [ ! -f "$repo_root/flake.nix" ]; then
|
|
||||||
printf 'Run bootstrap-host from the repo root\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ssh_identity_file=""
|
|
||||||
main_target=""
|
|
||||||
replica_target=""
|
|
||||||
selected_host=""
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
main-wiki|replica-wiki)
|
|
||||||
selected_host="$1"
|
|
||||||
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ "$selected_host" = "main-wiki" ]; then
|
|
||||||
main_target="$2"
|
|
||||||
else
|
|
||||||
replica_target="$2"
|
|
||||||
fi
|
|
||||||
ssh_identity_file="''${3:-}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
main_target="$1"
|
|
||||||
replica_target="$2"
|
|
||||||
ssh_identity_file="''${3:-}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
install_root="$(mktemp -d)"
|
|
||||||
cleanup() {
|
|
||||||
rm -rf "$install_root"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
ensure_host_key() {
|
|
||||||
local host_name="$1"
|
|
||||||
local private_key_file="$repo_root/.bootstrap/$host_name/host.age"
|
|
||||||
local public_key_file="$repo_root/secrets/hosts/$host_name.age.pub"
|
|
||||||
local public_key
|
|
||||||
|
|
||||||
mkdir -p "$(dirname "$private_key_file")" "$(dirname "$public_key_file")"
|
|
||||||
|
|
||||||
if [ ! -s "$private_key_file" ]; then
|
|
||||||
public_key="$(${pkgs.age}/bin/age-keygen -o "$private_key_file" | sed 's/^Public key: //')"
|
|
||||||
chmod 600 "$private_key_file"
|
|
||||||
printf 'Generated host age key for %s\n' "$host_name"
|
|
||||||
else
|
|
||||||
public_key="$(${pkgs.age}/bin/age-keygen -y "$private_key_file")"
|
|
||||||
printf 'Reused existing host age key for %s\n' "$host_name"
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '%s\n' "$public_key" > "$public_key_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_bootstrap() {
|
|
||||||
local host_name="$1"
|
|
||||||
local target_host="$2"
|
|
||||||
local private_key_file="$repo_root/.bootstrap/$host_name/host.age"
|
|
||||||
local -a nixos_anywhere_args
|
|
||||||
|
|
||||||
mkdir -p "$install_root/var/lib/agenix"
|
|
||||||
${pkgs.coreutils}/bin/install -m 0400 "$private_key_file" "$install_root/var/lib/agenix/host.age"
|
|
||||||
|
|
||||||
nixos_anywhere_args=(
|
|
||||||
--extra-files "$install_root"
|
|
||||||
--flake "path:$repo_root#$host_name"
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ -n "$ssh_identity_file" ]; then
|
|
||||||
nixos_anywhere_args+=( -i "$ssh_identity_file" )
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf 'Bootstrapping %s onto %s\n' "$host_name" "$target_host"
|
|
||||||
${
|
|
||||||
nixos-anywhere.packages.${system}.default
|
|
||||||
}/bin/nixos-anywhere "''${nixos_anywhere_args[@]}" "$target_host"
|
|
||||||
rm -f "$install_root/var/lib/agenix/host.age"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -n "$main_target" ]; then
|
|
||||||
ensure_host_key main-wiki
|
|
||||||
fi
|
|
||||||
if [ -n "$replica_target" ]; then
|
|
||||||
ensure_host_key replica-wiki
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf 'Rekeying agenix secrets\n'
|
|
||||||
${agenix.packages.${system}.default}/bin/agenix -r
|
|
||||||
|
|
||||||
if [ -n "$main_target" ]; then
|
|
||||||
run_bootstrap main-wiki "$main_target"
|
|
||||||
fi
|
|
||||||
if [ -n "$replica_target" ]; then
|
|
||||||
run_bootstrap replica-wiki "$replica_target"
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '\nBootstrap complete. The hosts should now join Tailscale using their configured hostnames.\n'
|
|
||||||
''}";
|
|
||||||
meta.description = "Install NixOS on one or both raw hosts and seed agenix identities";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -305,7 +171,6 @@
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
agenix.packages.${system}.default
|
agenix.packages.${system}.default
|
||||||
deploy-rs.packages.${system}.default
|
deploy-rs.packages.${system}.default
|
||||||
nixos-anywhere.packages.${system}.default
|
|
||||||
mariadb.client
|
mariadb.client
|
||||||
rsync
|
rsync
|
||||||
curl
|
curl
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ hostMeta, siteConfig, ... }:
|
{ hostMeta, siteConfig, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../shared/hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
networking.hostName = hostMeta.nixosName;
|
networking.hostName = hostMeta.nixosName;
|
||||||
|
|
@ -4,8 +4,11 @@
|
||||||
(modulesPath + "/virtualisation/digital-ocean-config.nix")
|
(modulesPath + "/virtualisation/digital-ocean-config.nix")
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.loader.systemd-boot.enable = true;
|
boot.loader.grub = {
|
||||||
boot.loader.efi.canTouchEfiVariables = true;
|
enable = true;
|
||||||
|
efiSupport = false;
|
||||||
|
devices = lib.mkForce [ "/dev/vda" ];
|
||||||
|
};
|
||||||
|
|
||||||
fileSystems."/" = {
|
fileSystems."/" = {
|
||||||
device = lib.mkDefault "/dev/disk/by-label/nixos";
|
device = lib.mkDefault "/dev/disk/by-label/nixos";
|
||||||
6
hosts/main-wiki.nix
Normal file
6
hosts/main-wiki.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./common.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
6
hosts/replica-wiki.nix
Normal file
6
hosts/replica-wiki.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./common.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{ hostMeta, siteConfig, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../shared/hardware-configuration.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.hostName = hostMeta.nixosName;
|
|
||||||
networking.domain = siteConfig.baseDomain;
|
|
||||||
|
|
||||||
system.stateVersion = "24.11";
|
|
||||||
}
|
|
||||||
13
modules/admin-users.nix
Normal file
13
modules/admin-users.nix
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{ lib, siteConfig, ... }:
|
||||||
|
{
|
||||||
|
users.users = lib.mapAttrs (
|
||||||
|
_: userCfg:
|
||||||
|
{
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
}
|
||||||
|
// userCfg
|
||||||
|
) siteConfig.adminUsers;
|
||||||
|
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
}
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{ config, hostMeta, ... }:
|
|
||||||
{
|
|
||||||
services.caddy = {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts.${hostMeta.publicDomain}.extraConfig = ''
|
|
||||||
encode zstd gzip
|
|
||||||
|
|
||||||
handle /health {
|
|
||||||
respond "ok" 200
|
|
||||||
}
|
|
||||||
|
|
||||||
${
|
|
||||||
if hostMeta.role == "replica" then
|
|
||||||
''
|
|
||||||
header {
|
|
||||||
X-Wiki-Mode "read-only"
|
|
||||||
}
|
|
||||||
|
|
||||||
''
|
|
||||||
else
|
|
||||||
""
|
|
||||||
}php_fastcgi unix//run/phpfpm/mediawiki.sock {
|
|
||||||
root ${config.services.mediawiki.finalPackage}/share/mediawiki
|
|
||||||
}
|
|
||||||
|
|
||||||
file_server {
|
|
||||||
root ${config.services.mediawiki.finalPackage}/share/mediawiki
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
time.timeZone = "America/Los_Angeles";
|
time.timeZone = "America/Los_Angeles";
|
||||||
i18n.defaultLocale = "en_US.UTF-8";
|
i18n.defaultLocale = "en_US.UTF-8";
|
||||||
|
|
||||||
|
services.journald.storage = "persistent";
|
||||||
|
|
||||||
services.timesyncd.enable = true;
|
services.timesyncd.enable = true;
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
|
|
|
||||||
29
modules/deploy-ssh.nix
Normal file
29
modules/deploy-ssh.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [ 22 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
settings = {
|
||||||
|
AllowAgentForwarding = false;
|
||||||
|
AllowGroups = [ "wheel" ];
|
||||||
|
AllowTcpForwarding = false;
|
||||||
|
ClientAliveCountMax = 2;
|
||||||
|
ClientAliveInterval = 300;
|
||||||
|
KbdInteractiveAuthentication = false;
|
||||||
|
LoginGraceTime = 20;
|
||||||
|
MaxAuthTries = 3;
|
||||||
|
MaxSessions = 4;
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
PermitRootLogin = "no";
|
||||||
|
PermitTunnel = false;
|
||||||
|
PermitUserEnvironment = false;
|
||||||
|
StreamLocalBindUnlink = false;
|
||||||
|
X11Forwarding = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
hostMeta,
|
|
||||||
siteConfig,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
services.mediawiki = {
|
|
||||||
enable = true;
|
|
||||||
name = siteConfig.wikiName;
|
|
||||||
url = "https://${hostMeta.publicDomain}";
|
|
||||||
webserver = "none";
|
|
||||||
passwordFile = config.age.secrets.mediawiki-admin-password.path;
|
|
||||||
|
|
||||||
database = {
|
|
||||||
type = "mysql";
|
|
||||||
name = siteConfig.database.name;
|
|
||||||
user = siteConfig.database.mediawikiUser;
|
|
||||||
passwordFile = config.age.secrets.mysql-mediawiki.path;
|
|
||||||
socket = "/run/mysqld/mysqld.sock";
|
|
||||||
createLocally = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
extensions = {
|
|
||||||
ParserFunctions = null;
|
|
||||||
WikiEditor = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
extraConfig = ''
|
|
||||||
$wgServer = "https://${hostMeta.publicDomain}";
|
|
||||||
$wgSitename = "${siteConfig.wikiName}";
|
|
||||||
$wgMetaNamespace = "${siteConfig.wikiName}";
|
|
||||||
$wgScriptPath = "";
|
|
||||||
$wgArticlePath = "/wiki/$1";
|
|
||||||
$wgUsePathInfo = true;
|
|
||||||
wfLoadSkin('Vector');
|
|
||||||
|
|
||||||
$wgEnableUploads = true;
|
|
||||||
$wgEnableEmail = false;
|
|
||||||
$wgShowIPinHeader = false;
|
|
||||||
|
|
||||||
$wgGroupPermissions['*']['edit'] = false;
|
|
||||||
$wgGroupPermissions['*']['createpage'] = false;
|
|
||||||
$wgGroupPermissions['*']['createtalk'] = false;
|
|
||||||
$wgGroupPermissions['*']['writeapi'] = false;
|
|
||||||
$wgGroupPermissions['*']['createaccount'] = false;
|
|
||||||
|
|
||||||
$wgGroupPermissions['user']['edit'] = true;
|
|
||||||
$wgGroupPermissions['user']['createpage'] = true;
|
|
||||||
$wgGroupPermissions['user']['createtalk'] = true;
|
|
||||||
$wgGroupPermissions['user']['upload'] = true;
|
|
||||||
$wgGroupPermissions['user']['writeapi'] = true;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
services.mediawiki.poolConfig =
|
|
||||||
if hostMeta.role == "primary" then
|
|
||||||
{
|
|
||||||
"pm" = "dynamic";
|
|
||||||
"pm.max_children" = 12;
|
|
||||||
"pm.start_servers" = 3;
|
|
||||||
"pm.min_spare_servers" = 2;
|
|
||||||
"pm.max_spare_servers" = 4;
|
|
||||||
"pm.max_requests" = 500;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
"pm" = "dynamic";
|
|
||||||
"pm.max_children" = 6;
|
|
||||||
"pm.start_servers" = 2;
|
|
||||||
"pm.min_spare_servers" = 1;
|
|
||||||
"pm.max_spare_servers" = 3;
|
|
||||||
"pm.max_requests" = 500;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.phpfpm.pools.mediawiki.phpOptions =
|
|
||||||
if hostMeta.role == "primary" then
|
|
||||||
''
|
|
||||||
opcache.enable=1
|
|
||||||
opcache.memory_consumption=192
|
|
||||||
opcache.interned_strings_buffer=16
|
|
||||||
opcache.max_accelerated_files=10000
|
|
||||||
realpath_cache_size=4096K
|
|
||||||
realpath_cache_ttl=600
|
|
||||||
''
|
|
||||||
else
|
|
||||||
''
|
|
||||||
opcache.enable=1
|
|
||||||
opcache.memory_consumption=128
|
|
||||||
opcache.interned_strings_buffer=16
|
|
||||||
opcache.max_accelerated_files=10000
|
|
||||||
realpath_cache_size=2048K
|
|
||||||
realpath_cache_ttl=600
|
|
||||||
'';
|
|
||||||
|
|
||||||
age.secrets.mediawiki-admin-password = {
|
|
||||||
file = ../secrets/mediawiki-admin-password.age;
|
|
||||||
owner = "mediawiki";
|
|
||||||
group = "mediawiki";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
age.secrets.mysql-mediawiki = {
|
|
||||||
file = ../secrets/mysql-mediawiki.age;
|
|
||||||
owner = "mediawiki";
|
|
||||||
group = "mediawiki";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
networking.firewall = {
|
|
||||||
enable = true;
|
|
||||||
allowedTCPPorts = [ 80 443 ];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.openssh = {
|
|
||||||
enable = true;
|
|
||||||
openFirewall = false;
|
|
||||||
settings = {
|
|
||||||
PasswordAuthentication = false;
|
|
||||||
KbdInteractiveAuthentication = false;
|
|
||||||
PermitRootLogin = "prohibit-password";
|
|
||||||
X11Forwarding = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
hostMeta,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
age.secrets.tailscale-auth = {
|
|
||||||
file = ../secrets/tailscale-auth.age;
|
|
||||||
owner = "root";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.tailscale = {
|
|
||||||
enable = true;
|
|
||||||
authKeyFile = config.age.secrets.tailscale-auth.path;
|
|
||||||
extraUpFlags = [ "--hostname=${hostMeta.tailscaleName}" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.interfaces.tailscale0.allowedTCPPorts =
|
|
||||||
if hostMeta.role == "primary" then
|
|
||||||
[
|
|
||||||
22
|
|
||||||
3306
|
|
||||||
]
|
|
||||||
else
|
|
||||||
[
|
|
||||||
22
|
|
||||||
873
|
|
||||||
];
|
|
||||||
networking.firewall.allowedUDPPorts = [ config.services.tailscale.port ];
|
|
||||||
networking.firewall.checkReversePath = "loose";
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{ pkgs, siteConfig, ... }:
|
|
||||||
{
|
|
||||||
systemd.services.mediawiki-image-sync = {
|
|
||||||
description = "Sync MediaWiki uploads to the replica";
|
|
||||||
after = [ "tailscale-autoconnect.service" ];
|
|
||||||
wants = [ "tailscale-autoconnect.service" ];
|
|
||||||
path = [ pkgs.rsync ];
|
|
||||||
serviceConfig.Type = "oneshot";
|
|
||||||
script = ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
${pkgs.rsync}/bin/rsync \
|
|
||||||
-az \
|
|
||||||
--delete \
|
|
||||||
/var/lib/mediawiki/images/ \
|
|
||||||
"rsync://${siteConfig.hosts.replica.tailscaleName}/mediawiki-images/"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.timers.mediawiki-image-sync = {
|
|
||||||
description = "Periodic upload sync from primary to replica";
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "hourly";
|
|
||||||
Persistent = true;
|
|
||||||
RandomizedDelaySec = "10m";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
siteConfig,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
dbName = siteConfig.database.name;
|
|
||||||
wikiUser = siteConfig.database.mediawikiUser;
|
|
||||||
replicationUser = siteConfig.database.replicationUser;
|
|
||||||
mysqlCli = "${pkgs.mariadb}/bin/mysql -u root";
|
|
||||||
bootstrapSql = pkgs.writeText "mysql-bootstrap.sql" ''
|
|
||||||
SET @wiki_pass = FROM_BASE64('$wiki_pass_b64');
|
|
||||||
SET @repl_pass = FROM_BASE64('$repl_pass_b64');
|
|
||||||
|
|
||||||
CREATE DATABASE IF NOT EXISTS \`${dbName}\`;
|
|
||||||
|
|
||||||
SET @create_wiki_user = CONCAT(
|
|
||||||
"CREATE USER IF NOT EXISTS '${wikiUser}'@'localhost' IDENTIFIED BY ",
|
|
||||||
QUOTE(@wiki_pass)
|
|
||||||
);
|
|
||||||
PREPARE stmt FROM @create_wiki_user;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
SET @alter_wiki_user = CONCAT(
|
|
||||||
"ALTER USER '${wikiUser}'@'localhost' IDENTIFIED BY ",
|
|
||||||
QUOTE(@wiki_pass)
|
|
||||||
);
|
|
||||||
PREPARE stmt FROM @alter_wiki_user;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
GRANT ALL PRIVILEGES ON \`${dbName}\`.* TO '${wikiUser}'@'localhost';
|
|
||||||
|
|
||||||
SET @create_repl_user = CONCAT(
|
|
||||||
"CREATE USER IF NOT EXISTS '${replicationUser}'@'%' IDENTIFIED BY ",
|
|
||||||
QUOTE(@repl_pass)
|
|
||||||
);
|
|
||||||
PREPARE stmt FROM @create_repl_user;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
SET @alter_repl_user = CONCAT(
|
|
||||||
"ALTER USER '${replicationUser}'@'%' IDENTIFIED BY ",
|
|
||||||
QUOTE(@repl_pass)
|
|
||||||
);
|
|
||||||
PREPARE stmt FROM @alter_repl_user;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO '${replicationUser}'@'%';
|
|
||||||
FLUSH PRIVILEGES;
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services.mysql = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.mariadb;
|
|
||||||
|
|
||||||
settings.mysqld = {
|
|
||||||
bind-address = "0.0.0.0";
|
|
||||||
server-id = 1;
|
|
||||||
log_bin = "mysql-bin";
|
|
||||||
log_slave_updates = 1;
|
|
||||||
binlog_format = "ROW";
|
|
||||||
gtid_strict_mode = 1;
|
|
||||||
innodb_file_per_table = 1;
|
|
||||||
innodb_buffer_pool_size = "4G";
|
|
||||||
innodb_log_file_size = "512M";
|
|
||||||
innodb_flush_method = "O_DIRECT";
|
|
||||||
innodb_flush_neighbors = 0;
|
|
||||||
innodb_io_capacity = 1000;
|
|
||||||
innodb_io_capacity_max = 2000;
|
|
||||||
max_connections = 80;
|
|
||||||
thread_cache_size = 100;
|
|
||||||
table_open_cache = 4000;
|
|
||||||
tmp_table_size = "64M";
|
|
||||||
max_heap_table_size = "64M";
|
|
||||||
performance_schema = true;
|
|
||||||
slow_query_log = 1;
|
|
||||||
long_query_time = 1;
|
|
||||||
skip_name_resolve = 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
age.secrets.mysql-replication = {
|
|
||||||
file = ../../secrets/mysql-replication.age;
|
|
||||||
owner = "mysql";
|
|
||||||
group = "mysql";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.mysql-bootstrap = {
|
|
||||||
description = "Create MediaWiki database and users";
|
|
||||||
after = [ "mysql.service" ];
|
|
||||||
requires = [ "mysql.service" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
};
|
|
||||||
script = ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
read_secret_b64() {
|
|
||||||
tr -d '\n' < "$1" | base64 -w0
|
|
||||||
}
|
|
||||||
|
|
||||||
wiki_pass_b64="$(read_secret_b64 ${config.age.secrets.mysql-mediawiki.path})"
|
|
||||||
repl_pass_b64="$(read_secret_b64 ${config.age.secrets.mysql-replication.path})"
|
|
||||||
|
|
||||||
${mysqlCli} <<SQL
|
|
||||||
$(< ${bootstrapSql})
|
|
||||||
SQL
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.mediawiki-init = {
|
|
||||||
after = [ "mysql-bootstrap.service" ];
|
|
||||||
requires = [ "mysql-bootstrap.service" ];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
services.mediawiki.extraConfig = lib.mkAfter ''
|
|
||||||
$wgReadOnly = "This wiki is a read-only replica.";
|
|
||||||
$wgEnableUploads = false;
|
|
||||||
'';
|
|
||||||
|
|
||||||
systemd.services.mediawiki-init.wantedBy = lib.mkForce [ ];
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
siteConfig,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
replicationUser = siteConfig.database.replicationUser;
|
|
||||||
mysqlCli = "${pkgs.mariadb}/bin/mysql -u root";
|
|
||||||
initReplicaSql = pkgs.writeText "mysql-init-replica.sql" ''
|
|
||||||
STOP SLAVE;
|
|
||||||
SET @repl_pass = FROM_BASE64('$repl_pass_b64');
|
|
||||||
SET @change_master = CONCAT(
|
|
||||||
"CHANGE MASTER TO ",
|
|
||||||
"MASTER_HOST='${siteConfig.hosts.primary.tailscaleName}', ",
|
|
||||||
"MASTER_USER='${replicationUser}', ",
|
|
||||||
"MASTER_PASSWORD=", QUOTE(@repl_pass), ", ",
|
|
||||||
"MASTER_USE_GTID=slave_pos"
|
|
||||||
);
|
|
||||||
PREPARE stmt FROM @change_master;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
START SLAVE;
|
|
||||||
SHOW SLAVE STATUS\G
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services.mysql = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.mariadb;
|
|
||||||
|
|
||||||
settings.mysqld = {
|
|
||||||
bind-address = "127.0.0.1";
|
|
||||||
server-id = 2;
|
|
||||||
relay_log = "relay-bin";
|
|
||||||
log_slave_updates = 1;
|
|
||||||
read_only = 1;
|
|
||||||
super_read_only = 1;
|
|
||||||
gtid_strict_mode = 1;
|
|
||||||
innodb_file_per_table = 1;
|
|
||||||
innodb_buffer_pool_size = "2G";
|
|
||||||
innodb_log_file_size = "256M";
|
|
||||||
innodb_flush_method = "O_DIRECT";
|
|
||||||
innodb_flush_neighbors = 0;
|
|
||||||
innodb_io_capacity = 500;
|
|
||||||
innodb_io_capacity_max = 1000;
|
|
||||||
max_connections = 40;
|
|
||||||
thread_cache_size = 50;
|
|
||||||
table_open_cache = 2000;
|
|
||||||
tmp_table_size = "32M";
|
|
||||||
max_heap_table_size = "32M";
|
|
||||||
performance_schema = true;
|
|
||||||
slow_query_log = 1;
|
|
||||||
long_query_time = 1;
|
|
||||||
skip_name_resolve = 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
age.secrets.mysql-replication = {
|
|
||||||
file = ../../secrets/mysql-replication.age;
|
|
||||||
owner = "mysql";
|
|
||||||
group = "mysql";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.rsyncd = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
globalSection = {
|
|
||||||
"use chroot" = false;
|
|
||||||
};
|
|
||||||
sections.mediawiki-images = {
|
|
||||||
path = "/var/lib/mediawiki/images";
|
|
||||||
comment = "MediaWiki upload mirror target";
|
|
||||||
"read only" = false;
|
|
||||||
list = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [
|
|
||||||
(pkgs.writeShellScriptBin "init-replica" ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
repl_pass_b64="$(tr -d '\n' < ${config.age.secrets.mysql-replication.path} | base64 -w0)"
|
|
||||||
|
|
||||||
${mysqlCli} <<SQL
|
|
||||||
$(< ${initReplicaSql})
|
|
||||||
SQL
|
|
||||||
'')
|
|
||||||
];
|
|
||||||
}
|
|
||||||
193
scripts/bootstrap-host.sh
Normal file
193
scripts/bootstrap-host.sh
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
nix run .#bootstrap-host -- --admin <name> <main-wiki|replica-wiki> <target-host> [ssh-identity-file]
|
||||||
|
nix run .#bootstrap-host -- --admin <name> <main-target-host> <replica-target-host> [ssh-identity-file]
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_root="$(pwd)"
|
||||||
|
if [ ! -f "$repo_root/flake.nix" ]; then
|
||||||
|
printf 'Run bootstrap-host from the repo root\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
admin_users_json='@ADMIN_USERS_JSON@'
|
||||||
|
|
||||||
|
pinned_nix_install_url='https://releases.nixos.org/nix/nix-2.24.14/install'
|
||||||
|
|
||||||
|
bootstrap_admin=""
|
||||||
|
ssh_identity_file=""
|
||||||
|
main_target=""
|
||||||
|
replica_target=""
|
||||||
|
failures=()
|
||||||
|
|
||||||
|
if [ "${1:-}" != "--admin" ] || [ "$#" -lt 4 ]; then
|
||||||
|
printf 'Bootstrap requires --admin <name>\n' >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
bootstrap_admin="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
main-wiki|replica-wiki)
|
||||||
|
if [ "$1" = "main-wiki" ]; then
|
||||||
|
main_target="$2"
|
||||||
|
else
|
||||||
|
replica_target="$2"
|
||||||
|
fi
|
||||||
|
ssh_identity_file="${3:-}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
main_target="$1"
|
||||||
|
replica_target="$2"
|
||||||
|
ssh_identity_file="${3:-}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
admin_keys() {
|
||||||
|
printf '%s' "$admin_users_json" | @JQ@ -r --arg user "$1" '.[$user].openssh.authorizedKeys.keys[]? | " \"" + . + "\""'
|
||||||
|
}
|
||||||
|
|
||||||
|
admin_exists() {
|
||||||
|
printf '%s' "$admin_users_json" | @JQ@ -e --arg user "$1" 'has($user)' >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! admin_exists "$bootstrap_admin"; then
|
||||||
|
printf 'Unknown admin user for bootstrap: %s\n' "$bootstrap_admin" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
make_host_module() {
|
||||||
|
local module_file="$1"
|
||||||
|
local admin_name="$2"
|
||||||
|
|
||||||
|
cat > "$module_file" <<MODULE
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
services.journald.storage = "persistent";
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
settings = {
|
||||||
|
AllowAgentForwarding = false;
|
||||||
|
AllowGroups = [ "wheel" ];
|
||||||
|
AllowTcpForwarding = false;
|
||||||
|
ClientAliveCountMax = 2;
|
||||||
|
ClientAliveInterval = 300;
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
KbdInteractiveAuthentication = false;
|
||||||
|
LoginGraceTime = 20;
|
||||||
|
MaxAuthTries = 3;
|
||||||
|
MaxSessions = 4;
|
||||||
|
PermitRootLogin = "no";
|
||||||
|
PermitTunnel = false;
|
||||||
|
PermitUserEnvironment = false;
|
||||||
|
StreamLocalBindUnlink = false;
|
||||||
|
X11Forwarding = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.${admin_name} = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
$(admin_keys "$admin_name")
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
|
services.do-agent.enable = false;
|
||||||
|
}
|
||||||
|
MODULE
|
||||||
|
}
|
||||||
|
|
||||||
|
run_bootstrap() {
|
||||||
|
local host_name="$1"
|
||||||
|
local target_host="$2"
|
||||||
|
local work_dir
|
||||||
|
local module_file
|
||||||
|
local remote_target
|
||||||
|
local try
|
||||||
|
local ssh_cmd=(ssh -o StrictHostKeyChecking=accept-new)
|
||||||
|
local scp_cmd=(scp -o StrictHostKeyChecking=accept-new)
|
||||||
|
local admin_target
|
||||||
|
|
||||||
|
work_dir="$(mktemp -d)"
|
||||||
|
module_file="$work_dir/host-bootstrap.nix"
|
||||||
|
remote_target="$target_host:/etc/nixos/host-bootstrap.nix"
|
||||||
|
admin_target="${bootstrap_admin}@${target_host#*@}"
|
||||||
|
|
||||||
|
make_host_module "$module_file" "$bootstrap_admin"
|
||||||
|
|
||||||
|
if [ -n "$ssh_identity_file" ]; then
|
||||||
|
ssh_cmd+=( -i "$ssh_identity_file" )
|
||||||
|
scp_cmd+=( -i "$ssh_identity_file" )
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${ssh_cmd[@]}" "$target_host" 'mkdir -p /etc/nixos'
|
||||||
|
"${scp_cmd[@]}" "$module_file" "$remote_target"
|
||||||
|
|
||||||
|
printf 'Infecting %s onto %s\n' "$host_name" "$target_host"
|
||||||
|
"${ssh_cmd[@]}" "$target_host" \
|
||||||
|
"umount /boot/efi 2>/dev/null || true; curl -fsSL https://raw.githubusercontent.com/elitak/nixos-infect/36f48d8feb89ca508261d7390355144fc0048932/nixos-infect | env NIX_INSTALL_URL='$pinned_nix_install_url' PROVIDER=digitalocean doNetConf=y NIX_CHANNEL=nixos-24.05 NIXOS_IMPORT=./host-bootstrap.nix bash -x" || true
|
||||||
|
|
||||||
|
printf 'Waiting for %s to reboot into NixOS\n' "$host_name"
|
||||||
|
for try in $(seq 1 60); do
|
||||||
|
if "${ssh_cmd[@]}" -o ConnectTimeout=5 "$admin_target" 'grep -q "^ID=nixos" /etc/os-release' 2>/dev/null; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! "${ssh_cmd[@]}" -o ConnectTimeout=5 "$admin_target" 'grep -q "^ID=nixos" /etc/os-release' 2>/dev/null; then
|
||||||
|
printf 'Bootstrap failed for %s: host did not come back as NixOS with %s access\n' "$host_name" "$bootstrap_admin" >&2
|
||||||
|
failures+=( "$host_name" )
|
||||||
|
rm -rf "$work_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'Finalizing network config on %s\n' "$host_name"
|
||||||
|
"${ssh_cmd[@]}" "$admin_target" '
|
||||||
|
sudo sed -i "/defaultGateway6 = {/,/};/d" /etc/nixos/networking.nix 2>/dev/null || true
|
||||||
|
sudo sed -i "/ipv6.routes = \[ { address = \"\"; prefixLength = 128; } \];/d" /etc/nixos/networking.nix 2>/dev/null || true
|
||||||
|
sudo nixos-rebuild switch
|
||||||
|
'
|
||||||
|
|
||||||
|
if ! "${ssh_cmd[@]}" "$admin_target" 'sudo -n true >/dev/null && test "$(systemctl is-system-running || true)" = running' 2>/dev/null; then
|
||||||
|
printf 'Bootstrap verification failed for %s: host is not healthy after first switch\n' "$host_name" >&2
|
||||||
|
failures+=( "$host_name" )
|
||||||
|
rm -rf "$work_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'Bootstrap verified for %s\n' "$host_name"
|
||||||
|
|
||||||
|
rm -rf "$work_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "${main_target:-}" ]; then
|
||||||
|
run_bootstrap main-wiki "$main_target" || true
|
||||||
|
fi
|
||||||
|
if [ -n "${replica_target:-}" ]; then
|
||||||
|
run_bootstrap replica-wiki "$replica_target" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#failures[@]}" -ne 0 ]; then
|
||||||
|
printf '\nBootstrap failed for: %s\n' "${failures[*]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\nBootstrap complete. The hosts should now be reachable as NixOS systems over public SSH as %s.\n' "$bootstrap_admin"
|
||||||
Binary file not shown.
|
|
@ -1,5 +1,11 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 Ziw7aw dohDDxsPMp1TRbvNh2qAPUFmW4cuMLnjislpRleHEHI
|
-> ssh-ed25519 Ziw7aw 5sI+R2UT4nP6T6WTm1EJ4elU8KXBlI7jzgTxVOXi/Sg
|
||||||
9MEq670uN4CzO8U/2HZAXr8MpI/vte/5pC2yQPKWemg
|
pQcdU7F0xoTiZQMyA+8L8bAUgg+7ftk1cQPCP5UOWvc
|
||||||
--- 2npxqFr1cEdGtDhZlb4zVpy04F4Xsfb2NAu3eTDUTYg
|
-> X25519 PRy6mfo+WzqtakJny8K9A0M8ilnEmkpREpa0wuMGNVI
|
||||||
»æØ¹LT<EFBFBD>
LÜt2là!q>Àý0"ËH5ò^Ìè¼—Ó ^˜ƒj5ÁQRwš×^à?MVon«iĵ,dD愘ž‹S-q¢€
|
Szmhsy/l6Qs5pX6TwcjPg3gxgQtNT1y0KTXEMPG/wZg
|
||||||
|
-> X25519 ja3QNUKZfQIkTCt8O41f0+3oh1dXk/v86ZKHUcvvKxI
|
||||||
|
6Qb1KMzfbyZMT2OJsckGRiWrcxS3xgCL7jXz0QWucZw
|
||||||
|
--- 7d03+3jqRYD4cII07DMedPieU+qIyl+b3KL+xcEUwro
|
||||||
|
þ¿Â휫5Dh?G^|ƤJd`üg4ïU¥ÒÀ'¯ä«:«t
|
||||||
|
–
LNÍIIÂfå˜cÕ›3²rFÚRå9€”¥êƒÄ
|
||||||
|
vŒD6•7ã’
|
||||||
Binary file not shown.
|
|
@ -14,7 +14,6 @@ let
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
"tailscale-auth.age".publicKeys = adminKeys ++ allHosts;
|
|
||||||
"mysql-mediawiki.age".publicKeys = adminKeys ++ allHosts;
|
"mysql-mediawiki.age".publicKeys = adminKeys ++ allHosts;
|
||||||
"mysql-replication.age".publicKeys = adminKeys ++ allHosts;
|
"mysql-replication.age".publicKeys = adminKeys ++ allHosts;
|
||||||
"mediawiki-admin-password.age".publicKeys = adminKeys ++ allHosts;
|
"mediawiki-admin-password.age".publicKeys = adminKeys ++ allHosts;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
age-encryption.org/v1
|
|
||||||
-> ssh-ed25519 Ziw7aw Fa0iIVw0anW/5eEZeeHjPQub7dSfowddlBHGnv/zZ2E
|
|
||||||
cXCR7bmRgwlrRGvJxAXmeceG8IZeEikBYpcK23WPn/c
|
|
||||||
--- tE9ZGzyHyQpZoBGr8m3YySYeSbpNlISsxKG7fIgqdwg
|
|
||||||
<EFBFBD>?Õ\ƒ\‹Ótîr-¼Dÿã{)‹ßÍ>þJ¼—r
Xz‡àñŸZ]¤“Eîáÿ¶šßeë1±œÝWúý÷§c[ÔQ†Æ³2²¢€Å<E282AC>š÷cfaf9Ž5äÚ
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue