button works deployed

late rebasing

account for 285
This commit is contained in:
Kiara Grouwstra 2025-05-01 14:16:12 +02:00
parent 1f99a4c6c3
commit 58c1999fd2
Signed by: kiara
SSH key fingerprint: SHA256:COspvLoLJ5WC5rFb9ZDe5urVCkK4LJZOsjfF4duRJFU
28 changed files with 654 additions and 47 deletions

View file

@ -82,3 +82,9 @@ jobs:
echo ~~~~~~~~~~~~~~~~~~~~~: $machine :~~~~~~~~~~~~~~~~~~~~~ echo ~~~~~~~~~~~~~~~~~~~~~: $machine :~~~~~~~~~~~~~~~~~~~~~
nix build .#checks.x86_64-linux.nixosConfigurations-$machine nix build .#checks.x86_64-linux.nixosConfigurations-$machine
done done
check-launch:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: cd launch && nix-build -A tests

View file

@ -27,6 +27,7 @@ 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 = [ optout = [
"npins" "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; });

View file

@ -8,7 +8,7 @@
}: }:
let let
inherit (builtins) readDir readFile fromJSON; inherit (builtins) readDir;
inherit (lib) inherit (lib)
attrNames attrNames
mkOption mkOption
@ -160,14 +160,12 @@ let
listSubdirectories = path: attrNames (filterAttrs (_: type: type == "directory") (readDir path)); listSubdirectories = path: attrNames (filterAttrs (_: type: type == "directory") (readDir path));
machines = listSubdirectories ../machines/dev; machines = listSubdirectories ../machines/dev;
testMachines = listSubdirectories ../machines/operator;
nixosConfigurations = nixosConfigurations =
genAttrs machines (makeConfiguration false) genAttrs machines (makeConfiguration false);
// genAttrs testMachines (makeConfiguration true);
vmOptions = vmOptions =
filterAttrs (_: value: value != null) # Filter out non-Fediversity VMs filterAttrs (_: value: value != null) # Filter out non-Fediversity VMs
(genAttrs machines (makeVmOptions false) // genAttrs testMachines (makeVmOptions true)); (genAttrs machines (makeVmOptions false));
in in
{ {
@ -180,17 +178,6 @@ in
## - We add a “test” deployment with all test machines. ## - We add a “test” deployment with all test machines.
nixops4Deployments = genAttrs machines makeDeployment' // { nixops4Deployments = genAttrs machines makeDeployment' // {
default = makeDeployment machines; default = makeDeployment machines;
test = makeTestDeployment (
fromJSON (
let
env = builtins.getEnv "DEPLOYMENT";
in
if env != "" then
env
else
builtins.trace "env var DEPLOYMENT not set, falling back to ../deployment/configuration.sample.json!" (readFile ../deployment/configuration.sample.json)
)
);
}; };
flake = { inherit nixosConfigurations vmOptions; }; flake = { inherit nixosConfigurations vmOptions; };

10
launch/.envrc Normal file
View 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

7
launch/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# generated
.auto.tfvars.json
.npins.json
.terraform/
.terraform.lock.hcl
.terraform.tfstate.lock.info
terraform.tfstate*

28
launch/README.md Normal file
View file

@ -0,0 +1,28 @@
# service deployment
deploys [NixOS](https://nixos.org/) templates using [OpenTofu](https://opentofu.org/).
## requirements
- [nix](https://nix.dev/)
## usage
### development
before using other commands, if not using direnv:
```sh
nix-shell
```
then to initialize, or after updating pins or TF providers:
```sh
setup
```
## implementing
proper documentation TODO.
until then, a reference implementation may be found in [`panel/`](https://git.fediversity.eu/Fediversity/Fediversity/src/branch/main/panel).

34
launch/default.nix Normal file
View file

@ -0,0 +1,34 @@
{
system ? builtins.currentSystem,
sources ? import ../npins,
pkgs ? import sources.nixpkgs { inherit system; },
}:
let
inherit (pkgs) lib;
setup = pkgs.writeScriptBin "setup" ''
echo '${lib.strings.toJSON sources}' > .npins.json
rm -f .terraform.lock.hcl
rm -rf .terraform/
tofu init
'';
in
{
# shell for testing TF directly
shell = pkgs.mkShellNoCC {
packages = [
(import ./tf.nix { inherit lib pkgs; })
pkgs.jaq
setup
];
};
tests = pkgs.callPackage ./tests.nix { };
# 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
View 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; };
};
}

159
launch/main.tf Normal file
View file

@ -0,0 +1,159 @@
locals {
system = "x86_64-linux"
# dependency paths pre-calculated from npins
pins = jsondecode(file("${path.root}/.npins.json"))
# nix path: expose pins, use nixpkgs in flake commands (`nix run`)
nix_path = "${join(":", [for name, path in local.pins : "${name}=${path}"])}:flake=${local.pins["nixpkgs"]}:flake"
# user-facing applications
application_configs = {
# FIXME: wrap applications at the interface to grab them in one go?
mastodon = {
cfg = var.mastodon
hostname = "test06"
}
pixelfed = {
cfg = var.pixelfed
hostname = "test04"
}
peertube = {
cfg = var.peertube
hostname = "test05"
}
}
# services shared between applications
peripherals = { for name, inst in {
garage = "test01"
} : name => {
hostname = inst
cfg = {
# enable if any user applications are enabled
enable = anytrue([for _, app in local.application_configs: try(app.cfg.enable, false)])
}
}
}
}
# hash of our code directory, used in dev to trigger re-deploy
# FIXME settle for pwd when in /nix/store?
# FIXME calculate separately to reduce false positives
data "external" "hash" {
program = ["sh", "-c", "echo \"{\\\"hash\\\":\\\"$(nix-hash ..)\\\"}\""]
}
# TF resource to build and deploy NixOS instances.
resource "terraform_data" "nixos" {
for_each = {for name, inst in merge(
local.peripherals,
local.application_configs,
) : name => inst if try(inst.cfg.enable, false)}
# trigger rebuild/deploy if (FIXME?) any potentially used config/code changed,
# preventing these (20+s, build being bottleneck) when nothing changed.
# terraform-nixos separates these to only deploy if instantiate changed,
# yet building even then - which may be not as bad using deploy on remote.
# having build/deploy one resource reflects wanting to prevent no-op rebuilds
# over preventing (with less false positives) no-op deployments,
# as i could not find a way to do prevent no-op rebuilds without merging them:
# - generic resources cannot have outputs, while we want info from the instantiation (unless built on host?).
# - `data` always runs, which is slow for deploy and especially build.
triggers_replace = [
data.external.hash.result,
var.domain,
var.initialUser,
local.system,
each.key,
each.value,
]
provisioner "local-exec" {
# directory to run the script from. we use the TF project root dir,
# here as a path relative from where TF is run from.
# note that absolute paths can cause false positives in triggers,
# so are generally discouraged in TF.
working_dir = path.root
environment = {
# nix path used on build, lets us refer to e.g. nixpkgs like `<nixpkgs>`
NIX_PATH = local.nix_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 = {
# note interpolations here TF ones
imports = [
# shared NixOS config
${path.root}/shared.nix
# FIXME: separate template options by service
${path.root}/options.nix
# for service `mastodon` import `mastodon.nix`
${path.root}/${each.key}.nix
# FIXME: get VM details from TF
${path.root}/../infra/test-machines/${each.value.hostname}
];
# nix path for debugging
nix.nixPath = [ "${local.nix_path}" ];
## FIXME: switch root authentication to users with password-less sudo, see #24
users.users.root.openssh.authorizedKeys.keys = let
keys = import ../keys;
in attrValues keys.contributors ++ [
# allow our panel vm access to the test machines
keys.panel
];
} //
# template parameters passed in from TF thru json
builtins.fromJSON "${replace(jsonencode({
terraform = {
domain = var.domain
hostname = each.value.hostname
initialUser = var.initialUser
}
}), "\"", "\\\"")}";
};
in
# info we want to get back out
{
substituters = builtins.concatStringsSep " " os.config.nix.settings.substituters;
trusted_public_keys = builtins.concatStringsSep " " os.config.nix.settings.trusted-public-keys;
drv_path = os.config.system.build.toplevel.drvPath;
out_path = os.config.system.build.toplevel;
}'
)
# instantiate the config in /nix/store
"$${command[@]}" -A out_path
# get the other info
json="$("$${command[@]}" --eval --strict --json)"
# DEPLOY
declare substituters trusted_public_keys drv_path
# set our variables using the json object
eval "export $(echo $json | jaq -r 'to_entries | map("\(.key)=\(.value)") | @sh')"
# FIXME: de-hardcode domain
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 BatchMode=yes
-o StrictHostKeyChecking=no
)
# get the realized derivation to deploy
outPath=$(nix-store --realize "$drv_path" "$${buildArgs[@]}")
# deploy the config by nix-copy-closure
NIX_SSHOPTS="$${sshOpts[*]}" nix-copy-closure --to "$host" "$outPath" --gzip --use-substitutes
# switch the remote host to the config
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
View 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
};
}

54
launch/options.nix Normal file
View file

@ -0,0 +1,54 @@
# TODO: could (part of) this be generated somehow? c.f #275
{
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";
};
};
};
};
};
}

20
launch/peertube.nix Normal file
View 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
View 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;
};
};
}

43
launch/resource.nix Normal file
View file

@ -0,0 +1,43 @@
{
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: switch root authentication to users with password-less sudo, see #24
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
View 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
View file

@ -0,0 +1 @@
(import ./. { }).shell

26
launch/tests.nix Normal file
View file

@ -0,0 +1,26 @@
{ lib, pkgs }:
let
defaults = {
virtualisation = {
memorySize = 2048;
cores = 2;
};
};
tf = pkgs.callPackage ./tf.nix { };
tfEnv = pkgs.callPackage ./tf-env.nix { };
in
lib.mapAttrs (name: test: pkgs.testers.runNixOSTest (test // { inherit name; })) {
tf-validate = {
inherit defaults;
nodes.server = {
environment.systemPackages = [
tf
tfEnv
];
};
testScript = ''
server.wait_for_unit("multi-user.target")
server.succeed("${lib.getExe tf} -chdir='${tfEnv}/launch' validate")
'';
};
}

34
launch/tf-env.nix Normal file
View file

@ -0,0 +1,34 @@
{
lib,
pkgs,
sources ? import ../npins,
...
}:
pkgs.stdenv.mkDerivation {
name = "tf-repo";
src =
with lib.fileset;
toSource {
root = ../.;
# don't copy ignored files
fileset = intersection (gitTracked ../.) ../.;
};
buildInputs = [
(import ./tf.nix { inherit lib pkgs; })
];
buildPhase = ''
runHook preBuild
pushd launch/
# calculated pins
echo '${lib.strings.toJSON sources}' > .npins.json
# generate TF lock for nix's TF providers
tofu init -input=false
popd
runHook postBuild
'';
installPhase = ''
runHook preInstall
cp -r . $out
runHook postInstall
'';
}

24
launch/tf.nix Normal file
View file

@ -0,0 +1,24 @@
# 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.external
]
);
in
# tf.withPlugins tfPlugins
# https://github.com/NixOS/nixpkgs/pull/358522
tf.withPlugins (p: pkgs.lib.lists.map tofuProvider (tfPlugins p))

51
launch/variables.tf Normal file
View file

@ -0,0 +1,51 @@
# TODO: (partially) generate, say from nix modules, c.f. #275
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
})
# FIXME: remove default when the form provides this value, see #285
default = {
displayName = "Testy McTestface"
username = "test"
email = "test@test.com"
password = "testtest"
}
}

View file

@ -39,6 +39,24 @@ in
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;
}; };

View file

@ -32,6 +32,8 @@ in
NPINS_DIRECTORY = toString ../npins; NPINS_DIRECTORY = toString ../npins;
CREDENTIALS_DIRECTORY = toString ./.credentials; CREDENTIALS_DIRECTORY = toString ./.credentials;
DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3"; 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 = ''
${lib.concatStringsSep "\n" ( ${lib.concatStringsSep "\n" (

16
panel/env.nix Normal file
View file

@ -0,0 +1,16 @@
{
lib,
pkgs,
...
}:
{
BIN_PATH = lib.makeBinPath [
pkgs.lix
pkgs.bash
pkgs.coreutils
pkgs.openssh
pkgs.git
pkgs.jaq # tf
(import ../launch/tf.nix { inherit lib pkgs; })
];
}

View file

@ -24,13 +24,12 @@ let
package = pkgs.callPackage ./package.nix { }; package = pkgs.callPackage ./package.nix { };
environment = { environment = {
DEPLOYMENT_FLAKE = cfg.deployment.flake;
DEPLOYMENT_NAME = cfg.deployment.name;
DATABASE_URL = "sqlite:////var/lib/${name}/db.sqlite3"; DATABASE_URL = "sqlite:////var/lib/${name}/db.sqlite3";
USER_SETTINGS_FILE = pkgs.concatText "configuration.py" [ USER_SETTINGS_FILE = pkgs.concatText "configuration.py" [
((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; };
}; };
python-environment = pkgs.python3.withPackages ( python-environment = pkgs.python3.withPackages (
@ -149,23 +148,6 @@ in
''; '';
default = pkgs.nixops4; default = pkgs.nixops4;
}; };
deployment = {
flake = mkOption {
type = types.path;
default = ../..;
description = ''
The path to the flake containing the deployment. This is used to run the deployment button.
'';
};
name = mkOption {
type = types.str;
default = "test";
description = ''
The name of the deployment within the flake.
'';
};
};
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
@ -211,6 +193,7 @@ in
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

View file

@ -1,5 +1,6 @@
{ {
lib, lib,
pkgs,
sqlite, sqlite,
python3, python3,
python3Packages, python3Packages,
@ -98,7 +99,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"
${lib.concatStringsSep "\n" ( ${lib.concatStringsSep "\n" (
map (file: "cp ${file.from} $out/${python3.sitePackages}/${file.to}") generated map (file: "cp ${file.from} $out/${python3.sitePackages}/${file.to}") generated
)} )}

View file

@ -1,3 +1,4 @@
# TODO upstream, see #248
{ {
lib, lib,
buildPythonPackage, buildPythonPackage,

View file

@ -240,8 +240,3 @@ if user_settings_file is not None:
# TODO(@fricklerhandwerk): # TODO(@fricklerhandwerk):
# The correct thing to do here would be using a helper function such as with `get_secret()` that will catch the exception and explain what's wrong and where to put the right values. # The correct thing to do here would be using a helper function such as with `get_secret()` that will catch the exception and explain what's wrong and where to put the right values.
# Replacing the `USER_SETTINGS_FILE` mechanism following the comment there would probably be a good thing. # Replacing the `USER_SETTINGS_FILE` mechanism following the comment there would probably be a good thing.
# Path of the root flake to trigger nixops from, see #94, and name of the
# deployment.
deployment_flake = env["DEPLOYMENT_FLAKE"]
deployment_name = env["DEPLOYMENT_NAME"]

View file

@ -1,6 +1,7 @@
from enum import Enum from enum import Enum
import json import json
import subprocess import subprocess
import logging
import os import os
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -19,6 +20,8 @@ from pydantic import BaseModel
from panel import models, settings from panel import models, settings
from panel.configuration import schema from panel.configuration import schema
logger = logging.getLogger(__name__)
class Index(TemplateView): class Index(TemplateView):
template_name = 'index.html' template_name = 'index.html'
@ -90,20 +93,28 @@ class DeploymentStatus(ConfigurationForm):
def deployment(self, config: BaseModel): def deployment(self, config: BaseModel):
env = { env = {
"PATH": os.environ.get("PATH"), "PATH": os.environ.get("PATH"),
# "TF_LOG": "info",
} | {
# pass in form info to our deployment # pass in form info to our deployment
"DEPLOYMENT": config.json() # FIXME: ensure sensitive info is protected
f"TF_VAR_{k}": v if isinstance(v, str) else json.dumps(v) for k, v in json.loads(config.model_dump_json()).items()
} }
logger.info("env: %s", env)
cwd = f"{settings.repo_dir}/launch"
cmd = [ cmd = [
"nixops4", "tofu",
# f"-chdir={cwd}",
"apply", "apply",
settings.deployment_name, f"-state={cwd}/terraform.tfstate", # FIXME: separate users' state
"--show-trace", "--auto-approve",
"--no-interactive", "-lock=false",
"-parallelism=1" # limit OOM risk
] ]
deployment_result = subprocess.run( deployment_result = subprocess.run(
cmd, cmd,
cwd = settings.deployment_flake, cwd = cwd,
env = env, env = env,
stderr = subprocess.STDOUT, stderr = subprocess.STDOUT,
) )
logger.debug("deployment_result: %s", deployment_result)
return deployment_result, config return deployment_result, config