feat(modules,gitea): secret handling for Gitea module
This commit is contained in:
251
modules/gitea.nix
Normal file
251
modules/gitea.nix
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
This file has code copied from NixOS nixpkgs's repostiory, and can be located at
|
||||
<https://github.com/NixOS/nixpkgs/blob/nixos-25.05/nixos/modules/services/misc/gitea.nix>
|
||||
|
||||
The original code is licensed under the MIT License (SPDX-License-Identifier: MIT),
|
||||
which a copy of the copyright notice and license can be found at
|
||||
<https://github.com/NixOS/nixpkgs/blob/nixos-25.05/COPYING> and below:
|
||||
|
||||
Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.services.gitea;
|
||||
|
||||
exe = getExe cfg.package;
|
||||
format = pkgs.formats.ini {};
|
||||
|
||||
secrets = let
|
||||
mkSecret = section: values:
|
||||
mapAttrsToList (k: v: {
|
||||
env = envEscape "GITEA__${section}__${k}__FILE";
|
||||
path = v;
|
||||
})
|
||||
values;
|
||||
envEscape = str: replaceStrings ["." "-"] ["_0X2E_" "_0X2D_"] (strings.toUpper str);
|
||||
in
|
||||
flatten (mapAttrsToList mkSecret cfg.secrets);
|
||||
in {
|
||||
options.services.gitea = {
|
||||
secrets = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
This is a small wrapper over systemd's `LoadCredential`.
|
||||
|
||||
It takes the same sections and keys as {option}`services.forgejo.settings`,
|
||||
but the value of each key is a path instead of a string or bool.
|
||||
|
||||
The path is then loaded as credential, exported as environment variable
|
||||
and then feed through
|
||||
<https://github.com/go-gitea/gitea/blob/v1.24.6/contrib/environment-to-ini/environment-to-ini.go>
|
||||
|
||||
It does the required environment variable escaping for you.
|
||||
|
||||
::: {.note}
|
||||
Keys specified here take priority over the ones in {option}`services.forgejo.settings`!
|
||||
:::
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
metrics = {
|
||||
TOKEN = "/run/keys/gitea-metrics-token";
|
||||
};
|
||||
camo = {
|
||||
HMAC_KEY = "/run/keys/gitea-camo-hmac";
|
||||
};
|
||||
service = {
|
||||
HCAPTCHA_SECRET = "/run/keys/gitea-hcaptcha-secret";
|
||||
HCAPTCHA_SITEKEY = "/run/keys/gitea-hcaptcha-sitekey";
|
||||
};
|
||||
}
|
||||
'';
|
||||
type = with types;
|
||||
submodule {
|
||||
freeformType = attrsOf (attrsOf path);
|
||||
options = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
services.gitea.settings = {
|
||||
DEFAULT = {
|
||||
RUN_MODE = mkDefault "prod";
|
||||
RUN_USER = mkDefault cfg.user;
|
||||
WORK_PATH = mkDefault cfg.stateDir;
|
||||
};
|
||||
|
||||
database = mkMerge [
|
||||
{
|
||||
DB_TYPE = cfg.database.type;
|
||||
}
|
||||
(mkIf (cfg.database.type == "postgres" || cfg.database.type == "mysql") {
|
||||
HOST =
|
||||
if cfg.database.socket != null
|
||||
then cfg.database.socket
|
||||
else cfg.database.host + ":" + toString cfg.database.port;
|
||||
NAME = cfg.database.name;
|
||||
USER = cfg.database.user;
|
||||
})
|
||||
(mkIf (cfg.database.type == "sqlite3") {
|
||||
PATH = cfg.database.path;
|
||||
})
|
||||
(mkIf (cfg.database.type == "postgres") {
|
||||
SSL_MODE = "disable";
|
||||
})
|
||||
];
|
||||
|
||||
repository = {
|
||||
ROOT = cfg.repositoryRoot;
|
||||
};
|
||||
|
||||
server = mkIf cfg.lfs.enable {
|
||||
LFS_START_SERVER = true;
|
||||
};
|
||||
|
||||
session = {
|
||||
COOKIE_NAME = mkDefault "session";
|
||||
};
|
||||
|
||||
security = {
|
||||
INSTALL_LOCK = true;
|
||||
};
|
||||
|
||||
lfs = mkIf cfg.lfs.enable {
|
||||
PATH = cfg.lfs.contentDir;
|
||||
};
|
||||
};
|
||||
|
||||
services.gitea.secrets = {
|
||||
security = {
|
||||
SECRET_KEY = "${cfg.customDir}/conf/secret_key";
|
||||
INTERNAL_TOKEN = "${cfg.customDir}/conf/internal_token";
|
||||
};
|
||||
|
||||
oauth2 = {
|
||||
JWT_SECRET = "${cfg.customDir}/conf/oauth2_jwt_secret";
|
||||
};
|
||||
|
||||
database = mkIf (cfg.database.passwordFile != null) {
|
||||
PASSWD = cfg.database.passwordFile;
|
||||
};
|
||||
|
||||
server = mkIf cfg.lfs.enable {
|
||||
LFS_JWT_SECRET = "${cfg.customDir}/conf/lfs_jwt_secret";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.gitea-secrets = mkIf (!cfg.useWizard) {
|
||||
description = "Gitea secret bootstrap helper";
|
||||
script = ''
|
||||
if [ ! -s '${cfg.secrets.security.SECRET_KEY}' ]; then
|
||||
${exe} generate secret SECRET_KEY > '${cfg.secrets.security.SECRET_KEY}'
|
||||
fi
|
||||
|
||||
if [ ! -s '${cfg.secrets.oauth2.JWT_SECRET}' ]; then
|
||||
${exe} generate secret JWT_SECRET > '${cfg.secrets.oauth2.JWT_SECRET}'
|
||||
fi
|
||||
|
||||
${optionalString cfg.lfs.enable ''
|
||||
if [ ! -s '${cfg.secrets.server.LFS_JWT_SECRET}' ]; then
|
||||
${exe} generate secret LFS_JWT_SECRET > '${cfg.secrets.server.LFS_JWT_SECRET}'
|
||||
fi
|
||||
''}
|
||||
|
||||
if [ ! -s '${cfg.secrets.security.INTERNAL_TOKEN}' ]; then
|
||||
${exe} generate secret INTERNAL_TOKEN > '${cfg.secrets.security.INTERNAL_TOKEN}'
|
||||
fi
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ReadWritePaths = [cfg.customDir];
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.gitea = {
|
||||
after =
|
||||
[
|
||||
"network.target"
|
||||
]
|
||||
++ optionals (cfg.database.type == "postgres") [
|
||||
"postgresql.service"
|
||||
]
|
||||
++ optionals (cfg.database.type == "mysql") [
|
||||
"mysql.service"
|
||||
]
|
||||
++ optionals (!cfg.useWizard) [
|
||||
"gitea-secrets.service"
|
||||
];
|
||||
requires =
|
||||
optionals (cfg.database.createDatabase && cfg.database.type == "postgres") [
|
||||
"postgresql.service"
|
||||
]
|
||||
++ optionals (cfg.database.createDatabase && cfg.database.type == "mysql") [
|
||||
"mysql.service"
|
||||
]
|
||||
++ optionals (!cfg.useWizard) [
|
||||
"gitea-secrets.service"
|
||||
];
|
||||
|
||||
# Copied from, to add secrets handling
|
||||
# https://github.com/NixOS/nixpkgs/blob/20c4598c84a671783f741e02bf05cbfaf4907cff/nixos/modules/services/misc/forgejo.nix#L696
|
||||
preStart = ''
|
||||
# copy custom configuration and generate random secrets if needed
|
||||
${optionalString (!cfg.useWizard) ''
|
||||
function gitea_setup {
|
||||
config='${cfg.customDir}/conf/app.ini'
|
||||
cp -f '${format.generate "app.ini" cfg.settings}' "$config"
|
||||
|
||||
chmod u+w "$config"
|
||||
${getExe' cfg.package "environment-to-ini"} --config "$config"
|
||||
chmod u-w "$config"
|
||||
}
|
||||
(umask 027; gitea_setup)
|
||||
''}
|
||||
|
||||
# run migrations/init the database
|
||||
${exe} migrate
|
||||
|
||||
# update all hooks' binary paths
|
||||
${exe} admin regenerate hooks
|
||||
|
||||
# update command option in authorized_keys
|
||||
if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
|
||||
then
|
||||
${exe} admin regenerate keys
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig.LoadCredential = map (e: "${e.env}:${e.path}") secrets;
|
||||
|
||||
environment = listToAttrs (map (e: nameValuePair e.env "%d/${e.env}") secrets);
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user