Compare commits
1 commit
master
...
tlater/aut
| Author | SHA1 | Date | |
|---|---|---|---|
| 80d0f8fc9f |
59 changed files with 1599 additions and 5836 deletions
|
|
@ -1,7 +0,0 @@
|
||||||
((rust-mode
|
|
||||||
. ((eglot-workspace-configuration
|
|
||||||
. (:rust-analyzer
|
|
||||||
(:cargo (:features "all")
|
|
||||||
:check (:command "clippy")
|
|
||||||
:rustfmt (:overrideCommand ["leptosfmt" "--stdin" "--rustfmt"])
|
|
||||||
:linkedProjects ["./pkgs/packages/webserver/Cargo.toml"]))))))
|
|
||||||
|
|
@ -1,47 +1,61 @@
|
||||||
{ flake-inputs }:
|
|
||||||
let
|
|
||||||
inherit (flake-inputs.nixpkgs) lib;
|
|
||||||
pkgs = flake-inputs.nixpkgs.legacyPackages.x86_64-linux;
|
|
||||||
checkLib = pkgs.callPackage ./lib.nix { };
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
x86_64-linux = lib.mergeAttrsList [
|
self,
|
||||||
flake-inputs.self.nixosConfigurations.hetzner-1.config.serviceTests
|
nixpkgs,
|
||||||
|
deploy-rs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
|
||||||
|
statix' = pkgs.statix.overrideAttrs (old: {
|
||||||
|
patches = old.patches ++ [
|
||||||
|
(pkgs.fetchpatch {
|
||||||
|
url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
|
||||||
|
hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
runNuCheck =
|
||||||
{
|
{
|
||||||
nix = checkLib.mkLint {
|
name,
|
||||||
name = "nix-lints";
|
packages,
|
||||||
fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.;
|
check,
|
||||||
|
}:
|
||||||
|
pkgs.stdenvNoCC.mkDerivation {
|
||||||
|
inherit name;
|
||||||
|
|
||||||
checkInputs = lib.attrValues {
|
src = nixpkgs.lib.cleanSourceWith {
|
||||||
inherit (pkgs) deadnix nixfmt-rfc-style;
|
src = self;
|
||||||
|
filter = nixpkgs.lib.cleanSourceFilter;
|
||||||
statix = pkgs.statix.overrideAttrs (old: {
|
|
||||||
patches = old.patches ++ [
|
|
||||||
(pkgs.fetchpatch {
|
|
||||||
url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
|
|
||||||
hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
statix check **/*.nix
|
|
||||||
deadnix --fail **/*.nix
|
|
||||||
nixfmt --check --strict **/*.nix
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
lockfile = checkLib.mkLint {
|
dontPatch = true;
|
||||||
name = "nix-lockfile";
|
dontConfigure = true;
|
||||||
fileset = ../flake.lock;
|
dontBuild = true;
|
||||||
checkInputs = lib.attrValues { inherit (flake-inputs.flint.packages.x86_64-linux) flint; };
|
dontInstall = true;
|
||||||
|
dontFixup = true;
|
||||||
|
doCheck = true;
|
||||||
|
|
||||||
script = ''
|
checkInputs = nixpkgs.lib.singleton pkgs.nushell ++ packages;
|
||||||
flint --fail-if-multiple-versions
|
|
||||||
'';
|
checkPhase = ''
|
||||||
};
|
nu ${check}
|
||||||
}
|
'';
|
||||||
];
|
};
|
||||||
}
|
in
|
||||||
|
nixpkgs.lib.recursiveUpdate {
|
||||||
|
lints = runNuCheck {
|
||||||
|
name = "lints";
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
pkgs.deadnix
|
||||||
|
pkgs.nixfmt-rfc-style
|
||||||
|
pkgs.shellcheck
|
||||||
|
statix'
|
||||||
|
];
|
||||||
|
|
||||||
|
check = ./lints.nu;
|
||||||
|
};
|
||||||
|
} (deploy-rs.lib.${system}.deployChecks self.deploy)
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
{ pkgs, lib, ... }:
|
|
||||||
{
|
|
||||||
mkLint =
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
fileset,
|
|
||||||
checkInputs ? [ ],
|
|
||||||
script,
|
|
||||||
}:
|
|
||||||
pkgs.stdenvNoCC.mkDerivation {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
src = lib.fileset.toSource {
|
|
||||||
root = ../.;
|
|
||||||
fileset = lib.fileset.difference fileset (
|
|
||||||
lib.fileset.fileFilter (
|
|
||||||
file: file.type != "regular" || file.name == "hardware-configuration.nix"
|
|
||||||
) ../.
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
checkInputs = [ pkgs.nushell ] ++ checkInputs;
|
|
||||||
|
|
||||||
checkPhase = ''
|
|
||||||
nu -c '${script}' | tee $out
|
|
||||||
'';
|
|
||||||
|
|
||||||
dontPatch = true;
|
|
||||||
dontConfigure = true;
|
|
||||||
dontBuild = true;
|
|
||||||
dontInstall = true;
|
|
||||||
dontFixup = true;
|
|
||||||
doCheck = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
lib,
|
config,
|
||||||
modulesPath,
|
modulesPath,
|
||||||
flake-inputs,
|
flake-inputs,
|
||||||
...
|
...
|
||||||
|
|
@ -8,13 +8,32 @@
|
||||||
imports = [
|
imports = [
|
||||||
flake-inputs.disko.nixosModules.disko
|
flake-inputs.disko.nixosModules.disko
|
||||||
flake-inputs.sops-nix.nixosModules.sops
|
flake-inputs.sops-nix.nixosModules.sops
|
||||||
"${modulesPath}/profiles/minimal.nix"
|
flake-inputs.tlaternet-webserver.nixosModules.default
|
||||||
|
|
||||||
../modules
|
"${modulesPath}/profiles/minimal.nix"
|
||||||
./nginx
|
(import ../modules)
|
||||||
./services
|
|
||||||
|
./services/auth
|
||||||
|
./services/backups.nix
|
||||||
|
./services/battery-manager.nix
|
||||||
|
./services/conduit
|
||||||
|
./services/crowdsec.nix
|
||||||
|
./services/foundryvtt.nix
|
||||||
|
./services/gitea.nix
|
||||||
|
./services/immich.nix
|
||||||
|
./services/metrics
|
||||||
|
./services/minecraft.nix
|
||||||
|
./services/nextcloud.nix
|
||||||
|
./services/webserver.nix
|
||||||
|
./services/wireguard.nix
|
||||||
|
# ./services/starbound.nix -- Not currently used
|
||||||
|
./services/postgres.nix
|
||||||
|
./nginx.nix
|
||||||
|
./sops.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
nixpkgs.overlays = [ (_: prev: { local = import ../pkgs { pkgs = prev; }; }) ];
|
||||||
|
|
||||||
nix = {
|
nix = {
|
||||||
extraOptions = ''
|
extraOptions = ''
|
||||||
experimental-features = nix-command flakes
|
experimental-features = nix-command flakes
|
||||||
|
|
@ -24,9 +43,49 @@
|
||||||
settings.trusted-users = [ "@wheel" ];
|
settings.trusted-users = [ "@wheel" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Optimization for minecraft servers, see:
|
||||||
|
# https://bugs.mojang.com/browse/MC-183518
|
||||||
|
boot.kernelParams = [
|
||||||
|
"highres=off"
|
||||||
|
"nohz=off"
|
||||||
|
];
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
usePredictableInterfaceNames = false;
|
usePredictableInterfaceNames = false;
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
|
|
||||||
|
firewall = {
|
||||||
|
allowedTCPPorts = [
|
||||||
|
# http
|
||||||
|
80
|
||||||
|
443
|
||||||
|
# ssh
|
||||||
|
2222
|
||||||
|
# matrix
|
||||||
|
8448
|
||||||
|
# starbound
|
||||||
|
21025
|
||||||
|
|
||||||
|
config.services.coturn.listening-port
|
||||||
|
config.services.coturn.tls-listening-port
|
||||||
|
config.services.coturn.alt-listening-port
|
||||||
|
config.services.coturn.alt-tls-listening-port
|
||||||
|
];
|
||||||
|
|
||||||
|
allowedUDPPorts = [
|
||||||
|
config.services.coturn.listening-port
|
||||||
|
config.services.coturn.tls-listening-port
|
||||||
|
config.services.coturn.alt-listening-port
|
||||||
|
config.services.coturn.alt-tls-listening-port
|
||||||
|
];
|
||||||
|
|
||||||
|
allowedUDPPortRanges = [
|
||||||
|
{
|
||||||
|
from = config.services.coturn.min-port;
|
||||||
|
to = config.services.coturn.max-port;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.network.enable = true;
|
systemd.network.enable = true;
|
||||||
|
|
@ -66,10 +125,9 @@
|
||||||
services.sudo.rssh = true;
|
services.sudo.rssh = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sops.defaultSopsFile = ../keys/production.yaml;
|
|
||||||
|
|
||||||
# Remove some unneeded packages
|
# Remove some unneeded packages
|
||||||
environment.defaultPackages = lib.mkForce [ ];
|
environment.defaultPackages = [ ];
|
||||||
|
|
||||||
system.stateVersion = "20.09";
|
system.stateVersion = "20.09";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,20 +43,11 @@
|
||||||
source = ../../keys/hosts/staging.key;
|
source = ../../keys/hosts/staging.key;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Pretend the acme renew succeeds.
|
|
||||||
#
|
|
||||||
# TODO(tlater): Set up pebble to retrieve certs "properly"
|
|
||||||
# instead
|
|
||||||
systemd.services."acme-order-renew-tlater.net".script = ''
|
|
||||||
touch out/acme-success
|
|
||||||
'';
|
|
||||||
|
|
||||||
virtualisation.vmVariant = {
|
virtualisation.vmVariant = {
|
||||||
virtualisation = {
|
virtualisation = {
|
||||||
memorySize = 3941;
|
memorySize = 3941;
|
||||||
cores = 2;
|
cores = 2;
|
||||||
graphics = false;
|
graphics = false;
|
||||||
diskSize = 1024 * 20;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
virtualisation.qemu = {
|
virtualisation.qemu = {
|
||||||
|
|
|
||||||
77
configuration/nginx.nix
Normal file
77
configuration/nginx.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
{
|
||||||
|
services = {
|
||||||
|
nginx = {
|
||||||
|
enable = true;
|
||||||
|
recommendedTlsSettings = true;
|
||||||
|
recommendedOptimisation = true;
|
||||||
|
recommendedGzipSettings = true;
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
clientMaxBodySize = "10G";
|
||||||
|
|
||||||
|
statusPage = true; # For metrics, should be accessible only from localhost
|
||||||
|
|
||||||
|
commonHttpConfig = ''
|
||||||
|
log_format upstream_time '$remote_addr - $remote_user [$time_local] '
|
||||||
|
'"$request" $status $body_bytes_sent '
|
||||||
|
'"$http_referer" "$http_user_agent" '
|
||||||
|
'rt=$request_time uct="$upstream_connect_time" '
|
||||||
|
'uht="$upstream_header_time" urt="$upstream_response_time"';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
logrotate.settings = {
|
||||||
|
# Override the default, just keep fewer logs
|
||||||
|
nginx.rotate = 6;
|
||||||
|
}
|
||||||
|
// lib.mapAttrs' (
|
||||||
|
virtualHost: _:
|
||||||
|
lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
|
||||||
|
frequency = "daily";
|
||||||
|
rotate = 2;
|
||||||
|
compress = true;
|
||||||
|
delaycompress = true;
|
||||||
|
su = "${config.services.nginx.user} ${config.services.nginx.group}";
|
||||||
|
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
|
||||||
|
}
|
||||||
|
) config.services.nginx.virtualHosts;
|
||||||
|
|
||||||
|
backups.acme = {
|
||||||
|
user = "acme";
|
||||||
|
paths = lib.mapAttrsToList (
|
||||||
|
virtualHost: _: "/var/lib/acme/${virtualHost}"
|
||||||
|
) config.services.nginx.virtualHosts;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = lib.mapAttrsToList (
|
||||||
|
virtualHost: _:
|
||||||
|
#
|
||||||
|
"d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
|
||||||
|
) config.services.nginx.virtualHosts;
|
||||||
|
|
||||||
|
security.acme = {
|
||||||
|
defaults.email = "tm@tlater.net";
|
||||||
|
acceptTerms = true;
|
||||||
|
|
||||||
|
certs."tlater.net" = {
|
||||||
|
extraDomainNames = [
|
||||||
|
"*.tlater.net"
|
||||||
|
"tlater.com"
|
||||||
|
"*.tlater.com"
|
||||||
|
];
|
||||||
|
dnsProvider = "porkbun";
|
||||||
|
group = "ssl-cert";
|
||||||
|
credentialFiles = {
|
||||||
|
PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
|
||||||
|
PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.ssl-cert = { };
|
||||||
|
|
||||||
|
systemd.services.nginx.serviceConfig.SupplementaryGroups = [
|
||||||
|
config.security.acme.certs."tlater.net".group
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./logging.nix
|
|
||||||
./ssl.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
options.services.nginx.domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The base domain name to append to virtual domain names";
|
|
||||||
};
|
|
||||||
|
|
||||||
config.services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
recommendedTlsSettings = true;
|
|
||||||
recommendedOptimisation = true;
|
|
||||||
recommendedGzipSettings = true;
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
clientMaxBodySize = "10G";
|
|
||||||
statusPage = true; # For metrics, should be accessible only from localhost
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
{
|
|
||||||
flake-inputs,
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
hostNames = lib.attrNames config.services.nginx.virtualHosts;
|
|
||||||
logPath = name: "/var/log/nginx/${name}/access.log";
|
|
||||||
logFormat = lib.concatStringsSep " " [
|
|
||||||
"$remote_addr - $remote_user [$time_local]"
|
|
||||||
''"$request" $status $body_bytes_sent''
|
|
||||||
''"$http_referer" "$http_user_agent"''
|
|
||||||
''rt=$request_time uct="$upstream_connect_time"''
|
|
||||||
''uht="$upstream_header_time" urt="$upstream_response_time"''
|
|
||||||
];
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# Extend the default configuration for nginx virtual hosts; we'd
|
|
||||||
# like to create log files for each of them, so that the prometheus
|
|
||||||
# nginxlog exporter can process per-host logs.
|
|
||||||
options.services.nginx.virtualHosts = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
config.extraConfig = ''
|
|
||||||
access_log ${logPath name} upstream_time;
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
# Create directories for host-specific logs with systemd tmpfiles
|
|
||||||
systemd.tmpfiles.settings."10-nginx-logs" = lib.listToAttrs (
|
|
||||||
map (
|
|
||||||
name:
|
|
||||||
lib.nameValuePair "/var/log/nginx/${name}" {
|
|
||||||
d = {
|
|
||||||
inherit (config.services.nginx) user group;
|
|
||||||
mode = "0750";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
) hostNames
|
|
||||||
);
|
|
||||||
|
|
||||||
services = {
|
|
||||||
# Set the nginx-wide log format
|
|
||||||
nginx.commonHttpConfig = ''
|
|
||||||
log_format upstream_time '${logFormat}';
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Set up nginxlog to read the file and log format defined above
|
|
||||||
# for each virtual host
|
|
||||||
prometheus.exporters.nginxlog = {
|
|
||||||
enable = true;
|
|
||||||
listenAddress = "127.0.0.1";
|
|
||||||
group = "nginx";
|
|
||||||
|
|
||||||
settings.namespaces = map (name: {
|
|
||||||
inherit name;
|
|
||||||
metrics_override.prefix = "nginxlog";
|
|
||||||
namespace_label = "vhost";
|
|
||||||
format = logFormat;
|
|
||||||
source.files = [ (logPath name) ];
|
|
||||||
}) hostNames;
|
|
||||||
};
|
|
||||||
|
|
||||||
logrotate.settings = {
|
|
||||||
# Override the nginx module default, just keep fewer logs
|
|
||||||
nginx.rotate = 6;
|
|
||||||
|
|
||||||
# Configure logrotate for host-specific logs
|
|
||||||
nginxVirtualHosts = {
|
|
||||||
files = map logPath hostNames;
|
|
||||||
|
|
||||||
frequency = "daily";
|
|
||||||
rotate = 2;
|
|
||||||
compress = true;
|
|
||||||
delaycompress = true;
|
|
||||||
su = "${config.services.nginx.user} ${config.services.nginx.group}";
|
|
||||||
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceTests =
|
|
||||||
let
|
|
||||||
testHostConfig =
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./.
|
|
||||||
../../modules/serviceTests/mocks.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
domain = "testHost";
|
|
||||||
virtualHosts."${config.services.nginx.domain}".locations."/".return = "200 ok";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
nginxMetricsWork = pkgs.testers.runNixOSTest {
|
|
||||||
name = "nginx-metrics-work";
|
|
||||||
node.specialArgs = { inherit flake-inputs; };
|
|
||||||
|
|
||||||
nodes = {
|
|
||||||
testHost = testHostConfig;
|
|
||||||
|
|
||||||
client =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
environment.systemPackages = [ pkgs.curl ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
import time
|
|
||||||
|
|
||||||
start_all()
|
|
||||||
|
|
||||||
testHost.wait_for_unit("nginx.service")
|
|
||||||
client.succeed("curl --max-time 10 http://testHost")
|
|
||||||
|
|
||||||
# Wait a bit for the prometheus exporter to scrape our logs
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
res = testHost.succeed("curl localhost:${builtins.toString config.services.prometheus.exporters.nginxlog.port}/metrics")
|
|
||||||
assert 'nginxlog_http_response_count_total{method="GET",status="200",vhost="testHost"} 1' in res, res
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
{
|
|
||||||
flake-inputs,
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
# Add a custom per-host option to enable HSTS
|
|
||||||
services.nginx.virtualHosts = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
options.enableHSTS = lib.mkEnableOption "HSTS";
|
|
||||||
config.extraConfig = lib.mkIf config.enableHSTS ''
|
|
||||||
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
# Certificate settings
|
|
||||||
security.acme = {
|
|
||||||
defaults.email = "tm@tlater.net";
|
|
||||||
acceptTerms = true;
|
|
||||||
|
|
||||||
certs."tlater.net" = {
|
|
||||||
extraDomainNames = [
|
|
||||||
"*.tlater.net"
|
|
||||||
"tlater.com"
|
|
||||||
"*.tlater.com"
|
|
||||||
];
|
|
||||||
dnsProvider = "porkbun";
|
|
||||||
group = config.users.groups.ssl-cert.name;
|
|
||||||
credentialFiles = {
|
|
||||||
PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
|
|
||||||
PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
users.groups.ssl-cert = { };
|
|
||||||
|
|
||||||
# Back up the SSL certificate, just in case
|
|
||||||
services.backups.acme = {
|
|
||||||
user = "acme";
|
|
||||||
paths = [ "/var/lib/acme/tlater.net" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.nginx.serviceConfig.SupplementaryGroups = [
|
|
||||||
config.security.acme.certs."tlater.net".group
|
|
||||||
];
|
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
"porkbun/api-key".owner = "acme";
|
|
||||||
"porkbun/secret-api-key".owner = "acme";
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceTests =
|
|
||||||
let
|
|
||||||
testHostConfig =
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./.
|
|
||||||
../../modules/serviceTests/mocks.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 443 ];
|
|
||||||
|
|
||||||
security.acme.certs."tlater.net".extraDomainNames = [ config.services.nginx.domain ];
|
|
||||||
|
|
||||||
# Pretend the acme renew succeeds.
|
|
||||||
#
|
|
||||||
# TODO(tlater): Set up pebble to retrieve certs "properly"
|
|
||||||
# instead
|
|
||||||
systemd.services."acme-order-renew-tlater.net".script = ''
|
|
||||||
touch out/acme-success
|
|
||||||
'';
|
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
domain = "testHost.test";
|
|
||||||
|
|
||||||
virtualHosts."${config.services.nginx.domain}.local" = {
|
|
||||||
useACMEHost = "tlater.net";
|
|
||||||
onlySSL = true;
|
|
||||||
enableHSTS = true;
|
|
||||||
locations."/".return = "200 ok";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
testNginxSSL = pkgs.testers.runNixOSTest {
|
|
||||||
name = "test-nginx-ssl";
|
|
||||||
|
|
||||||
node.specialArgs = { inherit flake-inputs; };
|
|
||||||
nodes = {
|
|
||||||
testHost = testHostConfig;
|
|
||||||
|
|
||||||
client =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
environment.systemPackages = [ pkgs.curl ];
|
|
||||||
networking.hosts."192.168.1.2" = [ "testHost.test" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
|
|
||||||
testHost.wait_for_unit("nginx.service")
|
|
||||||
testHost.copy_from_vm("/var/lib/acme/tlater.net/", "certs")
|
|
||||||
client.copy_from_host(f"{testHost.out_dir}/certs", "/certs")
|
|
||||||
|
|
||||||
res = client.succeed(" ".join([
|
|
||||||
"curl",
|
|
||||||
"--show-error",
|
|
||||||
"--silent",
|
|
||||||
"--dump-header -",
|
|
||||||
"--cacert /certs/tlater.net/fullchain.pem",
|
|
||||||
"https://testHost.test",
|
|
||||||
"-o /dev/null"
|
|
||||||
]))
|
|
||||||
|
|
||||||
assert "strict-transport-security: max-age=15552000; includeSubDomains" in res
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
87
configuration/services/auth/authelia.nix
Normal file
87
configuration/services/auth/authelia.nix
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
{ config, ... }:
|
||||||
|
let
|
||||||
|
instanceName = config.services.authelia.instances.main.name;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.authelia.instances.main = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
theme = "auto";
|
||||||
|
default_2fa_method = "totp";
|
||||||
|
|
||||||
|
authentication_backend = {
|
||||||
|
password_reset.disable = true;
|
||||||
|
password_change.disable = true;
|
||||||
|
|
||||||
|
file = {
|
||||||
|
inherit (config.sops.secrets."authelia/users") path;
|
||||||
|
|
||||||
|
search = {
|
||||||
|
email = true;
|
||||||
|
case_insensitive = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
storage.postgres = {
|
||||||
|
address = "unix:///run/postgresql";
|
||||||
|
database = "authelia";
|
||||||
|
username = "authelia";
|
||||||
|
};
|
||||||
|
|
||||||
|
session.cookies = [
|
||||||
|
{
|
||||||
|
domain = config.services.nginx.domain;
|
||||||
|
authelia_url = "https://auth.${config.services.nginx.domain}";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
notifier.filesystem.filename = ''{{ env "RUNTIME_DIRECTORY" }}/authelia-notifications'';
|
||||||
|
|
||||||
|
access_control = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
server = {
|
||||||
|
# Maybe a systemd socket can be used for this in the future,
|
||||||
|
# see:
|
||||||
|
# https://github.com/systemd/systemd/issues/23067#issuecomment-1212232155
|
||||||
|
address = "unix://${config.systemd.sockets."authelia-${instanceName}".socketConfig.ListenStream}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
secrets = {
|
||||||
|
jwtSecretFile = config.sops.secrets."authelia/jwt".path;
|
||||||
|
storageEncryptionKeyFile = config.sops.secrets."authelia/storage".path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.sockets."authelia-${instanceName}" = {
|
||||||
|
socketConfig = {
|
||||||
|
Accept = false;
|
||||||
|
ListenStream = "/var/run/authelia.sock";
|
||||||
|
SocketGroup = "authelia";
|
||||||
|
SocketMode = "0660";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."authelia-${instanceName}" = {
|
||||||
|
requires = [ "authelia-${instanceName}.socket" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
RuntimeDirectory = "authelia-${instanceName}";
|
||||||
|
SupplementaryGroups = [ "authelia" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO: Need to map these to systemd creds to pass them into the
|
||||||
|
# service because user permissions
|
||||||
|
sops.secrets = {
|
||||||
|
"authelia/users" = { };
|
||||||
|
"authelia/jwt" = { };
|
||||||
|
"authelia/storage" = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.authelia = { };
|
||||||
|
}
|
||||||
5
configuration/services/auth/default.nix
Normal file
5
configuration/services/auth/default.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./authelia.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -265,18 +265,5 @@ in
|
||||||
};
|
};
|
||||||
groups.backup = { };
|
groups.backup = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
"restic/storagebox-backups" = {
|
|
||||||
owner = "root";
|
|
||||||
group = "backup";
|
|
||||||
mode = "0440";
|
|
||||||
};
|
|
||||||
"restic/storagebox-ssh-key" = {
|
|
||||||
owner = "backup";
|
|
||||||
group = "backup";
|
|
||||||
mode = "0040";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,4 @@
|
||||||
log_level = "DEBUG";
|
log_level = "DEBUG";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
"battery-manager/email" = { };
|
|
||||||
"battery-manager/password" = { };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,37 +12,10 @@ let
|
||||||
turn-realm = "turn.${config.services.nginx.domain}";
|
turn-realm = "turn.${config.services.nginx.domain}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [ ./heisenbridge.nix ];
|
imports = [
|
||||||
|
./heisenbridge.nix
|
||||||
networking.firewall = {
|
./matrix-hookshot.nix
|
||||||
allowedTCPPorts = [
|
];
|
||||||
# These are for "normal" clients
|
|
||||||
80
|
|
||||||
443
|
|
||||||
|
|
||||||
# Federation happens on 8448
|
|
||||||
8448
|
|
||||||
|
|
||||||
config.services.coturn.listening-port
|
|
||||||
config.services.coturn.tls-listening-port
|
|
||||||
config.services.coturn.alt-listening-port
|
|
||||||
config.services.coturn.alt-tls-listening-port
|
|
||||||
];
|
|
||||||
|
|
||||||
allowedUDPPorts = [
|
|
||||||
config.services.coturn.listening-port
|
|
||||||
config.services.coturn.tls-listening-port
|
|
||||||
config.services.coturn.alt-listening-port
|
|
||||||
config.services.coturn.alt-tls-listening-port
|
|
||||||
];
|
|
||||||
|
|
||||||
allowedUDPPortRanges = [
|
|
||||||
{
|
|
||||||
from = config.services.coturn.min-port;
|
|
||||||
to = config.services.coturn.max-port;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
matrix-conduit = {
|
matrix-conduit = {
|
||||||
|
|
@ -206,11 +179,4 @@ in
|
||||||
systemd.services.coturn.serviceConfig.SupplementaryGroups = [
|
systemd.services.coturn.serviceConfig.SupplementaryGroups = [
|
||||||
config.security.acme.certs."tlater.net".group
|
config.security.acme.certs."tlater.net".group
|
||||||
];
|
];
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
"turn/env" = { };
|
|
||||||
"turn/secret" = {
|
|
||||||
owner = "turnserver";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,4 @@ in
|
||||||
# AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
# AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
# Accessed via systemd cred through /run/secrets/heisebridge
|
|
||||||
"heisenbridge/as-token" = { };
|
|
||||||
"heisenbridge/hs-token" = { };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
166
configuration/services/conduit/matrix-hookshot.nix
Normal file
166
configuration/services/conduit/matrix-hookshot.nix
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
matrixLib = pkgs.callPackage ./lib.nix { };
|
||||||
|
|
||||||
|
cfg = config.services.matrix-hookshot;
|
||||||
|
conduitCfg = config.services.matrix-conduit;
|
||||||
|
|
||||||
|
domain = conduitCfg.settings.global.server_name;
|
||||||
|
|
||||||
|
registration = matrixLib.writeRegistrationScript {
|
||||||
|
id = "matrix-hookshot";
|
||||||
|
url = "http://127.0.0.1:9993";
|
||||||
|
sender_localpart = "hookshot";
|
||||||
|
|
||||||
|
namespaces = {
|
||||||
|
aliases = [ ];
|
||||||
|
rooms = [ ];
|
||||||
|
users = [
|
||||||
|
{
|
||||||
|
regex = "@${cfg.settings.generic.userIdPrefix}.*:${domain}";
|
||||||
|
exclusive = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Encryption support
|
||||||
|
# TODO(tlater): Enable when
|
||||||
|
# https://github.com/matrix-org/matrix-hookshot/issues/1060 is
|
||||||
|
# fixed
|
||||||
|
# extraSettings = {
|
||||||
|
# "de.sorunome.msc2409.push_ephemeral" = true;
|
||||||
|
# push_ephemeral = true;
|
||||||
|
# "org.matrix.msc3202" = true;
|
||||||
|
# };
|
||||||
|
|
||||||
|
runtimeRegistration = "${cfg.registrationFile}";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# users = {
|
||||||
|
# users.matrix-hookshot = {
|
||||||
|
# home = "/run/matrix-hookshot";
|
||||||
|
# group = "matrix-hookshot";
|
||||||
|
# isSystemUser = true;
|
||||||
|
# };
|
||||||
|
|
||||||
|
# groups.matrix-hookshot = { };
|
||||||
|
# };
|
||||||
|
|
||||||
|
systemd.services.matrix-hookshot = {
|
||||||
|
serviceConfig = {
|
||||||
|
Type = lib.mkForce "exec";
|
||||||
|
|
||||||
|
LoadCredential = "matrix-hookshot:/run/secrets/matrix-hookshot";
|
||||||
|
inherit (registration) ExecStartPre;
|
||||||
|
|
||||||
|
# Some library in matrix-hookshot wants a home directory
|
||||||
|
Environment = [ "HOME=/run/matrix-hookshot" ];
|
||||||
|
|
||||||
|
# User = "matrix-hookshot";
|
||||||
|
DynamicUser = true;
|
||||||
|
StateDirectory = "matrix-hookshot";
|
||||||
|
RuntimeDirectory = "matrix-hookshot";
|
||||||
|
RuntimeDirectoryMode = "0700";
|
||||||
|
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictAddressFamilies = [
|
||||||
|
# "AF_UNIX"
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
];
|
||||||
|
LockPersonality = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
ProcSubset = "pid";
|
||||||
|
UMask = 77;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# services.redis.servers.matrix-hookshot = {
|
||||||
|
# enable = true;
|
||||||
|
# user = "matrix-hookshot";
|
||||||
|
# };
|
||||||
|
|
||||||
|
services.matrix-hookshot = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
serviceDependencies = [ "conduit.service" ];
|
||||||
|
|
||||||
|
registrationFile = "/run/matrix-hookshot/registration.yaml";
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
bridge = {
|
||||||
|
inherit domain;
|
||||||
|
url = "http://localhost:${toString conduitCfg.settings.global.port}";
|
||||||
|
mediaUrl = conduitCfg.settings.global.well_known.client;
|
||||||
|
port = 9993;
|
||||||
|
bindAddress = "127.0.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
|
bot.displayname = "Hookshot";
|
||||||
|
|
||||||
|
# cache.redisUri = "redis://${config.services.redis.servers.matrix-hookshot.unixSocket}";
|
||||||
|
|
||||||
|
generic = {
|
||||||
|
enabled = true;
|
||||||
|
outbound = false;
|
||||||
|
# Only allow webhooks from localhost for the moment
|
||||||
|
urlPrefix = "http://127.0.0.1:9000/webhook";
|
||||||
|
userIdPrefix = "_webhooks_";
|
||||||
|
allowJsTransformationFunctions = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO(tlater): Enable when
|
||||||
|
# https://github.com/matrix-org/matrix-hookshot/issues/1060 is
|
||||||
|
# fixed
|
||||||
|
# encryption.storagePath = "/var/lib/matrix-hookshot/cryptostore";
|
||||||
|
|
||||||
|
permissions = [
|
||||||
|
{
|
||||||
|
actor = "matrix.tlater.net";
|
||||||
|
services = [
|
||||||
|
{
|
||||||
|
service = "*";
|
||||||
|
level = "notifications";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
actor = "@tlater:matrix.tlater.net";
|
||||||
|
services = [
|
||||||
|
{
|
||||||
|
service = "*";
|
||||||
|
level = "admin";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
listeners = [
|
||||||
|
{
|
||||||
|
port = 9000;
|
||||||
|
resources = [ "webhooks" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
port = 9001;
|
||||||
|
resources = [ "metrics" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
metrics.enabled = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./backups.nix
|
|
||||||
./battery-manager.nix
|
|
||||||
./conduit
|
|
||||||
./crowdsec.nix
|
|
||||||
./foundryvtt.nix
|
|
||||||
./gitea.nix
|
|
||||||
./immich.nix
|
|
||||||
./metrics
|
|
||||||
./ntfy-sh
|
|
||||||
./minecraft.nix
|
|
||||||
./nextcloud.nix
|
|
||||||
./postgres.nix
|
|
||||||
# ./starbound.nix -- Not currently used
|
|
||||||
./webserver.nix
|
|
||||||
./wireguard.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -11,11 +11,6 @@ in
|
||||||
{
|
{
|
||||||
imports = [ flake-inputs.foundryvtt.nixosModules.foundryvtt ];
|
imports = [ flake-inputs.foundryvtt.nixosModules.foundryvtt ];
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
foundryvtt = {
|
foundryvtt = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -23,7 +18,7 @@ in
|
||||||
minifyStaticFiles = true;
|
minifyStaticFiles = true;
|
||||||
proxySSL = true;
|
proxySSL = true;
|
||||||
proxyPort = 443;
|
proxyPort = 443;
|
||||||
package = flake-inputs.foundryvtt.packages.${pkgs.stdenv.hostPlatform.system}.foundryvtt_13;
|
package = flake-inputs.foundryvtt.packages.${pkgs.system}.foundryvtt_13;
|
||||||
};
|
};
|
||||||
|
|
||||||
nginx.virtualHosts."${domain}" =
|
nginx.virtualHosts."${domain}" =
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,6 @@ let
|
||||||
domain = "gitea.${config.services.nginx.domain}";
|
domain = "gitea.${config.services.nginx.domain}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
forgejo = {
|
forgejo = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,11 @@ let
|
||||||
hostName = "immich.${config.services.nginx.domain}";
|
hostName = "immich.${config.services.nginx.domain}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
immich = {
|
immich = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.server.externalDomain = "https://${hostName}";
|
settings.server.externalDomain = "https://${hostName}";
|
||||||
|
|
||||||
# We're using vectorchord now
|
|
||||||
database.enableVectors = false;
|
|
||||||
|
|
||||||
environment.IMMICH_TELEMETRY_INCLUDE = "all";
|
environment.IMMICH_TELEMETRY_INCLUDE = "all";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
yaml = pkgs.formats.yaml { };
|
yaml = pkgs.formats.yaml { };
|
||||||
in
|
in
|
||||||
|
|
@ -63,6 +68,28 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
listenAddress = "127.0.0.1";
|
listenAddress = "127.0.0.1";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nginxlog = {
|
||||||
|
enable = true;
|
||||||
|
listenAddress = "127.0.0.1";
|
||||||
|
group = "nginx";
|
||||||
|
|
||||||
|
settings.namespaces = lib.mapAttrsToList (name: _: {
|
||||||
|
inherit name;
|
||||||
|
metrics_override.prefix = "nginxlog";
|
||||||
|
namespace_label = "vhost";
|
||||||
|
|
||||||
|
format = lib.concatStringsSep " " [
|
||||||
|
"$remote_addr - $remote_user [$time_local]"
|
||||||
|
''"$request" $status $body_bytes_sent''
|
||||||
|
''"$http_referer" "$http_user_agent"''
|
||||||
|
''rt=$request_time uct="$upstream_connect_time"''
|
||||||
|
''uht="$upstream_header_time" urt="$upstream_response_time"''
|
||||||
|
];
|
||||||
|
|
||||||
|
source.files = [ "/var/log/nginx/${name}/access.log" ];
|
||||||
|
}) config.services.nginx.virtualHosts;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO(tlater):
|
# TODO(tlater):
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,6 @@ let
|
||||||
domain = "metrics.${config.services.nginx.domain}";
|
domain = "metrics.${config.services.nginx.domain}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services.grafana = {
|
services.grafana = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
|
|
@ -57,19 +52,6 @@ in
|
||||||
access = "proxy";
|
access = "proxy";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
alerting.contactPoints.settings.contactPoints = [
|
|
||||||
{
|
|
||||||
name = "ntfy";
|
|
||||||
receivers = [
|
|
||||||
{
|
|
||||||
uid = "ntfy";
|
|
||||||
type = "webhook";
|
|
||||||
settings.url = "http://${config.services.ntfy-sh.settings.listen-http}/local-alerts?template=grafana";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -85,15 +67,4 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
"grafana/adminPassword" = {
|
|
||||||
owner = "grafana";
|
|
||||||
group = "grafana";
|
|
||||||
};
|
|
||||||
"grafana/secretKey" = {
|
|
||||||
owner = "grafana";
|
|
||||||
group = "grafana";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ let
|
||||||
blackbox_port = config.services.prometheus.exporters.blackbox.port;
|
blackbox_port = config.services.prometheus.exporters.blackbox.port;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
services.victoriametrics = {
|
config.services.victoriametrics = {
|
||||||
enable = true;
|
enable = true;
|
||||||
extraOptions = [ "-storage.minFreeDiskSpaceBytes=5GB" ];
|
extraOptions = [ "-storage.minFreeDiskSpaceBytes=5GB" ];
|
||||||
|
|
||||||
|
|
@ -89,13 +89,11 @@ in
|
||||||
"127.0.0.1:8082"
|
"127.0.0.1:8082"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Configured in the hookshot listeners, but it's hard to filter
|
||||||
|
# the correct values out of that config.
|
||||||
|
matrixHookshot.targets = [ "127.0.0.1:9001" ];
|
||||||
|
|
||||||
victorialogs.targets = [ config.services.victorialogs.bindAddress ];
|
victorialogs.targets = [ config.services.victorialogs.bindAddress ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets."forgejo/metrics-token" = {
|
|
||||||
owner = "forgejo";
|
|
||||||
group = "metrics";
|
|
||||||
mode = "0440";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,10 @@
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
nextcloud = pkgs.nextcloud32;
|
nextcloud = pkgs.nextcloud31;
|
||||||
hostName = "nextcloud.${config.services.nginx.domain}";
|
hostName = "nextcloud.${config.services.nginx.domain}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
nextcloud = {
|
nextcloud = {
|
||||||
inherit hostName;
|
inherit hostName;
|
||||||
|
|
@ -104,10 +99,5 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
# Ensure that this service doesn't start before postgres is ready
|
# Ensure that this service doesn't start before postgres is ready
|
||||||
systemd.services.nextcloud-setup.after = [ "postgresql.target" ];
|
systemd.services.nextcloud-setup.after = [ "postgresql.service" ];
|
||||||
|
|
||||||
sops.secrets."nextcloud/tlater" = {
|
|
||||||
owner = "nextcloud";
|
|
||||||
group = "nextcloud";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
flake-inputs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
domain = "ntfy.${config.services.nginx.domain}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ./downstream-module.nix ];
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services.ntfy-sh = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
environmentFile = config.sops.secrets."ntfy/users".path;
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
base-url = "https://${domain}";
|
|
||||||
listen-http = "127.0.0.1:2586";
|
|
||||||
behind-proxy = true;
|
|
||||||
|
|
||||||
# Paths
|
|
||||||
attachment-cache-dir = "/var/lib/ntfy-sh/attachments";
|
|
||||||
cache-file = "/var/lib/ntfy-sh/cache-file.db";
|
|
||||||
auth-file = "/var/lib/ntfy-sh/user.db";
|
|
||||||
auth-default-access = "deny-all";
|
|
||||||
auth-access = [ "*:local-*:wo" ];
|
|
||||||
|
|
||||||
# Don't want to host the front-end
|
|
||||||
web-root = "disable";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."ntfy.${config.services.nginx.domain}" = {
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = "tlater.net";
|
|
||||||
enableHSTS = true;
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyWebsockets = true;
|
|
||||||
proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
|
|
||||||
extraConfig = ''
|
|
||||||
client_max_body_size 0; # Stream request body to backend
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Don't allow writing to topics with the local prefix, *including*
|
|
||||||
# webhook writes, since they are set to write-only access from
|
|
||||||
# anyone.
|
|
||||||
locations."/local" = {
|
|
||||||
proxyWebsockets = true;
|
|
||||||
proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
|
|
||||||
extraConfig = ''
|
|
||||||
client_max_body_size 0; # Stream request body to backend
|
|
||||||
|
|
||||||
limit_except GET OPTIONS {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /trigger$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
sops.secrets."ntfy/users" = { };
|
|
||||||
|
|
||||||
serviceTests = {
|
|
||||||
testNtfyConfig = pkgs.testers.runNixOSTest {
|
|
||||||
name = "test-ntfy-config";
|
|
||||||
|
|
||||||
node.specialArgs = { inherit flake-inputs; };
|
|
||||||
nodes = {
|
|
||||||
testHost =
|
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./.
|
|
||||||
../../nginx
|
|
||||||
../../../modules/serviceTests/mocks.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
services.nginx.domain = "testHost";
|
|
||||||
|
|
||||||
# Don't care for testing SSL here
|
|
||||||
services.nginx.virtualHosts."ntfy.testHost" = {
|
|
||||||
forceSSL = lib.mkForce false;
|
|
||||||
enableHSTS = lib.mkForce false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
client =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
environment.systemPackages = [ pkgs.curl ];
|
|
||||||
networking.hosts."192.168.1.2" = [ "ntfy.testHost" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
def read_client_messages():
|
|
||||||
client.wait_for_unit("messages.service")
|
|
||||||
messages = [json.loads(line) for line in client.succeed("cat messages").split()]
|
|
||||||
client.succeed("systemctl stop messages.service")
|
|
||||||
client.succeed("rm messages")
|
|
||||||
|
|
||||||
print(messages)
|
|
||||||
|
|
||||||
return messages
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def client_subscribe(topic: str, timeout: int = 2):
|
|
||||||
systemd_invocation = [
|
|
||||||
"systemd-run",
|
|
||||||
"--unit messages",
|
|
||||||
"--property=Type=oneshot",
|
|
||||||
"--property=SuccessExitStatus=28",
|
|
||||||
"--remain-after-exit",
|
|
||||||
"--setenv=PATH=$PATH",
|
|
||||||
"--same-dir",
|
|
||||||
"--no-block",
|
|
||||||
"/bin/sh -c"
|
|
||||||
]
|
|
||||||
|
|
||||||
curl = [
|
|
||||||
"curl",
|
|
||||||
"--silent",
|
|
||||||
"--show-error",
|
|
||||||
f"--max-time {timeout}",
|
|
||||||
"-u tlater:insecure",
|
|
||||||
f"http://ntfy.testHost/{topic}/json",
|
|
||||||
"> messages"
|
|
||||||
]
|
|
||||||
|
|
||||||
client.succeed(f'{" ".join(systemd_invocation)} "{" ".join(curl)}"')
|
|
||||||
|
|
||||||
# Give some slack so the host doesn't send messages before
|
|
||||||
# we're listening
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
start_all()
|
|
||||||
testHost.wait_for_unit("ntfy-sh.service")
|
|
||||||
client.wait_until_succeeds("curl http://ntfy.testHost")
|
|
||||||
|
|
||||||
with subtest("subscribing and writing to local topics works"):
|
|
||||||
with client_subscribe("local-test"):
|
|
||||||
testHost.succeed("curl --fail --silent --show-error --data test http://127.0.0.1:2586/local-test")
|
|
||||||
|
|
||||||
messages = read_client_messages()
|
|
||||||
t.assertEqual(len(messages), 2)
|
|
||||||
t.assertEqual(messages[1].get("message"), "test")
|
|
||||||
|
|
||||||
with subtest("writing to non-local topics without auth fails"):
|
|
||||||
testHost.fail("curl --fail --silent --show-error --data test http://127.0.0.1:2586/test")
|
|
||||||
|
|
||||||
with subtest("writing to *any* topics from outside localhost fails"):
|
|
||||||
client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/test")
|
|
||||||
client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/local-test")
|
|
||||||
# GET requests work by default because websocket shenanigans
|
|
||||||
client.fail("curl --fail --silent --show-error http://ntfy.testHost/local-test/trigger?message=test")
|
|
||||||
|
|
||||||
with subtest("authenticated messaging works from outside localhost"):
|
|
||||||
with client_subscribe("test", 10):
|
|
||||||
client.succeed("curl -u tlater:insecure --fail --silent --show-error --data test http://ntfy.testHost/test")
|
|
||||||
client.succeed("curl -u tlater:insecure --fail --silent --show-error http://ntfy.testHost/test/trigger?message=test2")
|
|
||||||
|
|
||||||
messages = read_client_messages()
|
|
||||||
t.assertEqual(len(messages), 3)
|
|
||||||
t.assertEqual(messages[1].get("message"), "test")
|
|
||||||
t.assertEqual(messages[2].get("message"), "test2")
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.services.ntfy-sh;
|
|
||||||
settingsFormat = pkgs.formats.yaml { };
|
|
||||||
configFile = settingsFormat.generate "server.yml" cfg.settings;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# We don't use the upstream module because it's stupid; the author
|
|
||||||
# doesn't seem to understand `DynamicUser` (or at least be unaware of
|
|
||||||
# systemd credentials).
|
|
||||||
disabledModules = [ "services/misc/ntfy-sh.nix" ];
|
|
||||||
|
|
||||||
options.services.ntfy-sh = {
|
|
||||||
enable = lib.mkEnableOption "[ntfy-sh](https://ntfy.sh), a push notification service";
|
|
||||||
package = lib.mkPackageOption pkgs "ntfy-sh" { };
|
|
||||||
environmentFile = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.path;
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
Environment file; intended to be used for user provisioning.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = lib.mkOption {
|
|
||||||
inherit (settingsFormat) type;
|
|
||||||
default = { };
|
|
||||||
description = ''
|
|
||||||
Configuration for ntfy.sh, supported values are [here](https://ntfy.sh/docs/config/#config-options).
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config.systemd.services.ntfy-sh = {
|
|
||||||
description = "Push notifications server";
|
|
||||||
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "network.target" ];
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "exec";
|
|
||||||
ExecReload = "kill --signal HUP $MAINPID";
|
|
||||||
ExecStart = "${lib.getExe' cfg.package "ntfy"} serve -c ${configFile}";
|
|
||||||
|
|
||||||
EnvironmentFile = cfg.environmentFile;
|
|
||||||
|
|
||||||
StateDirectory = "ntfy-sh";
|
|
||||||
|
|
||||||
DynamicUser = true;
|
|
||||||
PrivateTmp = true;
|
|
||||||
NoNewPrivileges = true;
|
|
||||||
ProtectSystem = "full";
|
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectKernelModules = true;
|
|
||||||
ProtectKernelLogs = true;
|
|
||||||
ProtectControlGroups = true;
|
|
||||||
PrivateDevices = true;
|
|
||||||
RestrictSUIDSGID = true;
|
|
||||||
RestrictNamespaces = true;
|
|
||||||
RestrictRealtime = true;
|
|
||||||
MemoryDenyWriteExecute = true;
|
|
||||||
# Upstream Recommandation
|
|
||||||
LimitNOFILE = 20500;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -17,6 +17,10 @@
|
||||||
# that operation needs to be performed manually on the system as
|
# that operation needs to be performed manually on the system as
|
||||||
# well.
|
# well.
|
||||||
ensureUsers = [
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = "authelia";
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name = "grafana";
|
name = "grafana";
|
||||||
ensureDBOwnership = true;
|
ensureDBOwnership = true;
|
||||||
|
|
@ -28,6 +32,7 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
ensureDatabases = [
|
ensureDatabases = [
|
||||||
|
"authelia"
|
||||||
"grafana"
|
"grafana"
|
||||||
"nextcloud"
|
"nextcloud"
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
{
|
{ pkgs, lib, ... }:
|
||||||
flake-inputs,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
inherit (lib) concatStringsSep;
|
inherit (lib) concatStringsSep;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [ 21025 ];
|
|
||||||
|
|
||||||
# Sadly, steam-run requires some X libs
|
# Sadly, steam-run requires some X libs
|
||||||
environment.noXlibs = false;
|
environment.noXlibs = false;
|
||||||
|
|
||||||
|
|
@ -18,9 +11,7 @@ in
|
||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${
|
ExecStart = "${pkgs.local.starbound}/bin/launch-starbound ${./configs/starbound.json}";
|
||||||
flake-inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.starbound
|
|
||||||
}/bin/launch-starbound ${./configs/starbound.json}";
|
|
||||||
|
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
|
|
||||||
|
|
@ -123,7 +114,4 @@ in
|
||||||
paths = [ "/var/lib/private/starbound/storage/universe/" ];
|
paths = [ "/var/lib/private/starbound/storage/universe/" ];
|
||||||
pauseServices = [ "starbound.service" ];
|
pauseServices = [ "starbound.service" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Accessed via systemd cred through /run/secrets/steam
|
|
||||||
sops.secrets."steam/tlater" = { };
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,28 @@
|
||||||
{
|
{ config, ... }:
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
flake-inputs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
inherit (config.services.nginx) domain;
|
inherit (config.services.nginx) domain;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
services.tlaternet-webserver = {
|
||||||
80
|
enable = true;
|
||||||
443
|
listen = {
|
||||||
];
|
addr = "127.0.0.1";
|
||||||
|
port = 8000;
|
||||||
systemd.services.tlaternet-webserver = {
|
|
||||||
description = "tlater.net webserver";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "network.target" ];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
${lib.getExe flake-inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.webserver}
|
|
||||||
'';
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
TLATERNET_NTFY_INSTANCE = "http://${config.services.ntfy-sh.settings.listen-http}";
|
|
||||||
LEPTOS_SITE_ADDR = "127.0.0.1:8000";
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "exec";
|
|
||||||
LoadCredential = "ntfy-topic:/run/secrets/tlaternet/ntfy-topic";
|
|
||||||
|
|
||||||
DynamicUser = true;
|
|
||||||
ProtectHome = true; # Override the default (read-only)
|
|
||||||
PrivateDevices = true;
|
|
||||||
PrivateIPC = true;
|
|
||||||
PrivateUsers = true;
|
|
||||||
ProtectHostname = true;
|
|
||||||
ProtectClock = true;
|
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectKernelModules = true;
|
|
||||||
ProtectKernelLogs = true;
|
|
||||||
ProtectControlGroups = true;
|
|
||||||
RestrictAddressFamilies = [
|
|
||||||
"AF_UNIX"
|
|
||||||
"AF_INET"
|
|
||||||
"AF_INET6"
|
|
||||||
];
|
|
||||||
RestrictNamespaces = true;
|
|
||||||
LockPersonality = true;
|
|
||||||
MemoryDenyWriteExecute = true;
|
|
||||||
RestrictRealtime = true;
|
|
||||||
RestrictSUIDSGID = true;
|
|
||||||
SystemCallArchitectures = "native";
|
|
||||||
SystemCallFilter = [
|
|
||||||
"@system-service"
|
|
||||||
"~@privileged @resources @setuid @keyring"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Set up SSL
|
# Set up SSL
|
||||||
services.nginx.virtualHosts."${domain}" = {
|
services.nginx.virtualHosts."${domain}" =
|
||||||
serverAliases = [ "www.${domain}" ];
|
let
|
||||||
|
inherit (config.services.tlaternet-webserver.listen) addr port;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
serverAliases = [ "www.${domain}" ];
|
||||||
|
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
useACMEHost = "tlater.net";
|
useACMEHost = "tlater.net";
|
||||||
enableHSTS = true;
|
enableHSTS = true;
|
||||||
|
|
||||||
locations."/".proxyPass =
|
locations."/".proxyPass = "http://${addr}:${toString port}";
|
||||||
"http://${config.systemd.services.tlaternet-webserver.environment.LEPTOS_SITE_ADDR}";
|
};
|
||||||
};
|
|
||||||
|
|
||||||
sops.secrets = {
|
|
||||||
"tlaternet/ntfy-topic" = { };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,4 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets."wireguard/server-key" = {
|
|
||||||
owner = "root";
|
|
||||||
group = "systemd-network";
|
|
||||||
mode = "0440";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
89
configuration/sops.nix
Normal file
89
configuration/sops.nix
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
{
|
||||||
|
sops = {
|
||||||
|
defaultSopsFile = ../keys/production.yaml;
|
||||||
|
|
||||||
|
secrets = {
|
||||||
|
"battery-manager/email" = { };
|
||||||
|
|
||||||
|
"battery-manager/password" = { };
|
||||||
|
|
||||||
|
# Gitea
|
||||||
|
"forgejo/metrics-token" = {
|
||||||
|
owner = "forgejo";
|
||||||
|
group = "metrics";
|
||||||
|
mode = "0440";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Grafana
|
||||||
|
"grafana/adminPassword" = {
|
||||||
|
owner = "grafana";
|
||||||
|
group = "grafana";
|
||||||
|
};
|
||||||
|
"grafana/secretKey" = {
|
||||||
|
owner = "grafana";
|
||||||
|
group = "grafana";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Heisenbridge
|
||||||
|
"heisenbridge/as-token" = { };
|
||||||
|
"heisenbridge/hs-token" = { };
|
||||||
|
|
||||||
|
# Matrix-hookshot
|
||||||
|
"matrix-hookshot/as-token" = { };
|
||||||
|
"matrix-hookshot/hs-token" = { };
|
||||||
|
|
||||||
|
# Nextcloud
|
||||||
|
"nextcloud/tlater" = {
|
||||||
|
owner = "nextcloud";
|
||||||
|
group = "nextcloud";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Porkbub/ACME
|
||||||
|
"porkbun/api-key" = {
|
||||||
|
owner = "acme";
|
||||||
|
};
|
||||||
|
"porkbun/secret-api-key" = {
|
||||||
|
owner = "acme";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Restic
|
||||||
|
"restic/local-backups" = {
|
||||||
|
owner = "root";
|
||||||
|
group = "backup";
|
||||||
|
mode = "0440";
|
||||||
|
};
|
||||||
|
"restic/storagebox-backups" = {
|
||||||
|
owner = "root";
|
||||||
|
group = "backup";
|
||||||
|
mode = "0440";
|
||||||
|
};
|
||||||
|
"restic/storagebox-ssh-key" = {
|
||||||
|
owner = "backup";
|
||||||
|
group = "backup";
|
||||||
|
mode = "0040";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Steam
|
||||||
|
"steam/tlater" = { };
|
||||||
|
|
||||||
|
# Turn
|
||||||
|
"turn/env" = { };
|
||||||
|
"turn/secret" = {
|
||||||
|
owner = "turnserver";
|
||||||
|
};
|
||||||
|
"turn/ssl-key" = {
|
||||||
|
owner = "turnserver";
|
||||||
|
};
|
||||||
|
"turn/ssl-cert" = {
|
||||||
|
owner = "turnserver";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Wireguard
|
||||||
|
"wireguard/server-key" = {
|
||||||
|
owner = "root";
|
||||||
|
group = "systemd-network";
|
||||||
|
mode = "0440";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
962
flake.lock
generated
962
flake.lock
generated
File diff suppressed because it is too large
Load diff
58
flake.nix
58
flake.nix
|
|
@ -2,34 +2,20 @@
|
||||||
description = "tlater.net host configuration";
|
description = "tlater.net host configuration";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05-small";
|
||||||
|
|
||||||
## Nix/OS utilities
|
|
||||||
|
|
||||||
disko = {
|
disko = {
|
||||||
url = "github:nix-community/disko";
|
url = "github:nix-community/disko";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
deploy-rs.url = "github:serokell/deploy-rs";
|
||||||
deploy-rs = {
|
|
||||||
url = "github:serokell/deploy-rs";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
sops-nix = {
|
sops-nix = {
|
||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
tlaternet-webserver = {
|
||||||
## Programs
|
url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
|
||||||
|
|
||||||
flint = {
|
|
||||||
url = "github:NotAShelf/flint";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
## Services
|
|
||||||
|
|
||||||
foundryvtt = {
|
foundryvtt = {
|
||||||
url = "github:reckenrode/nix-foundryvtt";
|
url = "github:reckenrode/nix-foundryvtt";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
@ -37,13 +23,7 @@
|
||||||
|
|
||||||
sonnenshift = {
|
sonnenshift = {
|
||||||
url = "git+ssh://git@github.com/sonnenshift/battery-manager";
|
url = "git+ssh://git@github.com/sonnenshift/battery-manager";
|
||||||
inputs = {
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
nixpkgs.follows = "nixpkgs";
|
|
||||||
crate2nix.inputs = {
|
|
||||||
flake-compat.follows = "deploy-rs/flake-compat";
|
|
||||||
devshell.inputs.flake-utils.follows = "deploy-rs/utils";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -52,6 +32,7 @@
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
sops-nix,
|
sops-nix,
|
||||||
|
deploy-rs,
|
||||||
...
|
...
|
||||||
}@inputs:
|
}@inputs:
|
||||||
let
|
let
|
||||||
|
|
@ -66,25 +47,6 @@
|
||||||
./configuration/hardware-specific/vm.nix
|
./configuration/hardware-specific/vm.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# deploy-rs unfortunately uses an `import nixpkgs`, and its
|
|
||||||
# library functions depend on an instantiated nixpkgs, so we
|
|
||||||
# can't get around multi-nixpkgs-eval.
|
|
||||||
inherit
|
|
||||||
(import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
overlays = [
|
|
||||||
inputs.deploy-rs.overlays.default
|
|
||||||
(_: prev: {
|
|
||||||
deploy-rs = {
|
|
||||||
inherit (nixpkgs.legacyPackages.${system}) deploy-rs;
|
|
||||||
inherit (prev.deploy-rs) lib;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
})
|
|
||||||
deploy-rs
|
|
||||||
;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
##################
|
##################
|
||||||
|
|
@ -112,7 +74,7 @@
|
||||||
|
|
||||||
profiles.system = {
|
profiles.system = {
|
||||||
user = "root";
|
user = "root";
|
||||||
path = deploy-rs.lib.activate.nixos self.nixosConfigurations.hetzner-1;
|
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.hetzner-1;
|
||||||
};
|
};
|
||||||
|
|
||||||
sshUser = "tlater";
|
sshUser = "tlater";
|
||||||
|
|
@ -128,7 +90,7 @@
|
||||||
#########
|
#########
|
||||||
# Tests #
|
# Tests #
|
||||||
#########
|
#########
|
||||||
checks = import ./checks { flake-inputs = inputs; };
|
checks.${system} = import ./checks (inputs // { inherit system; });
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# Garbage collection root #
|
# Garbage collection root #
|
||||||
|
|
@ -166,15 +128,13 @@
|
||||||
|
|
||||||
packages = nixpkgs.lib.attrValues {
|
packages = nixpkgs.lib.attrValues {
|
||||||
inherit (sops-nix.packages.${system}) sops-import-keys-hook sops-init-gpg-key;
|
inherit (sops-nix.packages.${system}) sops-import-keys-hook sops-init-gpg-key;
|
||||||
inherit (deploy-rs) deploy-rs;
|
inherit (deploy-rs.packages.${system}) default;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
minecraft = nixpkgs.legacyPackages.${system}.mkShell {
|
minecraft = nixpkgs.legacyPackages.${system}.mkShell {
|
||||||
packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; };
|
packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; };
|
||||||
};
|
};
|
||||||
|
|
||||||
webserver = self.packages.${system}.webserver.devShell;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,7 @@
|
||||||
tlaternet:
|
authelia:
|
||||||
ntfy-topic: ENC[AES256_GCM,data:BSNLP9hLQKufDf3STknQ,iv:4WeYARwDEg84fh8qMa4szssQeK2orBl72oiIRywXCi8=,tag:GtS7YAlQiuGAWaUn+JxaYg==,type:str]
|
users: ""
|
||||||
ntfy:
|
jwt: ENC[AES256_GCM,data:oKA1B7zZAzTZL4nBdHvPENVx7M2BgbMBmNtetri0qCVB7qNFIgbnwVCJFiDvjKxxNdedqUKBZZL5QJbTlPNRxCVdFgBBMFiib3khxMP8kzqff2MgJZxumonlJt5Jmh8tVxwLRJwE/2fp/N9w2hRs0vhfmMyAA4y7RZv3R9/eaKM=,iv:2iTAwP6dipPBMskyygnBJHJ53E0nmHYcGyWDrODEs1Q=,tag:koSEZtQQzOzpbQBgUP5ZHw==,type:str]
|
||||||
#ENC[AES256_GCM,data:B5sUuCWTheo/Lz5kAN5/8NeiT7ohQ6IRYjD7k/c0,iv:E85yGjNcd9RGOgBKFPxRB6LqdqISNcjlvp/pqpR7VPg=,tag:1NrwtWyoVp4qDWiqAt/few==,type:comment]
|
storage: ENC[AES256_GCM,data:bO+bHu6jRvfbLU6xIDaE2JwXpNnMK916Upv43ycg9fCb+U5hqQfqBBwC2xVEVXtCBRq1VER+gc8rs8/XDT9vZkvMUqAHj4RqXHyzX0UjwsvccBJSLfoLUiT6obk3oVLo5CY7R2TukPuyFXPbMUOrBk9gnbk7z4IWzcwNnuOKBT4=,iv:RmKIS/cgZ0tUQDFF2yfaJnfTvPaeadjG0LPXKIzYFrA=,tag:XmqDhDf3Ja1BsyrYmzTKDg==,type:str]
|
||||||
users: ENC[AES256_GCM,data:zW02xRlLvqJ0/rLJMX9/LSW7CQ26nIyifOLYba4AkstQcrVMOIV1Je4QwaKoy2TPhDcVcjLg6Ce2/0qxpO7Q3rqoXqACbPAUwcxzn14KOt7tEb3IE2S//0Y/law=,iv:sV5Dvplr6A5ivrI/+Cyl6mC+Zxo8NORxfuhEZ/75JbU=,tag:NzqxoN3Hr0YO2CXiVdEo+A==,type:str]
|
|
||||||
porkbun:
|
porkbun:
|
||||||
api-key: ENC[AES256_GCM,data:A5J1sqwq6hs=,iv:77Mar3IX7mq7z7x6s9sSeGNVYc1Wv78HptJElEC7z3Q=,tag:eM/EF9TxKu+zcbJ1SYXiuA==,type:str]
|
api-key: ENC[AES256_GCM,data:A5J1sqwq6hs=,iv:77Mar3IX7mq7z7x6s9sSeGNVYc1Wv78HptJElEC7z3Q=,tag:eM/EF9TxKu+zcbJ1SYXiuA==,type:str]
|
||||||
secret-api-key: ENC[AES256_GCM,data:8Xv+jWYaWMI=,iv:li4tdY0pch5lksftMmfMVS729caAwfaacoztaQ49az0=,tag:KhfElBGzVH4ByFPfuQsdhw==,type:str]
|
secret-api-key: ENC[AES256_GCM,data:8Xv+jWYaWMI=,iv:li4tdY0pch5lksftMmfMVS729caAwfaacoztaQ49az0=,tag:KhfElBGzVH4ByFPfuQsdhw==,type:str]
|
||||||
|
|
@ -21,17 +20,24 @@ steam:
|
||||||
heisenbridge:
|
heisenbridge:
|
||||||
as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str]
|
as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str]
|
||||||
hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str]
|
hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str]
|
||||||
|
matrix-hookshot:
|
||||||
|
as-token: ENC[AES256_GCM,data:uSUOo4f2KqA=,iv:Xb9G8Ecv6m59m51kDw2bOfq3SMJt4g9/6/EdH74R+KM=,tag:K9MSfO2c2Y4rlf0eYrmTnw==,type:str]
|
||||||
|
hs-token: ENC[AES256_GCM,data:0KsyA06InL4=,iv:zAR0Y1fk8SyodcSLBHlQ8I+BAmttz9Hkd8Q3OREFqs4=,tag:t1Et8N/3seq95DeGoUd7Sw==,type:str]
|
||||||
wireguard:
|
wireguard:
|
||||||
server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str]
|
server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str]
|
||||||
restic:
|
restic:
|
||||||
|
local-backups: ENC[AES256_GCM,data:3QjEv03t7wE=,iv:y/6Lv4eUbZZfGPwUONykz8VNL62cAJuWaJy9yk3aAmk=,tag:wMlGsepuG9JjwtUKGWSibw==,type:str]
|
||||||
storagebox-backups: ENC[AES256_GCM,data:NEHk57B3YtI=,iv:0/qnqMVK0662sgfDQoLxcW7L09SKF8E5liCnjaQ2+2k=,tag:RU0BPwGgvI9bgOPr8VItmA==,type:str]
|
storagebox-backups: ENC[AES256_GCM,data:NEHk57B3YtI=,iv:0/qnqMVK0662sgfDQoLxcW7L09SKF8E5liCnjaQ2+2k=,tag:RU0BPwGgvI9bgOPr8VItmA==,type:str]
|
||||||
storagebox-ssh-key: ENC[AES256_GCM,data:65+kbJPO90y+rRh3Q5cqLDtQa3VFfbaDPPo1nJLqxgAB7Wm3J7K4qUYAKPcYnkWV4/xFz63R2uCNaq5xv+vuZA==,iv:O7AeE/ujp5p1P7nff7PpghQfN2tQUYBSWL+EHRbE5yA=,tag:Pu/+bEAQuqwmD1Rc//t0cA==,type:str]
|
storagebox-ssh-key: ENC[AES256_GCM,data:65+kbJPO90y+rRh3Q5cqLDtQa3VFfbaDPPo1nJLqxgAB7Wm3J7K4qUYAKPcYnkWV4/xFz63R2uCNaq5xv+vuZA==,iv:O7AeE/ujp5p1P7nff7PpghQfN2tQUYBSWL+EHRbE5yA=,tag:Pu/+bEAQuqwmD1Rc//t0cA==,type:str]
|
||||||
turn:
|
turn:
|
||||||
env: ENC[AES256_GCM,data:xjIz/AY109lyiL5N01p5T3HcYco/rM5CJSRTtg==,iv:16bW6OpyOK/QL0QPGQp/Baa9xyT8E3ZsYkwqmjuofk0=,tag:J5re3uKxIykw3YunvQWBgg==,type:str]
|
env: ENC[AES256_GCM,data:xjIz/AY109lyiL5N01p5T3HcYco/rM5CJSRTtg==,iv:16bW6OpyOK/QL0QPGQp/Baa9xyT8E3ZsYkwqmjuofk0=,tag:J5re3uKxIykw3YunvQWBgg==,type:str]
|
||||||
secret: ENC[AES256_GCM,data:eQ7dAocoZtg=,iv:fgzjTPv30WqTKlLy+yMn5MsKQgjhPnwlGFFwYEg3gWs=,tag:1ze33U1NBkgMX/9SiaBNQg==,type:str]
|
secret: ENC[AES256_GCM,data:eQ7dAocoZtg=,iv:fgzjTPv30WqTKlLy+yMn5MsKQgjhPnwlGFFwYEg3gWs=,tag:1ze33U1NBkgMX/9SiaBNQg==,type:str]
|
||||||
|
ssl-key: ENC[AES256_GCM,data:RYfwHjBvwFXgXxXIEuWUzaycTdrCvmPivsNvvUIwDRynS5G2Dl6RCVp1w9zuLvoNun5ncUPGGuLMmVqN2wkJlw==,iv:UKI3bVTY7iTDNvp5UqrZ3QlQkMZ5p2bjgODEc6DCBfQ=,tag:sz7VTyRWyZxAsP4nE48DnA==,type:str]
|
||||||
|
#ENC[AES256_GCM,data:bxhKzU5Tzezl749CDu8e8kxa7ahGuZFaPa9K3kxuD+4sg5Hi3apgDlC0n8oK0DeiK4Ks7+9Cyw==,iv:T/zVJUpNAv1rR0a9+6SDTG08ws2A1hFBs5Ia3TpT0uk=,tag:uGXb1VryM+lIJ8r0I5durA==,type:comment]
|
||||||
|
ssl-cert: ENC[AES256_GCM,data:xHUr14CjKslgbGh/n5jYSOuCw9JRxS6YXE4fxS+aJzFcNeSeGNqoipPeuJupZGBnQP/FCqohiHY=,iv:/OEsVqRshGL9NIvntMC42EPZSNL0u6EfhtUBqgV7qog=,tag:4pxtNjuvy/ibm6nDtKdSkw==,type:str]
|
||||||
sops:
|
sops:
|
||||||
lastmodified: "2025-12-01T11:39:26Z"
|
lastmodified: "2025-10-20T20:04:21Z"
|
||||||
mac: ENC[AES256_GCM,data:11VQAYk8Am0k8OO6BtU17qpuEhcJ8ylRhJWQNHVAsmi5BCFjD1zU3NkWhtSstPrBcqHMenG+9XuEzpNnbccHI2ru0qlILsQvNj5OKo96FnvYtzApYlApoAzOetCx08Lfxa4RGLN/XCUSuccjBIU2PZRWEK+z+Cm1wHUFeqc1xPc=,iv:6y9j55Cld+GoOVGWAqsEgURRna6dHA2mGZwHVA+ZOE8=,tag:bSZi3nYmYrn3nFT2+RBPUQ==,type:str]
|
mac: ENC[AES256_GCM,data:kRrmVm3PQooRA/MoHgDb9EaRnoKY9CJxAflus9Po8NBmyQxV6Ehjf8DlI6yf7ZpPlhV+VHZJamyPD+hsHp1hSr8krvr0o52ZQdKn4MJQzSQXa4K9i3i0+glj7cNGs2SzTJnKwN9lxBywZpbVDlkXmvRQYLE9tWPWoSBdurOibjw=,iv:2iBQ1cYT85mCc7jf2GTEOjNiHBlR/F76Dvjl/k5dyLA=,tag:Z7dY2i0KWmmoVp7VJjq1Sw==,type:str]
|
||||||
pgp:
|
pgp:
|
||||||
- created_at: "2025-10-03T21:38:26Z"
|
- created_at: "2025-10-03T21:38:26Z"
|
||||||
enc: |-
|
enc: |-
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ in
|
||||||
# To add completions; sadly need to hand-roll this since
|
# To add completions; sadly need to hand-roll this since
|
||||||
# neither `symlinkJoin` nor `buildEnv` have collision
|
# neither `symlinkJoin` nor `buildEnv` have collision
|
||||||
# handling.
|
# handling.
|
||||||
(pkgs.runCommandLocal "cscli" { } ''
|
(pkgs.runCommandNoCCLocal "cscli" { } ''
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
ln -s ${cscli}/bin $out/bin
|
ln -s ${cscli}/bin $out/bin
|
||||||
ln -s ${cfg.package}/share $out/share
|
ln -s ${cfg.package}/share $out/share
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (flake-inputs.self.packages.${pkgs.stdenv.hostPlatform.system}) crowdsec-firewall-bouncer;
|
inherit (flake-inputs.self.packages.${pkgs.system}) crowdsec-firewall-bouncer;
|
||||||
|
|
||||||
crowdsecCfg = config.security.crowdsec;
|
crowdsecCfg = config.security.crowdsec;
|
||||||
cfg = crowdsecCfg.remediationComponents.firewallBouncer;
|
cfg = crowdsecCfg.remediationComponents.firewallBouncer;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./crowdsec
|
./crowdsec
|
||||||
./serviceTests/stub.nix
|
./nginxExtensions.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
modules/nginxExtensions.nix
Normal file
59
modules/nginxExtensions.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
services.nginx.domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "The base domain name to append to virtual domain names";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts =
|
||||||
|
let
|
||||||
|
extraVirtualHostOptions =
|
||||||
|
{ name, config, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
enableHSTS = lib.mkEnableOption "Enable HSTS";
|
||||||
|
|
||||||
|
addAccessLog = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Add special logging to `/var/log/nginx/''${serverName}`
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
extraConfig = lib.concatStringsSep "\n" [
|
||||||
|
(lib.optionalString config.enableHSTS ''
|
||||||
|
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
|
||||||
|
'')
|
||||||
|
(lib.optionalString config.addAccessLog ''
|
||||||
|
access_log /var/log/nginx/${name}/access.log upstream_time;
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
lib.mkOption { type = lib.types.attrsOf (lib.types.submodule extraVirtualHostOptions); };
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# Don't attempt to run acme if the domain name is not tlater.net
|
||||||
|
systemd.services =
|
||||||
|
let
|
||||||
|
confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]'';
|
||||||
|
in
|
||||||
|
lib.mapAttrs' (
|
||||||
|
cert: _:
|
||||||
|
lib.nameValuePair "acme-${cert}" {
|
||||||
|
serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' '';
|
||||||
|
}
|
||||||
|
) config.security.acme.certs;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
Module containing mock definitions for service test runners.
|
|
||||||
*/
|
|
||||||
{ flake-inputs, lib, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
flake-inputs.sops-nix.nixosModules.sops
|
|
||||||
../.
|
|
||||||
../../configuration/services/backups.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
sops.defaultSopsFile = ../../keys/staging.yaml;
|
|
||||||
environment.etc."staging.key" = {
|
|
||||||
mode = "0400";
|
|
||||||
source = ../../keys/hosts/staging.key;
|
|
||||||
};
|
|
||||||
services.openssh = {
|
|
||||||
enable = true;
|
|
||||||
hostKeys = lib.mkForce [
|
|
||||||
{
|
|
||||||
type = "rsa";
|
|
||||||
bits = 4096;
|
|
||||||
path = "/etc/staging.key";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/**
|
|
||||||
Module to make writing service-specific tests easy.
|
|
||||||
*/
|
|
||||||
{ lib, ... }:
|
|
||||||
let
|
|
||||||
inherit (lib) mkOption types;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
serviceTests = mkOption {
|
|
||||||
type = types.attrsOf types.package;
|
|
||||||
|
|
||||||
description = ''
|
|
||||||
NixOS tests to run.
|
|
||||||
'';
|
|
||||||
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
1
pkgs/packages/webserver/.gitignore
vendored
1
pkgs/packages/webserver/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
target/
|
|
||||||
3453
pkgs/packages/webserver/Cargo.lock
generated
3453
pkgs/packages/webserver/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,68 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "tlaternet-webserver"
|
|
||||||
version = "0.2.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
axum = { version = "0.8.7", features = ["macros"], optional = true }
|
|
||||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
|
||||||
figment = { version = "0.10.19", features = ["toml", "env"] }
|
|
||||||
leptos = "0.8.3"
|
|
||||||
leptos_axum = { version = "0.8.3", optional = true }
|
|
||||||
leptos_meta = "0.8.3"
|
|
||||||
leptos_router = "0.8.3"
|
|
||||||
markdown_view_leptos = "0.1.3"
|
|
||||||
reqwest = "0.12.24"
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
|
||||||
thiserror = "2.0.17"
|
|
||||||
tokio = { version = "1.48.0", features = ["rt-multi-thread"], optional = true }
|
|
||||||
url = "2.5.7"
|
|
||||||
wasm-bindgen = { version = "=0.2.100", optional = true }
|
|
||||||
web-sys = "^0.3.77"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
hydrate = [
|
|
||||||
"leptos/hydrate",
|
|
||||||
"dep:console_error_panic_hook",
|
|
||||||
"dep:wasm-bindgen",
|
|
||||||
]
|
|
||||||
ssr = [
|
|
||||||
"dep:axum",
|
|
||||||
"dep:tokio",
|
|
||||||
"dep:leptos_axum",
|
|
||||||
"leptos/ssr",
|
|
||||||
"leptos_meta/ssr",
|
|
||||||
"leptos_router/ssr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[profile.wasm-release]
|
|
||||||
inherits = "release"
|
|
||||||
opt-level = 'z'
|
|
||||||
lto = true
|
|
||||||
codegen-units = 1
|
|
||||||
panic = "abort"
|
|
||||||
|
|
||||||
[package.metadata.leptos]
|
|
||||||
output-name = "tlaternet-webserver"
|
|
||||||
site-root = "target/site"
|
|
||||||
site-pkg-dir = "pkg"
|
|
||||||
style-file = "style/main.scss"
|
|
||||||
site-addr = "127.0.0.1:3000"
|
|
||||||
reload-port = 3001
|
|
||||||
browserquery = "defaults"
|
|
||||||
env = "DEV"
|
|
||||||
bin-features = ["ssr"]
|
|
||||||
bin-default-features = false
|
|
||||||
lib-features = ["hydrate"]
|
|
||||||
lib-default-features = false
|
|
||||||
lib-profile-release = "wasm-release"
|
|
||||||
watch-additional-files = ["config.toml"]
|
|
||||||
|
|
||||||
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
|
|
||||||
# [Windows] for non-WSL use "npx.cmd playwright test"
|
|
||||||
# This binary name can be checked in Powershell with Get-Command npx
|
|
||||||
end2end-cmd = "npx playwright test"
|
|
||||||
end2end-dir = "end2end"
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Can be specified with systemd creds instead
|
|
||||||
credentials_directory = "dev-creds"
|
|
||||||
ntfy_instance = "https://ntfy.sh"
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
max_width = 100
|
|
||||||
tab_spaces = 2
|
|
||||||
attr_value_brace_style = "WhenRequired"
|
|
||||||
macro_names = [ "leptos::view", "view" ]
|
|
||||||
closing_tag_style = "SelfClosing"
|
|
||||||
|
|
@ -1,295 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
stdenvNoCC,
|
|
||||||
fetchFromGitHub,
|
|
||||||
fetchurl,
|
|
||||||
symlinkJoin,
|
|
||||||
makeBinaryWrapper,
|
|
||||||
|
|
||||||
pkg-config,
|
|
||||||
openssl,
|
|
||||||
cargo-leptos,
|
|
||||||
dart-sass,
|
|
||||||
rustPlatform,
|
|
||||||
wasm-bindgen-cli_0_2_100,
|
|
||||||
binaryen,
|
|
||||||
inkscape,
|
|
||||||
|
|
||||||
mkShell,
|
|
||||||
clangStdenv,
|
|
||||||
rust-analyzer,
|
|
||||||
rustc,
|
|
||||||
rustfmt,
|
|
||||||
leptosfmt,
|
|
||||||
cargo,
|
|
||||||
clippy,
|
|
||||||
|
|
||||||
writers,
|
|
||||||
ast-grep,
|
|
||||||
nix-prefetch-github,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cargoMetadata = lib.pipe ./Cargo.toml [
|
|
||||||
builtins.readFile
|
|
||||||
builtins.fromTOML
|
|
||||||
];
|
|
||||||
|
|
||||||
sass-dependencies = {
|
|
||||||
bulma = stdenvNoCC.mkDerivation (drv: {
|
|
||||||
pname = "bulma";
|
|
||||||
version = "1.0.4";
|
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "jgthms";
|
|
||||||
repo = "bulma";
|
|
||||||
rev = drv.version;
|
|
||||||
hash = "sha256-hlejqBI6ayzhm15IymrzhTevkl3xffMfdTasZ2CmAas=";
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/node_modules/bulma/
|
|
||||||
cp -r ./sass $out/node_modules/bulma/
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
|
|
||||||
fontsource-scss = stdenvNoCC.mkDerivation {
|
|
||||||
pname = "fontsource-scss";
|
|
||||||
version = "0.2.2";
|
|
||||||
|
|
||||||
src = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@fontsource-utils/scss/-/scss-0.2.2.tgz";
|
|
||||||
hash = "sha256-2BkCBhh01kZfMHhjHMMLDtUeesi7Uy7eMoeM1BAqX38=";
|
|
||||||
};
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/node_modules/@fontsource-utils/scss/
|
|
||||||
cp -r . $out/node_modules/@fontsource-utils/scss/
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
fontsource-nunito = stdenvNoCC.mkDerivation {
|
|
||||||
pname = "fontsource-nunito";
|
|
||||||
version = "5.2.7";
|
|
||||||
|
|
||||||
src = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@fontsource-variable/nunito/-/nunito-5.2.7.tgz";
|
|
||||||
hash = "sha256-xSt1sDpVL/hVYzffKTgN/t7uLI3JadDWtTfWow2jiPM=";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = [
|
|
||||||
"out"
|
|
||||||
"assets"
|
|
||||||
];
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/node_modules/@fontsource-variable/nunito/
|
|
||||||
cp -r . $out/node_modules/@fontsource-variable/nunito/
|
|
||||||
|
|
||||||
mkdir -p $assets/@fontsource-variable/
|
|
||||||
cp -r files/ $assets/@fontsource-variable/nunito
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
fontsource-arimo = stdenvNoCC.mkDerivation {
|
|
||||||
pname = "fontsource-nunito";
|
|
||||||
version = "5.2.8";
|
|
||||||
|
|
||||||
src = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@fontsource-variable/arimo/-/arimo-5.2.8.tgz";
|
|
||||||
hash = "sha256-jD1IGqy02j4bqMRAwbCgiIz/h97WPrTSd3eZ09nptHA=";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = [
|
|
||||||
"out"
|
|
||||||
"assets"
|
|
||||||
];
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/node_modules/@fontsource-variable/arimo/
|
|
||||||
cp -r . $out/node_modules/@fontsource-variable/arimo/
|
|
||||||
|
|
||||||
mkdir -p $assets/@fontsource-variable/
|
|
||||||
cp -r files/ $assets/@fontsource-variable/arimo
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
other-dependencies = {
|
|
||||||
hack-font = stdenvNoCC.mkDerivation (drv: {
|
|
||||||
pname = "hack-font";
|
|
||||||
version = "3.003";
|
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "source-foundry";
|
|
||||||
repo = "Hack";
|
|
||||||
rev = "v${drv.version}";
|
|
||||||
hash = "sha256-qGDtBvKecdfsleUBfXFezllz9Op679a030Qcj/oBs1o=";
|
|
||||||
};
|
|
||||||
|
|
||||||
dontBuild = true;
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
cp -r build/web/fonts $out/hack-font/
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
icons = stdenvNoCC.mkDerivation {
|
|
||||||
pname = "tlaternet-icons";
|
|
||||||
version = "1.0";
|
|
||||||
|
|
||||||
src = ./public/icon.svg;
|
|
||||||
|
|
||||||
dontUnpack = true;
|
|
||||||
|
|
||||||
nativeBuildInputs = [ inkscape ];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
inkscape -w 48 -h 48 $src --export-filename=favicon-48.png
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
cp $src $out/icon.svg
|
|
||||||
cp favicon-48.png $out/
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
assets = symlinkJoin {
|
|
||||||
name = "assets-${cargoMetadata.package.name}";
|
|
||||||
paths = [
|
|
||||||
sass-dependencies.fontsource-arimo.assets
|
|
||||||
sass-dependencies.fontsource-nunito.assets
|
|
||||||
other-dependencies.hack-font
|
|
||||||
icons
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Hack to allow importing sass *without* resorting to using `npm`
|
|
||||||
# and dealing with the insanity that is `package.json` files.
|
|
||||||
#
|
|
||||||
# dart-sass *in theory* supports completely arbitrary logic for
|
|
||||||
# package importing via the `pkg:` url prefix, but unfortunately
|
|
||||||
# currently the only implementation of it that is available in the
|
|
||||||
# upstream binary *requires* using a `node_modules` file in the
|
|
||||||
# repository root. See here:
|
|
||||||
# https://sass-lang.com/documentation/at-rules/use/#node-js-package-importer
|
|
||||||
#
|
|
||||||
# This wouldn't be so bad if it supported an environment variable or
|
|
||||||
# command line arg to specify what the repo root should be, but it
|
|
||||||
# doesn't; so instead we use the load-path to specify a package
|
|
||||||
# import root.
|
|
||||||
#
|
|
||||||
# As a consequence, we cannot use the `pkg:` prefix, so package
|
|
||||||
# imports are indistinguishable from relative path imports. This
|
|
||||||
# isn't the end of the world, but:
|
|
||||||
#
|
|
||||||
# TODO(tlater): See if we can talk to upstream about an
|
|
||||||
# implementation better suited for use with nix, perhaps in
|
|
||||||
# dart-sass (add an env variable?) or in cargo-leptos (add a config
|
|
||||||
# option that can also be set with an env variable, and use the sass
|
|
||||||
# protocol instead of the raw exe?).
|
|
||||||
dart-sass-with-packages =
|
|
||||||
let
|
|
||||||
packages = symlinkJoin {
|
|
||||||
name = "sass-packages";
|
|
||||||
paths = lib.attrValues sass-dependencies;
|
|
||||||
stripPrefix = "/node_modules";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
symlinkJoin {
|
|
||||||
inherit (dart-sass) version;
|
|
||||||
pname = "dart-sass-with-packages";
|
|
||||||
|
|
||||||
paths = [ dart-sass ];
|
|
||||||
nativeBuildInputs = [ makeBinaryWrapper ];
|
|
||||||
|
|
||||||
postBuild = ''
|
|
||||||
wrapProgram $out/bin/sass \
|
|
||||||
--add-flag --load-path=${packages}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
rustPlatform.buildRustPackage (drv: {
|
|
||||||
inherit (cargoMetadata.package) version;
|
|
||||||
pname = cargoMetadata.package.name;
|
|
||||||
cargoLock.lockFile = drv.src + /Cargo.lock;
|
|
||||||
|
|
||||||
src = lib.fileset.toSource {
|
|
||||||
root = ./.;
|
|
||||||
fileset = lib.fileset.fromSource (lib.sources.cleanSource ./.);
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
cargo-leptos
|
|
||||||
rustc.llvmPackages.lld
|
|
||||||
dart-sass-with-packages
|
|
||||||
makeBinaryWrapper
|
|
||||||
pkg-config
|
|
||||||
wasm-bindgen-cli_0_2_100
|
|
||||||
binaryen
|
|
||||||
];
|
|
||||||
|
|
||||||
buildInputs = [ openssl ];
|
|
||||||
|
|
||||||
LEPTOS_ASSETS_DIR = assets.outPath;
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
runHook preBuild
|
|
||||||
cargo leptos build --release
|
|
||||||
runHook postBuild
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
mkdir -p $out/bin $out/share
|
|
||||||
cp target/release/tlaternet-webserver $out/bin
|
|
||||||
cp -r target/site $out/share
|
|
||||||
wrapProgram $out/bin/tlaternet-webserver \
|
|
||||||
--set LEPTOS_SITE_ROOT $out/share/site \
|
|
||||||
--set LEPTOS_ASSETS_DIR ${assets.outPath}
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta.mainProgram = "tlaternet-webserver";
|
|
||||||
|
|
||||||
passthru = {
|
|
||||||
dependencies = sass-dependencies;
|
|
||||||
|
|
||||||
devShell = mkShell.override { stdenv = clangStdenv; } {
|
|
||||||
packages = [
|
|
||||||
pkg-config
|
|
||||||
openssl
|
|
||||||
cargo-leptos
|
|
||||||
dart-sass-with-packages
|
|
||||||
# lld is exposed as ld by the clangStdenv, adding it
|
|
||||||
# explicitly with bintools makes it work
|
|
||||||
rustc.llvmPackages.lld
|
|
||||||
|
|
||||||
rust-analyzer
|
|
||||||
rustc
|
|
||||||
rustfmt
|
|
||||||
leptosfmt
|
|
||||||
cargo
|
|
||||||
clippy
|
|
||||||
];
|
|
||||||
|
|
||||||
LEPTOS_ASSETS_DIR = assets.outPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
updateScript = writers.writeNuBin "update-${cargoMetadata.package.name}" {
|
|
||||||
makeWrapperArgs = [
|
|
||||||
"--prefix"
|
|
||||||
"PATH"
|
|
||||||
":"
|
|
||||||
(lib.makeBinPath [
|
|
||||||
ast-grep
|
|
||||||
nix-prefetch-github
|
|
||||||
])
|
|
||||||
];
|
|
||||||
} ./update.nu;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.0"
|
|
||||||
width="260.000000pt"
|
|
||||||
height="260.000000pt"
|
|
||||||
viewBox="0 0 260.000000 260.000000"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
id="svg70"
|
|
||||||
sodipodi:docname="icon.svg"
|
|
||||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
|
|
||||||
<metadata
|
|
||||||
id="metadata76">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<defs
|
|
||||||
id="defs74" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
inkscape:pagecheckerboard="true"
|
|
||||||
inkscape:document-rotation="0"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1916"
|
|
||||||
inkscape:window-height="1059"
|
|
||||||
id="namedview72"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="2.2269231"
|
|
||||||
inkscape:cx="173.33333"
|
|
||||||
inkscape:cy="173.33333"
|
|
||||||
inkscape:window-x="-2"
|
|
||||||
inkscape:window-y="13"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="layer1" />
|
|
||||||
<g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
inkscape:label="Background">
|
|
||||||
<rect
|
|
||||||
style="fill:#99d1ce;stroke-width:0.75;fill-opacity:1"
|
|
||||||
id="rect843"
|
|
||||||
width="260"
|
|
||||||
height="260"
|
|
||||||
x="2.0117849e-08"
|
|
||||||
y="2.0117849e-08" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
style="fill:#0f0f0f;fill-opacity:1;stroke:#991dce;stroke-width:0.1"
|
|
||||||
id="path62"
|
|
||||||
d="M 2.0117848e-8,130 V 260 H 130 260 V 130 2.0117848e-8 H 130 2.0117848e-8 Z M 132.5,19.099999 c 1.4,1.300001 2,3.6 2.3,8.300001 l 0.4,6.4 8.2,0.699999 C 160.7,35.9 177.9,40.300001 182.89999,44.600002 188.9,49.6 189.1,58.399998 183.3,62.999999 180,65.6 176.6,65.5 161.7,62.499998 155,61.199999 145.99999,59.8 141.79999,59.399999 l -7.8,-0.700001 V 85.7 v 27 L 147,116.9 c 34.8,11.2 49.4,24.6 50.69999,46.39999 C 198.7,178 194.8,188.9 184.79999,199.5 174.6,210.4 162.6,216.5 144.89999,219.6 l -9.69999,1.7 -0.4,7.5 c -0.3,6.29999 -0.7,7.69999 -2.7,9.29999 -3.5,2.8 -8.5,2.5 -11,-0.69999 -1.7,-2.10001 -2.1,-4.1 -2.1,-9.4 v -6.7 l -7.1,-0.70001 c -8.8,-0.79999 -23.900001,-4 -34.400001,-7.3 -10.300001,-3.3 -15.5,-7.99999 -15.5,-14 0,-5.59999 3.5,-10.49999 8.2,-11.7 2.999999,-0.8 5.4,-0.39999 12.500002,1.7 C 93.099998,192.5 103,194.59999 112.3,195.5 l 6.7,0.7 v -29.1 c 0,-22.50001 -0.3,-29.20001 -1.20001,-29.5 C 93.099998,130.3 81.199997,124.9 72.900001,117.3 58.5,103.9 54.800001,82.499998 63.699999,64.300001 70.999998,49.399999 91.399998,37.199999 113.4,34.499999 L 119,33.8 v -5.7 c 0,-10.400001 7.1,-15.1 13.5,-9.000001 z" />
|
|
||||||
<path
|
|
||||||
style="fill:#0f0f0f;stroke:#991dce;stroke-width:0.1;fill-opacity:1"
|
|
||||||
id="path64"
|
|
||||||
d="m 113.2,60.6 c -18.4,4.7 -25.8,18.1 -17.7,32 1.3,2.2 4,5.3 6,6.8 3.7,2.9 16.4,9 17.1,8.3 0.3,-0.2 0.3,-11.2 0.2,-24.4 l -0.3,-24 z" />
|
|
||||||
<path
|
|
||||||
style="fill:#0f0f0f;stroke:#991dce;stroke-width:0.1;fill-opacity:1"
|
|
||||||
id="path66"
|
|
||||||
d="m 134,169.1 v 26.2 l 4.4,-0.5 c 9.6,-1.1 21.1,-8.9 24.2,-16.4 1.9,-4.6 1.7,-12.1 -0.5,-16.9 -2.6,-5.7 -10,-11.8 -18.6,-15.4 -4,-1.7 -7.8,-3.1 -8.4,-3.1 -0.8,0 -1.1,8.2 -1.1,26.1 z" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,56 +0,0 @@
|
||||||
use leptos::prelude::*;
|
|
||||||
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet};
|
|
||||||
use leptos_router::{
|
|
||||||
components::{Route, Router, Routes},
|
|
||||||
StaticSegment,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod homepage;
|
|
||||||
mod mail;
|
|
||||||
|
|
||||||
use crate::components::Navbar;
|
|
||||||
use homepage::HomePage;
|
|
||||||
use mail::Mail;
|
|
||||||
|
|
||||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
|
||||||
view! {
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-theme="dark">
|
|
||||||
<head>
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/favicon-48.png" sizes="48x48" />
|
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="author" content="Tristan Daniël Maat" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<AutoReload options=options.clone() />
|
|
||||||
<HydrationScripts options />
|
|
||||||
<MetaTags />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<App />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn App() -> impl IntoView {
|
|
||||||
provide_meta_context();
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<Stylesheet href="/pkg/tlaternet-webserver.css" />
|
|
||||||
|
|
||||||
<Navbar />
|
|
||||||
|
|
||||||
// content for this welcome page
|
|
||||||
<Router>
|
|
||||||
<main>
|
|
||||||
<Routes fallback=|| "Page not found.".into_view()>
|
|
||||||
<Route path=StaticSegment("") view=HomePage />
|
|
||||||
<Route path=StaticSegment("mail") view=Mail />
|
|
||||||
</Routes>
|
|
||||||
</main>
|
|
||||||
</Router>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
use leptos::prelude::*;
|
|
||||||
use leptos_meta::{Meta, Title};
|
|
||||||
use markdown_view_leptos::markdown_view;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn HomePage() -> impl IntoView {
|
|
||||||
view! {
|
|
||||||
<Meta name="description" content="tlater.net homepage" />
|
|
||||||
<Title text="Welcome to tlater.net!" />
|
|
||||||
|
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="title has-text-weight-normal is-family-monospace">
|
|
||||||
"$ "<span id="typed-welcome" />
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column content">
|
|
||||||
{markdown_view!(
|
|
||||||
r#"
|
|
||||||
### About Me
|
|
||||||
|
|
||||||
Looks like you found my website. I suppose introductions are in order.
|
|
||||||
|
|
||||||
My name's Tristan, I'm an avid Dutch-South African software
|
|
||||||
engineer. You probably either met me at an open source conference, a
|
|
||||||
hackathon, a badminton session or at a roleplaying table.
|
|
||||||
"#
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="column content">
|
|
||||||
{markdown_view!(
|
|
||||||
r#"### My Work
|
|
||||||
|
|
||||||
I'm interested in a variety of things in the open source
|
|
||||||
world. Perhaps thanks to my pursuit of the perfect Linux desktop, this
|
|
||||||
has revolved a lot around reproducible build and deployment systems
|
|
||||||
for the last few years, initially starting with
|
|
||||||
[BuildStream](https://buildstream.build/) back in ~2017. I gave a
|
|
||||||
couple of talks on it at build meetups in the UK in subsequent years,
|
|
||||||
though sadly most evidence of that appears to have disappeared.
|
|
||||||
|
|
||||||
Since then this has culminated in a strong fondness for
|
|
||||||
[NixOS](https://nixos.org/) and Nix, as its active community makes
|
|
||||||
private use cases much more feasible. As such, I have a vested
|
|
||||||
interest in making this community as large as possible - I post a lot
|
|
||||||
on the NixOS [discourse](https://discourse.nixos.org/) trying to help
|
|
||||||
newcomers out where I can.
|
|
||||||
|
|
||||||
I also just enjoy Programming, my core languages for personal work are
|
|
||||||
currently probably Rust and Python, although I have a very varied
|
|
||||||
background. This is in part due to my former work as a consultant,
|
|
||||||
which required new languages every few months. I have experience from
|
|
||||||
JavaScript over Elm to Kotlin, but eventually I hope I might only need
|
|
||||||
to write Rust ;)
|
|
||||||
|
|
||||||
If you're interested in seeing these things for yourself, visit my
|
|
||||||
[Gitlab](https://gitlab.com/tlater) and
|
|
||||||
[GitHub](https://github.com/tlater) pages.
|
|
||||||
"#
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
use leptos::{html::Form, logging};
|
|
||||||
use leptos_meta::{Meta, Title};
|
|
||||||
use leptos::{prelude::*, server_fn::codec::JsonEncoding};
|
|
||||||
use markdown_view_leptos::markdown_view;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[server]
|
|
||||||
async fn submit_mail(mail: String, subject: String, message: String) -> Result<String, MailError> {
|
|
||||||
use crate::AppState;
|
|
||||||
const NTFY_TOPIC_CREDENTIAL_NAME: &str = "ntfy-topic";
|
|
||||||
|
|
||||||
let mail = format!("From: {}\nSubject: {}\n{}", mail, subject, message);
|
|
||||||
logging::log!("{}", mail);
|
|
||||||
|
|
||||||
let state = use_context::<AppState>().unwrap();
|
|
||||||
let ntfy_topic = std::fs::read_to_string(
|
|
||||||
state
|
|
||||||
.config
|
|
||||||
.credentials_directory
|
|
||||||
.join(NTFY_TOPIC_CREDENTIAL_NAME),
|
|
||||||
)?;
|
|
||||||
let ntfy_url = state.config.ntfy_instance.join(ntfy_topic.trim())?;
|
|
||||||
|
|
||||||
state
|
|
||||||
.http_client
|
|
||||||
.post(ntfy_url)
|
|
||||||
.body(mail)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|err| err.without_url())?
|
|
||||||
.error_for_status()
|
|
||||||
.map_err(|err| err.without_url())?;
|
|
||||||
|
|
||||||
leptos_axum::redirect("/mail");
|
|
||||||
Ok("Mail successfully sent!".to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
fn MailForm() -> impl IntoView {
|
|
||||||
let form: NodeRef<Form> = NodeRef::new();
|
|
||||||
let submit_mail = ServerAction::<SubmitMail>::new();
|
|
||||||
let pending = submit_mail.pending();
|
|
||||||
let flash_message = submit_mail.value();
|
|
||||||
|
|
||||||
Effect::new(move ||
|
|
||||||
if flash_message.get().is_some_and(|m| m.is_ok()) {
|
|
||||||
if let Some(form) = form.get() {
|
|
||||||
form.reset();
|
|
||||||
} else {
|
|
||||||
logging::warn!("Failed to reset form");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<Show when=move || { flash_message.get().is_some() }>
|
|
||||||
<div
|
|
||||||
class="notification is-light"
|
|
||||||
class=("is-success", move || flash_message.get().is_some_and(|m| m.is_ok()))
|
|
||||||
class=("is-danger", move || flash_message.get().is_some_and(|m| m.is_err()))
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="delete"
|
|
||||||
aria-label="Close"
|
|
||||||
on:click=move |_| {
|
|
||||||
*flash_message.write() = None;
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span role="alert">
|
|
||||||
{move || match flash_message.get() {
|
|
||||||
None => "".to_owned(),
|
|
||||||
Some(Ok(message)) => message,
|
|
||||||
Some(Err(error)) => format!("{}", error),
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<ActionForm node_ref=form action=submit_mail>
|
|
||||||
<fieldset disabled=move || pending.get()>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="mail">
|
|
||||||
Email address
|
|
||||||
</label>
|
|
||||||
<div class="control">
|
|
||||||
<input
|
|
||||||
id="mail"
|
|
||||||
class="input"
|
|
||||||
type="email"
|
|
||||||
placeholder="Your address"
|
|
||||||
name="mail"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="subject">
|
|
||||||
Subject
|
|
||||||
</label>
|
|
||||||
<div class="control">
|
|
||||||
<input
|
|
||||||
id="subject"
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
placeholder="E.g. There's a typo on your home page!"
|
|
||||||
name="subject"
|
|
||||||
autocomplete="off"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="message">
|
|
||||||
Message
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="message"
|
|
||||||
class="textarea"
|
|
||||||
type="text"
|
|
||||||
rows="6"
|
|
||||||
name="message"
|
|
||||||
autocomplete="off"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control">
|
|
||||||
<div class="field">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="button is-link"
|
|
||||||
class=("is-loading", move || pending.get())
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</ActionForm>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Mail() -> impl IntoView {
|
|
||||||
view! {
|
|
||||||
<Meta name="description" content="tlater.net mail submission" />
|
|
||||||
<Title text="Mail submission" />
|
|
||||||
|
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="title has-text-weight-normal">Contact Me</h1>
|
|
||||||
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
<MailForm />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column content">
|
|
||||||
{markdown_view!(
|
|
||||||
r#"
|
|
||||||
Any messages you enter here are directly forwarded to me. I aim to
|
|
||||||
respond within a day.
|
|
||||||
|
|
||||||
Don't be upset about the form, I want to avoid the spam publishing
|
|
||||||
your email address brings with it... And minimize the amount of mail
|
|
||||||
that doesn't reach me, this form is an exception in all my spam
|
|
||||||
filters, you see ;)
|
|
||||||
"#
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum MailError {
|
|
||||||
#[error("This form appears to currently be broken :(")]
|
|
||||||
Permanent,
|
|
||||||
#[error("Mail service appears to be down; please try again later")]
|
|
||||||
Temporary,
|
|
||||||
#[error("Server error: {0}")]
|
|
||||||
ServerFnError(ServerFnErrorErr),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<url::ParseError> for MailError {
|
|
||||||
fn from(error: url::ParseError) -> Self {
|
|
||||||
logging::error!("Invalid ntfy URL: {error}");
|
|
||||||
Self::Permanent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for MailError {
|
|
||||||
fn from(error: std::io::Error) -> Self {
|
|
||||||
logging::error!("Couldn't read ntfy topic secret: {error}");
|
|
||||||
Self::Permanent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<reqwest::Error> for MailError {
|
|
||||||
fn from(error: reqwest::Error) -> Self {
|
|
||||||
logging::error!("Failed to connect to ntfy: {error}");
|
|
||||||
Self::Temporary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromServerFnError for MailError {
|
|
||||||
type Encoder = JsonEncoding;
|
|
||||||
|
|
||||||
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
|
|
||||||
Self::ServerFnError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
use leptos::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Navbar() -> impl IntoView {
|
|
||||||
let (active, set_active) = signal(false);
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<a class="navbar-item has-text-primary is-uppercase" href="/">
|
|
||||||
tlater
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
on:click=move |_| { set_active.update(|active: &mut bool| *active = !*active) }
|
|
||||||
class="navbar-burger"
|
|
||||||
class=("is-active", move || active.get())
|
|
||||||
aria-label="menu"
|
|
||||||
aria-controls="main-navigation"
|
|
||||||
aria-expanded=move || if active.get() { "true" } else { "false" }
|
|
||||||
>
|
|
||||||
<span aria-hidden="true" />
|
|
||||||
<span aria-hidden="true" />
|
|
||||||
<span aria-hidden="true" />
|
|
||||||
<span aria-hidden="true" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main-navigation" class="navbar-menu" class=("is-active", move || active.get())>
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item" href="/mail">
|
|
||||||
"E-Mail"
|
|
||||||
</a>
|
|
||||||
<a class="navbar-item" href="https://www.gitlab.com/tlater">
|
|
||||||
GitLab
|
|
||||||
</a>
|
|
||||||
<a class="navbar-item" href="https://www.github.com/TLATER">
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
pub mod app;
|
|
||||||
mod components;
|
|
||||||
|
|
||||||
#[cfg(feature = "hydrate")]
|
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
|
||||||
pub fn hydrate() {
|
|
||||||
use crate::app::*;
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
leptos::mount::hydrate_body(App);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub use appstate::{AppState, Config};
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
mod appstate {
|
|
||||||
use axum::extract::FromRef;
|
|
||||||
use figment::{providers::Format, Figment};
|
|
||||||
use leptos::config::LeptosOptions;
|
|
||||||
use reqwest::Client;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
pub struct Config {
|
|
||||||
pub credentials_directory: PathBuf,
|
|
||||||
pub ntfy_instance: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn parse() -> Self {
|
|
||||||
let config_path = std::env::var_os("TLATERNET_CONFIG")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.or_else(|| {
|
|
||||||
std::env::current_dir()
|
|
||||||
.map(|dir| dir.join("config.toml"))
|
|
||||||
.ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
let config: Result<Config, figment::Error> = if let Some(config_path) = config_path {
|
|
||||||
Figment::new().merge(figment::providers::Toml::file(config_path))
|
|
||||||
} else {
|
|
||||||
Figment::new()
|
|
||||||
}
|
|
||||||
.merge(figment::providers::Env::raw().only(&["CREDENTIALS_DIRECTORY"]))
|
|
||||||
.merge(figment::providers::Env::prefixed("TLATERNET_"))
|
|
||||||
.extract();
|
|
||||||
|
|
||||||
match config {
|
|
||||||
Ok(config) => {
|
|
||||||
if !config.credentials_directory.join("ntfy-topic").exists() {
|
|
||||||
leptos::logging::error!("Failed to find ntfy-topic credential");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
config
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
leptos::logging::error!("Failed to parse configuration: {:?}", error);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(FromRef, Debug, Clone)]
|
|
||||||
pub struct AppState {
|
|
||||||
pub config: Config,
|
|
||||||
pub http_client: Client,
|
|
||||||
pub leptos_options: LeptosOptions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
use axum::Router;
|
|
||||||
use leptos::logging::log;
|
|
||||||
use leptos::prelude::*;
|
|
||||||
use leptos_axum::{LeptosRoutes, generate_route_list};
|
|
||||||
use tlaternet_webserver::app::*;
|
|
||||||
use tlaternet_webserver::{AppState, Config};
|
|
||||||
|
|
||||||
let config = Config::parse();
|
|
||||||
|
|
||||||
let (addr, leptos_options) = {
|
|
||||||
let conf = get_configuration(None).unwrap();
|
|
||||||
let addr = conf.leptos_options.site_addr;
|
|
||||||
let leptos_options = conf.leptos_options;
|
|
||||||
(addr, leptos_options)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate the list of routes in your Leptos App
|
|
||||||
let routes = generate_route_list(App);
|
|
||||||
let http_client = reqwest::Client::new();
|
|
||||||
|
|
||||||
let state = AppState {
|
|
||||||
config,
|
|
||||||
http_client,
|
|
||||||
leptos_options,
|
|
||||||
};
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.leptos_routes(&state, routes, {
|
|
||||||
let leptos_options = state.leptos_options.clone();
|
|
||||||
move || shell(leptos_options.clone())
|
|
||||||
})
|
|
||||||
.fallback::<_, (_, _, axum::extract::State<AppState>, _)>(
|
|
||||||
leptos_axum::file_and_error_handler(shell),
|
|
||||||
)
|
|
||||||
.with_state(state);
|
|
||||||
|
|
||||||
// run our app with hyper
|
|
||||||
// `axum::Server` is a re-export of `hyper::Server`
|
|
||||||
log!("listening on http://{}", &addr);
|
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
|
||||||
axum::serve(listener, app.into_make_service())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "ssr"))]
|
|
||||||
pub fn main() {
|
|
||||||
// no client-side main function
|
|
||||||
// unless we want this to work with e.g., Trunk for pure client-side testing
|
|
||||||
// see lib.rs for hydration function instead
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
@use "bulma/sass/utilities/initial-variables" as iv with (
|
|
||||||
$black: #0f0f0f,
|
|
||||||
$grey-darker: #11151c,
|
|
||||||
$grey-light: #dddddd,
|
|
||||||
$white: #ffffff,
|
|
||||||
|
|
||||||
$orange: #d26937,
|
|
||||||
$yellow: #b58900,
|
|
||||||
$green: #2aa889,
|
|
||||||
$cyan: #99d1ce,
|
|
||||||
$blue: #195466,
|
|
||||||
$red: #dc322f,
|
|
||||||
);
|
|
||||||
|
|
||||||
iv.$family-sans-serif: "Nunito", iv.$family-sans-serif;
|
|
||||||
iv.$family-monospace: "Hack", iv.$family-monospace;
|
|
||||||
|
|
||||||
@forward "bulma/sass/utilities/functions";
|
|
||||||
@use "bulma/sass/utilities/derived-variables" with (
|
|
||||||
$link: iv.$cyan,
|
|
||||||
$primary: iv.$cyan,
|
|
||||||
);
|
|
||||||
@forward "bulma/sass/utilities/controls";
|
|
||||||
|
|
||||||
@forward "bulma/sass/form";
|
|
||||||
|
|
||||||
@forward "bulma/sass/base" with (
|
|
||||||
$body-background-color: iv.$black,
|
|
||||||
$body-color: iv.$grey-light,
|
|
||||||
|
|
||||||
$hr-background-color: iv.$grey-light,
|
|
||||||
$hr-height: 1px,
|
|
||||||
);
|
|
||||||
@forward "bulma/sass/themes";
|
|
||||||
|
|
||||||
@forward "bulma/sass/elements/button";
|
|
||||||
@use "bulma/sass/elements/content" with (
|
|
||||||
$content-heading-weight: iv.$weight-semibold,
|
|
||||||
);
|
|
||||||
@use "bulma/sass/elements/notification";
|
|
||||||
@use "bulma/sass/elements/delete";
|
|
||||||
|
|
||||||
@use "bulma/sass/elements/title" with (
|
|
||||||
$title-color: iv.$cyan,
|
|
||||||
);
|
|
||||||
|
|
||||||
@forward "bulma/sass/grid/columns";
|
|
||||||
|
|
||||||
@forward "bulma/sass/helpers/typography";
|
|
||||||
@forward "bulma/sass/helpers/color";
|
|
||||||
|
|
||||||
@forward "bulma/sass/layout/container";
|
|
||||||
@forward "bulma/sass/layout/section";
|
|
||||||
|
|
||||||
@forward "bulma/sass/components/navbar" with (
|
|
||||||
$navbar-burger-color: iv.$grey-light,
|
|
||||||
);
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
@use "@fontsource-utils/scss/src/mixins" as fontsource with (
|
|
||||||
$display: auto
|
|
||||||
);
|
|
||||||
@use "@fontsource-variable/arimo/scss/metadata.scss" as arimo;
|
|
||||||
@use "@fontsource-variable/nunito/scss/metadata.scss" as nunito;
|
|
||||||
|
|
||||||
@include fontsource.faces(
|
|
||||||
$metadata: nunito.$metadata,
|
|
||||||
$weights: (
|
|
||||||
300,
|
|
||||||
400,
|
|
||||||
500,
|
|
||||||
600,
|
|
||||||
700,
|
|
||||||
),
|
|
||||||
$subsets: latin,
|
|
||||||
$styles: (
|
|
||||||
normal,
|
|
||||||
italic,
|
|
||||||
),
|
|
||||||
$family: "Nunito",
|
|
||||||
$directory: "/@fontsource-variable/nunito"
|
|
||||||
);
|
|
||||||
|
|
||||||
@include fontsource.faces(
|
|
||||||
$metadata: arimo.$metadata,
|
|
||||||
$weights: 400,
|
|
||||||
$subsets: latin,
|
|
||||||
$styles: normal,
|
|
||||||
$family: "Arimo",
|
|
||||||
$directory: "/@fontsource-variable/arimo"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Hack *does* come with its own CSS, but it's broken and hasn't seen
|
|
||||||
// a release since https://github.com/source-foundry/Hack/issues/467
|
|
||||||
// was resolved.
|
|
||||||
|
|
||||||
$variants: regular normal 400, bold normal 700, italic italic 400, bolditalic italic 700;
|
|
||||||
|
|
||||||
@each $name, $style, $weights in $variants {
|
|
||||||
@font-face {
|
|
||||||
font-family: "Hack";
|
|
||||||
font-style: $style;
|
|
||||||
font-display: auto;
|
|
||||||
font-weight: $weights;
|
|
||||||
src: url("/hack-font/hack-#{$name}-subset.woff2") format("woff2-variations");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
@use "fonts";
|
|
||||||
@use "custom-bulma";
|
|
||||||
@use "typed";
|
|
||||||
|
|
||||||
#typed-welcome {
|
|
||||||
@include typed.typed-text(typed-welcome, "Welcome to tlater.net!", 1.2s);
|
|
||||||
}
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
@use "sass:math";
|
|
||||||
@use "sass:list";
|
|
||||||
|
|
||||||
@mixin test {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin typed-text($id, $text, $duration) {
|
|
||||||
&::before {
|
|
||||||
@include typed($id, $text, $duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
@include cursor($id, 6s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Animate a blinking cursor.
|
|
||||||
@mixin cursor($id, $duration) {
|
|
||||||
$name: cursor-#{$id};
|
|
||||||
// The number of times we need to blink is = the number of full
|
|
||||||
// seconds (500ms * 2) that fit in the total duration, rounded up,
|
|
||||||
// and doubled.
|
|
||||||
$iterations: math.ceil(math.div($duration, 1s)) * 2;
|
|
||||||
|
|
||||||
animation: $name ease-in-out 500ms $iterations alternate;
|
|
||||||
content: " ";
|
|
||||||
|
|
||||||
@keyframes #{$name} {
|
|
||||||
from {
|
|
||||||
content: " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
content: "█";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Animate a piece of text as if it was being typed by a human.
|
|
||||||
@mixin typed($id, $text, $duration) {
|
|
||||||
word-break: break-all;
|
|
||||||
|
|
||||||
// We don't want a linearly typed set of text, which makes this
|
|
||||||
// significantly more complex.
|
|
||||||
//
|
|
||||||
// CSS animations normally do not permit per-frame changes in
|
|
||||||
// duration (since the total animation time is fixed). This means we
|
|
||||||
// need to create multiple animations, and delay them so that they
|
|
||||||
// happen in the time sequence we want.
|
|
||||||
//
|
|
||||||
// We generate the raw values with _generate-animations, and then
|
|
||||||
// split up the result into the animation API.
|
|
||||||
$frames: str-length($text);
|
|
||||||
$animations: _generate-animations($id, $frames, 1.2s);
|
|
||||||
|
|
||||||
animation-name: _unzip($animations, 1);
|
|
||||||
animation-delay: _unzip($animations, 3);
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
content: "";
|
|
||||||
|
|
||||||
// We need to type each character in separate animations, see above
|
|
||||||
// comment.
|
|
||||||
@each $name, $character in $animations {
|
|
||||||
@keyframes #{$name} {
|
|
||||||
from {
|
|
||||||
content: str-slice($text, 0, $character);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
content: str-slice($text, 0, $character + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unzip a nested set of lists, taking the nth value of each sublist.
|
|
||||||
@function _unzip($lists, $i) {
|
|
||||||
$out: ();
|
|
||||||
$sep: comma;
|
|
||||||
@each $sublist in $lists {
|
|
||||||
$out: list.append($out, list.nth($sublist, $i), $sep);
|
|
||||||
}
|
|
||||||
@return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the sum of all numbers in a list.
|
|
||||||
@function _sum($list) {
|
|
||||||
$out: 0;
|
|
||||||
@each $val in $list {
|
|
||||||
$out: $out + $val;
|
|
||||||
}
|
|
||||||
@return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produce a list from a shorter list by repeating it up until size
|
|
||||||
/// $length.
|
|
||||||
@function _round-robin($base, $length) {
|
|
||||||
$out: ();
|
|
||||||
$sep: list.separator($out);
|
|
||||||
@for $i from 0 through $length {
|
|
||||||
$out: list.append($out, list.nth($base, $i % list.length($base) + 1));
|
|
||||||
}
|
|
||||||
@return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate the actual animation values.
|
|
||||||
///
|
|
||||||
/// This generates a nested list as:
|
|
||||||
///
|
|
||||||
/// (keyframe-name, index, start time)
|
|
||||||
///
|
|
||||||
/// The duration of each frame is taken from the internal $delays in a
|
|
||||||
/// round robin fashion, to give some amount of human-like variance to
|
|
||||||
/// the duration of each frame.
|
|
||||||
///
|
|
||||||
/// Start time is set to the time at which the frame should start to
|
|
||||||
/// achieve the desired frame-by-frame duration.
|
|
||||||
@function _generate-animations($id, $number, $total_duration) {
|
|
||||||
$out: ();
|
|
||||||
$sep: list.separator($out);
|
|
||||||
|
|
||||||
// A set of "human-like" delays for each typed character. In
|
|
||||||
// practice, my typing seems to be about 20-70ms, but it looks a bit
|
|
||||||
// nicer to increase all typing by 20ms to make the effect more
|
|
||||||
// noticeable.
|
|
||||||
//
|
|
||||||
// Numbers generated once with a random number generator, rather
|
|
||||||
// than using `math.random()`, since they end up in CSS verbatim,
|
|
||||||
// and the build would be non-reproducible if we didn't do it this
|
|
||||||
// way. Using `math.random() wouldn't change this dynamically each
|
|
||||||
// time the page loads anyway, so we don't really lose anything by
|
|
||||||
// pre-generating these numbers.
|
|
||||||
$delays: 69ms, 83ms, 49ms, 48ms, 52ms, 59ms, 40ms, 71ms, 80ms, 67ms;
|
|
||||||
|
|
||||||
@for $animation from 0 through $number {
|
|
||||||
$out: list.append(
|
|
||||||
$out,
|
|
||||||
(
|
|
||||||
type-#{$id}-#{$animation},
|
|
||||||
$animation,
|
|
||||||
_sum(_round_robin($delays, $animation))
|
|
||||||
),
|
|
||||||
$sep
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@return $out;
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
const self = "pkgs/packages/webserver/package.nix"
|
|
||||||
let tmpdir = mktemp -d webserver-update.XXXXXXXXXX
|
|
||||||
|
|
||||||
let dependencies = {
|
|
||||||
fontsource-scss: (prefetch-npm @fontsource-utils/scss)
|
|
||||||
fontsource-arimo: (prefetch-npm @fontsource-variable/arimo)
|
|
||||||
fontsource-nunito: (prefetch-npm @fontsource-variable/nunito)
|
|
||||||
|
|
||||||
bulma: (prefetch-github jgthms bulma)
|
|
||||||
hack-font: (prefetch-github source-foundry Hack)
|
|
||||||
}
|
|
||||||
|
|
||||||
cd (git rev-parse --show-toplevel)
|
|
||||||
|
|
||||||
$dependencies | items {|name, metadata| $metadata | update-dependency $name }
|
|
||||||
|
|
||||||
rm -r $tmpdir
|
|
||||||
|
|
||||||
cd (dirname $self)
|
|
||||||
cargo update
|
|
||||||
|
|
||||||
def prefetch-npm [package: string] {
|
|
||||||
let metadata = http get $'https://registry.npmjs.org/($package)'
|
|
||||||
let version = $metadata.dist-tags.latest
|
|
||||||
let url = ($metadata.versions | get $version).dist.tarball
|
|
||||||
let tarball = ($tmpdir | path join "package.tgz")
|
|
||||||
|
|
||||||
http get $url | save -f $tarball
|
|
||||||
|
|
||||||
let hash = nix hash file $tarball
|
|
||||||
|
|
||||||
{
|
|
||||||
url: $url
|
|
||||||
version: $version
|
|
||||||
hash: $hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def prefetch-github [owner: string, repo: string] {
|
|
||||||
let metadata = http get $'https://api.github.com/repos/($owner)/($repo)/releases/latest'
|
|
||||||
let prefetch = nix-prefetch-github --rev $metadata.tag_name --json $owner $repo | from json
|
|
||||||
$prefetch | select hash | insert version ($metadata.name | str trim --left --char v)
|
|
||||||
}
|
|
||||||
|
|
||||||
def update-dependency [dependency_name: string] {
|
|
||||||
const replace_attribute_template = "
|
|
||||||
id: update-attribute
|
|
||||||
language: nix
|
|
||||||
utils:
|
|
||||||
is-attribute:
|
|
||||||
kind: string_fragment
|
|
||||||
inside:
|
|
||||||
kind: binding
|
|
||||||
stopBy: end
|
|
||||||
has:
|
|
||||||
field: attrpath
|
|
||||||
regex: '{attribute}'
|
|
||||||
|
|
||||||
rule:
|
|
||||||
matches: is-attribute
|
|
||||||
not:
|
|
||||||
regex: '{replacement}'
|
|
||||||
inside:
|
|
||||||
kind: binding
|
|
||||||
stopBy: end
|
|
||||||
has:
|
|
||||||
field: attrpath
|
|
||||||
regex: '{dependency_name}'
|
|
||||||
|
|
||||||
fix: '{replacement}'"
|
|
||||||
|
|
||||||
let template_data = (
|
|
||||||
$in | if ($in has url) {
|
|
||||||
[{attribute: url replacement: $in.url}]
|
|
||||||
} else { [] }
|
|
||||||
) ++ [
|
|
||||||
{attribute: version replacement: $in.version}
|
|
||||||
{attribute: hash replacement: $in.hash}
|
|
||||||
];
|
|
||||||
|
|
||||||
let ast_grep_rule = (
|
|
||||||
$template_data
|
|
||||||
| each { $in | insert dependency_name $dependency_name | format pattern $replace_attribute_template }
|
|
||||||
| str join "\n---\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
ast-grep scan --update-all --inline-rules $ast_grep_rule $self
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue