feat(nixos): add org-agenda-api hosting with nginx + Let's Encrypt
Add NixOS module to host org-agenda-api container on railbird-sf: - org-agenda-api-host.nix: New module with nginx reverse proxy and ACME - nginx configured for rbsf.tplinkdns.com with automatic TLS - Container runs on port 51847 (random high port) - Supports nix-built container images via imageFile option Configure railbird-sf to use the new module: - Build org-agenda-api container from flake - Pass container to machine config via specialArgs - Set up agenix secret for container environment Note: Requires creating secrets file with AUTH_PASSWORD and GIT_SSH_PRIVATE_KEY environment variables. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
./wsl.nix
|
./wsl.nix
|
||||||
./wyoming.nix
|
./wyoming.nix
|
||||||
./xmonad.nix
|
./xmonad.nix
|
||||||
|
./org-agenda-api-host.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
|
|||||||
@@ -223,6 +223,20 @@
|
|||||||
};
|
};
|
||||||
defaultConfigurationParams =
|
defaultConfigurationParams =
|
||||||
builtins.listToAttrs (map mkConfigurationParams machineFilenames);
|
builtins.listToAttrs (map mkConfigurationParams machineFilenames);
|
||||||
|
# Build org-agenda-api container for a given system
|
||||||
|
mkOrgAgendaApiContainer = system: let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
orgApiRev = builtins.substring 0 7 (org-agenda-api.rev or "unknown");
|
||||||
|
dotfilesRev = builtins.substring 0 7 (self.rev or self.dirtyRev or "dirty");
|
||||||
|
dotfilesOrgApi = import ./org-agenda-api.nix {
|
||||||
|
inherit pkgs system inputs;
|
||||||
|
};
|
||||||
|
tangledConfig = dotfilesOrgApi.org-agenda-custom-config;
|
||||||
|
containerLib = import ../org-agenda-api/container.nix {
|
||||||
|
inherit pkgs system tangledConfig org-agenda-api orgApiRev dotfilesRev;
|
||||||
|
};
|
||||||
|
in containerLib.containers.colonelpanic;
|
||||||
|
|
||||||
customParams = {
|
customParams = {
|
||||||
biskcomp = {
|
biskcomp = {
|
||||||
system = "aarch64-linux";
|
system = "aarch64-linux";
|
||||||
@@ -230,6 +244,11 @@
|
|||||||
air-gapped-pi = {
|
air-gapped-pi = {
|
||||||
system = "aarch64-linux";
|
system = "aarch64-linux";
|
||||||
};
|
};
|
||||||
|
railbird-sf = {
|
||||||
|
specialArgs = {
|
||||||
|
orgAgendaApiContainer = mkOrgAgendaApiContainer "x86_64-linux";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
mkConfig = {
|
mkConfig = {
|
||||||
system ? "x86_64-linux",
|
system ? "x86_64-linux",
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
{ config, lib, pkgs, forEachUser, ... }:
|
{ config, lib, pkgs, forEachUser, inputs, orgAgendaApiContainer ? null, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../configuration.nix
|
../configuration.nix
|
||||||
|
inputs.agenix.nixosModules.default
|
||||||
];
|
];
|
||||||
|
|
||||||
networking.hostName = "railbird-sf";
|
networking.hostName = "railbird-sf";
|
||||||
|
|
||||||
|
# org-agenda-api hosting with nginx + Let's Encrypt
|
||||||
|
age.secrets.org-api-env = {
|
||||||
|
file = ../secrets/org-api-passwords.age;
|
||||||
|
# Readable by the podman container service
|
||||||
|
};
|
||||||
|
|
||||||
|
services.org-agenda-api-host = {
|
||||||
|
enable = true;
|
||||||
|
domain = "rbsf.tplinkdns.com";
|
||||||
|
containerImage = "colonelpanic-org-agenda-api";
|
||||||
|
containerImageFile = orgAgendaApiContainer;
|
||||||
|
secretsFile = config.age.secrets.org-api-env.path;
|
||||||
|
};
|
||||||
|
|
||||||
hardware.enableRedistributableFirmware = true;
|
hardware.enableRedistributableFirmware = true;
|
||||||
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
|
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
|
||||||
boot.initrd.kernelModules = [ ];
|
boot.initrd.kernelModules = [ ];
|
||||||
|
|||||||
137
nixos/org-agenda-api-host.nix
Normal file
137
nixos/org-agenda-api-host.nix
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# org-agenda-api-host.nix - Host org-agenda-api container with nginx + Let's Encrypt
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.org-agenda-api-host;
|
||||||
|
# Random high port to avoid conflicts
|
||||||
|
containerPort = 51847;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.org-agenda-api-host = {
|
||||||
|
enable = mkEnableOption "org-agenda-api container hosting";
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "rbsf.tplinkdns.com";
|
||||||
|
description = "Domain name for the service (used for Let's Encrypt)";
|
||||||
|
};
|
||||||
|
|
||||||
|
acmeEmail = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "IvanMalison@gmail.com";
|
||||||
|
description = "Email for Let's Encrypt certificate notifications";
|
||||||
|
};
|
||||||
|
|
||||||
|
containerImageFile = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
description = "Nix-built container image (tarball from dockerTools)";
|
||||||
|
};
|
||||||
|
|
||||||
|
containerImage = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "colonelpanic-org-agenda-api";
|
||||||
|
description = "Container image name (used when imageFile is provided)";
|
||||||
|
};
|
||||||
|
|
||||||
|
gitSyncRepository = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "git@github.com:colonelpanic8/org.git";
|
||||||
|
description = "Git repository to sync org files from";
|
||||||
|
};
|
||||||
|
|
||||||
|
gitUserEmail = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "IvanMalison@gmail.com";
|
||||||
|
description = "Git user email for commits";
|
||||||
|
};
|
||||||
|
|
||||||
|
gitUserName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "Ivan Malison";
|
||||||
|
description = "Git user name for commits";
|
||||||
|
};
|
||||||
|
|
||||||
|
authUser = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "imalison";
|
||||||
|
description = "Basic auth username";
|
||||||
|
};
|
||||||
|
|
||||||
|
secretsFile = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = "Path to agenix-decrypted secrets file containing AUTH_PASSWORD and GIT_SSH_PRIVATE_KEY";
|
||||||
|
};
|
||||||
|
|
||||||
|
timezone = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "America/Los_Angeles";
|
||||||
|
description = "Timezone for the container";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# Enable ACME for Let's Encrypt
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults.email = cfg.acmeEmail;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Nginx reverse proxy with TLS
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
recommendedTlsSettings = true;
|
||||||
|
recommendedOptimisation = true;
|
||||||
|
recommendedGzipSettings = true;
|
||||||
|
|
||||||
|
virtualHosts.${cfg.domain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:${toString containerPort}";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_connect_timeout 75s;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Open firewall for HTTP/HTTPS
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||||
|
|
||||||
|
# Container service using podman
|
||||||
|
virtualisation.oci-containers = {
|
||||||
|
backend = "podman";
|
||||||
|
containers.org-agenda-api = {
|
||||||
|
image = cfg.containerImage;
|
||||||
|
imageFile = cfg.containerImageFile;
|
||||||
|
autoStart = true;
|
||||||
|
ports = [ "127.0.0.1:${toString containerPort}:80" ];
|
||||||
|
environment = {
|
||||||
|
TZ = cfg.timezone;
|
||||||
|
GIT_SYNC_REPOSITORY = cfg.gitSyncRepository;
|
||||||
|
GIT_USER_EMAIL = cfg.gitUserEmail;
|
||||||
|
GIT_USER_NAME = cfg.gitUserName;
|
||||||
|
AUTH_USER = cfg.authUser;
|
||||||
|
};
|
||||||
|
environmentFiles = [ cfg.secretsFile ];
|
||||||
|
extraOptions = [
|
||||||
|
"--pull=never" # Image is from nix store, don't try to pull
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ensure container restarts on failure
|
||||||
|
systemd.services.podman-org-agenda-api = {
|
||||||
|
serviceConfig = {
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "10s";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ in
|
|||||||
"discourse-admin-password.age".publicKeys = keys.hostKeys;
|
"discourse-admin-password.age".publicKeys = keys.hostKeys;
|
||||||
"discourse-secret-key-base.age".publicKeys = keys.hostKeys;
|
"discourse-secret-key-base.age".publicKeys = keys.hostKeys;
|
||||||
"vaultwarden-environment-file.age".publicKeys = keys.hostKeys;
|
"vaultwarden-environment-file.age".publicKeys = keys.hostKeys;
|
||||||
"org-api-passwords.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys;
|
"org-api-passwords.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys ++ keys.railbird-sf;
|
||||||
"google-assistant-integration-service-key.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys;
|
"google-assistant-integration-service-key.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys;
|
||||||
"zwave-js.json.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys;
|
"zwave-js.json.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user