Skip to content

Commit

Permalink
feat: add oci support
Browse files Browse the repository at this point in the history
  • Loading branch information
blaggacao committed Jul 27, 2024
1 parent c7cf8f3 commit 62efc9f
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 14 deletions.
35 changes: 24 additions & 11 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
inputs.nixpkgs.url = "github:nixos/nixpkgs/release-24.05";

inputs = {
std.url = "github:divnix/std/v0.33.2";
std.url = "github:divnix/std/v0.33.3";
# std.url = "/home/blaggacao/src/github.com/divnix/std";
devshell.url = "github:numtide/devshell";
devshell.inputs.nixpkgs.follows = "nixpkgs";
Expand All @@ -65,7 +65,7 @@
arion.inputs.nixpkgs.follows = "nixpkgs";
# arion.inputs.hercules-ci-effects.follows = "";
n2c.url = "github:nlewo/nix2container";
n2c.inputs.nixpkgs.follows = "nixpkgs";
# n2c.inputs.nixpkgs.follows = "nixpkgs";
std.inputs = {
n2c.follows = "n2c";
nixpkgs.follows = "nixpkgs";
Expand Down
1 change: 0 additions & 1 deletion src/nixos/main.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
lib,
pkgs,
config,
frappix,
...
}:
with lib; let
Expand Down
1 change: 1 addition & 0 deletions src/nixos/systemd.nix
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ with builtins; let

PrivateTmp = true; # gunicorn requires /tmp

# on change: also update oci
BindReadOnlyPaths = [
# static shared data
"${apps}:${cfg.benchDirectory}/sites/apps.txt"
Expand Down
14 changes: 14 additions & 0 deletions src/oci-images.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
let
inherit (inputs.nixpkgs) lib;
inherit (cell) pkgs oci;

evaled = lib.evalModules {
modules = [
{_module.args = {inherit pkgs;};}
oci.frappix
oci.testrig
];
};
in {
frappix-base = evaled.config.oci.frappix.image;
}
33 changes: 33 additions & 0 deletions src/oci.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
frappix = {
meta.description = "The main frappix OCI module";
__functor = _: {
pkgs,
lib,
...
}: {
# load our custom `pkgs`
_module.args = {
inherit (pkgs) frappix;
inherit (inputs.std.lib) ops;
};
_file = ./oci.nix;
imports = map (m: lib.modules.setDefaultModuleLocation m m) [
./oci/main.nix
];
};
};
testrig = {
meta.description = "The frappix example OCI image profile";
__functor = _: {
pkgs,
lib,
...
}: {
_file = ./nixos.nix;
imports = map (m: lib.modules.setDefaultModuleLocation m m) [
./oci/testrig.nix
];
};
};
}
212 changes: 212 additions & 0 deletions src/oci/main.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
{
lib,
pkgs,
config,
ops,
...
}:
with lib; let
cfg = config.oci.frappix;
mkInternal = mkOption {
internal = true;
type = types.raw;
};
apps = builtins.toFile "apps" (
concatMapStringsSep "\n" (app: app.pname) cfg.apps
);
in {
#
# Interface
#
options.oci.frappix = {
name = mkOption {
type = types.str;
description = mdDoc ''
Name of the image in the form REGISTRY/NAME.
Tags are hashes and automatically calculated.
The full image name is accessible under `config.oci.frappix.image.name`
'';
example = literalExpression "ghcr.is/myorg/frappix";
};
debug = mkEnableOption (mdDoc "make debug image with some extra tools in the environment");
package = mkOption {
type = types.package;
default = pkgs.frappix.frappe;
description = mkDoc ''
The frappe base package to use.
'';
example = literalExpression pkgs.frappix.frappe;
};

apps = mkOption {
type = with types; listOf package;
default = [];
description = mkDoc ''
Apps that should be installed into this image.
Always includes frappe.
'';
example = literalExpression [
pkgs.frappix.erpnext
pkgs.frappix.insight
pkgs.frappix.gameplan
];
};

/*
Internal interface used in the nginx & systemd implementations
*/
benchDirectory = mkInternal;
combinedAssets = mkInternal;
penv = mkInternal;
packages = mkInternal;
environment = mkInternal // {type = types.attrs;};

operable = mkInternal;
image = mkInternal;
};

#
# Implementation
#
config.oci.frappix = {
apps = [cfg.package];

# preprocessing
combinedAssets = pkgs.mkSiteAssets cfg.apps;
penv = cfg.package.pythonModule.buildEnv.override {extraLibs = cfg.apps;};
packages = flatten (catAttrs "packages" cfg.apps);

# invariants for oci
benchDirectory = "/var/lib/frappix";
environment = {
FRAPPE_STREAM_LOGGING = "1";
FRAPPE_SITES_ROOT = "${cfg.benchDirectory}/sites";
FRAPPE_BENCH_ROOT = "${cfg.benchDirectory}";
NODE_PATH = concatMapStringsSep ":" (app: "${cfg.combinedAssets}/share/apps/" + app.pname + "/node_modules") cfg.apps;
PYTHON_PATH = "${cfg.penv}/${cfg.package.pythonModule.sitePackages}";
};

# output attributes
operable = ops.mkOperable {
inherit (cfg) package;
runtimeInputs =
cfg.packages
++ [
cfg.penv
# our custom ultra-slim bench command
pkgs.bench
];
debugInputs = [
pkgs.netcat
pkgs.tcpdump
];
runtimeEnv = cfg.environment;
runtimeScript = let
# sites dir is a volume, so we can only copy in place during startup
copyToSitesDir = concatStringsSep "; " [
# "cp -u ${cfg.benchDirectory}/sites-overlay/* ${cfg.benchDirectory}/sites/"
];
# some reference on workers vs threads:
# https://medium.com/building-the-system/gunicorn-3-means-of-concurrency-efbb547674b7
webserver = concatStringsSep " " [
"python -m gunicorn"
"--bind 0.0.0.0:8000"
"frappe.app:application"
"--preload"
];
in
# bash
''
${copyToSitesDir}
# Check if any arguments are provided
if [ "$#" -eq 0 ]; then
exec ${webserver} \
--timeout=120 \
--workers=2 \
--threads=4 \
--worker-class=gthread \
--worker-tmp-dir=/dev/shm
fi
# Check the first argument
case "$1" in
--worker=*)
QUEUE="''${1#*=}"
exec bench frappe worker --queue "$QUEUE"
;;
--scheduler)
echo "Starting scheduler (no further output)."
exec bench frappe schedule
;;
--websocket)
exec node "${cfg.package.src}/socketio.js"
;;
*)
exec ${webserver} "$@"
;;
esac
'';
};
image = ops.mkStandardOCI {
meta = {
description = "Frappix OCI image";
};
inherit (cfg) operable name;
config.WorkingDir = "${cfg.benchDirectory}";

setup = [
pkgs.coreutils
# our custom lightweight bench depends on /usr/bin/env
# link it from the coreutls QoL above
(ops.mkSetup "links" [] ''
mkdir -p $out/usr/bin
ln -s /bin/env $out/usr/bin/env
'')
# trick `buildEnv` and prevent $out`${cfg.benchDirectory}` to be a symlink
(pkgs.runCommand "" {} ''
mkdir -p $out/${cfg.benchDirectory}
'')
# trick `buildEnv` and prevent $out`${cfg.benchDirectory}/sites` to be a symlink
(pkgs.runCommand "" {} ''
mkdir -p $out/${cfg.benchDirectory}/sites
'')
# trick `buildEnv` and prevent $out`${cfg.benchDirectory}/config` to be a symlink
(pkgs.runCommand "" {} ''
mkdir -p $out/${cfg.benchDirectory}/config
'')
(pkgs.runCommand "" {} ''
mkdir -p $out/${cfg.benchDirectory}/config
touch $out/${cfg.benchDirectory}/.touch
'')
# on change: also update systemd
# frappe expeced runtime paths, akin to Systemd'd BindReadOnlyPaths
(ops.mkSetup "bench-path-contracts" [
{
regex = "${cfg.benchDirectory}/(sites|config).*";
mode = "0777";
uname = "nobody";
gname = "nobody";
uid = 65534;
gid = 65534;
}
] ''
mkdir -p $out/${cfg.benchDirectory}/sites
ln -s ${cfg.package}/share/patches.txt $out/${cfg.benchDirectory}/patches.txt
# because dynamic website theme generator
ln -s ${cfg.combinedAssets}/share/apps $out/${cfg.benchDirectory}/apps
ln -s ${apps} $out/${cfg.benchDirectory}/sites/apps.txt
# because lazy loading via `frappe.client.get_js
ln -s ${cfg.combinedAssets}/share/sites/assets $out/${cfg.benchDirectory}/sites/assets
'')
];
};
};
}
9 changes: 9 additions & 0 deletions src/oci/testrig.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{pkgs, ...}: {
oci.frappix = {
name = "ghcr.io/blaggacao/frappix-test-oci";
debug = false;
apps = with pkgs.frappix; [
erpnext
];
};
}
Loading

0 comments on commit 62efc9f

Please sign in to comment.