forked from fediversity/fediversity
Compare commits
10 commits
98c4490b4e
...
ce5126c0fa
Author | SHA1 | Date | |
---|---|---|---|
ce5126c0fa | |||
c98663ae71 | |||
3700b6e383 | |||
e3b816d85e | |||
afbbcbc22d | |||
c5fe0157b0 | |||
53d3791eaa | |||
53658e9880 | |||
3364d6c972 | |||
8f0bcc35f0 |
11 changed files with 104 additions and 54 deletions
|
@ -27,3 +27,9 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: nix build .#checks.x86_64-linux.peertube -L
|
- run: nix build .#checks.x86_64-linux.peertube -L
|
||||||
|
|
||||||
|
check-panel:
|
||||||
|
runs-on: native
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: cd panel && nix-build -A tests
|
||||||
|
|
|
@ -58,9 +58,13 @@
|
||||||
packages = [
|
packages = [
|
||||||
pkgs.nil
|
pkgs.nil
|
||||||
inputs'.agenix.packages.default
|
inputs'.agenix.packages.default
|
||||||
inputs'.nixops4.packages.default
|
pkgs.openssh
|
||||||
pkgs.httpie
|
pkgs.httpie
|
||||||
pkgs.jq
|
pkgs.jq
|
||||||
|
# exposing this env var as a hack to pass info in from form
|
||||||
|
(inputs'.nixops4.packages.default.overrideAttrs {
|
||||||
|
impureEnvVars = [ "DEPLOYMENT" ];
|
||||||
|
})
|
||||||
];
|
];
|
||||||
shellHook = config.pre-commit.installationScript;
|
shellHook = config.pre-commit.installationScript;
|
||||||
};
|
};
|
||||||
|
|
|
@ -143,7 +143,17 @@ 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 (readFile ./test-machines/configuration.json));
|
test = makeTestDeployment (
|
||||||
|
fromJSON (
|
||||||
|
let
|
||||||
|
env = builtins.getEnv "DEPLOYMENT";
|
||||||
|
in
|
||||||
|
if env != "" then
|
||||||
|
env
|
||||||
|
else
|
||||||
|
builtins.trace "env var DEPLOYMENT not set, falling back to ./test-machines/configuration.json!" (readFile ./test-machines/configuration.json)
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
flake.nixosConfigurations =
|
flake.nixosConfigurations =
|
||||||
genAttrs machines (makeConfiguration false)
|
genAttrs machines (makeConfiguration false)
|
||||||
|
|
|
@ -4,15 +4,10 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
name = "panel";
|
name = "panel";
|
||||||
panel = (import ../../../panel/default.nix { }).package;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../../../panel/nix/configuration.nix
|
(import ../../../panel { }).module
|
||||||
];
|
|
||||||
|
|
||||||
environment.systemPackages = [
|
|
||||||
panel
|
|
||||||
];
|
];
|
||||||
|
|
||||||
security.acme = {
|
security.acme = {
|
||||||
|
@ -22,18 +17,11 @@ in
|
||||||
|
|
||||||
services.${name} = {
|
services.${name} = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = panel;
|
|
||||||
production = true;
|
production = true;
|
||||||
domain = "demo.fediversity.eu";
|
domain = "demo.fediversity.eu";
|
||||||
host = "0.0.0.0";
|
|
||||||
secrets = {
|
secrets = {
|
||||||
SECRET_KEY = config.age.secrets.panel-secret-key.path;
|
SECRET_KEY = config.age.secrets.panel-secret-key.path;
|
||||||
};
|
};
|
||||||
port = 8000;
|
port = 8000;
|
||||||
settings = {
|
|
||||||
DATABASE_URL = "sqlite:///var/lib/${name}/db.sqlite3";
|
|
||||||
CREDENTIALS_DIRECTORY = "/var/lib/${name}/.credentials";
|
|
||||||
STATIC_ROOT = "/var/lib/${name}/static";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,36 +8,36 @@
|
||||||
},
|
},
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
package = pkgs.callPackage ./nix/package.nix { };
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
pkgs' = pkgs.extend (_final: _prev: { panel = package; });
|
|
||||||
|
|
||||||
manage = pkgs.writeScriptBin "manage" ''
|
manage = pkgs.writeScriptBin "manage" ''
|
||||||
exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@
|
exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
shell = pkgs.mkShellNoCC {
|
shell = pkgs.mkShellNoCC {
|
||||||
inputsFrom = [ package ];
|
inputsFrom = [ (pkgs.callPackage ./nix/package.nix { }) ];
|
||||||
packages = [
|
packages = [
|
||||||
pkgs.npins
|
pkgs.npins
|
||||||
manage
|
manage
|
||||||
];
|
];
|
||||||
env = {
|
env = {
|
||||||
NPINS_DIRECTORY = toString ../npins;
|
NPINS_DIRECTORY = toString ../npins;
|
||||||
|
# explicitly use nix, as e.g. lix does not have configurable-impure-env
|
||||||
|
NIX_BIN = lib.getExe pkgs.nix;
|
||||||
|
REPO_DIR = toString ../.;
|
||||||
|
CREDENTIALS_DIRECTORY = builtins.toString ./.credentials;
|
||||||
|
DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3";
|
||||||
};
|
};
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
# in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd.
|
# in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd.
|
||||||
# use this directory for testing with local secrets
|
# use this directory for testing with local secrets
|
||||||
mkdir -p .credentials
|
mkdir -p $CREDENTIALS_DIRECTORY
|
||||||
echo secret > ${builtins.toString ./.credentials}/SECRET_KEY
|
echo secret > ${builtins.toString ./.credentials}/SECRET_KEY
|
||||||
export CREDENTIALS_DIRECTORY=${builtins.toString ./.credentials}
|
|
||||||
export DATABASE_URL="sqlite:///${toString ./src}/db.sqlite3"
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
tests = pkgs'.callPackage ./nix/tests.nix { };
|
module = import ./nix/configuration.nix;
|
||||||
inherit package;
|
tests = pkgs.callPackage ./nix/tests.nix { };
|
||||||
|
|
||||||
# re-export inputs so they can be overridden granularly
|
# re-export inputs so they can be overridden granularly
|
||||||
# (they can't be accessed from the outside any other way)
|
# (they can't be accessed from the outside any other way)
|
||||||
|
|
|
@ -12,7 +12,6 @@ let
|
||||||
mkEnableOption
|
mkEnableOption
|
||||||
mkIf
|
mkIf
|
||||||
mkOption
|
mkOption
|
||||||
mkPackageOption
|
|
||||||
optionalString
|
optionalString
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
|
@ -22,23 +21,15 @@ let
|
||||||
name = "panel";
|
name = "panel";
|
||||||
|
|
||||||
cfg = config.services.${name};
|
cfg = config.services.${name};
|
||||||
|
package = pkgs.callPackage ./package.nix { };
|
||||||
|
|
||||||
database-url = "sqlite:////var/lib/${name}/db.sqlite3";
|
database-url = "sqlite:////var/lib/${name}/db.sqlite3";
|
||||||
|
|
||||||
python-environment = pkgs.python3.withPackages (
|
python-environment = pkgs.python3.withPackages (
|
||||||
ps:
|
ps: with ps; [
|
||||||
with ps;
|
package
|
||||||
[
|
|
||||||
uvicorn
|
uvicorn
|
||||||
cfg.package
|
|
||||||
dj-database-url
|
|
||||||
django-compressor
|
|
||||||
django-debug-toolbar
|
|
||||||
django-libsass
|
|
||||||
django_4
|
|
||||||
setuptools
|
|
||||||
]
|
]
|
||||||
++ cfg.package.propagatedBuildInputs
|
|
||||||
);
|
);
|
||||||
|
|
||||||
configFile = pkgs.concatText "configuration.py" [
|
configFile = pkgs.concatText "configuration.py" [
|
||||||
|
@ -48,7 +39,7 @@ let
|
||||||
|
|
||||||
manage-service = writeShellApplication {
|
manage-service = writeShellApplication {
|
||||||
name = "manage";
|
name = "manage";
|
||||||
text = ''exec ${cfg.package}/bin/manage.py "$@"'';
|
text = ''exec ${package}/bin/manage.py "$@"'';
|
||||||
};
|
};
|
||||||
|
|
||||||
manage-admin = writeShellApplication {
|
manage-admin = writeShellApplication {
|
||||||
|
@ -58,13 +49,13 @@ let
|
||||||
text =
|
text =
|
||||||
''
|
''
|
||||||
systemd-run --pty \
|
systemd-run --pty \
|
||||||
--same-dir \
|
|
||||||
--wait \
|
--wait \
|
||||||
--collect \
|
--collect \
|
||||||
--service-type=exec \
|
--service-type=exec \
|
||||||
--unit "manage-${name}.service" \
|
--unit "manage-${name}.service" \
|
||||||
--property "User=${name}" \
|
--property "User=${name}" \
|
||||||
--property "Group=${name}" \
|
--property "Group=${name}" \
|
||||||
|
--property "WorkingDirectory=/var/lib/${name}" \
|
||||||
--property "Environment=DATABASE_URL=${database-url} USER_SETTINGS_FILE=${configFile}" \
|
--property "Environment=DATABASE_URL=${database-url} USER_SETTINGS_FILE=${configFile}" \
|
||||||
''
|
''
|
||||||
+ optionalString (credentials != [ ]) (
|
+ optionalString (credentials != [ ]) (
|
||||||
|
@ -83,8 +74,6 @@ in
|
||||||
{
|
{
|
||||||
options.services.${name} = {
|
options.services.${name} = {
|
||||||
enable = mkEnableOption "Service configuration for `${name}`";
|
enable = mkEnableOption "Service configuration for `${name}`";
|
||||||
# NOTE: this requires that the package is present in `pkgs`
|
|
||||||
package = mkPackageOption pkgs name { };
|
|
||||||
production = mkOption {
|
production = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
|
@ -145,6 +134,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
nixpkgs.overlays = [ (import ./overlay.nix) ];
|
||||||
|
|
||||||
environment.systemPackages = [ manage-admin ];
|
environment.systemPackages = [ manage-admin ];
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
|
@ -181,16 +172,15 @@ in
|
||||||
preStart = ''
|
preStart = ''
|
||||||
# Auto-migrate on first run or if the package has changed
|
# Auto-migrate on first run or if the package has changed
|
||||||
versionFile="/var/lib/${name}/package-version"
|
versionFile="/var/lib/${name}/package-version"
|
||||||
if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
|
if [[ $(cat "$versionFile" 2>/dev/null) != ${package} ]]; then
|
||||||
manage migrate --no-input
|
manage migrate --no-input
|
||||||
manage collectstatic --no-input --clear
|
manage collectstatic --no-input --clear
|
||||||
manage compress --force
|
manage compress --force
|
||||||
echo ${cfg.package} > "$versionFile"
|
echo ${package} > "$versionFile"
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
script = ''
|
script = ''
|
||||||
export PYTHONPATH=$PYTHONPATH:${cfg.package}/lib/python3.12/site-packages
|
uvicorn ${name}.asgi:application --host ${cfg.host} --port ${toString cfg.port}
|
||||||
${python-environment}/bin/python -m uvicorn ${name}.asgi:application --host ${cfg.host} --port ${toString cfg.port}
|
|
||||||
'';
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
|
|
|
@ -3,6 +3,8 @@ let
|
||||||
# TODO: specify project/service name globally
|
# TODO: specify project/service name globally
|
||||||
name = "panel";
|
name = "panel";
|
||||||
defaults = {
|
defaults = {
|
||||||
|
# XXX: we have to duplicate this here despite it being defined in the service module, otherwise the test framework will error out
|
||||||
|
nixpkgs.overlays = lib.mkForce [ (import ./overlay.nix) ];
|
||||||
services.${name} = {
|
services.${name} = {
|
||||||
enable = true;
|
enable = true;
|
||||||
production = false;
|
production = false;
|
||||||
|
@ -26,6 +28,7 @@ lib.mapAttrs (name: test: pkgs.testers.runNixOSTest (test // { inherit name; }))
|
||||||
# run all application-level tests managed by Django
|
# run all application-level tests managed by Django
|
||||||
# https://docs.djangoproject.com/en/5.0/topics/testing/overview/
|
# https://docs.djangoproject.com/en/5.0/topics/testing/overview/
|
||||||
testScript = ''
|
testScript = ''
|
||||||
|
server.wait_for_unit("${name}.service")
|
||||||
server.succeed("manage test ${name}")
|
server.succeed("manage test ${name}")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
@ -34,7 +37,6 @@ lib.mapAttrs (name: test: pkgs.testers.runNixOSTest (test // { inherit name; }))
|
||||||
nodes.server = _: { imports = [ ./configuration.nix ]; };
|
nodes.server = _: { imports = [ ./configuration.nix ]; };
|
||||||
# check that the admin interface is served
|
# check that the admin interface is served
|
||||||
testScript = ''
|
testScript = ''
|
||||||
server.wait_for_unit("multi-user.target")
|
|
||||||
server.wait_for_unit("${name}.service")
|
server.wait_for_unit("${name}.service")
|
||||||
server.wait_for_open_port(8000)
|
server.wait_for_open_port(8000)
|
||||||
server.succeed("curl --fail -L -H 'Host: example.org' http://localhost/admin")
|
server.succeed("curl --fail -L -H 'Host: example.org' http://localhost/admin")
|
||||||
|
@ -45,11 +47,11 @@ lib.mapAttrs (name: test: pkgs.testers.runNixOSTest (test // { inherit name; }))
|
||||||
inherit defaults;
|
inherit defaults;
|
||||||
nodes.server = _: { imports = [ ./configuration.nix ]; };
|
nodes.server = _: { imports = [ ./configuration.nix ]; };
|
||||||
extraPythonPackages = ps: with ps; [ beautifulsoup4 ];
|
extraPythonPackages = ps: with ps; [ beautifulsoup4 ];
|
||||||
|
# type checking on `beautifulsoup4` will error out
|
||||||
skipTypeCheck = true;
|
skipTypeCheck = true;
|
||||||
# check that stylesheets are pre-processed and served
|
# check that stylesheets are pre-processed and served
|
||||||
testScript = ''
|
testScript = ''
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
server.wait_for_unit("multi-user.target")
|
|
||||||
server.wait_for_unit("${name}.service")
|
server.wait_for_unit("${name}.service")
|
||||||
server.wait_for_open_port(8000)
|
server.wait_for_open_port(8000)
|
||||||
stdout = server.succeed("curl --fail -H 'Host: example.org' http://localhost")
|
stdout = server.succeed("curl --fail -H 'Host: example.org' http://localhost")
|
||||||
|
|
|
@ -39,11 +39,10 @@ class Configuration(BaseModel):
|
||||||
# XXX: hard-code available apex domains for now,
|
# XXX: hard-code available apex domains for now,
|
||||||
# they will be prefixed by the user name
|
# they will be prefixed by the user name
|
||||||
class Domain(Enum):
|
class Domain(Enum):
|
||||||
EU = "fediversity.eu"
|
|
||||||
NET = "fediversity.net"
|
NET = "fediversity.net"
|
||||||
|
|
||||||
domain: Domain = Field(
|
domain: Domain = Field(
|
||||||
default=Domain.EU,
|
default=Domain.NET,
|
||||||
description="DNS domain where to expose services"
|
description="DNS domain where to expose services"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
def get_secret(name: str, encoding: str = "utf-8") -> str:
|
def get_secret(name: str, encoding: str = "utf-8") -> str:
|
||||||
|
# In the NixOS deployment, this variable is set by `systemd` via `LoadCredential`
|
||||||
|
# https://systemd.io/CREDENTIALS/
|
||||||
credentials_dir = env.get("CREDENTIALS_DIRECTORY")
|
credentials_dir = env.get("CREDENTIALS_DIRECTORY")
|
||||||
|
|
||||||
if credentials_dir is None:
|
if credentials_dir is None:
|
||||||
|
@ -171,8 +173,9 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
# 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: we may want to do this with a flat environment instead, and get all values from `os.environ.get()`.
|
# TODO(@fricklerhandwerk):
|
||||||
# this would make it more obvious which moving parts there are, if that environment is specified for development/staging/production in a visible place.
|
# we may want to do this with a flat environment instead, and get all values from `os.environ.get()`.
|
||||||
|
# this would make it more obvious which moving parts there are, if that environment is specified for development/staging/production in a visible place.
|
||||||
user_settings_file = env.get("USER_SETTINGS_FILE", None)
|
user_settings_file = env.get("USER_SETTINGS_FILE", None)
|
||||||
if user_settings_file is not None:
|
if user_settings_file is not None:
|
||||||
spec = importlib.util.spec_from_file_location("user_settings", user_settings_file)
|
spec = importlib.util.spec_from_file_location("user_settings", user_settings_file)
|
||||||
|
@ -182,3 +185,15 @@ if user_settings_file is not None:
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
sys.modules["user_settings"] = module
|
sys.modules["user_settings"] = module
|
||||||
from user_settings import * # noqa: F403 # pyright: ignore [reportMissingImports]
|
from user_settings import * # noqa: F403 # pyright: ignore [reportMissingImports]
|
||||||
|
|
||||||
|
# non-Django application settings
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
# Replacing the `USER_SETTINGS_FILE` mechanism following the comment there would probably be a good thing.
|
||||||
|
|
||||||
|
# a dir of nix supporting experimental feature `configurable-impure-env`.
|
||||||
|
nix_bin=env['NIX_BIN']
|
||||||
|
# path of the root flake to trigger nixops from, see #94.
|
||||||
|
# to deploy this should be specified, for dev just use a relative path.
|
||||||
|
repo_dir = env["REPO_DIR"]
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
|
|
||||||
<button class="button" disabled>Deploy</button>
|
<button class="button" type="submit" name="deploy">Deploy</button>
|
||||||
<button class="button" type="submit" >Save</button>
|
<button class="button" type="submit" name="save">Save</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
@ -6,7 +8,7 @@ from django.contrib.auth.models import User
|
||||||
from django.views.generic import TemplateView, DetailView
|
from django.views.generic import TemplateView, DetailView
|
||||||
from django.views.generic.edit import FormView
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
from panel import models
|
from panel import models, settings
|
||||||
from panel.configuration import forms
|
from panel.configuration import forms
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +43,40 @@ class ConfigurationForm(LoginRequiredMixin, FormView):
|
||||||
operator=self.request.user,
|
operator=self.request.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check for deploy button
|
||||||
|
if "deploy" in self.request.POST.keys():
|
||||||
|
submission = obj.parsed_value.model_dump_json()
|
||||||
|
# FIXME: let the user specify these from the form (#190)
|
||||||
|
dummy_user = {
|
||||||
|
"initialUser": {
|
||||||
|
"displayName": "Testy McTestface",
|
||||||
|
"username": "test",
|
||||||
|
"password": "testtest",
|
||||||
|
"email": "test@test.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
# serialize back and forth now we still need to manually inject the dummy user
|
||||||
|
deployment = json.dumps(dummy_user | json.loads(submission))
|
||||||
|
env = {
|
||||||
|
# pass in form info to our deployment
|
||||||
|
"DEPLOYMENT": deployment,
|
||||||
|
}
|
||||||
|
cmd = [
|
||||||
|
settings.nix_bin,
|
||||||
|
"develop",
|
||||||
|
# workaround to pass in info to nixops4 thru env vars, tho impure :(
|
||||||
|
"--extra-experimental-features",
|
||||||
|
"configurable-impure-env",
|
||||||
|
"--command",
|
||||||
|
"nixops4",
|
||||||
|
"apply",
|
||||||
|
"test",
|
||||||
|
]
|
||||||
|
subprocess.run(
|
||||||
|
cmd,
|
||||||
|
cwd=settings.repo_dir,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
# TODO(@fricklerhandwerk):
|
# TODO(@fricklerhandwerk):
|
||||||
|
|
Loading…
Add table
Reference in a new issue