mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-02-16 17:14:00 +00:00
Merge pull request #248310 from emilylange/nixos/gitea-forgejo-split
nixos/forgejo: fork from nixos/gitea (split) close https://github.com/NixOS/nixpkgs/issues/244866
This commit is contained in:
commit
30bcc6c39d
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@ -305,6 +305,10 @@ pkgs/development/python-modules/buildcatrust/ @ajs124 @lukegb @mweinelt
|
||||
/nixos/modules/services/misc/matrix-conduit.nix @piegamesde
|
||||
/nixos/tests/matrix-conduit.nix @piegamesde
|
||||
|
||||
# Forgejo
|
||||
nixos/modules/services/misc/forgejo.nix @bendlas @emilylange
|
||||
pkgs/applications/version-management/forgejo @bendlas @emilylange
|
||||
|
||||
# Dotnet
|
||||
/pkgs/build-support/dotnet @IvarWithoutBones
|
||||
/pkgs/development/compilers/dotnet @IvarWithoutBones
|
||||
|
@ -646,6 +646,7 @@
|
||||
./services/misc/etesync-dav.nix
|
||||
./services/misc/evdevremapkeys.nix
|
||||
./services/misc/felix.nix
|
||||
./services/misc/forgejo.nix
|
||||
./services/misc/freeswitch.nix
|
||||
./services/misc/fstrim.nix
|
||||
./services/misc/gammu-smsd.nix
|
||||
|
668
nixos/modules/services/misc/forgejo.nix
Normal file
668
nixos/modules/services/misc/forgejo.nix
Normal file
@ -0,0 +1,668 @@
|
||||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.forgejo;
|
||||
opt = options.services.forgejo;
|
||||
format = pkgs.formats.ini { };
|
||||
|
||||
exe = lib.getExe cfg.package;
|
||||
|
||||
pg = config.services.postgresql;
|
||||
useMysql = cfg.database.type == "mysql";
|
||||
usePostgresql = cfg.database.type == "postgres";
|
||||
useSqlite = cfg.database.type == "sqlite3";
|
||||
|
||||
inherit (lib)
|
||||
literalExpression
|
||||
mdDoc
|
||||
mkChangedOptionModule
|
||||
mkDefault
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
mkPackageOptionMD
|
||||
mkRemovedOptionModule
|
||||
mkRenamedOptionModule
|
||||
optionalAttrs
|
||||
optionals
|
||||
optionalString
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "appName" ] [ "services" "forgejo" "settings" "DEFAULT" "APP_NAME" ])
|
||||
(mkRemovedOptionModule [ "services" "forgejo" "extraConfig" ] "services.forgejo.extraConfig has been removed. Please use the freeform services.forgejo.settings option instead")
|
||||
(mkRemovedOptionModule [ "services" "forgejo" "database" "password" ] "services.forgejo.database.password has been removed. Please use services.forgejo.database.passwordFile instead")
|
||||
|
||||
# copied from services.gitea; remove at some point
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "cookieSecure" ] [ "services" "forgejo" "settings" "session" "COOKIE_SECURE" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "disableRegistration" ] [ "services" "forgejo" "settings" "service" "DISABLE_REGISTRATION" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "domain" ] [ "services" "forgejo" "settings" "server" "DOMAIN" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "httpAddress" ] [ "services" "forgejo" "settings" "server" "HTTP_ADDR" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "httpPort" ] [ "services" "forgejo" "settings" "server" "HTTP_PORT" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "log" "level" ] [ "services" "forgejo" "settings" "log" "LEVEL" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "log" "rootPath" ] [ "services" "forgejo" "settings" "log" "ROOT_PATH" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "rootUrl" ] [ "services" "forgejo" "settings" "server" "ROOT_URL" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "ssh" "clonePort" ] [ "services" "forgejo" "settings" "server" "SSH_PORT" ])
|
||||
(mkRenamedOptionModule [ "services" "forgejo" "staticRootPath" ] [ "services" "forgejo" "settings" "server" "STATIC_ROOT_PATH" ])
|
||||
(mkChangedOptionModule [ "services" "forgejo" "enableUnixSocket" ] [ "services" "forgejo" "settings" "server" "PROTOCOL" ] (
|
||||
config: if config.services.forgejo.enableUnixSocket then "http+unix" else "http"
|
||||
))
|
||||
(mkRemovedOptionModule [ "services" "forgejo" "ssh" "enable" ] "services.forgejo.ssh.enable has been migrated into freeform setting services.forgejo.settings.server.DISABLE_SSH. Keep in mind that the setting is inverted")
|
||||
];
|
||||
|
||||
options = {
|
||||
services.forgejo = {
|
||||
enable = mkEnableOption (mdDoc "Forgejo");
|
||||
|
||||
package = mkPackageOptionMD pkgs "forgejo" { };
|
||||
|
||||
useWizard = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = mdDoc ''
|
||||
Whether to use the built-in installation wizard instead of
|
||||
declaratively managing the {file}`app.ini` config file in nix.
|
||||
'';
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
default = "/var/lib/forgejo";
|
||||
type = types.str;
|
||||
description = mdDoc "Forgejo data directory.";
|
||||
};
|
||||
|
||||
customDir = mkOption {
|
||||
default = "${cfg.stateDir}/custom";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/custom"'';
|
||||
type = types.str;
|
||||
description = mdDoc ''
|
||||
Base directory for custom templates and other options.
|
||||
|
||||
If {option}`${opt.useWizard}` is disabled (default), this directory will also
|
||||
hold secrets and the resulting {file}`app.ini` config at runtime.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "forgejo";
|
||||
description = mdDoc "User account under which Forgejo runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "forgejo";
|
||||
description = mdDoc "Group under which Forgejo runs.";
|
||||
};
|
||||
|
||||
database = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "sqlite3" "mysql" "postgres" ];
|
||||
example = "mysql";
|
||||
default = "sqlite3";
|
||||
description = mdDoc "Database engine to use.";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = mdDoc "Database host address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = if !usePostgresql then 3306 else pg.port;
|
||||
defaultText = literalExpression ''
|
||||
if config.${opt.database.type} != "postgresql"
|
||||
then 3306
|
||||
else config.${options.services.postgresql.port}
|
||||
'';
|
||||
description = mdDoc "Database host port.";
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "forgejo";
|
||||
description = mdDoc "Database name.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "forgejo";
|
||||
description = mdDoc "Database user.";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/forgejo-dbpassword";
|
||||
description = mdDoc ''
|
||||
A file containing the password corresponding to
|
||||
{option}`${opt.database.user}`.
|
||||
'';
|
||||
};
|
||||
|
||||
socket = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
|
||||
defaultText = literalExpression "null";
|
||||
example = "/run/mysqld/mysqld.sock";
|
||||
description = mdDoc "Path to the unix socket file to use for authentication.";
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/data/forgejo.db";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/forgejo.db"'';
|
||||
description = mdDoc "Path to the sqlite3 database file.";
|
||||
};
|
||||
|
||||
createDatabase = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = mdDoc "Whether to create a local database automatically.";
|
||||
};
|
||||
};
|
||||
|
||||
dump = {
|
||||
enable = mkEnableOption (mdDoc "periodic dumps via the [built-in {command}`dump` command](https://forgejo.org/docs/latest/admin/command-line/#dump)");
|
||||
|
||||
interval = mkOption {
|
||||
type = types.str;
|
||||
default = "04:31";
|
||||
example = "hourly";
|
||||
description = mdDoc ''
|
||||
Run a Forgejo dump at this interval. Runs by default at 04:31 every day.
|
||||
|
||||
The format is described in
|
||||
{manpage}`systemd.time(7)`.
|
||||
'';
|
||||
};
|
||||
|
||||
backupDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/dump";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/dump"'';
|
||||
description = mdDoc "Path to the directory where the dump archives will be stored.";
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type = types.enum [ "zip" "tar" "tar.sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" "tar.zst" ];
|
||||
default = "zip";
|
||||
description = mdDoc "Archive format used to store the dump file.";
|
||||
};
|
||||
|
||||
file = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = mdDoc "Filename to be used for the dump. If `null` a default name is chosen by forgejo.";
|
||||
example = "forgejo-dump";
|
||||
};
|
||||
};
|
||||
|
||||
lfs = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = mdDoc "Enables git-lfs support.";
|
||||
};
|
||||
|
||||
contentDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/data/lfs";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/lfs"'';
|
||||
description = mdDoc "Where to store LFS files.";
|
||||
};
|
||||
};
|
||||
|
||||
repositoryRoot = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/repositories";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
|
||||
description = mdDoc "Path to the git repositories.";
|
||||
};
|
||||
|
||||
mailerPasswordFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/run/keys/forgejo-mailpw";
|
||||
description = mdDoc "Path to a file containing the SMTP password.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = { };
|
||||
description = mdDoc ''
|
||||
Free-form settings written directly to the `app.ini` configfile file.
|
||||
Refer to <https://forgejo.org/docs/latest/admin/config-cheat-sheet/> for supported values.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
DEFAULT = {
|
||||
RUN_MODE = "dev";
|
||||
};
|
||||
"cron.sync_external_users" = {
|
||||
RUN_AT_START = true;
|
||||
SCHEDULE = "@every 24h";
|
||||
UPDATE_EXISTING = true;
|
||||
};
|
||||
mailer = {
|
||||
ENABLED = true;
|
||||
MAILER_TYPE = "sendmail";
|
||||
FROM = "do-not-reply@example.org";
|
||||
SENDMAIL_PATH = "''${pkgs.system-sendmail}/bin/sendmail";
|
||||
};
|
||||
other = {
|
||||
SHOW_FOOTER_VERSION = false;
|
||||
};
|
||||
}
|
||||
'';
|
||||
type = types.submodule {
|
||||
freeformType = format.type;
|
||||
options = {
|
||||
log = {
|
||||
ROOT_PATH = mkOption {
|
||||
default = "${cfg.stateDir}/log";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
|
||||
type = types.str;
|
||||
description = mdDoc "Root path for log files.";
|
||||
};
|
||||
LEVEL = mkOption {
|
||||
default = "Info";
|
||||
type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
|
||||
description = mdDoc "General log level.";
|
||||
};
|
||||
};
|
||||
|
||||
server = {
|
||||
PROTOCOL = mkOption {
|
||||
type = types.enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
|
||||
default = "http";
|
||||
description = mdDoc ''Listen protocol. `+unix` means "over unix", not "in addition to."'';
|
||||
};
|
||||
|
||||
HTTP_ADDR = mkOption {
|
||||
type = types.either types.str types.path;
|
||||
default = if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0";
|
||||
defaultText = literalExpression ''if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0"'';
|
||||
description = mdDoc "Listen address. Must be a path when using a unix socket.";
|
||||
};
|
||||
|
||||
HTTP_PORT = mkOption {
|
||||
type = types.port;
|
||||
default = 3000;
|
||||
description = mdDoc "Listen port. Ignored when using a unix socket.";
|
||||
};
|
||||
|
||||
DOMAIN = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = mdDoc "Domain name of your server.";
|
||||
};
|
||||
|
||||
ROOT_URL = mkOption {
|
||||
type = types.str;
|
||||
default = "http://${cfg.settings.server.DOMAIN}:${toString cfg.settings.server.HTTP_PORT}/";
|
||||
defaultText = literalExpression ''"http://''${config.services.forgejo.settings.server.DOMAIN}:''${toString config.services.forgejo.settings.server.HTTP_PORT}/"'';
|
||||
description = mdDoc "Full public URL of Forgejo server.";
|
||||
};
|
||||
|
||||
STATIC_ROOT_PATH = mkOption {
|
||||
type = types.either types.str types.path;
|
||||
default = cfg.package.data;
|
||||
defaultText = literalExpression "config.${opt.package}.data";
|
||||
example = "/var/lib/forgejo/data";
|
||||
description = mdDoc "Upper level of template and static files path.";
|
||||
};
|
||||
|
||||
DISABLE_SSH = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = mdDoc "Disable external SSH feature.";
|
||||
};
|
||||
|
||||
SSH_PORT = mkOption {
|
||||
type = types.port;
|
||||
default = 22;
|
||||
example = 2222;
|
||||
description = mdDoc ''
|
||||
SSH port displayed in clone URL.
|
||||
The option is required to configure a service when the external visible port
|
||||
differs from the local listening port i.e. if port forwarding is used.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
session = {
|
||||
COOKIE_SECURE = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = mdDoc ''
|
||||
Marks session cookies as "secure" as a hint for browsers to only send
|
||||
them via HTTPS. This option is recommend, if Forgejo is being served over HTTPS.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
|
||||
message = "services.forgejo.database.user must match services.forgejo.user if the database is to be automatically provisioned";
|
||||
}
|
||||
];
|
||||
|
||||
services.forgejo.settings = {
|
||||
DEFAULT = {
|
||||
RUN_MODE = mkDefault "prod";
|
||||
RUN_USER = mkDefault cfg.user;
|
||||
WORK_PATH = mkDefault cfg.stateDir;
|
||||
};
|
||||
|
||||
database = mkMerge [
|
||||
{
|
||||
DB_TYPE = cfg.database.type;
|
||||
}
|
||||
(mkIf (useMysql || usePostgresql) {
|
||||
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;
|
||||
PASSWD = "#dbpass#";
|
||||
})
|
||||
(mkIf useSqlite {
|
||||
PATH = cfg.database.path;
|
||||
})
|
||||
(mkIf usePostgresql {
|
||||
SSL_MODE = "disable";
|
||||
})
|
||||
];
|
||||
|
||||
repository = {
|
||||
ROOT = cfg.repositoryRoot;
|
||||
};
|
||||
|
||||
server = mkIf cfg.lfs.enable {
|
||||
LFS_START_SERVER = true;
|
||||
LFS_JWT_SECRET = "#lfsjwtsecret#";
|
||||
};
|
||||
|
||||
session = {
|
||||
COOKIE_NAME = mkDefault "session";
|
||||
};
|
||||
|
||||
security = {
|
||||
SECRET_KEY = "#secretkey#";
|
||||
INTERNAL_TOKEN = "#internaltoken#";
|
||||
INSTALL_LOCK = true;
|
||||
};
|
||||
|
||||
mailer = mkIf (cfg.mailerPasswordFile != null) {
|
||||
PASSWD = "#mailerpass#";
|
||||
};
|
||||
|
||||
oauth2 = {
|
||||
JWT_SECRET = "#oauth2jwtsecret#";
|
||||
};
|
||||
|
||||
lfs = mkIf cfg.lfs.enable {
|
||||
PATH = cfg.lfs.contentDir;
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
|
||||
enable = mkDefault true;
|
||||
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = cfg.database.user;
|
||||
ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
|
||||
enable = mkDefault true;
|
||||
package = mkDefault pkgs.mariadb;
|
||||
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = cfg.database.user;
|
||||
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
|
||||
# If we have a folder or symlink with Forgejo locales, remove it
|
||||
# And symlink the current Forgejo locales in place
|
||||
"L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
|
||||
|
||||
] ++ optionals cfg.lfs.enable [
|
||||
"d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.forgejo = {
|
||||
description = "Forgejo (Beyond coding. We forge.)";
|
||||
after = [
|
||||
"network.target"
|
||||
] ++ optionals usePostgresql [
|
||||
"postgresql.service"
|
||||
] ++ optionals useMysql [
|
||||
"mysql.service"
|
||||
];
|
||||
requires = optionals (cfg.database.createDatabase && usePostgresql) [
|
||||
"postgresql.service"
|
||||
] ++ optionals (cfg.database.createDatabase && useMysql) [
|
||||
"mysql.service"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ cfg.package pkgs.git pkgs.gnupg ];
|
||||
|
||||
# In older versions the secret naming for JWT was kind of confusing.
|
||||
# The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET
|
||||
# wasn't persistent at all.
|
||||
# To fix that, there is now the file oauth2_jwt_secret containing the
|
||||
# values for JWT_SECRET and the file jwt_secret gets renamed to
|
||||
# lfs_jwt_secret.
|
||||
# We have to consider this to stay compatible with older installations.
|
||||
preStart =
|
||||
let
|
||||
runConfig = "${cfg.customDir}/conf/app.ini";
|
||||
secretKey = "${cfg.customDir}/conf/secret_key";
|
||||
oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
|
||||
oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
|
||||
lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
|
||||
internalToken = "${cfg.customDir}/conf/internal_token";
|
||||
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
|
||||
in
|
||||
''
|
||||
# copy custom configuration and generate random secrets if needed
|
||||
${lib.optionalString (!cfg.useWizard) ''
|
||||
function forgejo_setup {
|
||||
cp -f '${format.generate "app.ini" cfg.settings}' '${runConfig}'
|
||||
|
||||
if [ ! -s '${secretKey}' ]; then
|
||||
${exe} generate secret SECRET_KEY > '${secretKey}'
|
||||
fi
|
||||
|
||||
# Migrate LFS_JWT_SECRET filename
|
||||
if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
|
||||
mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
|
||||
fi
|
||||
|
||||
if [ ! -s '${oauth2JwtSecret}' ]; then
|
||||
${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
|
||||
fi
|
||||
|
||||
${optionalString cfg.lfs.enable ''
|
||||
if [ ! -s '${lfsJwtSecret}' ]; then
|
||||
${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
|
||||
fi
|
||||
''}
|
||||
|
||||
if [ ! -s '${internalToken}' ]; then
|
||||
${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
|
||||
fi
|
||||
|
||||
chmod u+w '${runConfig}'
|
||||
${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
|
||||
${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
|
||||
${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
|
||||
|
||||
${optionalString cfg.lfs.enable ''
|
||||
${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
|
||||
''}
|
||||
|
||||
${optionalString (cfg.database.passwordFile != null) ''
|
||||
${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
|
||||
''}
|
||||
|
||||
${optionalString (cfg.mailerPasswordFile != null) ''
|
||||
${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
|
||||
''}
|
||||
chmod u-w '${runConfig}'
|
||||
}
|
||||
(umask 027; forgejo_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 = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
ExecStart = "${exe} web --pid /run/forgejo/forgejo.pid";
|
||||
Restart = "always";
|
||||
# Runtime directory and mode
|
||||
RuntimeDirectory = "forgejo";
|
||||
RuntimeDirectoryMode = "0755";
|
||||
# Proc filesystem
|
||||
ProcSubset = "pid";
|
||||
ProtectProc = "invisible";
|
||||
# Access write directories
|
||||
ReadWritePaths = [ cfg.customDir cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
|
||||
UMask = "0027";
|
||||
# Capabilities
|
||||
CapabilityBoundingSet = "";
|
||||
# Security
|
||||
NoNewPrivileges = true;
|
||||
# Sandboxing
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RemoveIPC = true;
|
||||
PrivateMounts = true;
|
||||
# System Call Filtering
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" "setrlimit" ];
|
||||
};
|
||||
|
||||
environment = {
|
||||
USER = cfg.user;
|
||||
HOME = cfg.stateDir;
|
||||
# `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497
|
||||
# is resolved.
|
||||
GITEA_WORK_DIR = cfg.stateDir;
|
||||
GITEA_CUSTOM = cfg.customDir;
|
||||
};
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "forgejo") {
|
||||
forgejo = {
|
||||
home = cfg.stateDir;
|
||||
useDefaultShell = true;
|
||||
group = cfg.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "forgejo") {
|
||||
forgejo = { };
|
||||
};
|
||||
|
||||
systemd.services.forgejo-dump = mkIf cfg.dump.enable {
|
||||
description = "forgejo dump";
|
||||
after = [ "forgejo.service" ];
|
||||
path = [ cfg.package ];
|
||||
|
||||
environment = {
|
||||
USER = cfg.user;
|
||||
HOME = cfg.stateDir;
|
||||
# `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497
|
||||
# is resolved.
|
||||
GITEA_WORK_DIR = cfg.stateDir;
|
||||
GITEA_CUSTOM = cfg.customDir;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = cfg.user;
|
||||
ExecStart = "${exe} dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
|
||||
WorkingDirectory = cfg.dump.backupDir;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.forgejo-dump = mkIf cfg.dump.enable {
|
||||
description = "Forgejo dump timer";
|
||||
partOf = [ "forgejo-dump.service" ];
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig.OnCalendar = cfg.dump.interval;
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ bendlas emilylange ];
|
||||
}
|
@ -284,7 +284,7 @@ in {
|
||||
fluentd = handleTest ./fluentd.nix {};
|
||||
fluidd = handleTest ./fluidd.nix {};
|
||||
fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
|
||||
forgejo = handleTest ./gitea.nix { giteaPackage = pkgs.forgejo; };
|
||||
forgejo = handleTest ./forgejo.nix { };
|
||||
freenet = handleTest ./freenet.nix {};
|
||||
freeswitch = handleTest ./freeswitch.nix {};
|
||||
freshrss-sqlite = handleTest ./freshrss-sqlite.nix {};
|
||||
|
157
nixos/tests/forgejo.nix
Normal file
157
nixos/tests/forgejo.nix
Normal file
@ -0,0 +1,157 @@
|
||||
{ system ? builtins.currentSystem
|
||||
, config ? { }
|
||||
, pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
## gpg --faked-system-time='20230301T010000!' --quick-generate-key snakeoil ed25519 sign
|
||||
signingPrivateKey = ''
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
lFgEY/6jkBYJKwYBBAHaRw8BAQdADXiZRV8RJUyC9g0LH04wLMaJL9WTc+szbMi7
|
||||
5fw4yP8AAQCl8EwGfzSLm/P6fCBfA3I9znFb3MEHGCCJhJ6VtKYyRw7ktAhzbmFr
|
||||
ZW9pbIiUBBMWCgA8FiEE+wUM6VW/NLtAdSixTWQt6LZ4x50FAmP+o5ACGwMFCQPC
|
||||
ZwAECwkIBwQVCgkIBRYCAwEAAh4FAheAAAoJEE1kLei2eMedFTgBAKQs1oGFZrCI
|
||||
TZP42hmBTKxGAI1wg7VSdDEWTZxut/2JAQDGgo2sa4VHMfj0aqYGxrIwfP2B7JHO
|
||||
GCqGCRf9O/hzBA==
|
||||
=9Uy3
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
'';
|
||||
signingPrivateKeyId = "4D642DE8B678C79D";
|
||||
|
||||
supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
|
||||
makeGForgejoTest = type: nameValuePair type (makeTest {
|
||||
name = "forgejo-${type}";
|
||||
meta.maintainers = with maintainers; [ bendlas emilylange ];
|
||||
|
||||
nodes = {
|
||||
server = { config, pkgs, ... }: {
|
||||
virtualisation.memorySize = 2047;
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
database = { inherit type; };
|
||||
settings.service.DISABLE_REGISTRATION = true;
|
||||
settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
|
||||
settings.actions.ENABLED = true;
|
||||
};
|
||||
environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq ];
|
||||
services.openssh.enable = true;
|
||||
|
||||
specialisation.runner = {
|
||||
inheritParentConfig = true;
|
||||
configuration.services.gitea-actions-runner.instances."test" = {
|
||||
enable = true;
|
||||
name = "ci";
|
||||
url = "http://localhost:3000";
|
||||
labels = [
|
||||
# don't require docker/podman
|
||||
"native:host"
|
||||
];
|
||||
tokenFile = "/var/lib/forgejo/runner_token";
|
||||
};
|
||||
};
|
||||
};
|
||||
client1 = { config, pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
};
|
||||
client2 = { config, pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }:
|
||||
let
|
||||
inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
|
||||
serverSystem = nodes.server.system.build.toplevel;
|
||||
in
|
||||
''
|
||||
GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no"
|
||||
REPO = "forgejo@server:test/repo"
|
||||
PRIVK = "${snakeOilPrivateKey}"
|
||||
|
||||
start_all()
|
||||
|
||||
client1.succeed("mkdir /tmp/repo")
|
||||
client1.succeed("mkdir -p $HOME/.ssh")
|
||||
client1.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
|
||||
client1.succeed("chmod 0400 $HOME/.ssh/privk")
|
||||
client1.succeed("git -C /tmp/repo init")
|
||||
client1.succeed("echo hello world > /tmp/repo/testfile")
|
||||
client1.succeed("git -C /tmp/repo add .")
|
||||
client1.succeed("git config --global user.email test@localhost")
|
||||
client1.succeed("git config --global user.name test")
|
||||
client1.succeed("git -C /tmp/repo commit -m 'Initial import'")
|
||||
client1.succeed(f"git -C /tmp/repo remote add origin {REPO}")
|
||||
|
||||
server.wait_for_unit("forgejo.service")
|
||||
server.wait_for_open_port(3000)
|
||||
server.wait_for_open_port(22)
|
||||
server.succeed("curl --fail http://localhost:3000/")
|
||||
|
||||
server.succeed(
|
||||
"su -l forgejo -c 'gpg --homedir /var/lib/forgejo/data/home/.gnupg "
|
||||
+ "--import ${toString (pkgs.writeText "forgejo.key" signingPrivateKey)}'"
|
||||
)
|
||||
|
||||
assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
|
||||
|
||||
server.succeed(
|
||||
"curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. "
|
||||
+ "Please contact your site administrator.'"
|
||||
)
|
||||
server.succeed(
|
||||
"su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea admin user create "
|
||||
+ "--username test --password totallysafe --email test@localhost'"
|
||||
)
|
||||
|
||||
api_token = server.succeed(
|
||||
"curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens "
|
||||
+ "-H 'Accept: application/json' -H 'Content-Type: application/json' -d "
|
||||
+ "'{\"name\":\"token\",\"scopes\":[\"all\"]}' | jq '.sha1' | xargs echo -n"
|
||||
)
|
||||
|
||||
server.succeed(
|
||||
"curl --fail -X POST http://localhost:3000/api/v1/user/repos "
|
||||
+ "-H 'Accept: application/json' -H 'Content-Type: application/json' "
|
||||
+ f"-H 'Authorization: token {api_token}'"
|
||||
+ ' -d \'{"auto_init":false, "description":"string", "license":"mit", "name":"repo", "private":false}\'''
|
||||
)
|
||||
|
||||
server.succeed(
|
||||
"curl --fail -X POST http://localhost:3000/api/v1/user/keys "
|
||||
+ "-H 'Accept: application/json' -H 'Content-Type: application/json' "
|
||||
+ f"-H 'Authorization: token {api_token}'"
|
||||
+ ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\'''
|
||||
)
|
||||
|
||||
client1.succeed(
|
||||
f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git -C /tmp/repo push origin master"
|
||||
)
|
||||
|
||||
client2.succeed("mkdir -p $HOME/.ssh")
|
||||
client2.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
|
||||
client2.succeed("chmod 0400 $HOME/.ssh/privk")
|
||||
client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}")
|
||||
client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"')
|
||||
|
||||
server.wait_until_succeeds(
|
||||
'test "$(curl http://localhost:3000/api/v1/repos/test/repo/commits '
|
||||
+ '-H "Accept: application/json" | jq length)" = "1"',
|
||||
timeout=10
|
||||
)
|
||||
|
||||
with subtest("Testing runner registration"):
|
||||
server.succeed(
|
||||
"su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/forgejo/runner_token"
|
||||
)
|
||||
server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test")
|
||||
server.wait_for_unit("gitea-runner-test.service")
|
||||
server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'")
|
||||
'';
|
||||
});
|
||||
in
|
||||
|
||||
listToAttrs (map makeGForgejoTest supportedDbTypes)
|
Loading…
Reference in New Issue
Block a user