forked from Fediversity/Fediversity
terraform-nixos #1
34 changed files with 801 additions and 46 deletions
21
flake.lock
generated
21
flake.lock
generated
|
@ -571,6 +571,26 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"home-manager_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1743860185,
|
||||||
|
"narHash": "sha256-TkhfJ+vH+iGxLQL6RJLObMmldAQpysVJ+p1WnnKyIeQ=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "b5e29565131802cc8adee7dccede794226da8614",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mk-naked-shell": {
|
"mk-naked-shell": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
@ -1215,6 +1235,7 @@
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"git-hooks": "git-hooks",
|
"git-hooks": "git-hooks",
|
||||||
|
"home-manager": "home-manager_2",
|
||||||
"nixops4": "nixops4",
|
"nixops4": "nixops4",
|
||||||
"nixops4-nixos": "nixops4-nixos",
|
"nixops4-nixos": "nixops4-nixos",
|
||||||
"nixpkgs": "nixpkgs_7"
|
"nixpkgs": "nixpkgs_7"
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
git-hooks.url = "github:cachix/git-hooks.nix";
|
git-hooks.url = "github:cachix/git-hooks.nix";
|
||||||
|
home-manager.url = "github:nix-community/home-manager";
|
||||||
|
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
agenix.url = "github:ryantm/agenix";
|
agenix.url = "github:ryantm/agenix";
|
||||||
|
|
||||||
disko.url = "github:nix-community/disko";
|
disko.url = "github:nix-community/disko";
|
||||||
|
@ -43,7 +45,10 @@
|
||||||
pre-commit.settings.hooks =
|
pre-commit.settings.hooks =
|
||||||
let
|
let
|
||||||
## Add a directory here if pre-commit hooks shouldn't apply to it.
|
## Add a directory here if pre-commit hooks shouldn't apply to it.
|
||||||
optout = [ "npins" ];
|
optout = [
|
||||||
|
"npins"
|
||||||
|
"launch/.terraform"
|
||||||
|
];
|
||||||
excludes = map (dir: "^${dir}/") optout;
|
excludes = map (dir: "^${dir}/") optout;
|
||||||
addExcludes = lib.mapAttrs (_: c: c // { inherit excludes; });
|
addExcludes = lib.mapAttrs (_: c: c // { inherit excludes; });
|
||||||
in
|
in
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{ lib, ... }:
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (lib) mkDefault;
|
inherit (lib) mkDefault;
|
||||||
|
nixPath = "/run/current-system/nixpkgs";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
@ -16,6 +16,15 @@ in
|
||||||
system.stateVersion = "24.05"; # do not change
|
system.stateVersion = "24.05"; # do not change
|
||||||
nixpkgs.hostPlatform = mkDefault "x86_64-linux";
|
nixpkgs.hostPlatform = mkDefault "x86_64-linux";
|
||||||
|
|
||||||
|
# use flake's nixpkgs over channels
|
||||||
|
nix.nixPath = [ "nixpkgs=${nixPath}" ];
|
||||||
|
system.extraSystemBuilderCmds = ''
|
||||||
|
ln -sv ${pkgs.path} $out/nixpkgs
|
||||||
|
'';
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"L+ ${nixPath} - - - - ${pkgs.path}"
|
||||||
|
];
|
||||||
|
|
||||||
## This is just nice to have, but it is also particularly important for the
|
## This is just nice to have, but it is also particularly important for the
|
||||||
## Forgejo CI runners because the Nix configuration in the actions is directly
|
## Forgejo CI runners because the Nix configuration in the actions is directly
|
||||||
## taken from here.
|
## taken from here.
|
||||||
|
|
|
@ -34,6 +34,7 @@ in
|
||||||
imports = [
|
imports = [
|
||||||
inputs.agenix.nixosModules.default
|
inputs.agenix.nixosModules.default
|
||||||
inputs.disko.nixosModules.default
|
inputs.disko.nixosModules.default
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
./options.nix
|
./options.nix
|
||||||
./nixos
|
./nixos
|
||||||
];
|
];
|
||||||
|
@ -54,6 +55,10 @@ in
|
||||||
|
|
||||||
## FIXME: Remove direct root authentication once the NixOps4 NixOS provider
|
## FIXME: Remove direct root authentication once the NixOps4 NixOS provider
|
||||||
## supports users with password-less sudo.
|
## supports users with password-less sudo.
|
||||||
users.users.root.openssh.authorizedKeys.keys = attrValues keys.contributors;
|
users.users.root.openssh.authorizedKeys.keys = attrValues keys.contributors ++ [
|
||||||
|
# allow our panel vm access to the test machines
|
||||||
|
keys.panel
|
||||||
|
];
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,26 @@ let
|
||||||
{ vmName, isTestVm }:
|
{ vmName, isTestVm }:
|
||||||
{
|
{
|
||||||
_module.args = { inherit inputs; };
|
_module.args = { inherit inputs; };
|
||||||
imports = [
|
imports =
|
||||||
./common/resource.nix
|
[
|
||||||
(if isTestVm then ./test-machines + "/${vmName}" else ./machines + "/${vmName}")
|
./common/resource.nix
|
||||||
];
|
]
|
||||||
|
++ (
|
||||||
|
if isTestVm then
|
||||||
|
[
|
||||||
|
./test-machines/${vmName}
|
||||||
|
{
|
||||||
|
nixos.module.users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
# allow our panel vm access to the test machines
|
||||||
|
(import ../keys).panel
|
||||||
|
];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[
|
||||||
|
./machines/${vmName}
|
||||||
|
]
|
||||||
|
);
|
||||||
fediversityVm.name = vmName;
|
fediversityVm.name = vmName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,46 @@ in
|
||||||
defaults.email = "beheer@procolix.com";
|
defaults.email = "beheer@procolix.com";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
age.secrets.panel-ssh-key = {
|
||||||
|
owner = name;
|
||||||
|
mode = "400";
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.ssh.startAgent = true;
|
||||||
|
|
||||||
|
home-manager = {
|
||||||
|
users.${name}.home = {
|
||||||
|
stateVersion = "25.05";
|
||||||
|
file.".ssh/config" = {
|
||||||
|
text = ''
|
||||||
|
IdentityFile ${config.age.secrets.panel-ssh-key.path}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
services.${name} = {
|
services.${name} = {
|
||||||
enable = true;
|
enable = true;
|
||||||
production = true;
|
production = true;
|
||||||
domain = "demo.fediversity.eu";
|
domain = "demo.fediversity.eu";
|
||||||
|
# FIXME: make it work without this duplication
|
||||||
|
settings =
|
||||||
|
let
|
||||||
|
cfg = config.services.${name};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
STATIC_ROOT = "/var/lib/${name}/static";
|
||||||
|
DEBUG = false;
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
cfg.domain
|
||||||
|
cfg.host
|
||||||
|
"localhost"
|
||||||
|
"[::1]"
|
||||||
|
];
|
||||||
|
CSRF_TRUSTED_ORIGINS = [ "https://${cfg.domain}" ];
|
||||||
|
COMPRESS_OFFLINE = true;
|
||||||
|
LIBSASS_OUTPUT_STYLE = "compressed";
|
||||||
|
};
|
||||||
secrets = {
|
secrets = {
|
||||||
SECRET_KEY = config.age.secrets.panel-secret-key.path;
|
SECRET_KEY = config.age.secrets.panel-secret-key.path;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,4 +34,5 @@ in
|
||||||
{
|
{
|
||||||
contributors = collectKeys ./contributors;
|
contributors = collectKeys ./contributors;
|
||||||
systems = collectKeys ./systems;
|
systems = collectKeys ./systems;
|
||||||
|
panel = removeTrailingWhitespace (readFile ./panel-ssh-key.pub);
|
||||||
}
|
}
|
||||||
|
|
1
keys/panel-ssh-key.pub
Normal file
1
keys/panel-ssh-key.pub
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICWfTPs64ImqGh3c/Y+3zqB9YVr5ApsKiS/aTLGXUTzb panel@fedi201
|
10
launch/.envrc
Normal file
10
launch/.envrc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# the shebang is ignored, but nice for editors
|
||||||
|
|
||||||
|
# shellcheck shell=bash
|
||||||
|
if type -P lorri &>/dev/null; then
|
||||||
|
eval "$(lorri direnv --flake .)"
|
||||||
|
else
|
||||||
|
echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
|
||||||
|
use flake
|
||||||
|
fi
|
5
launch/.gitignore
vendored
Normal file
5
launch/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.auto.tfvars.json
|
||||||
|
.npins.json
|
||||||
|
.terraform/
|
||||||
|
.terraform.tfstate.lock.info
|
||||||
|
terraform.tfstate*
|
16
launch/.terraform.lock.hcl
generated
Normal file
16
launch/.terraform.lock.hcl
generated
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# This file is maintained automatically by "tofu init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/hashicorp/external" {
|
||||||
|
version = "2.3.4"
|
||||||
|
hashes = [
|
||||||
|
"h1:HfVaWMC7Tz+tRfoWZtGCX2MATcgX3HsexoirWdi/voo=",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/hashicorp/null" {
|
||||||
|
version = "3.2.3"
|
||||||
|
hashes = [
|
||||||
|
"h1:qTlGDGC3RmXIPLgwsIh4LHG/DrAR6T6L+Wn6egnQnwE=",
|
||||||
|
]
|
||||||
|
}
|
24
launch/README.md
Normal file
24
launch/README.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# service deployment
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
<-- TODO: port to just -->
|
||||||
|
|
||||||
|
### updating npins
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd launch/
|
||||||
|
$ echo "$(nix eval --json -f ../npins)" > .npins.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### local development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ nix-shell
|
||||||
|
$ eval "$(ssh-agent -s)"
|
||||||
|
# set your ssh key, e.g.:
|
||||||
|
$ ssh_key="$(readlink -f ~/.ssh/id_ed25519)"
|
||||||
|
$ echo "{\"ssh_private_key_file\": \"${ssh_key}\", \"deploy_environment\": {\"SSH_AUTH_SOCK\": \"${SSH_AUTH_SOCK}\"}}" > .auto.tfvars.json
|
||||||
|
$ rm -rf .terraform/
|
||||||
|
$ tofu init
|
||||||
|
```
|
31
launch/default.nix
Normal file
31
launch/default.nix
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
system ? builtins.currentSystem,
|
||||||
|
sources ? import ../npins,
|
||||||
|
inputs ? import sources.flake-inputs {
|
||||||
|
root = ../.;
|
||||||
|
},
|
||||||
|
# match the same version of opentofu that is deployed by the root flake
|
||||||
|
pkgs ? import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
},
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
shell = pkgs.mkShellNoCC {
|
||||||
|
packages = [
|
||||||
|
pkgs.npins
|
||||||
|
pkgs.jaq # tf
|
||||||
|
(import ./tf.nix { inherit lib pkgs; })
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# re-export inputs so they can be overridden granularly
|
||||||
|
# (they can't be accessed from the outside any other way)
|
||||||
|
inherit
|
||||||
|
sources
|
||||||
|
system
|
||||||
|
pkgs
|
||||||
|
;
|
||||||
|
}
|
34
launch/garage.nix
Normal file
34
launch/garage.nix
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
## NOTE: All of these secrets are publicly available in this source file
|
||||||
|
## and will end up in the Nix store. We don't care as they are only ever
|
||||||
|
## used for testing anyway.
|
||||||
|
##
|
||||||
|
## FIXME: Generate and store in NixOps4's state.
|
||||||
|
mastodonS3KeyConfig =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
|
||||||
|
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
|
||||||
|
};
|
||||||
|
peertubeS3KeyConfig =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
|
||||||
|
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
|
||||||
|
};
|
||||||
|
pixelfedS3KeyConfig =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
|
||||||
|
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fediversity = {
|
||||||
|
garage.enable = true;
|
||||||
|
pixelfed = pixelfedS3KeyConfig { inherit pkgs; };
|
||||||
|
mastodon = mastodonS3KeyConfig { inherit pkgs; };
|
||||||
|
peertube = peertubeS3KeyConfig { inherit pkgs; };
|
||||||
|
};
|
||||||
|
}
|
179
launch/main.tf
Normal file
179
launch/main.tf
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
variable "domain" {
|
||||||
|
type = string
|
||||||
|
default = "fediversity.net"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "mastodon" {
|
||||||
|
type = object({
|
||||||
|
enable = bool
|
||||||
|
})
|
||||||
|
default = {
|
||||||
|
enable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pixelfed" {
|
||||||
|
type = object({
|
||||||
|
enable = bool
|
||||||
|
})
|
||||||
|
default = {
|
||||||
|
enable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "peertube" {
|
||||||
|
type = object({
|
||||||
|
enable = bool
|
||||||
|
})
|
||||||
|
default = {
|
||||||
|
enable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "initialUser" {
|
||||||
|
type = object({
|
||||||
|
displayName = string
|
||||||
|
username = string
|
||||||
|
email = string
|
||||||
|
# TODO: mark (nested) credentials as sensitive
|
||||||
|
# https://discuss.hashicorp.com/t/is-it-possible-to-mark-an-attribute-of-an-object-as-sensitive/24649/2
|
||||||
|
password = string
|
||||||
|
})
|
||||||
|
default = {
|
||||||
|
displayName = "Testy McTestface"
|
||||||
|
username = "test"
|
||||||
|
email = "test@test.com"
|
||||||
|
password = "testtest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ssh_private_key_file" {
|
||||||
|
type = string
|
||||||
|
description = "Path to private key used to connect to the target_host"
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "deploy_environment" {
|
||||||
|
type = map(string)
|
||||||
|
description = "Extra environment variables to be set during deployment."
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
system = "x86_64-linux"
|
||||||
|
pins = jsondecode(file("${path.module}/.npins.json"))
|
||||||
|
peripheral_configs = {
|
||||||
|
garage = "test01"
|
||||||
|
}
|
||||||
|
application_configs = {
|
||||||
|
mastodon = {
|
||||||
|
cfg = var.mastodon
|
||||||
|
hostname = "test06"
|
||||||
|
}
|
||||||
|
pixelfed = {
|
||||||
|
cfg = var.pixelfed
|
||||||
|
hostname = "test04"
|
||||||
|
}
|
||||||
|
peertube = {
|
||||||
|
cfg = var.peertube
|
||||||
|
hostname = "test05"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peripherals = { for name, inst in local.peripheral_configs : name => {
|
||||||
|
hostname = inst
|
||||||
|
cfg = {
|
||||||
|
enable = anytrue([for _, app in local.application_configs: app.cfg.enable])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# FIXME settle for pwd when in /nix/store?
|
||||||
|
# FIXME calculate separately to reduce false positives
|
||||||
|
data "external" "hash" {
|
||||||
|
program = ["echo", "{\"hash\":\"$(nix-hash ..)\"}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# merged instantiate/deploy to prevent 24+s instantiates when nothing changed.
|
||||||
|
# terraform-nixos separates these to only deploy if instantiate changed.
|
||||||
|
# FIXME find a better solution for this. current considerations were:
|
||||||
|
# - generic resources cannot have outputs, while we want info from the instantiation (unless built on host?).
|
||||||
|
# - `data` always runs, which is slow for deploy/instantiation.
|
||||||
|
resource "terraform_data" "nixos" {
|
||||||
|
for_each = {for name, inst in merge(
|
||||||
|
local.peripherals,
|
||||||
|
local.application_configs,
|
||||||
|
) : name => inst if inst.cfg.enable}
|
||||||
|
|
||||||
|
triggers_replace = [
|
||||||
|
data.external.hash.result,
|
||||||
|
var.deploy_environment,
|
||||||
|
var.domain,
|
||||||
|
var.initialUser,
|
||||||
|
local.system,
|
||||||
|
each.key,
|
||||||
|
each.value,
|
||||||
|
]
|
||||||
|
|
||||||
|
provisioner "local-exec" {
|
||||||
|
working_dir = path.root
|
||||||
|
environment = merge(var.deploy_environment, {
|
||||||
|
NIX_PATH = join(":", [for name, path in local.pins : "${name}=${path}"]),
|
||||||
|
})
|
||||||
|
# TODO: refactor back to command="ignoreme" interpreter=concat([]) to protect sensitive data from error logs?
|
||||||
|
# TODO: build on target?
|
||||||
|
command = <<-EOF
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# INSTANTIATE
|
||||||
|
command=(
|
||||||
|
nix-instantiate
|
||||||
|
--expr
|
||||||
|
'let
|
||||||
|
os = import <nixpkgs/nixos> {
|
||||||
|
system = "${local.system}";
|
||||||
|
configuration = {
|
||||||
|
terraform = builtins.fromJSON "${replace(jsonencode({
|
||||||
|
domain = var.domain
|
||||||
|
hostname = each.value.hostname
|
||||||
|
initialUser = var.initialUser
|
||||||
|
}), "\"", "\\\"")}";
|
||||||
|
imports = [
|
||||||
|
${path.root}/options.nix
|
||||||
|
${path.root}/shared.nix
|
||||||
|
${path.root}/${each.key}.nix
|
||||||
|
# FIXME: get VM details from TF
|
||||||
|
${path.root}/../infra/test-machines/${each.value.hostname}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
substituters = builtins.concatStringsSep " " os.config.nix.binaryCaches;
|
||||||
|
trusted_public_keys = builtins.concatStringsSep " " os.config.nix.binaryCachePublicKeys;
|
||||||
|
drv_path = os.config.system.build.toplevel.drvPath;
|
||||||
|
out_path = os.config.system.build.toplevel;
|
||||||
|
}'
|
||||||
|
)
|
||||||
|
"$${command[@]}" -A out_path
|
||||||
|
json="$("$${command[@]}" --eval --strict --json)"
|
||||||
|
|
||||||
|
# DEPLOY
|
||||||
|
declare substituters trusted_public_keys drv_path
|
||||||
|
eval "export $(echo $json | jaq -r 'to_entries | map("\(.key)=\(.value)") | @sh')"
|
||||||
|
host="root@${each.value.hostname}.abundos.eu" # FIXME: #24
|
||||||
|
buildArgs=(
|
||||||
|
--option extra-binary-caches https://cache.nixos.org/
|
||||||
|
--option substituters $substituters
|
||||||
|
--option trusted-public-keys $trusted_public_keys
|
||||||
|
)
|
||||||
|
sshOpts=(
|
||||||
|
-o StrictHostKeyChecking=no
|
||||||
|
-o BatchMode=yes
|
||||||
|
-o "IdentityFile='${var.ssh_private_key_file}'"
|
||||||
|
)
|
||||||
|
outPath=$(nix-store --realize "$drv_path" "$${buildArgs[@]}")
|
||||||
|
NIX_SSHOPTS="$${sshOpts[*]}" nix-copy-closure --to "$host" "$outPath" --gzip --use-substitutes
|
||||||
|
ssh "$${sshOpts[@]}" "$host" "nix-env --profile /nix/var/nix/profiles/system --set $outPath; $outPath/bin/switch-to-configuration switch"
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
}
|
17
launch/mastodon.nix
Normal file
17
launch/mastodon.nix
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
mastodonS3KeyConfig =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
|
||||||
|
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fediversity = {
|
||||||
|
mastodon = mastodonS3KeyConfig { inherit pkgs; } // {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
temp.cores = 1; # FIXME: should come from NixOps4 eventually
|
||||||
|
};
|
||||||
|
}
|
53
launch/options.nix
Normal file
53
launch/options.nix
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption;
|
||||||
|
inherit (types) str enum submodule;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.terraform = {
|
||||||
|
domain = mkOption {
|
||||||
|
type = enum [
|
||||||
|
"fediversity.net"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
Apex domain under which the services will be deployed.
|
||||||
|
'';
|
||||||
|
default = "fediversity.net";
|
||||||
|
};
|
||||||
|
hostname = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = ''
|
||||||
|
Internal name of the host, e.g. test01
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
initialUser = mkOption {
|
||||||
|
description = ''
|
||||||
|
Some services require an initial user to access them.
|
||||||
|
This option sets the credentials for such an initial user.
|
||||||
|
'';
|
||||||
|
type = submodule {
|
||||||
|
options = {
|
||||||
|
displayName = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "Display name of the user";
|
||||||
|
};
|
||||||
|
username = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "Username for login";
|
||||||
|
};
|
||||||
|
email = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "User's email address";
|
||||||
|
};
|
||||||
|
password = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "Password for login";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
15
launch/pass-ssh-key.sh
Executable file
15
launch/pass-ssh-key.sh
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
export host="$host"
|
||||||
|
|
||||||
|
mkdir -p etc/ssh
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
|
||||||
|
for keyname in ssh_host_ed25519_key ssh_host_ed25519_key.pub; do
|
||||||
|
if [[ $keyname == *.pub ]]; then
|
||||||
|
umask 0133
|
||||||
|
else
|
||||||
|
umask 0177
|
||||||
|
fi
|
||||||
|
cp "$SCRIPT_DIR/../infra/test-machines/${host}/${keyname}" ./etc/ssh/${keyname}
|
||||||
|
done
|
20
launch/peertube.nix
Normal file
20
launch/peertube.nix
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
peertubeS3KeyConfig =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
|
||||||
|
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fediversity = {
|
||||||
|
peertube = peertubeS3KeyConfig { inherit pkgs; } // {
|
||||||
|
enable = true;
|
||||||
|
## NOTE: Only ever used for testing anyway.
|
||||||
|
##
|
||||||
|
## FIXME: Generate and store in NixOps4's state.
|
||||||
|
secretsFile = pkgs.writeText "secret" "574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
16
launch/pixelfed.nix
Normal file
16
launch/pixelfed.nix
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
pixelfedS3KeyConfig =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
|
||||||
|
s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fediversity = {
|
||||||
|
pixelfed = pixelfedS3KeyConfig { inherit pkgs; } // {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
44
launch/resource.nix
Normal file
44
launch/resource.nix
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) attrValues elem mkDefault;
|
||||||
|
inherit (lib.attrsets) concatMapAttrs optionalAttrs;
|
||||||
|
inherit (lib.strings) removeSuffix;
|
||||||
|
|
||||||
|
secretsPrefix = ../secrets;
|
||||||
|
secrets = import (secretsPrefix + "/secrets.nix");
|
||||||
|
keys = import ../keys;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fediversityVm.hostPublicKey = mkDefault keys.systems.${config.fediversityVm.name};
|
||||||
|
|
||||||
|
## The configuration of the machine. We strive to keep in this file only the
|
||||||
|
## options that really need to be injected from the resource. Everything else
|
||||||
|
## should go into the `./nixos` subdirectory.
|
||||||
|
imports = [
|
||||||
|
../infra/common/options.nix
|
||||||
|
../infra/common/nixos
|
||||||
|
];
|
||||||
|
|
||||||
|
## Read all the secrets, filter the ones that are supposed to be readable
|
||||||
|
## with this host's public key, and add them correctly to the configuration
|
||||||
|
## as `age.secrets.<name>.file`.
|
||||||
|
age.secrets = concatMapAttrs (
|
||||||
|
name: secret:
|
||||||
|
optionalAttrs (elem config.fediversityVm.hostPublicKey secret.publicKeys) {
|
||||||
|
${removeSuffix ".age" name}.file = secretsPrefix + "/${name}";
|
||||||
|
}
|
||||||
|
) secrets;
|
||||||
|
|
||||||
|
## FIXME: Remove direct root authentication once the NixOps4 NixOS provider
|
||||||
|
## supports users with password-less sudo.
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = attrValues keys.contributors ++ [
|
||||||
|
# allow our panel vm access to the test machines
|
||||||
|
keys.panel
|
||||||
|
];
|
||||||
|
}
|
26
launch/shared.nix
Normal file
26
launch/shared.nix
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (config.terraform) hostname domain initialUser;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
<disko/module.nix>
|
||||||
|
<agenix/modules/age.nix>
|
||||||
|
../services/fediversity
|
||||||
|
./resource.nix
|
||||||
|
];
|
||||||
|
fediversityVm.name = hostname;
|
||||||
|
fediversity = {
|
||||||
|
inherit domain;
|
||||||
|
temp.initialUser = {
|
||||||
|
inherit (initialUser) username email displayName;
|
||||||
|
# FIXME: disgusting, but nvm, this is going to be replaced by
|
||||||
|
# proper central authentication at some point
|
||||||
|
passwordFile = pkgs.writeText "password" initialUser.password;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
1
launch/shell.nix
Normal file
1
launch/shell.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(import ./. { }).shell
|
26
launch/tf-env.nix
Normal file
26
launch/tf-env.nix
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
sources ? import ../npins,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
name = "tf-repo";
|
||||||
|
src = ../.;
|
||||||
|
buildInputs = [
|
||||||
|
(import ./tf.nix { inherit lib pkgs; })
|
||||||
|
];
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
pushd launch/
|
||||||
|
echo '${lib.strings.toJSON sources}' > .npins.json
|
||||||
|
tofu init -input=false
|
||||||
|
popd
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
cp -r . $out
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
}
|
25
launch/tf.nix
Normal file
25
launch/tf.nix
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# FIXME: use overlays so this gets imported just once?
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
tofuProvider =
|
||||||
|
provider:
|
||||||
|
provider.override (oldArgs: {
|
||||||
|
provider-source-address =
|
||||||
|
lib.replaceStrings [ "https://registry.terraform.io/providers" ] [ "registry.opentofu.org" ]
|
||||||
|
oldArgs.homepage;
|
||||||
|
});
|
||||||
|
tf = pkgs.opentofu;
|
||||||
|
tfPlugins = (
|
||||||
|
p: [
|
||||||
|
p.null
|
||||||
|
p.external
|
||||||
|
]
|
||||||
|
);
|
||||||
|
in
|
||||||
|
# tf.withPlugins tfPlugins
|
||||||
|
# https://github.com/NixOS/nixpkgs/pull/358522
|
||||||
|
tf.withPlugins (p: pkgs.lib.lists.map tofuProvider (tfPlugins p))
|
|
@ -1,5 +1,44 @@
|
||||||
{
|
{
|
||||||
"pins": {
|
"pins": {
|
||||||
|
"agenix": {
|
||||||
|
"type": "Git",
|
||||||
|
"repository": {
|
||||||
|
"type": "GitHub",
|
||||||
|
"owner": "ryantm",
|
||||||
|
"repo": "agenix"
|
||||||
|
},
|
||||||
|
"branch": "main",
|
||||||
|
"revision": "e600439ec4c273cf11e06fe4d9d906fb98fa097c",
|
||||||
|
"url": "https://github.com/ryantm/agenix/archive/e600439ec4c273cf11e06fe4d9d906fb98fa097c.tar.gz",
|
||||||
|
"hash": "006ngydiykjgqs85cl19h9klq8kaqm5zs0ng51dnwy7nzgqxzsdr"
|
||||||
|
},
|
||||||
|
"disko": {
|
||||||
|
"type": "GitRelease",
|
||||||
|
"repository": {
|
||||||
|
"type": "GitHub",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko"
|
||||||
|
},
|
||||||
|
"pre_releases": false,
|
||||||
|
"version_upper_bound": null,
|
||||||
|
"release_prefix": null,
|
||||||
|
"version": "v1.11.0",
|
||||||
|
"revision": "cdf8deded8813edfa6e65544f69fdd3a59fa2bb4",
|
||||||
|
"url": "https://api.github.com/repos/nix-community/disko/tarball/v1.11.0",
|
||||||
|
"hash": "13brimg7z7k9y36n4jc1pssqyw94nd8qvgfjv53z66lv4xkhin92"
|
||||||
|
},
|
||||||
|
"flake-inputs": {
|
||||||
|
"type": "Git",
|
||||||
|
"repository": {
|
||||||
|
"type": "GitHub",
|
||||||
|
"owner": "fricklerhandwerk",
|
||||||
|
"repo": "flake-inputs"
|
||||||
|
},
|
||||||
|
"branch": "main",
|
||||||
|
"revision": "559574c9cbb8af262f3944b67d60fbf0f6ad03c3",
|
||||||
|
"url": "https://github.com/fricklerhandwerk/flake-inputs/archive/559574c9cbb8af262f3944b67d60fbf0f6ad03c3.tar.gz",
|
||||||
|
"hash": "0gbhmp6x2vdzvfnsvqzal3g8f8hx2ia6r73aibc78kazf78m67x6"
|
||||||
|
},
|
||||||
"htmx": {
|
"htmx": {
|
||||||
"type": "GitRelease",
|
"type": "GitRelease",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -30,8 +69,8 @@
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"type": "Channel",
|
"type": "Channel",
|
||||||
"name": "nixpkgs-unstable",
|
"name": "nixpkgs-unstable",
|
||||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.05pre711046.8edf06bea5bc/nixexprs.tar.xz",
|
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.05pre777917.b7ba7f9f45c5/nixexprs.tar.xz",
|
||||||
"hash": "1mwsn0rvfm603svrq3pca4c51zlix5gkyr4gl6pxhhq3q6xs5s8y"
|
"hash": "0jb6b7sv66bn06pchj2l88z0i5dlz0c2vb3z6pjjlq2p8q11zigg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version": 3
|
"version": 3
|
||||||
|
|
|
@ -20,11 +20,18 @@ in
|
||||||
pkgs.npins
|
pkgs.npins
|
||||||
manage
|
manage
|
||||||
];
|
];
|
||||||
env = import ./env.nix { inherit lib pkgs; } // {
|
env =
|
||||||
NPINS_DIRECTORY = toString ../npins;
|
let
|
||||||
CREDENTIALS_DIRECTORY = toString ./.credentials;
|
inherit (builtins) toString;
|
||||||
DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3";
|
in
|
||||||
};
|
import ./env.nix { inherit lib pkgs; }
|
||||||
|
// {
|
||||||
|
NPINS_DIRECTORY = toString ../npins;
|
||||||
|
CREDENTIALS_DIRECTORY = toString ./.credentials;
|
||||||
|
DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3";
|
||||||
|
# locally: use a fixed relative reference, so we can use our newest files without copying to the store
|
||||||
|
REPO_DIR = toString ../.;
|
||||||
|
};
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
ln -sf ${sources.htmx}/dist/htmx.js src/panel/static/htmx.min.js
|
ln -sf ${sources.htmx}/dist/htmx.js src/panel/static/htmx.min.js
|
||||||
# in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd.
|
# in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd.
|
||||||
|
|
|
@ -3,16 +3,15 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
|
||||||
inherit (builtins) toString;
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
REPO_DIR = toString ../.;
|
|
||||||
# explicitly use nix, as e.g. lix does not have configurable-impure-env
|
|
||||||
BIN_PATH = lib.makeBinPath [
|
BIN_PATH = lib.makeBinPath [
|
||||||
# explicitly use nix, as e.g. lix does not have configurable-impure-env
|
pkgs.lix
|
||||||
pkgs.nix
|
pkgs.bash
|
||||||
# nixops error maybe due to our flake git hook: executing 'git': No such file or directory
|
pkgs.coreutils
|
||||||
|
pkgs.openssh
|
||||||
pkgs.git
|
pkgs.git
|
||||||
|
pkgs.jaq # tf
|
||||||
|
(import ../launch/tf.nix { inherit lib pkgs; })
|
||||||
];
|
];
|
||||||
|
SSH_PRIVATE_KEY_FILE = "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ let
|
||||||
((pkgs.formats.pythonVars { }).generate "settings.py" cfg.settings)
|
((pkgs.formats.pythonVars { }).generate "settings.py" cfg.settings)
|
||||||
(builtins.toFile "extra-settings.py" cfg.extra-settings)
|
(builtins.toFile "extra-settings.py" cfg.extra-settings)
|
||||||
];
|
];
|
||||||
|
REPO_DIR = import ../../launch/tf-env.nix { inherit lib pkgs; };
|
||||||
|
SSH_PRIVATE_KEY_FILE = config.age.secrets.panel-ssh-key.path;
|
||||||
};
|
};
|
||||||
|
|
||||||
python-environment = pkgs.python3.withPackages (
|
python-environment = pkgs.python3.withPackages (
|
||||||
|
@ -157,17 +159,14 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
users.users.${name} = {
|
users.users.${name}.isNormalUser = true;
|
||||||
isSystemUser = true;
|
|
||||||
group = name;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups.${name} = { };
|
|
||||||
systemd.services.${name} = {
|
systemd.services.${name} = {
|
||||||
description = "${name} ASGI server";
|
description = "${name} ASGI server";
|
||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
path = [
|
path = [
|
||||||
|
pkgs.openssh
|
||||||
python-environment
|
python-environment
|
||||||
manage-service
|
manage-service
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
|
pkgs,
|
||||||
sqlite,
|
sqlite,
|
||||||
python3,
|
python3,
|
||||||
sources ? import ../../npins,
|
sources ? import ../../npins,
|
||||||
|
@ -11,7 +12,7 @@ let
|
||||||
root = ../src;
|
root = ../src;
|
||||||
fileset = intersection (gitTracked ../../.) ../src;
|
fileset = intersection (gitTracked ../../.) ../src;
|
||||||
};
|
};
|
||||||
pyproject = with lib; fromTOML pyproject-toml;
|
pyproject = fromTOML pyproject-toml;
|
||||||
# TODO: define this globally
|
# TODO: define this globally
|
||||||
name = "panel";
|
name = "panel";
|
||||||
# TODO: we may want this in a file so it's easier to read statically
|
# TODO: we may want this in a file so it's easier to read statically
|
||||||
|
@ -58,7 +59,9 @@ python3.pkgs.buildPythonPackage {
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp -v ${src}/manage.py $out/bin/manage.py
|
cp -v ${src}/manage.py $out/bin/manage.py
|
||||||
chmod +x $out/bin/manage.py
|
chmod +x $out/bin/manage.py
|
||||||
wrapProgram $out/bin/manage.py --prefix PYTHONPATH : "$PYTHONPATH"
|
wrapProgram $out/bin/manage.py \
|
||||||
|
--set REPO_DIR "${import ../../launch/tf-env.nix { inherit lib pkgs; }}" \
|
||||||
|
--prefix PYTHONPATH : "$PYTHONPATH"
|
||||||
cp ${sources.htmx}/dist/htmx.min.js* $out/${python3.sitePackages}/panel/static/
|
cp ${sources.htmx}/dist/htmx.min.js* $out/${python3.sitePackages}/panel/static/
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,19 @@ For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import dj_database_url
|
import dj_database_url
|
||||||
|
|
||||||
from os import environ as env
|
from os import environ as env
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
STORE_PATTERN = re.compile("^/nix/store/[^/]+$")
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
@ -171,6 +176,53 @@ COMPRESS_PRECOMPILERS = [
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"filters": {
|
||||||
|
"require_debug_false": {
|
||||||
|
"()": "django.utils.log.RequireDebugFalse",
|
||||||
|
},
|
||||||
|
"require_debug_true": {
|
||||||
|
"()": "django.utils.log.RequireDebugTrue",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"formatters": {
|
||||||
|
"django.server": {
|
||||||
|
"()": "django.utils.log.ServerFormatter",
|
||||||
|
"format": "[{server_time}] {message}",
|
||||||
|
"style": "{",
|
||||||
|
},
|
||||||
|
"standard": {
|
||||||
|
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"level": "INFO",
|
||||||
|
# "filters": ["require_debug_true"],
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "standard",
|
||||||
|
},
|
||||||
|
"django.server": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "django.server",
|
||||||
|
},
|
||||||
|
"mail_admins": {
|
||||||
|
"level": "ERROR",
|
||||||
|
"filters": ["require_debug_false"],
|
||||||
|
"class": "django.utils.log.AdminEmailHandler",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": "DEBUG" if DEBUG else "INFO",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
# Customization via user settings
|
# Customization via user settings
|
||||||
# This must be at the end, as it must be able to override the above
|
# This must be at the end, as it must be able to override the above
|
||||||
# TODO(@fricklerhandwerk):
|
# TODO(@fricklerhandwerk):
|
||||||
|
@ -197,3 +249,12 @@ bin_path=env['BIN_PATH']
|
||||||
# path of the root flake to trigger nixops from, see #94.
|
# path of the root flake to trigger nixops from, see #94.
|
||||||
# to deploy this should be specified, for dev just use a relative path.
|
# to deploy this should be specified, for dev just use a relative path.
|
||||||
repo_dir = env["REPO_DIR"]
|
repo_dir = env["REPO_DIR"]
|
||||||
|
|
||||||
|
output = subprocess.run(["ssh-agent"], capture_output=True, text=True, env={"PATH": bin_path}).stdout
|
||||||
|
ssh_auth_sock = re.search("(?<==)([^;]*)", output)[1]
|
||||||
|
ENV_VARS = {
|
||||||
|
"ssh_private_key_file": env["SSH_PRIVATE_KEY_FILE"],
|
||||||
|
"deploy_environment": {
|
||||||
|
"SSH_AUTH_SOCK": ssh_auth_sock,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import json
|
import json
|
||||||
|
from os.path import expanduser
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
@ -13,6 +15,8 @@ from django.shortcuts import render
|
||||||
from panel import models, settings
|
from panel import models, settings
|
||||||
from panel.configuration import forms
|
from panel.configuration import forms
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Index(TemplateView):
|
class Index(TemplateView):
|
||||||
template_name = 'index.html'
|
template_name = 'index.html'
|
||||||
|
@ -102,7 +106,7 @@ class DeploymentStatus(ConfigurationForm):
|
||||||
# Check for deploy button
|
# Check for deploy button
|
||||||
if "deploy" in self.request.POST.keys():
|
if "deploy" in self.request.POST.keys():
|
||||||
deployment_result, deployment_params = self.deployment(obj)
|
deployment_result, deployment_params = self.deployment(obj)
|
||||||
deployment_succeeded = deployment_result.returncode == 0
|
deployment_succeeded = deployment_result == 0
|
||||||
|
|
||||||
return render(self.request, "partials/deployment_result.html", {
|
return render(self.request, "partials/deployment_result.html", {
|
||||||
"deployment_succeeded": deployment_succeeded,
|
"deployment_succeeded": deployment_succeeded,
|
||||||
|
@ -134,25 +138,31 @@ class DeploymentStatus(ConfigurationForm):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# serialize back and forth now we still need to manually inject the dummy user
|
# serialize back and forth now we still need to manually inject the dummy user
|
||||||
deployment_params = json.dumps(dummy_user | json.loads(submission))
|
deployment_params = dummy_user | json.loads(submission)
|
||||||
env = {
|
env = {
|
||||||
"PATH": settings.bin_path,
|
"PATH": settings.bin_path,
|
||||||
|
# used in terraform for ssh-copy-id to make `.ssh` in for ssh-copy-id.
|
||||||
|
# run thru subprocess, HOME points to the read-only `/var/empty`.
|
||||||
|
# in local dev, it will just reject the `/tmp` and make it in HOME after all.
|
||||||
|
"HOME": "/tmp",
|
||||||
|
"XDG_CACHE_HOME": "/tmp",
|
||||||
|
# "TF_LOG": "info",
|
||||||
|
} | {
|
||||||
# pass in form info to our deployment
|
# pass in form info to our deployment
|
||||||
"DEPLOYMENT": deployment_params,
|
# FIXME: ensure sensitive info is protected
|
||||||
|
f"TF_VAR_{k}": v if isinstance(v, str) else json.dumps(v) for k, v in (settings.ENV_VARS | deployment_params).items()
|
||||||
}
|
}
|
||||||
|
logger.info("env: %s", env)
|
||||||
|
cwd = f"{settings.repo_dir}/launch"
|
||||||
cmd = [
|
cmd = [
|
||||||
"nix",
|
"tofu",
|
||||||
"develop",
|
# f"-chdir={cwd}",
|
||||||
"--extra-experimental-features",
|
|
||||||
"configurable-impure-env",
|
|
||||||
"--command",
|
|
||||||
"nixops4",
|
|
||||||
"apply",
|
"apply",
|
||||||
"test",
|
f"-state={cwd}/terraform.tfstate", # FIXME: separate users' state
|
||||||
|
"--auto-approve",
|
||||||
|
"-lock=false",
|
||||||
|
"-parallelism=1" # limit OOM risk
|
||||||
]
|
]
|
||||||
deployment_result = subprocess.run(
|
deployment_result = subprocess.run(cmd, cwd=cwd, env=env)
|
||||||
cmd,
|
logger.debug("deployment_result: %s", deployment_result)
|
||||||
cwd=settings.repo_dir,
|
return deployment_result, deployment_params
|
||||||
env=env,
|
|
||||||
)
|
|
||||||
return deployment_result, json.loads(deployment_params)
|
|
||||||
|
|
BIN
secrets/panel-ssh-key.age
Normal file
BIN
secrets/panel-ssh-key.age
Normal file
Binary file not shown.
|
@ -28,6 +28,7 @@ concatMapAttrs
|
||||||
forgejo-email-password = [ vm02116 ];
|
forgejo-email-password = [ vm02116 ];
|
||||||
forgejo-runner-token = [ ];
|
forgejo-runner-token = [ ];
|
||||||
panel-secret-key = [ fedi201 ];
|
panel-secret-key = [ fedi201 ];
|
||||||
|
panel-ssh-key = [ fedi201 ];
|
||||||
wiki-basicauth-htpasswd = [ vm02187 ];
|
wiki-basicauth-htpasswd = [ vm02187 ];
|
||||||
wiki-password = [ vm02187 ];
|
wiki-password = [ vm02187 ];
|
||||||
wiki-smtp-password = [ vm02187 ];
|
wiki-smtp-password = [ vm02187 ];
|
||||||
|
|
Loading…
Add table
Reference in a new issue