switch to bash deployment

This commit is contained in:
Kiara Grouwstra 2025-04-03 15:37:06 +02:00
parent 319a6c391c
commit 246380e00f
Signed by: kiara
SSH key fingerprint: SHA256:COspvLoLJ5WC5rFb9ZDe5urVCkK4LJZOsjfF4duRJFU
16 changed files with 84 additions and 312 deletions

View file

@ -43,10 +43,7 @@
pre-commit.settings.hooks =
let
## Add a directory here if pre-commit hooks shouldn't apply to it.
optout = [
"npins"
"launch/.terraform"
];
optout = [ "npins" ];
excludes = map (dir: "^${dir}/") optout;
addExcludes = lib.mapAttrs (_: c: c // { inherit excludes; });
in

4
launch/.gitignore vendored
View file

@ -1,4 +0,0 @@
.auto.tfvars.json
.terraform/
.terraform.tfstate.lock.info
terraform.tfstate*

View file

@ -1,19 +0,0 @@
# service deployment
## usage
### updating TF modules
```sh
$ npins update nixos-anywhere
$ cd launch/
$ echo "{\"nixos-anywhere\": $(nix-instantiate --eval --json -E '(import ../npins).nixos-anywhere.outPath')}" > .auto.tfvars.json
```
### local development
```sh
$ nix-shell
$ rm -rf .terraform/
$ tofu init
```

View file

@ -1,31 +0,0 @@
{
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.jq # implicit dep of nixos-anywhere TF: https://github.com/nix-community/nixos-anywhere/issues/416
(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
;
}

16
launch/deploy.sh Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -uex -o pipefail
declare domain hostname config initialUser
eval "$(jq -r '@sh "domain=\(.domain) hostname=\(.hostname) config=\(.config) initialUser=\(.initialUser)"')"
TARGET_HOST="${hostname}.abundos.eu"
TARGET="root@${TARGET_HOST}"
wrapper="$(mktemp -d)/wrapper.nix"
echo "(import $(readlink -f "./${config}.nix")).extendModules { specialArgs.terraform = { domain = \"${domain}\"; hostname = \"${hostname}\"; initialUser = builtins.fromJSON ''${initialUser}''; }; }" > "$wrapper"
NIXOS_SYSTEM=$(nix build --no-link --json --option show-trace true --file "$wrapper" "config.system.build.toplevel" | jq -r '.[].outputs.out')
sshOpts=(-p 22 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no)
NIX_SSHOPTS="${sshOpts[*]}" nix copy -s --experimental-features nix-command --to "ssh://$TARGET" "$NIXOS_SYSTEM"
switchCommand="nix-env -p /nix/var/nix/profiles/system --set $(printf "%q" "$NIXOS_SYSTEM"); /nix/var/nix/profiles/system/bin/switch-to-configuration switch"
deploy_status=0
# shellcheck disable=SC2029
ssh "${sshOpts[@]}" "$TARGET" "$switchCommand" || deploy_status="$?"
exit "$deploy_status"

11
launch/env.nix Normal file
View file

@ -0,0 +1,11 @@
{
pkgs,
...
}:
pkgs.stdenv.mkDerivation {
name = "fediversity-repo";
src = ../.;
installPhase = ''
cp -r . $out
'';
}

View file

@ -1,92 +0,0 @@
variable "nixos-anywhere" {
type = string
}
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"
}
}
# module "garage" {
# source = "./vm"
# count = var.mastodon.enable || var.pixelfed.enable || var.peertube.enable ? 1 : 0
# domain = var.domain
# hostname = "test01"
# config = "garage"
# initialUser = var.initialUser
# nixos-anywhere = var.nixos-anywhere
# }
module "mastodon" {
source = "./vm"
count = var.mastodon.enable ? 1 : 0
domain = var.domain
hostname = "test02"
config = "mastodon"
initialUser = var.initialUser
nixos-anywhere = var.nixos-anywhere
}
module "pixelfed" {
source = "./vm"
count = var.pixelfed.enable ? 1 : 0
domain = var.domain
hostname = "test04"
config = "pixelfed"
initialUser = var.initialUser
nixos-anywhere = var.nixos-anywhere
}
module "peertube" {
source = "./vm"
count = var.peertube.enable ? 1 : 0
domain = var.domain
hostname = "test03"
config = "peertube"
initialUser = var.initialUser
nixos-anywhere = var.nixos-anywhere
}

View file

@ -1,15 +0,0 @@
#!/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

View file

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

View file

@ -1,36 +0,0 @@
{
lib,
pkgs,
sources ? import ../npins,
...
}:
pkgs.stdenv.mkDerivation {
name = "tf-repo";
src = ../.;
buildInputs = [
(import ./tf.nix { inherit lib pkgs; })
];
buildPhase = ''
runHook preBuild
pushd launch/
# pass nixos-anywhere path to TF through variable
# when switching TF to nix take this directly from `inputs`
# https://codeberg.org/kiara/e2ed-hetzner/commit/84b2a349d3e48ea2a17340bceff762d834fd4046
echo "{\"nixos-anywhere\": \"${sources.nixos-anywhere}\"}" > .auto.tfvars.json
# point to the relevant providers
tofu init -input=false
popd
runHook postBuild
'';
# FIXME: can the above even work without a connection?
installPhase = ''
runHook preInstall
cp -r . $out
runHook postInstall
'';
}

View file

@ -1,25 +0,0 @@
# 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))

View file

@ -1,51 +0,0 @@
variable "nixos-anywhere" {
type = string
}
variable "domain" {
type = string
}
variable "hostname" {
type = string
}
variable "config" {
type = string
}
variable "initialUser" {
type = object({
displayName = string
username = string
password = string
email = string
})
}
module "deploy" {
# source = "github.com/nix-community/nixos-anywhere//terraform/all-in-one"
source = "${var.nixos-anywhere}//terraform/all-in-one"
file = "${path.module}/../${var.config}.nix"
nixos_system_attr = "config.system.build.toplevel"
nixos_partitioner_attr = "config.system.build.diskoScript"
# when instance id changes, it will trigger a reinstall
instance_id = var.hostname
target_user = "root"
target_host = "${var.hostname}.abundos.eu"
extra_files_script = "${path.module}/../pass-ssh-key.sh"
extra_environment = {
host = var.hostname
}
special_args = {
terraform = {
domain = var.domain
hostname = var.hostname
initialUser = var.initialUser
}
}
nix_options = {
show-trace = true
}
# build_on_remote = true
}

View file

@ -11,6 +11,5 @@
pkgs.openssh
pkgs.git
pkgs.jq # implicit dep of nixos-anywhere TF: https://github.com/nix-community/nixos-anywhere/issues/416
(import ../launch/tf.nix { inherit lib pkgs; })
];
}

View file

@ -29,7 +29,7 @@ let
((pkgs.formats.pythonVars { }).generate "settings.py" cfg.settings)
(builtins.toFile "extra-settings.py" cfg.extra-settings)
];
REPO_DIR = import ../../launch/tf-env.nix { inherit lib pkgs; };
REPO_DIR = import ../../launch/env.nix { inherit lib pkgs; };
};
python-environment = pkgs.python3.withPackages (

View file

@ -60,7 +60,7 @@ python3.pkgs.buildPythonPackage {
cp -v ${src}/manage.py $out/bin/manage.py
chmod +x $out/bin/manage.py
wrapProgram $out/bin/manage.py \
--set REPO_DIR "${import ../../launch/tf-env.nix { inherit lib pkgs; }}" \
--set REPO_DIR "${import ../../launch/env.nix { inherit lib pkgs; }}" \
--prefix PYTHONPATH : "$PYTHONPATH"
cp ${sources.htmx}/dist/htmx.min.js* $out/${python3.sitePackages}/panel/static/
'';

View file

@ -1,6 +1,6 @@
from enum import Enum
import json
from os.path import expanduser
import logging
import subprocess
import os
@ -14,6 +14,8 @@ from django.shortcuts import render
from panel import models, settings
from panel.configuration import forms
logger = logging.getLogger(__name__)
class Index(TemplateView):
template_name = 'index.html'
@ -103,7 +105,7 @@ class DeploymentStatus(ConfigurationForm):
# Check for deploy button
if "deploy" in self.request.POST.keys():
deployment_result, deployment_params = self.deployment(obj)
if deployment_result.returncode == 0:
if deployment_result == 0:
deployment_status = "Deployment Succeeded"
else:
deployment_status = "Deployment Failed"
@ -118,39 +120,60 @@ class DeploymentStatus(ConfigurationForm):
})
def deployment(self, obj):
submission = obj.parsed_value.model_dump_json()
testvms = {
"garage": "test01",
"mastodon": "test06",
"pixelfed": "test04",
"peertube": "test03",
}
submission = json.loads(obj.parsed_value.model_dump_json())
logger.debug(f"submission: {submission}")
# FIXME: let the user specify these from the form (#190)
dummy_user = {
"initialUser": {
"initialUser": json.dumps({
"displayName": "Testy McTestface",
"username": "test",
"password": "testtest",
"email": "test@test.com",
},
}),
}
deployment_result = 0
if submission["enable"]:
services = [
"mastodon",
"pixelfed",
"peertube",
]
for service in [
"garage",
] + services:
if (service == "garage" and any(submission[srvc]["enable"] for srvc in services)) or submission[service]["enable"]:
hostname = testvms[service]
# serialize back and forth now we still need to manually inject the dummy user
deployment_params = dummy_user | json.loads(submission)
deployment_params = json.dumps({
"config": service,
"hostname": hostname,
"initialUser": json.dumps({
"displayName": "Testy McTestface",
"username": "test",
"password": "testtest",
"email": "test@test.com",
}),
} | submission)
logger.debug(f"deployment_params: {deployment_params}")
deployment_result = deployment_result or subprocess.run(
["./deploy.sh"],
cwd=f"{settings.repo_dir}/launch",
env={
"PATH": settings.bin_path,
# used in nixos-anywhere 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",
} | {
},
# pass in form info to our deployment
# FIXME: ensure sensitive info is protected
f"TF_VAR_{k}": v if isinstance(v, str) else json.dumps(v) for k, v in deployment_params.items()
}
cwd = f"{settings.repo_dir}/launch"
cmd = [
"tofu",
# f"-chdir={cwd}",
"apply",
f"-state={cwd}/terraform.tfstate", # FIXME: separate users' state
"--auto-approve",
"-lock=false",
]
deployment_result = subprocess.run(cmd, cwd=cwd, env=env)
print(deployment_result)
return deployment_result, deployment_params
input=deployment_params,
text=True,
).returncode
else:
logger.debug(f"service {service} disabled")
else:
# FIXME: implement disable
logger.debug("deployment disabled")
return deployment_result, json.loads(deployment_params)