Files
nix/modules/server/forgejo.nix
Gustavo "Guz" L. de Mello 5ce99497d7 refactor!: I went too close into the sun
Simlified everything, things were getting out of control
2024-04-08 16:37:29 -03:00

420 lines
12 KiB
Nix

{
config,
lib,
pkgs,
utils,
...
}: let
cfg = config.server.forgejo;
users = builtins.attrValues (builtins.mapAttrs
(username: info: {
name =
if isNull info.name
then username
else info.name;
email = info.email;
password = info.password;
admin = info.admin;
})
cfg.settings.users);
settingsFormat = pkgs.formats.yaml {};
in {
imports = [];
options.server.forgejo = with lib;
with lib.types; {
enable = mkEnableOption "";
user = mkOption {
type = str;
default = "git";
};
actions.enable = mkOption {
type = bool;
default = false;
};
actions.runnerToken = mkOption {
type = str;
};
actions.package = mkOption {
type = package;
default = pkgs.forgejo-actions-runner;
};
actions.instanceUrl = mkOption {
type = str;
default = "https://${cfg.domain}";
};
actions.url = mkOption {
type = str;
default = "http://localhost:${toString cfg.port}";
};
actions.instanceName = mkOption {
type = str;
default = "${cfg.user}Forgejo${toString cfg.port}";
};
actions.settings = mkOption {
type = submodule {
freeformType = settingsFormat.type;
options = {
runner.insecure = mkOption {
type = bool;
default = true;
};
};
};
default = {};
};
package = mkOption {
type = package;
default = pkgs.forgejo;
};
cliAlias = mkOption {
type = bool;
default = true;
};
domain = mkOption {
type = str;
default = "forgejo." + config.server.domain;
};
port = mkOption {
type = port;
default = 3020;
};
data = {
root = mkOption {
type = path;
default = config.server.storage + /forgejo;
};
};
handleUndeclaredUsers = mkOption {
type = bool;
default = false;
};
settings = {
users = mkOption {
type = attrsOf (submodule ({
config,
lib,
...
}:
with lib;
with lib.types; {
options = {
name = mkOption {
type = nullOr (either str path);
default = null;
};
password = mkOption {
type = either str path;
};
email = mkOption {
type = either str path;
};
admin = mkOption {
type = bool;
default = false;
};
};
}));
default = {};
};
name = mkOption {
type = str;
default = "Forgejo: Beyond coding. We forge";
};
prod = mkOption {
type = bool;
default = true;
};
repo.defaultUnits = mkOption {
type = listOf (enum [
"repo.code"
"repo.releases"
"repo.issues"
"repo.pulls"
"repo.wiki"
"repo.projects"
"repo.packages"
"repo.actions"
]);
default = [
"repo.code"
"repo.issues"
"repo.pulls"
];
};
repo.disabledUnits = mkOption {
type = listOf (enum [
"repo.issues"
"repo.ext_issues"
"repo.pulls"
"repo.wiki"
"repo.ext_wiki"
"repo.projects"
"repo.packages"
"repo.actions"
]);
default = [];
};
repo.pushCreate = mkOption {
type = bool;
default = false;
};
cors.enable = mkOption {
type = bool;
default = false;
};
cors.domains = mkOption {
type = listOf str;
default = [];
};
cors.methods = mkOption {
type = listOf str;
default = [];
};
ui.defaultTheme = mkOption {
type = str;
default = "forgejo-auto";
};
ui.themes = mkOption {
type = listOf str;
default = [
"forgejo-auto"
"forgejo-light"
"forgejo-dark"
"auto"
"gitea"
"arc-green"
];
};
server.protocol = mkOption {
type = enum ["http" "https" "fcgi" "http+unix" "fcgi+unix"];
default = "http";
};
server.domain = mkOption {
type = str;
default = cfg.domain;
};
server.port = mkOption {
type = port;
default = cfg.port;
};
server.address = mkOption {
type = either str path;
default =
if hasSuffix "+unix" cfg.settings.server.protocol
then "/run/forgejo/forgejo.sock"
else "0.0.0.0";
};
server.url = mkOption {
type = str;
default = "http://${cfg.settings.server.domain}:${toString cfg.settings.server.port}";
};
server.offline = mkOption {
type = bool;
default = false;
};
server.compression = mkOption {
type = bool;
default = false;
};
server.landingPage = mkOption {
type = enum ["home" "explore" "organizations" "login" str];
default = "home";
};
service.registration = mkOption {
type = bool;
default = false;
};
security.allowBypassGiteaEnv = mkOption {
type = bool;
default = false;
};
};
};
config = let
inherit (utils) escapeSystemdPath;
in
with lib;
mkIf cfg.enable {
users.users."${cfg.user}" = {
home = cfg.data.root;
useDefaultShell = true;
group = cfg.user;
isSystemUser = true;
initialPassword = "1313";
};
users.groups."${cfg.user}" = {};
virtualisation.docker.enable = cfg.actions.enable;
services.gitea-actions-runner.instances."${cfg.actions.instanceName}" = mkIf cfg.actions.enable {
enable = true;
name = cfg.actions.instanceName;
url = cfg.actions.instanceUrl;
token = cfg.actions.runnerToken;
settings = cfg.actions.settings;
labels = [
"host:host"
"shell:host://-self-hosted"
"debian-latest:docker://node:18-bullseye"
"ubuntu-latest:docker://node:18-bullseye"
"debian-slim:docker://node:18-bullseye-slim"
"ubuntu-slim:docker://node:18-bullseye-slim"
"alpine-latest:docker://alpine:latest"
];
};
services.gitea-actions-runner.package = cfg.actions.package;
systemd.services."${escapeSystemdPath cfg.actions.instanceName}".serviceConfig.User = cfg.user;
services.forgejo = {
enable = true;
package = cfg.package;
user = cfg.user;
group = cfg.user;
stateDir = toString cfg.data.root;
useWizard = false;
database = {
user = cfg.user;
type = "sqlite3";
};
settings = with builtins; {
actions = mkIf cfg.actions.enable {
ENABLED = true;
DEFAULT_ACTIONS_URL = cfg.actions.url;
};
DEFAULT = {
APP_NAME = cfg.settings.name;
RUN_MODE =
if cfg.settings.prod
then "prod"
else "dev";
};
repository = {
DISABLED_REPO_UNITS = concatStringsSep "," cfg.settings.repo.disabledUnits;
DEFAULT_REPO_UNITS = concatStringsSep "," cfg.settings.repo.defaultUnits;
ENABLE_PUSH_CREATE_USER = cfg.settings.repo.pushCreate;
ENABLE_PUSH_CREATE_ORG = cfg.settings.repo.pushCreate;
};
cors = {
ENABLED = cfg.settings.cors.enable;
ALLOW_DOMAIN = concatStringsSep "," cfg.settings.cors.domains;
METHODS = concatStringsSep "," cfg.settings.cors.methods;
};
ui = {
DEFAULT_THEME = cfg.settings.ui.defaultTheme;
THEMES = concatStringsSep "," cfg.settings.ui.themes;
};
server = {
PROTOCOL = cfg.settings.server.protocol;
DOMAIN = cfg.settings.server.domain;
ROOT_URL = cfg.settings.server.url;
HTTP_ADDR = cfg.settings.server.address;
HTTP_PORT = cfg.settings.server.port;
OFFLINE_MODE = cfg.settings.server.offline;
ENABLE_GZIP = cfg.settings.server.compression;
LANDING_PAGE = cfg.settings.server.landingPage;
};
security = {
ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET =
if cfg.settings.security.allowBypassGiteaEnv
then false
else true;
};
service = {
DISABLE_REGISTRATION =
if cfg.settings.service.registration
then false
else true;
};
};
};
systemd.services."homelab-forgejo-setup" = with builtins; {
script = ''
configFile="${toString cfg.data.root}/custom/conf/app.ini";
touch $configFile
gum="${pkgs.gum}/bin/gum"
forgejo="${cfg.package}/bin/gitea --config $configFile"
user="$forgejo admin user"
awk="${pkgs.gawk}/bin/awk"
declaredUsers=(${toString (map (user: "${
if isPath user.name
then "$(cat ${toString user.name})"
else user.name
}")
users)});
$gum log --structured --time timeonly --level info "HANDLING UNDECLARED USERS"
$user list | $awk '{print $2}' | tail -n +2 | while read username; do
if printf '%s\0' "''${declaredUsers[@]}" | grep -Fxqz -- "$username"; then
$gum log --structured --time timeonly --level warn "Declared user already exists, ignoring" username $username;
else
if [[ "$($user list | tail -n +2 | $awk '{print $2 " " $5}' | grep "$username " | $awk '{print $2}')" == "true" ]]; then
$gum log --structured --time timeonly --level warn "Undeclared user is a ADMIN, ignoring" username $username;
else
${
if cfg.handleUndeclaredUsers
then ''
$gum log --structured --time timeonly --level warn "DELETING undeclared user" username $username;
$user delete -u "$username";
''
else ''
$gum log --structured --time timeonly --level warn "UNDECLARED user, please declare it in the config so it's reproducible" username "$username";
''
}
fi
fi
done
${toString (map (user: ''
username="${
if isPath user.name
then "\"$(cat ${toString user.name})\""
else user.name
}";
email="${
if isPath user.email
then "\"$(cat ${toString user.email})\""
else user.email
}";
password="${
if isPath user.password
then "\"$(cat ${toString user.password})\""
else user.password
}";
if [[ "$($user list | grep "$username" | $awk '{print $2}')" ]]; then
$gum log --structured --time timeonly --level warn "User with username already exists" username $username;
elif [[ "$($user list | grep "$email" | $awk '{print $3}')" ]]; then
$gum log --structured --time timeonly --level warn "User with email already exists" email $email;
else
$gum log --structured --time timeonly --level info ${
if user.admin
then "Creating ADMIN user"
else "Creating user"
} username $username email $email password $password;
$user create --username $username --email $email --password $password ${
if user.admin
then "--admin"
else ""
};
fi
'')
users)}
'';
wantedBy = ["multi-user.target"];
after = ["forgejo.service"];
serviceConfig = {
Type = "oneshot";
User = cfg.user;
Group = cfg.user;
};
};
};
}