1
0
Fork 0

module draft

This commit is contained in:
Lukas Wurzinger 2025-06-01 23:22:42 +02:00
parent e2e0f134da
commit 22c105586b
No known key found for this signature in database
4 changed files with 339 additions and 54 deletions

105
flake.lock generated
View file

@ -1,5 +1,21 @@
{ {
"nodes": { "nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": { "flake-parts": {
"inputs": { "inputs": {
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
@ -18,13 +34,56 @@
"type": "github" "type": "github"
} }
}, },
"gitignore": {
"inputs": {
"nixpkgs": [
"hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1747372754,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1745526057, "lastModified": 1748460289,
"narHash": "sha256-ITSpPDwvLBZBnPRS2bUcHY3gZSwis/uTe255QgMtTLA=", "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f771eb401a46846c1aebd20552521b233dd7e18b", "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -62,11 +121,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1744599653, "lastModified": 1748562898,
"narHash": "sha256-nysSwVVjG4hKoOjhjvE6U5lIKA8sEr1d1QzEfZsannU=", "narHash": "sha256-STk4QklrGpM3gliPKNJdBLSQvIrqRuwHI/rnYb/5rh8=",
"owner": "pyproject-nix", "owner": "pyproject-nix",
"repo": "build-system-pkgs", "repo": "build-system-pkgs",
"rev": "7dba6dbc73120e15b558754c26024f6c93015dd7", "rev": "33bd58351957bb52dd1700ea7eeefe34de06a892",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -82,11 +141,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1743438845, "lastModified": 1746540146,
"narHash": "sha256-1GSaoubGtvsLRwoYwHjeKYq40tLwvuFFVhGrG8J9Oek=", "narHash": "sha256-QxdHGNpbicIrw5t6U3x+ZxeY/7IEJ6lYbvsjXmcxFIM=",
"owner": "pyproject-nix", "owner": "pyproject-nix",
"repo": "pyproject.nix", "repo": "pyproject.nix",
"rev": "8063ec98edc459571d042a640b1c5e334ecfca1e", "rev": "e09c10c24ebb955125fda449939bfba664c467fd",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -98,12 +157,34 @@
"root": { "root": {
"inputs": { "inputs": {
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"hooks": "hooks",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pyproject-build-systems": "pyproject-build-systems", "pyproject-build-systems": "pyproject-build-systems",
"pyproject-nix": "pyproject-nix", "pyproject-nix": "pyproject-nix",
"treefmt": "treefmt",
"uv2nix": "uv2nix" "uv2nix": "uv2nix"
} }
}, },
"treefmt": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1748243702,
"narHash": "sha256-9YzfeN8CB6SzNPyPm2XjRRqSixDopTapaRsnTpXUEY8=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "1f3f7b784643d488ba4bf315638b2b0a4c5fb007",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
},
"uv2nix": { "uv2nix": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -114,11 +195,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1745697651, "lastModified": 1748398512,
"narHash": "sha256-r4A/fkiCenEapHkjJWPiNUZEfviuXMCr6mRozJ5dC4o=", "narHash": "sha256-99mf47Kjl/rj716cSjeA6ubZLlhNudmC4HRg/6UMfvs=",
"owner": "pyproject-nix", "owner": "pyproject-nix",
"repo": "uv2nix", "repo": "uv2nix",
"rev": "cb6508484d534dafd097713b575f2aebc3417de0", "rev": "f006d191d4ff5894d2ead6299e2eaf3659bc46b0",
"type": "github" "type": "github"
}, },
"original": { "original": {

113
flake.nix
View file

@ -4,6 +4,14 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
treefmt = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
pyproject-nix = { pyproject-nix = {
url = "github:pyproject-nix/pyproject.nix"; url = "github:pyproject-nix/pyproject.nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@ -21,42 +29,70 @@
}; };
}; };
outputs = { outputs =
self, {
nixpkgs, self,
flake-parts, nixpkgs,
uv2nix, flake-parts,
pyproject-nix, hooks,
pyproject-build-systems, treefmt,
... uv2nix,
} @ inputs: let pyproject-nix,
workspace = uv2nix.lib.workspace.loadWorkspace {workspaceRoot = ./.;}; pyproject-build-systems,
overlay = workspace.mkPyprojectOverlay { ...
sourcePreference = "wheel"; }@inputs:
}; let
in workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
flake-parts.lib.mkFlake {inherit inputs;} { overlay = workspace.mkPyprojectOverlay {
systems = ["x86_64-linux" "aarch64-linux"]; sourcePreference = "wheel";
};
in
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
hooks.flakeModule
treefmt.flakeModule
];
perSystem = { systems = nixpkgs.lib.systems.flakeExposed;
pkgs,
inputs', perSystem =
lib, {
... config,
}: let pkgs,
python = pkgs.python313; inputs',
pythonSet = lib,
(pkgs.callPackage pyproject-nix.build.packages { ...
inherit python; }:
}).overrideScope let
( python = pkgs.python313;
lib.composeManyExtensions [ pythonSet =
pyproject-build-systems.overlays.default (pkgs.callPackage pyproject-nix.build.packages {
overlay inherit python;
] }).overrideScope
); (
in { lib.composeManyExtensions [
devShells.default = pkgs.mkShell { pyproject-build-systems.overlays.default
overlay
]
);
in
{
treefmt = {
projectRootFile = "flake.nix";
programs.nixfmt = {
enable = true;
package = pkgs.nixfmt-rfc-style;
};
programs.ruff.enable = true;
};
pre-commit.settings.hooks = {
treefmt.enable = true;
};
devShells.default = pkgs.mkShell {
packages = [ packages = [
python python
pkgs.libffi pkgs.libffi
@ -71,11 +107,14 @@
LD_LIBRARY_PATH = lib.makeLibraryPath pkgs.pythonManylinuxPackages.manylinux1; LD_LIBRARY_PATH = lib.makeLibraryPath pkgs.pythonManylinuxPackages.manylinux1;
}; };
shellHook = '' shellHook = ''
${config.pre-commit.installationScript}
unset PYTHONPATH unset PYTHONPATH
''; '';
}; };
packages.default = pythonSet.mkVirtualEnv "forgesync" workspace.deps.default; packages.default = pythonSet.mkVirtualEnv "forgesync" workspace.deps.default;
}; };
flake.nixosModules.default = import ./module.nix self;
}; };
} }

167
module.nix Normal file
View file

@ -0,0 +1,167 @@
self:
{
lib,
pkgs,
utils,
config,
...
}:
let
cfg = config.services.forgesync;
inherit (lib) types;
inherit (utils.systemdUtils.unitOptions) unitOption;
in
{
options.services.forgesync = {
enable = lib.mkEnableOption "Forgesync";
package = lib.mkPackageOption self.packages.${pkgs.system} "default" { };
jobs = lib.mkOption {
description = ''
Synchronization jobs to run.
'';
default = { };
type = types.attrsOf (
types.submodule {
options = {
settings = lib.mkOption {
default = { };
example = {
from-instance = "https://codeberg.org/api/v1";
to = "github";
to-instance = "https://api.github.com";
remirror = true;
description-template = "{description} (Mirror of {url})";
mirror-interval = "8h0m0s";
immediate = true;
log = "INFO";
};
description = ''
Settings for this Forgesync job.
'';
type =
let
simples = [
types.bool
types.str
types.int
types.float
];
in
types.attrsOf (
types.oneOf (
simples
++ [
(types.listOf (types.oneOf simples))
]
)
);
};
secretFile = lib.mkOption {
type = types.path;
description = ''
The EnvironmentFile for secrets required for Forgesync: `FROM_TOKEN`, `TO_TOKEN` and `MIRROR_TOKEN`.
'';
};
timerConfig = lib.mkOption {
type = types.nullOr (types.attrsOf unitOption);
default = {
OnCalendar = "daily";
Persistent = true;
};
description = ''
When to run the job.
'';
};
inhibit = lib.mkOption {
default = [ ];
type = types.listOf (types.strMatching "^[^:]+$");
example = [
"sleep"
];
description = ''
Run the Forgesync process with an inhibition lock taken;
see {manpage}`systemd-inhibit(1)` for a list of possible operations.
'';
};
};
}
);
};
};
config = lib.mkIf cfg.enable {
systemd = lib.mkMerge (
lib.mapAttrsToList (
jobName: job:
let
unitName = "forgesync-job-${jobName}";
description = "Forgesync job ${jobName}";
in
{
timers.${unitName} = {
wantedBy = [ "timers.target" ];
inherit description;
inherit (job) timerConfig;
};
services.${unitName} = {
after = [ "network.target" ];
inherit description;
serviceConfig = {
Type = "oneshot";
DynamicUser = true;
ExecStart =
let
inhibitArgs = [
(lib.getExe' config.systemd.package "systemd-inhibit")
"--mode"
"block"
"--who"
description
"--what"
(lib.concatStringsSep ":" job.inhibit)
"--why"
"Scheduled Forgesync job ${jobName}"
"--"
];
args =
(lib.optionals (job.inhibit != [ ]) inhibitArgs)
++ [ (lib.getExe cfg.package) ]
++ (lib.cli.toGNUCommandLine { mkOptionName = k: "--${k}"; } job.settings);
in
utils.escapeSystemdExecArgs args;
EnvironmentFile = job.secretFile;
NoNewPrivileges = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
DevicePolicy = "closed";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
};
};
}
) cfg.jobs
);
};
}

View file

@ -20,11 +20,9 @@ class PushMirrorConfig:
def overlay(self: Self, other: Self) -> Self: def overlay(self: Self, other: Self) -> Self:
result = type(self)() result = type(self)()
for f in fields(self): for f in fields(self):
value = ( # pyright: ignore[reportAny] other_attr = getattr(other, f.name) # pyright: ignore[reportAny]
getattr(other, f.name) self_attr = getattr(self, f.name) # pyright: ignore[reportAny]
if getattr(other, f.name) is not None value = other_attr if other_attr is not None else self_attr # pyright: ignore[reportAny]
else getattr(self, f.name)
)
setattr(result, f.name, value) setattr(result, f.name, value)
return result return result