Compare commits

...

11 commits

Author SHA1 Message Date
Kiana Sheibani bb8f338c68
feat: add assertion to ensure correctness of options 2024-11-20 02:23:49 -05:00
Kiana Sheibani c978882918
refactor: separate module options into options.nix 2024-11-20 02:23:20 -05:00
Kiana Sheibani cb94d7c6fb
docs: document API 2024-11-20 02:08:31 -05:00
Kiana Sheibani 475017cf87
fix: use specialArgs to prevent infinite loop
The `_module.args` option can't be used to pass modules to import, since
it can only be used during the config phase, not the import phase.
2024-11-20 01:15:44 -05:00
Kiana Sheibani 966a1ec8f6
fix: correct option name 2024-11-20 01:15:30 -05:00
Kiana Sheibani e845f3468a
fix: add catchall to module args 2024-11-20 01:15:09 -05:00
Kiana Sheibani f30ba6a566
fix: resolve parsing error 2024-11-20 01:14:36 -05:00
Kiana Sheibani 628ec321c0
refactor: overhaul pretty much all the organization 2024-11-20 00:59:56 -05:00
Kiana Sheibani 4d05c0c645
refactor: remove default.nix file in module dir 2024-11-19 22:48:31 -05:00
Kiana Sheibani c01d40b692
feat: add MIT license 2024-11-19 22:47:40 -05:00
Kiana Sheibani 6163ab5f08
docs: document modules 2024-11-19 22:36:15 -05:00
19 changed files with 340 additions and 147 deletions

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2024 Kiana Sheibani
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.

View file

@ -14,21 +14,71 @@ automatically.
In short, it's my personal web server.
## Modules
As with all good NixOS configurations, Aether is split into *modules* that
each provide different functionality. These are stored in the `modules/` directory.
### Module Checklist
- [x] `wireless` - WiFi support
- [x] `ssh` - SSH configuration
- [x] `forgejo` - Code forge
- [x] `fail2ban` - IP moderation
- [ ] `site` - Personal website
- [ ] `mail` - Mail server
- [ ] `backup` - Automated backup system
- [ ] `cachix` - Nix build caching
## Deployment
Aether is designed to separate deployment from the logical specification of the
system, and thus to support any machine that can run NixOS. Currently, it is
deployed physically to a
Aether is designed to separate individual machine details from the abstract
specification of the system, allowing for deployment to several different
types of system. This is handled using *deployments* in the `deploy/` directory.
Currently, I deploy Aether physically to a
[Raspberry Pi 5](https://wiki.nixos.org/wiki/NixOS_on_ARM/Raspberry_Pi_5)
running a [modified UEFI bootloader](https://github.com/worproject/rpi5-uefi)
to provide Linux support.
to provide Linux support. The NixOS code for this can be found in `deploy/rpi5/`.
## Module Checklist
## External Usage
- [x] `forgejo.nix` - Code forge
- [ ] `site.nix` - Personal website
- [ ] `mail.nix` - Mail server
- [ ] `backup.nix` - Automated backup system
- [ ] `cachix.nix` - Nix build caching
If you use NixOS and are interested in any of these modules, you can import
them for your own config!
Add this repository as a flake input:
``` nix
{
inputs.aether.url = "https://git.tokinanpa.dev/toki/aether/archive/main.tar.gz";
}
```
Aether modules are then exposed under `nixosModules.<name>` and deployments
under `nixosModules.deploy-<name>`. You can activate a module by adding it
to your `imports`:
``` nix
{
imports = with aether.nixosModules; [
# Deployment
deploy-rpi5
# Modules
forgejo
ssh
];
# Required by forgejo module
aether.domain = "...";
}
```
Any number of modules can be activated at once, but activating more than one
deployment will cause issues, so that should be avoided.
Some modules have options that can be used to configure their effects. If a
module has options, they can be found in the `options.nix` file inside the
module directory. More general options used by multiple modules are
documented in `modules/options.nix`.
[^1]: Adapted from [Book II.1](http://classics.mit.edu/Aristotle/heavens.2.ii.html).

68
aether/config.nix Normal file
View file

@ -0,0 +1,68 @@
{ config, lib, pkgs, aether, ... }:
{
networking.hostName = "toki-aether";
time.timeZone = "America/New_York";
nix.package = pkgs.nixVersions.latest;
nix.settings.experimental-features = [ "nix-command" "flakes" ];
users.mutableUsers = false;
users.users.root = {
hashedPassword = "$y$j9T$LHeAgn5XytQM5DLfGSDT30$9OD3eIua5vEy4/GFBbT1oe1UnlNxDHt9thqsiqcGXy7";
openssh.authorizedKeys.keys = (import secrets/secrets.nix).keys;
};
environment.systemPackages = with pkgs; [
openssl
rsync
curl
git
wget
];
# Aether modules
imports = [
aether.aether
aether.deploy-rpi5
];
aether.domain = "tokinanpa.dev";
aether.acmeEmail = "kiana.a.sheibani@gmail.com";
aether.forgejo.templates = ./forgejo-templates;
services.forgejo.settings = {
DEFAULT.APP_NAME = "Code by toki!";
service.DISABLE_REGISTRATION = true;
repository = {
DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls";
ENABLE_PUSH_CREATE_USER = true;
DEFAULT_PUSH_CREATE_PRIVATE = false;
PREFERRED_LICENSES = "MIT,GPL-3.0-or-later";
};
mirror.DEFAULT_INTERVAL = "1h";
indexer = {
REPO_INDEXER_ENABLED = true;
REPO_INDEXER_EXCLUDE = "**.pdf, **.png, **.jpg, **.jpeg, **.svg, **.web, **.gpg, **.age";
};
ui = {
DEFAULT_THEME = "forgejo-dark";
GRAPH_MAX_COMMIT_NUM = 250;
};
"ui.meta" = {
AUTHOR = "Kiana Sheibani";
DESCRIPTION = "Code by toki! Powered by Forgejo";
KEYWORDS = "git,forge,forgejo,toki,tokinanpa";
};
"service.explore".DISABLE_USERS_PAGE = true;
federation.ENABLED = true;
};
system.stateVersion = "24.05";
}

View file

@ -1,45 +0,0 @@
{ config, lib, pkgs, rpi5-kernel, ... }:
{
imports = [ ./hardware-configuration.nix ./modules ];
boot.kernelPackages = rpi5-kernel.legacyPackages.aarch64-linux.linuxPackages_rpi5;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = false;
nix.package = pkgs.nixVersions.latest;
nix.settings.experimental-features = [ "nix-command" "flakes" ];
time.timeZone = "America/New_York";
networking.hostName = "toki-aether";
networking.wireless.iwd.enable = true;
networking.wireless.iwd.settings = {
Settings.AutoConnect = true;
Network.EnableIPv6 = false;
General.EnableNetworkConfiguration = true;
};
services.openssh.enable = true;
services.openssh.settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
};
users.mutableUsers = false;
users.users.root = {
hashedPassword = "$y$j9T$LHeAgn5XytQM5DLfGSDT30$9OD3eIua5vEy4/GFBbT1oe1UnlNxDHt9thqsiqcGXy7";
openssh.authorizedKeys.keys = (import secrets/secrets.nix).keys;
};
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
environment.systemPackages = with pkgs; [
openssl
rsync
curl
git
wget
];
system.stateVersion = "24.05";
}

18
deploy/rpi5/default.nix Normal file
View file

@ -0,0 +1,18 @@
{ config, lib, ... }:
{
options.aether.deploy.rpi5 = {
kernelPackages = lib.mkOption {
type = lib.types.raw;
description = "Kernel package to use for Raspberry Pi 5 support";
};
};
config =
let cfg = config.aether.deploy.rpi5;
in {
nixpkgs.system = "aarch64-linux";
boot.kernelPackages = cfg.kernelPackages;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = false;
};
}

View file

@ -1,5 +1,5 @@
{
description = "Server system conf";
description = "Aether - web server configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@ -12,15 +12,36 @@ inputs = {
agenix.inputs.darwin.follows = "";
};
outputs = inputs@{ self, nixpkgs, agenix, ... }:
{
outputs = inputs@{ self, nixpkgs, agenix, rpi5-kernel, ... }:
let
inherit (nixpkgs) lib;
moduleNames =
let sub = builtins.readDir ./modules;
in builtins.filter
(d: sub.${d} == "directory")
(builtins.attrNames sub);
modules = lib.genAttrs moduleNames (name: ./modules/${name});
in {
nixosModules =
modules
// {
aether.imports = lib.attrValues modules;
deploy-rpi5 = {
imports = [ ./deploy/rpi5 ];
aether.deploy.rpi5.kernelPackages =
rpi5-kernel.legacyPackages.aarch64-linux.linuxPackages_rpi5;
};
};
nixosConfigurations."toki-aether" =
nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
specialArgs.aether = self.nixosModules;
modules = [
{ _module.args = inputs; }
agenix.nixosModules.default
./config.nix
./aether/hardware-configuration.nix
./aether/config.nix
];
};
nixosConfigurations.default = self.nixosConfigurations."toki-aether";

View file

@ -1,5 +0,0 @@
{ ... }:
{
security.acme.acceptTerms = true;
security.acme.defaults.email = "kiana.a.sheibani@gmail.com";
}

View file

@ -1,8 +0,0 @@
{ ... }:
{
imports = [
./acme.nix
./fail2ban.nix
./forgejo.nix
];
}

View file

@ -1,73 +0,0 @@
{ config, ... }:
let
cfg = config.services.forgejo;
srv = cfg.settings.server;
in {
services.nginx.enable = true;
services.nginx.virtualHosts.${srv.DOMAIN} = {
forceSSL = true;
enableACME = true;
extraConfig = ''
client_max_body_size 512M;
'';
locations."/".proxyPass = "http://localhost:${builtins.toString srv.HTTP_PORT}";
};
services.forgejo = {
enable = true;
user = "git";
group = cfg.user;
database.user = cfg.user;
settings = {
DEFAULT.APP_NAME = "Code by toki!";
server = {
DOMAIN = "git.tokinanpa.dev";
ROOT_URL = "https://${srv.DOMAIN}/";
};
service.DISABLE_REGISTRATION = true;
repository = {
DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls";
ENABLE_PUSH_CREATE_USER = true;
DEFAULT_PUSH_CREATE_PRIVATE = false;
PREFERRED_LICENSES = "MIT,GPL-3.0-or-later";
};
mirror.DEFAULT_INTERVAL = "1h";
indexer = {
REPO_INDEXER_ENABLED = true;
REPO_INDEXER_EXCLUDE = "**.pdf, **.png, **.jpg, **.jpeg, **.svg, **.web, **.gpg, **.age";
};
ui = {
DEFAULT_THEME = "forgejo-dark";
GRAPH_MAX_COMMIT_NUM = 250;
};
"ui.meta" = {
AUTHOR = "Kiana Sheibani";
DESCRIPTION = "Code by toki! Powered by Forgejo";
KEYWORDS = "git,forge,forgejo,toki,tokinanpa";
};
"service.explore".DISABLE_USERS_PAGE = true;
federation.ENABLED = true;
};
};
systemd.tmpfiles.rules = [
"L+ ${cfg.stateDir}/custom/templates - - - - ${./forgejo-templates}"
];
users.users.${cfg.user} = {
home = cfg.stateDir;
useDefaultShell = true;
group = cfg.group;
isSystemUser = true;
};
users.groups.${cfg.group} = {};
}

View file

@ -0,0 +1,55 @@
{ config, lib, ... }:
let
cfg = config.aether.forgejo;
forgejo = config.services.forgejo;
srv = forgejo.settings.server;
in {
imports = [ ./options.nix ];
# Web server
services.nginx.enable = true;
services.nginx.virtualHosts.${srv.DOMAIN} = {
forceSSL = config.aether.https;
enableACME = config.aether.https;
extraConfig = ''
client_max_body_size 512M;
'';
locations."/".proxyPass = "http://localhost:${builtins.toString srv.HTTP_PORT}";
};
security.acme.acceptTerms = config.aether.https;
security.acme.defaults.email = cfg.acmeEmail;
networking.firewall.allowedTCPPorts =
[ 80 ] ++ lib.optional config.aether.https 443;
# Forgejo
services.forgejo = {
enable = true;
user = cfg.user;
group = forgejo.user;
database.user = forgejo.user;
settings.server = {
DOMAIN = lib.optionalString (!(builtins.isNull cfg.subdomain)) "${cfg.subdomain}."
+ config.aether.domain;
ROOT_URL = "https://${srv.DOMAIN}/";
};
};
systemd.tmpfiles.rules =
lib.optional
(!(builtins.isNull cfg.templates))
"L+ ${cfg.stateDir}/custom/templates - - - - ${cfg.templates}";
}
// lib.mkIf cfg.createUser {
users.users.${forgejo.user} = {
home = forgejo.stateDir;
useDefaultShell = true;
group = forgejo.group;
isSystemUser = true;
};
users.groups.${forgejo.group} = {};
}

View file

@ -0,0 +1,52 @@
args@{ config, lib, ... }:
{
options.aether = {
inherit (import ../options.nix args)
domain
https
acmeEmail;
forgejo = {
subdomain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "git";
description = ''
The subdomain to host the Forgejo instance under.
If null, then Forgejo is hosted at the domain itself.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = "git";
description = ''
The user to run Forgejo with.
'';
};
createUser = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to create the Forgejo user automatically.
'';
};
templates = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
A directory of templates for customizing Forgejo's appearance.
'';
};
};
};
config.assertions = lib.mkIf config.aether.https [
{
assertion = !(builtins.isNull config.aether.acmeEmail);
message = "HTTPS support requires providing a contact email";
}
];
}

19
modules/options.nix Normal file
View file

@ -0,0 +1,19 @@
{ lib, ... }:
{
domain = lib.mkOption {
type = lib.types.str;
description = "The domain name the server is hosted on.";
};
https = lib.mkOption {
type = lib.types.boolByOr;
default = true;
description = "Whether to force HTTPS connections for websites.";
};
acmeEmail = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Email address for ACME.";
};
}

10
modules/ssh/default.nix Normal file
View file

@ -0,0 +1,10 @@
{ ... }:
{
services.openssh.enable = true;
services.openssh.settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
};
networking.firewall.allowedTCPPorts = [ 22 ];
}

View file

@ -0,0 +1,9 @@
{ ... }:
{
networking.wireless.iwd.enable = true;
networking.wireless.iwd.settings = {
Settings.AutoConnect = true;
Network.EnableIPv6 = false;
General.EnableNetworkConfiguration = true;
};
}