15 Commits

Author SHA1 Message Date
javalsai 8d6be9fcf0 docs: add sections and fix typos/errors
Check / Nix flake (push) Failing after 9s
Lint / Nix expressions (push) Failing after 11s
2026-05-04 02:00:47 +02:00
ErrorNoInternet 4c52994bf8 treewide: initialize npins 2026-05-04 02:00:47 +02:00
ErrorNoInternet b964fe3e89 treewide: refactor code 2026-05-04 02:00:47 +02:00
javalsai 9008f6fdb9 nixos/security: add acme through dns challenge
few side refactors of this:
- no more `dns.domain`, it all must rely on `fqdn`, prevents
  inconsistencies.
- also added an specific host `tuxcord-acmetest` that uses the key zone
  for `nix.tuxcord.net` to test certificate pulling.
2026-05-04 02:00:47 +02:00
javalsai 701a477d42 docs: document installation, secrets and setup steps 2026-05-04 02:00:47 +02:00
javalsai b491abe065 nixos/services: make dns configuration easier 2026-05-04 02:00:47 +02:00
javalsai ddb136f971 nixos/service: add dns (bind named server) 2026-05-04 02:00:47 +02:00
javalsai d8a90697e9 nixos/programs: add bind utils 2026-05-04 02:00:47 +02:00
javalsai 433645f459 lib/ssh: add more ssh keys 2026-05-04 02:00:44 +02:00
javalsai dd7ad60710 nixos/services: add gitea server
Check / Nix flake (push) Failing after 9s
Lint / Nix expressions (push) Failing after 10s
2026-05-04 01:56:34 +02:00
javalsai fd18ae4a78 nixos/services: add nginx base configuration 2026-05-04 01:56:34 +02:00
javalsai d7deaa187c nixos/networking: add own fqdn to extraHosts 2026-05-04 01:56:34 +02:00
javalsai c6d66902bb nixos/hosts: add tuxcord-vm host configuration 2026-05-04 01:56:34 +02:00
ErrorNoInternet 4704a887fa nixos: separate openssh firewall port 2026-05-04 01:56:34 +02:00
javalsai eaaffcc289 lib/ssh: add more ssh keys 2026-05-04 01:56:32 +02:00
26 changed files with 830 additions and 50 deletions
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,34 @@
age-encryption.org/v1
-> ssh-ed25519 Wl2fDA ChDpKnwSPrXx13z22y4Q7+E6T+6Jr6pL6ZCxGidDhz4
VqzsCq9P5KSFRoKu+LR02VwR1qO8tbVyPnOf0dUw0HQ
-> ssh-ed25519 zNC8SA MQvBihnVCRdXg4PdrTZ3mhvzwyJeACVXfPNawPsRMl4
8HOZLbg9FuKD9k+0lS+3FksXMhLYXVOaa/7zzTgX+jc
-> ssh-ed25519 EiAAKw pxeU5N5J9ItEXP3Q2mOvWEjOe552atnfEMw1m/scbws
kswNWzaK5cKuyWeuRMxizL1tR63IaAbxkT6Yk2hplkc
-> ssh-rsa eFi+Zw
otzKOxpWdae4NnDceLHW6bYlZaXWYb2N2A6PLp6CJ/TRzT4F6aKO9oIG3dyGyVy6
JYNYDCrqgr0OrJdBB1pADbrhXxlaTlMW3K/5FkhPj4GOJQqYR/7148EHtv721eDi
mqJExC7lbEzO7ZcWT5ohPT8hP2Gv9xcKCRiAVrybo0HR4+tQrYDpr9PTrYy0lTOE
e9Ik3eT3+ub9FdJhgfKaGJVQS9cpuyghXN1AlO0EPTVwiOoYLZ/KSmrV+2hxaaui
KJ7VVjhWrbcFq0zDuZaxS93Ot3MN91TjOTqWewuj/Ahnl/pxOPzsy8zTN0FwkIXh
6dvM21kQQg/r5Lq40yQYxk6HA2vkzZm4PYFbuEegG0XG3CoLcvPme2hWec6fbbL4
0M3RsIKS5yLNdgtB4r6uzjyqaOO6TYP1Yndb7wd7befwoNkPXBfEfxvChoZEoXCF
/IlFtUqOcZLTLkL+yfqU60RRkvurbZVYtMI7yTpCAKUsMMJoD6ZA49E/1FSVkFxR
-> ssh-ed25519 QovoLQ P6/XAKtF+DGLR6VhsHV8/LwAacQj4TySaH/A+c7qW1A
NrZTXLxCCDqTV3FHg9P4FyJ+3Up7Nm+Docrv/YKxDYA
-> ssh-rsa OFkEIg
qABFIWIcl7Qq8UXzxwXyVJQRgCxxXWDr4nUyOExxuVZcksDJPHN1VN1lbizEFXtN
eIujZ2PodAuy05NKP0k+BhV9PH7NoXzpgKYD3mVFU8xJo/3gytUgbIg2EHAUmctK
PAwdCbA8hT5T7KZV17hGNEkVjV8h6haRYuKCZIYkwIwFGRC++OqMZSOSB+2iAFSA
QBGQCn835EjE57M+yCOVvSQagqoaimNKx7Idtkjqvx3wOvuy4Px2FxtE4PloW9i2
zR2NH9NAsGcUA+i8eFO3KKtXFoKm6QsRUPVn0o3nsxDXjYO/OSB6yS7GNjLJSkfO
n+UcKy5/lHNQqtBSLg+OLH0ZSj+nyHX5Dk1Cq+MB0J0KbcXA6XVm6+hhQE5xzegP
RSzgKxmqCEAcSGD4Li4nJJK3bwB971njDVyIaNBRC+7T8zY6h7LIZlB7Bq2SQ3Wb
tsnYHOldgfkkAEqkS8oQzxNMe94BzsfL5/JuPHF5+gx9ljLB1kOk5a0B6YWiVCyE
Iy/PH2ikTa3CYcNOfGnlkfBsk7Sf6C5ZvYaVWrb13Cxh5DZFGg4HIc58pmKkvq4B
o07/I6nA8Dw/j0hX/wtF8h8CsASTkRlu09GIPimV1f3gAALFwmYOEFjSEJ4tE++O
Dz/v5lzBUw7TDYGrylLLerOd+mH5FYl8ai2OFu9dtsE
--- oWcM9irmHBy2/btfhFIoLfsdkQQV1GFY4q0wy3q9h4U
F(è«BFÛR‡QíC¯÷
æ-%wâzJAv‰CaŸ.ñØ,v¢+EðÂQÐ{®·â]
Binary file not shown.
+32 -15
View File
@@ -1,17 +1,34 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 Wl2fDA JMymqEdh+xJbl8VcL5wg7Y2Dk4667DzNO85RCskX+0Q -> ssh-ed25519 Wl2fDA dM0TgKtswZcbEV9tGGY26YCksV2xadHWXv7D/KksAWk
ZQqF0eYpvrLujGdIvAMbfwPnKGa+mfNvAhHGMdXiYaI 1vCcuHmVP2xiHd/7hh0z2Hiq/EeA8uvdsRtQReC5hNY
-> ssh-ed25519 zNC8SA UCQhQA4f3OiNoxejDBMabnls3LjS0GQmvIqPpjB/FH8 -> ssh-ed25519 zNC8SA uTO/3ePjgiKqk3jeRGZX5D3LjzhSBlp2rD2ZakKmfX0
0qvv6W1heZiE1DDYEj1U5N2e99DZLxlJ6A8EoZ31DhM tVkEEcP/KfD9x52l7iz5F3hKK0LSckjXWK5YP2aeBt4
-> ssh-rsa 3G83yA -> ssh-ed25519 EiAAKw Etu0I4IzJ3BB2SzCeiexx+dhcLUO5d2Ws+WiJyLk/Sw
Gnpw8t6njIXGm98jTS47Afx6TogPnIJP59rapF0CkYkDXZNrW7WK+fcERHLN2+a+ 9GBcZPsIXO3mXbri3lFYjtBBu0wFYul6hKsCvBKVLFs
PSkjwkql3LfAtCNqrIJZwWLj/URnKQF5N3ZKwOa1+wsM3GeUzjvaQwPZunj4jyFs -> ssh-rsa eFi+Zw
IJlL+ika2sBk/HvOa1r6ntj2cvLM1fIhbs9bOEZW3br3M3sfXk386TgrytqzM248 uOZsBC+IMHdX2h9Jq/CF/L3BsxDW+dULk04JQbDeM85Mrxxdrv2X3w7AW8YU2KS+
3xS2iIwIBmBiI5Xem8KO2+J/2Vk9Px/ZPkBpdIAaZAmihe3g/VWNKHhXrwdM9ZA7 Xg8LnzH01z4Nfs89uysM/lsWptc9qMeaK9o0oHC+tSJH4Ch43MejbmFYjFibHaCm
tHgw5ohK8ug88ep9XCIFD75DPeK/60wqAdkGs4PE6THcsKqhN061TAEq3SWRl8wp krQM7dAGIJwc/o0+ykaCrbXSvXAyfd6Nw1izou2ZcDRI7mTipOZO8F949SIk//Rc
Kd17yAzHDLhsbdWXT/Q912Y4YJCB3TnD0MFGzPF7sks2NknB6yowwjnCGlqzf5rW UJgPLqpGwScEfrHf4f6tySC4LmD0bPIV1xDpmmXcS7c83E9+iVOtb5Y1In6CQrF1
RBKHp6PTM+x/eDi89vS+uIBtyGFaFU7wBTl4FzJpKoOsRIDYNktGkJSxdTzrMO1n XZQCb9MkPySbuicwR022CySb+lc7Ru44RdqBgV1e+wphyZCoqCk09i18egV3hNs6
XqXtJtqZaXN7UExA+ko9ln446I7RG8c3hNGx4A4bR1xUEUE8WD/TMhjzrbzysYSl iEul3M8dqV27yRKrWIUD5jT2tUszTNJfreiuZl9eDmLkcVWExkWzqWPUFJ48hQiZ
89Z4Evn04vZGoeL67K5q93lSRHz109zT/KIJSQMZpbaecGAoiZDM8Mdq3KzawGSG
ENQazx6lnGoMccvxFhjrVqfYj3U4S/pnCow5fatvkBQSyysL63UxE5ivcFUHHppB
--- BuKW3bW48i1OD38J2bj5sRkn+zg/WKiLtf8zgycCr2A -> ssh-ed25519 QovoLQ wgg0cFlYEVafE3rXK4GrID3RTatZdKPYzsjT18WskFM
g­©2Ôkâ5 ÀóìØ!]0kÉW€³ÍØ¡t>éQžk[I3Vâ4EÔàwc`L;UvžVe)m©mé¿€ Û¸ bgv+7an3xgdqf6WaiB1FFkXObcykUnvH6lJmX5gFJkQ
-> ssh-rsa OFkEIg
IIQbFB6VUwbB+ZtKR7Ayg9Im6vMU1AzqHT8CBagA5fwJ7Vp1GuX1X9SxL9hMPkd3
4osEbSu3JJDMwfC6AfFtcEjmxjmRYyiYlzmIjhVEsaTlwyeucAPd+fdj+TPjHidZ
dffizNEOiENY49jlmWTjMqYKnBsSP9GfH4ZsKpCaWMm2h9p687weuXFfbYfjYMII
a3C4iG8m+mZ4crYTKZu6WPbnHn9g0pMxZBs4v6MnBHk6eEJ0uiJvrzYApoFE5om7
9AknL27ra/+A1UQl+1kzLT+IivJa8FCfZ+zF1RYLRvSATlIzCqCiBiayAsVtQg5O
girBRnlAJTPisszyoAhsqbECvD6bJfwlTW0STg/M1u3ZPMTGL4V0gJgynANmjb7Y
TXd11zuhjRYgOBAj09trQFTmmwIgPvvu8+VXNDNPAp02ffBT8kMUvSEik98/35x1
Dwvm38t05O6nqyHUF957CRVTzPQPAnb5Cd+Rw/joID2YPyFN9IZwE4mi2Bf3zdZo
roxtqCupmWkpxMNN7GZJrmCE/Lh6YV4DgUd6VNQc7QlGsq5K4XRT7aa+s+17cC8e
HCxQfGM8sMe9T6IK+K4p6qTqluyI/X0r95kGfzhNmgzufc44i6X497i3fDSVoLpx
Uo7Ao3QRNPyaUXcqTTIg8Kx9YiLQC3tDblVJjIZU89o
--- Vb9o/bhuN6XXjfK04haEEUXnuIA02j4GH9PmAh0ayN8
óE¬dGs;’ްÀ± ü
ñ,OHˆÿœˆ{²¶>ú*wAÃLÌÄ\©0SQöÖ*{6fô‰+Xš¨.
+9
View File
@@ -5,4 +5,13 @@ let
in in
{ {
"ntfy.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users; "ntfy.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
# tsig-keygen etc.sub.domain.tld.
"dns/tuxcord.net/tuxcord.net.key.age".publicKeys = [ tuxcord-ca ] ++ [ users.error users.javalsai ];
# "dns/tuxcord.net/XXX.tuxcord.net.key.age".publicKeys = [ tuxcord-ca ] ++ [ users.XXX ];
"dns/tuxcord.test/tuxcord.test.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
"dns/tuxcord.test/sub.tuxcord.test.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
"dns/nix.tuxcord.net/nix.tuxcord.net.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
} }
+7 -1
View File
@@ -16,13 +16,19 @@ To test the environment, you can launch a virtualized NixOS system derived from
nix run '.#nixosConfigurations.<system>.config.system.build.vm' nix run '.#nixosConfigurations.<system>.config.system.build.vm'
``` ```
Here, `<system>` refers to the hostname of the system you want to test (e.g., tuxcord-ca). Here, `<system>` refers to the hostname of the system you want to test (e.g., tuxcord-test).
Note that this will create a `qcow2` image file in the current directory. Nix will automatically manage changes to the configuration and update the image file accordingly while keeping part of its mutable state (e.g., root bash history). Note that this will create a `qcow2` image file in the current directory. Nix will automatically manage changes to the configuration and update the image file accordingly while keeping part of its mutable state (e.g., root bash history).
> [!WARNING] > [!WARNING]
> Not all changes are applied automatically. Updates such as user passwords changes or modifications to the filesystem layout will require deleting the image file so that Nix can re-create it from scratch. > Not all changes are applied automatically. Updates such as user passwords changes or modifications to the filesystem layout will require deleting the image file so that Nix can re-create it from scratch.
# Access
The initial password for the `root` account is `tuxcord`.
SSH login is enabled for the configured user keys, if using the VM test configuration, yo will have to use the bridged IP.
# Tooling # Tooling
Tooling used to aid in development. Tooling used to aid in development.
+9 -1
View File
@@ -42,7 +42,15 @@ Host specific configuration can be found at `nixos/hosts/tuxcord-XX`. This is us
To learn how to get started, refer to the [Getting Started guide](./GETTING_STARTED.md). To learn how to get started, refer to the [Getting Started guide](./GETTING_STARTED.md).
The guide contains basic instructions as to how to use Nix for this repository, as well as tools to help in certain tasks. The guide contains basic instructions as to how to use Nix for this repository, as well as tools to help in certain tasks, some of this tools might be assumed across document resources.
It might also be useful to read the [installation section](#installation) to learn how to configure your testing environment.
# Installation
Though the nix configuration already does most heavy-lifting already. There's some minor configuration that has to be done to the mutable state of the machine.
Each aspect of it should be carefully explained in the [Setup Guide](./SETUP.md).
# Contributions # Contributions
+34
View File
@@ -0,0 +1,34 @@
# Secrets
Secrets are managed with `agenix` in the `agenix/` directory. This allows to declaratively define secrets as well as which keys are allowed to decrypt them.
# Usage
The `agenix` help menu is already very helpful, but here you have a survival guide:
- `agenix` commands should run relative to the `agenix/` directory.
- `agenix -d` allows you to descrypt such file if you possess any of the decryption keys.
- `agenix -e` decrypts (if present) and opens the file in your editor to re-encrypt when exited.
- `agenix -r` re-encypts `*.age` files in the case you ever change its decryption keys.
# Secrets
There is a `ntfy.age` secret file which contents look like:
```sh
NTFY_TOPIC=readable-name_XXXXXXXXXX
```
This secret file is meant to be sources by shells before using [ntfy.sh](<https://ntfy.sh/>) to push important notifications. This topic could contain sensitive information and must be kept secret amongst administrators.
## DNS TSIG Keys
The DNS server takes zone updates through `nsupdate` with symmetric TSIG keys.
These keys can be generated using `tsig-keygen <key-name>` (historically they were done with `dnssec-keygen` and `HMAC` algorithms, but this is no longer supported).
When DNS is enabled for a host, it will look for `dns/${fqdn}/${zone}.key` secrets.
- The key whose zone matches the `${fqdn}` will be allowed to tramit updates for all the domain.
- Keys restrained to a specific `${zone}` will only be allowed to edit records of such zone.
- All keys must be named with the zone they affect, final dot included, so that (e.g. `tuxcord.net/javalsai.tuxcord.net.key` must be generated by `tsig-keygen javalsai.tuxcord.net.`).
+66
View File
@@ -0,0 +1,66 @@
# Setup Steps
The first configuration of the server needs some configuration of its mutable state:
Setup also heavily relies on the secrets configured, make sure you [understand agenix](./SECRETS.md) good enough.
# Root Password
The `root` password is `tuxcord` by default on all system configurations. For security, it's important to remember to change it as soon as an installation is done.
The root account is intended to be kept active in case there ever is the need to perform a TTY login. But this will be rare so do keep a security complex password saved somewhere and don't share it beyond the necessary amount.
# SSH Keys
Most agenix secrets have to be decrypted by the machine nixos is being installed to. For this, agenix uses the ssh host keys.
This also means that no secrets will be accessible on the host right after a base installation, with the default fresh ssh keys.
Will will need to either migrate ssh keys from another host, if you are doing a migration, or take the public keys of the new host out to encrypt agenix secrets against them. These keys, both public and private are present in `/etc/ssh/ssh_host_*`, we have a strong preference in favor of elliptic curve cryptography.
Also note that ssh keys are persistent and will be saved across machine boots and virtual machine rebuilds, so you don't need to repeat this process every time.
# DNS SOA Record
If the DNS server is enabled for the host (`dns.enable`), the host will have a DNS server for itself that can take updates with `nsupdate`.
This section assumes surface-level knowledge of DNS records, as well as a IPv4-only server.
First of all, make sure that the secrets for `nsudpate` ([DNS TSIG Keys](./SECRETS.md)) are in place, otherwise the server won't be able to take updates and will remain on an empty useless state.
You need to tell it it has authority over itself (`SOA` -> `Start Of Authority`) for it to take updates and serve changes properly, as well as configure the base `A` records.
`/var/dns/${fqdn}.zone`:
```zone
$ORIGIN ${fqdn}.
$TTL 14400 ; 4 hours
@ IN SOA ${fqdn}. ${adminEmail} (
2026050301 ; serial
3600 ; refresh (1 hour)
1800 ; retry (30 minutes)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
@ A ${ip}
ns1 A ${ip}
@ NS ns1
ns2 A ${ip}
@ NS ns2
* CNAME ${fqdn}.
```
Note the template variables:
- You need to place your FQDN in `${fqdn}` (there are shorter ways, but this guarantees functionality even if the hostname and domain don't match).
- Also an IPv4 address at `${ip}`.
- And lastly, `${adminEmail}` the SOA record has a `RNAME` field that takes the administrator's email address with dot notation (`test@example.com` would be written as `test.example.com.`).
- The values related to serial number and lifetimes can and should be tweaked depending on the use-case. Especially `serial`, note that it resembles a date.
Then restart bind with `systemctl restart bind`. Make sure that the file is owned by `named:named`.
> [!NOTE]
> This file is **mutable**, bind can and **will** change it with `nsupdate`s, it also tends to format and compact this template into a certain layout.
+5 -1
View File
@@ -1,4 +1,8 @@
{ {
error = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDzdpxex2GlFVf5G2qsh3Ixa/XCMjnbq4JSTmAev7WYJ error.nointernet@gmail.com"; error = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDzdpxex2GlFVf5G2qsh3Ixa/XCMjnbq4JSTmAev7WYJ error.nointernet@gmail.com";
javalsai = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCufzCHLqMfuHpKKisd9Y+3l6hudbQQyaHg1lgQs5XEO58f0dIoUK3gc8iVO6dGGeY5q2o0cDcildHiT0PYc96rq7WJLCY00mAuclEuhmRSPjsei2IT3rWTawIheD2tWq+vAQjIBKibYWnVYwNOsbR3Zz1uKG/LNqqDnyYO/t4iMmhO1qcl6j8dRVBtzWYu3TnTrwx45sj54Y9hqZZiwB1xlzhHznSw6YPOe51hUO/yUtXKF2FCyfG7LHELZBMXkPQD6h4mDu+QNPN2D5RGd+Q5WzHcXcrXH/DvogVW6g3YGpBjTNKllCjGJYdYgjcjzQOS3I8ZOL6CUQzcZt2QwO3P42s4cjGzBwIub2zFnMOCyGgbKCYh3G7KKcde9qAX0yl8k+XNPIletJAV7pDrivzmgRLdy3iWud+q8TytkDLhcd/7g+pE6FE8y3IbejwXGNUn8CXJOKWH5zk0MVWSpNqz+6rlV43iPb4yuO7TFVnzuw/wKyOoc8RlFGEb/XLXwPs="; javalsai = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFjavnLqxIzFLIUpUWDOwhlYeoII4Qk1/9e0yWWxD/P";
max = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDxVfJhzPDZ108UjB3Vj/akzlzYn27kyAw29AuYAr7gvG5vrqhLUYYmK8t+ZVWVpc1g6cK7OF1oUn2E5Qfmy6wqyZQXftAZ4OcRS0MB71W1bAcRq3rGe6KQDm8RSEeygX+zO+2Z6zQmVWgPr/I+JFQZ8wiWdP8X8djqTRdhqUD+SR3ZgTcnY3aLmeB/I56rcZQ3lKIeg/pEsyQ8weptlV0rTWamna6Z7Nw48VwWNSI+6EqfW2/4/edm0Ue8jMNqNZ0yx+kHJbudPgZgSR1SiR2rqlEEUaiQJQQV3VdY4DhGm7143ZSKUxyKlfTuQ7qR1zSIg6f5V71A37ik9YiSbBlOZO86swR4qHESoMNf608IuqRt2NdALHwozFPUCu16qnhu5JTk8twSAzrAhOk5zWQj1LYMoQEBhcFSmwir/1gE71NSjYtqXGVAdfkVmZ4uqG5+a1D7H3VXWOqu/j839M045O1ZBY6X3lKDsEJ1Z1+LCl/NojWnvPtJUHYI6+SdQ6k=";
vectorum = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCwfPaylCSN7ZqB6Trz8CmQlyzf0NUIy06uschdIOkdzjUNe/dPGbyEFZy/4SDBhg585x8hwfhfYjLGrneYq+O42DBDTDKxduWnIdl5zgPRqt7jB59jkukf9WUdpUdZsKCM5K97HCnizNEKGRnYllVPVQSapPhOm5dZlUD9YVv1UqbDuxtWOLvArkL52e9T+yL6FagRg6NPqA70MXPMk+S2H7lotFVxP2Eg//BCaQ0/H1vhNy6P4N6LLq3sVK1DSJyd2v8zHkdb2Zo0/Ygukol10KizSsEcihm8+bXp699uSgWIsaIQgDZlE1yx2iabmzQST1kL9+USnZZBZ+KxwtLCCI8mpCv6sxlhq2Zzim5HvsyMYM+zdHWIn1s2c6mEl4ntBAB4s5wAggS5Gjh/BfJLSvGsTFMC/XYX4gWFXynY5NlcopeENL2Afg4gbQvKkxYkWB/TMZWuqj5c5kCy+7F0881DpYxapq6kQ6IE4gkGiEQdhVFGWEFoV/k9iHrl6aanqvFtvuBHHgkXAGPpHAZDVZdp9lU0tqNQIM/eGINq4Or6wd9XDYyj5ezDEBxx1pPgweUDZrNe9+vKR+3AwbzB/XQPxTCcjd4d7Yx58jPLflFP1dDYT+3bvp+vA7UpHcJnISbVNu0SiSaIqLYhwDj1od5l6JDfRCqnMF1T59nRCQ==";
pickzelle = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPUYQUWoL8iGc+PSrRrHyNwcOcmgGwPvJAM9HRJkPqcW pixel@DOOM-Machine";
} }
+21 -5
View File
@@ -4,6 +4,7 @@
lib, lib,
pkgs, pkgs,
self, self,
config,
... ...
}: }:
let let
@@ -27,6 +28,8 @@ in
./vm.nix ./vm.nix
]; ];
age.secrets.ntfy.file = "${self}/agenix/ntfy.age";
nix = { nix = {
package = inputs'.nix-super.packages.default; package = inputs'.nix-super.packages.default;
@@ -92,13 +95,26 @@ in
networking = { networking = {
networkmanager.enable = true; networkmanager.enable = true;
firewall = { firewall.enable = true;
enable = true;
allowedTCPPorts = [ extraHosts =
22 let
subdomains = [
""
".git"
]; ];
}; in
builtins.foldl' (
hosts-acc: domain-prefix:
let
host = "${domain-prefix}${config.networking.fqdn}";
in
hosts-acc
+ ''
127.0.0.1 ${host}
::1 ${host}
''
) "" subdomains;
}; };
virtualisation.podman.enable = true; virtualisation.podman.enable = true;
+3
View File
@@ -31,5 +31,8 @@ in
{ {
flake.nixosConfigurations = { flake.nixosConfigurations = {
tuxcord-ca = mkSystem "tuxcord-ca" "x86_64-linux"; tuxcord-ca = mkSystem "tuxcord-ca" "x86_64-linux";
tuxcord-test = mkSystem "tuxcord-test" "x86_64-linux";
tuxcord-acmetest = mkSystem "tuxcord-acmetest" "x86_64-linux";
}; };
} }
+11
View File
@@ -0,0 +1,11 @@
{
acme = {
enable = true;
rfc2136.nameserver = "tuxcord.net";
};
dns.enable = true;
networking.fqdn = "nix.tuxcord.net";
time.timeZone = "Europe/Madrid";
}
+8
View File
@@ -4,5 +4,13 @@
./storage.nix ./storage.nix
]; ];
acme = {
enable = true;
useSelfDns = true;
};
dns.enable = true;
networking.fqdn = "tuxcord.net";
time.timeZone = "Canada/Eastern"; time.timeZone = "Canada/Eastern";
} }
+6
View File
@@ -0,0 +1,6 @@
{
acme.enable = false;
dns.enable = true;
networking.fqdn = "tuxcord.test";
}
+89
View File
@@ -0,0 +1,89 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.acme;
inherit (lib)
mkIf
mkEnableOption
mkOption
types
;
inherit (config.networking) fqdn;
in
{
# we'll only support rfc2136 based challenges
options.acme = {
enable = mkEnableOption "" // {
default = true;
};
useSelfDns = mkOption {
default = false;
description = "Sets values of the self DNS if enabled, otherwise requires manual `rfc2136` nameserver and key values.";
};
rfc2136 = {
key = mkOption {
type = types.path;
default = config.age.secrets."dns/${fqdn}.key.age".path;
};
nameserver = mkOption {
type = types.str;
default = if cfg.useSelfDns then fqdn else null;
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = with cfg.rfc2136; nameserver != null && key != null;
message = "ACME needs rfc2136 parameters to work, consider using `useSelfDns` option.";
}
];
environment.persistence."/persist".directories = [
{
directory = "/var/lib/acme";
group = "acme";
user = "acme";
}
];
security.acme = {
acceptTerms = true;
defaults = {
email = "error@tuxcord.net";
reloadServices = [ "nginx" ];
postRun = ''
source ${config.age.secrets.ntfy.path}
${pkgs.ntfy-sh}/bin/ntfy publish -T recycle -t "${config.host.name}" "HTTPS certificate has been renewed"
'';
};
certs."${fqdn}" = {
dnsProvider = "rfc2136";
environmentFile =
with cfg.rfc2136;
builtins.toFile "dns-01-challenge.cfg" ''
RFC2136_NAMESERVER=${nameserver}
RFC2136_TSIG_FILE="${key}"
'';
extraDomainNames = [
"*.${fqdn}"
"${fqdn}"
];
inherit (config.services.nginx) group;
};
};
};
}
+5 -1
View File
@@ -1,9 +1,13 @@
{ {
imports = [ imports = [
./acme.nix
./dns.nix
./fail2ban.nix ./fail2ban.nix
./sysctl.nix ./gitea.nix
./host.nix ./host.nix
./nginx.nix
./snapper.nix ./snapper.nix
./substituters.nix ./substituters.nix
./sysctl.nix
]; ];
} }
+102
View File
@@ -0,0 +1,102 @@
{
config,
lib,
self,
...
}:
let
cfg = config.dns;
inherit (lib)
mkEnableOption
mkIf
strings
;
inherit (config.networking) fqdn;
agenixDnsDir = "${self}/agenix/dns/${fqdn}";
agenixKeys = builtins.attrNames (builtins.readDir agenixDnsDir);
keys = map (
filename:
let
zonesub = _: "zonesub";
subdomain = name: "subdomain ${name}";
zoneDomain =
if strings.hasSuffix ".key.age" filename then
strings.removeSuffix ".key.age" filename
else
throw "${filename} is not a `.key.age` file";
in
{
inherit (config.age.secrets."dns/${filename}") path;
name = zoneDomain;
type = if zoneDomain == fqdn then zonesub else subdomain;
}
) agenixKeys;
in
{
options.dns = {
enable = mkEnableOption "" // {
default = true;
};
};
config = mkIf cfg.enable {
age.secrets = builtins.listToAttrs (
map (
filename:
let
path = "${agenixDnsDir}/${filename}";
in
{
name = "dns/${filename}";
value = {
file = path;
group = "named";
owner = if config.acme.enable then "acme" else "named";
mode = "440";
};
}
) agenixKeys
);
services.bind = {
enable = true;
extraConfig = builtins.concatStringsSep "\n" (map (key: "include \"${key.path}\";") keys);
zones."${fqdn}" = {
# grant "tuxcord.net" zonesub ANY;
extraConfig = ''
update-policy {
${builtins.concatStringsSep "\n" (
map (key: "grant \"${key.name}\" ${key.type key.name} ANY;") keys
)}
};
'';
file = "/var/dns/${fqdn}.zone"; # need to put default stuff
master = true;
};
};
environment.persistence."/persist".directories = [
{
directory = "/var/dns";
group = "named";
user = "named";
}
];
networking.firewall =
let
ports = [ config.services.bind.listenOnPort ];
in
{
allowedTCPPorts = ports;
allowedUDPPorts = ports;
};
};
}
+40
View File
@@ -0,0 +1,40 @@
{ config, ... }:
let
inherit (config.networking) fqdn;
acmeEnabled = config.acme.enable;
in
{
services.gitea = {
enable = true;
appName = "TuxCord Gitea";
database.type = "mysql";
lfs.enable = true;
settings = {
server = {
DOMAIN = fqdn;
ROOT_URL = "${if acmeEnabled then "https" else "http"}://${fqdn}/";
HTTP_PORT = 3000;
};
service = {
DISABLE_REGISTRATION = true;
REQUIRE_SIGNIN_VIEW = false;
};
repository = {
ENABLE_PUSH_CREATE_USER = true;
ENABLE_PUSH_CREATE_ORG = true;
DEFAULT_BRANCH = "main";
};
# ui.DEFAULT_THEME = "...";
# TODO: once we have email setup this would be nice
mailer.ENABLED = true;
};
};
}
+45
View File
@@ -0,0 +1,45 @@
{ config, ... }:
let
inherit (config.networking) fqdn;
mkVhost =
attrs:
let
acmeEnabled = config.acme.enable;
in
{
forceSSL = acmeEnabled;
useACMEHost = if acmeEnabled then fqdn else null;
}
// attrs;
mkProxy = port: {
proxyPass = "http://127.0.0.1:${toString port}/";
};
in
{
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
# services.nginx.virtualHosts."${fqdn}" = {
# addSSL = true;
# root = "/var/www/myhost.org";
# default = true;
# };
virtualHosts."git.${fqdn}" = mkVhost {
locations."/" = mkProxy config.services.gitea.settings.server.HTTP_PORT;
};
};
networking.firewall.allowedTCPPorts = [
80
443
];
}
+7 -1
View File
@@ -1,17 +1,23 @@
{ self, ... }:
{ {
services.openssh = { services.openssh = {
enable = true; enable = true;
settings = { settings = {
ClientAliveInterval = 300; ClientAliveInterval = 300;
KbdInteractiveAuthentication = false; KbdInteractiveAuthentication = false;
PasswordAuthentication = false; PasswordAuthentication = false;
PermitRootLogin = "no"; PermitRootLogin = "no";
}; };
}; };
<<<<<<< HEAD
networking.firewall.allowedTCPPorts = [ 22 ];
||||||| parent of 1c2f11d (lib/ssh: add more ssh keys)
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues { users.users.root.openssh.authorizedKeys.keys = builtins.attrValues {
inherit (import "${self}/lib/ssh/keys.nix") error javalsai; inherit (import "${self}/lib/ssh/keys.nix") error javalsai;
}; };
=======
>>>>>>> 1c2f11d (lib/ssh: add more ssh keys)
} }
+1
View File
@@ -41,6 +41,7 @@ in
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
atop atop
bat bat
bind
btdu btdu
btop btop
compsize compsize
+29 -11
View File
@@ -16,6 +16,9 @@ let
{ {
name = "vectorum"; name = "vectorum";
} }
{
name = "pickzelle";
}
]; ];
adminGroups = [ adminGroups = [
@@ -27,20 +30,29 @@ let
"wheel" "wheel"
]; ];
mkUser = name: uid: options: { getSSHKeys =
username:
let
sshKeys = import "${self}/lib/ssh/keys.nix";
in
if (builtins.hasAttr username sshKeys) then
lib.lists.toList sshKeys.${username}
else
lib.warn "user ${username} declared without ssh key" [ ];
mkUser =
name: uid: options:
let
admin = options.admin or false;
in
{
users.users.${name} = { users.users.${name} = {
isNormalUser = true; isNormalUser = true;
extraGroups = lib.optionals (options.admin or false) adminGroups; extraGroups = lib.optionals admin adminGroups;
inherit uid; inherit uid;
openssh.authorizedKeys.keys = openssh.authorizedKeys.keys = getSSHKeys name;
let
keys = import "${self}/lib/ssh/keys.nix";
in
if (builtins.hasAttr name keys) then
[ keys.${name} ]
else
lib.warn "user ${name} declared without ssh key" [ ];
}; };
systemd.slices."user-${builtins.toString uid}".sliceConfig = { systemd.slices."user-${builtins.toString uid}".sliceConfig = {
@@ -67,5 +79,11 @@ lib.recursiveUpdate
users users
).options ).options
{ {
users.users.root.initialPassword = "tuxcord"; users.users.root = {
initialPassword = "tuxcord";
openssh.authorizedKeys.keys = lib.lists.concatLists (
map (user: getSSHKeys user.name) (builtins.filter (user: user.options.admin or false) users)
);
};
} }
+249
View File
@@ -0,0 +1,249 @@
/*
This file is provided under the MIT licence:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
# Generated by npins. Do not modify; will be overwritten regularly
let
# Backwards-compatibly make something that previously didn't take any arguments take some
# The function must return an attrset, and will unfortunately be eagerly evaluated
# Same thing, but it catches eval errors on the default argument so that one may still call it with other arguments
mkFunctor =
fn:
let
e = builtins.tryEval (fn { });
in
(if e.success then e.value else { error = fn { }; }) // { __functor = _self: fn; };
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range =
first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatStrings = builtins.concatStringsSep "";
# If the environment variable NPINS_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
# (Taken from Niv for compatibility)
mayOverride =
name: path:
let
envVarName = "NPINS_OVERRIDE_${saneName}";
saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
ersatz = builtins.getEnv envVarName;
in
if ersatz == "" then
path
else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" (
if builtins.substring 0 1 ersatz == "/" then
/. + ersatz
else
/. + builtins.getEnv "PWD" + "/${ersatz}"
);
mkSource =
name: spec:
{
pkgs ? null,
}:
assert spec ? type;
let
# Unify across builtin and pkgs fetchers.
# `fetchGit` requires a wrapper because of slight API differences.
fetchers =
if pkgs == null then
{
inherit (builtins) fetchTarball fetchurl;
# For some fucking reason, fetchGit has a different signature than the other builtin fetchers …
fetchGit = args: (builtins.fetchGit args).outPath;
}
else
{
fetchTarball =
{
url,
sha256,
}:
pkgs.fetchzip {
inherit url sha256;
extension = "tar";
};
inherit (pkgs) fetchurl;
fetchGit =
{
url,
submodules,
rev,
name,
narHash,
}:
pkgs.fetchgit {
inherit url rev name;
fetchSubmodules = submodules;
hash = narHash;
};
};
# Dispatch to the correct code path based on the type
path =
if spec.type == "Git" then
mkGitSource fetchers spec
else if spec.type == "GitRelease" then
mkGitSource fetchers spec
else if spec.type == "PyPi" then
mkPyPiSource fetchers spec
else if spec.type == "Channel" then
mkChannelSource fetchers spec
else if spec.type == "Tarball" then
mkTarballSource fetchers spec
else if spec.type == "Container" then
mkContainerSource pkgs spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = mayOverride name path; };
mkGitSource =
{
fetchTarball,
fetchGit,
...
}:
{
repository,
revision,
url ? null,
submodules,
hash,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null && !submodules then
fetchTarball {
inherit url;
sha256 = hash;
}
else
let
url =
if repository.type == "Git" then
repository.url
else if repository.type == "GitHub" then
"https://github.com/${repository.owner}/${repository.repo}.git"
else if repository.type == "GitLab" then
"${repository.server}/${repository.repo_path}.git"
else if repository.type == "Forgejo" then
"${repository.server}/${repository.owner}/${repository.repo}.git"
else
throw "Unrecognized repository type ${repository.type}";
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName url revision;
in
fetchGit {
rev = revision;
narHash = hash;
inherit name submodules url;
};
mkPyPiSource =
{ fetchurl, ... }:
{
url,
hash,
...
}:
fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ fetchTarball, ... }:
{
url,
hash,
...
}:
fetchTarball {
inherit url;
sha256 = hash;
};
mkTarballSource =
{ fetchTarball, ... }:
{
url,
locked_url ? url,
hash,
...
}:
fetchTarball {
url = locked_url;
sha256 = hash;
};
mkContainerSource =
pkgs:
{
image_name,
image_tag,
image_digest,
...
}:
if pkgs == null then
builtins.throw "container sources require passing in a Nixpkgs value: https://github.com/andir/npins/blob/master/README.md#using-the-nixpkgs-fetchers"
else
pkgs.dockerTools.pullImage {
imageName = image_name;
imageDigest = image_digest;
finalImageTag = image_tag;
};
in
mkFunctor (
{
input ? ./sources.json,
}:
let
data =
if builtins.isPath input then
# while `readFile` will throw an error anyways if the path doesn't exist,
# we still need to check beforehand because *our* error can be caught but not the one from the builtin
# *piegames sighs*
if builtins.pathExists input then
builtins.fromJSON (builtins.readFile input)
else
throw "Input path ${toString input} does not exist"
else if builtins.isAttrs input then
input
else
throw "Unsupported input type ${builtins.typeOf input}, must be a path or an attrset";
version = data.version;
in
if version == 7 then
builtins.mapAttrs (name: spec: mkFunctor (mkSource name spec)) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
)
+4
View File
@@ -0,0 +1,4 @@
{
"pins": {},
"version": 7
}