diff --git a/lib/default.nix b/lib/default.nix
new file mode 100644
index 0000000..fdb48ea
--- /dev/null
+++ b/lib/default.nix
@@ -0,0 +1,13 @@
+{ lib }:
+{
+  template = file: newPath: replacements:
+    let
+      templatePath = newPath + ".template";
+      sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements);
+    in
+      ''
+      ln -fs ${file} ${templatePath}
+      rm ${newPath} || :
+      sed ${sedPatterns} ${templatePath} > ${newPath}
+      '';
+}
diff --git a/modules/blocks/authelia.nix b/modules/blocks/authelia.nix
index 89259d6..6f19f1e 100644
--- a/modules/blocks/authelia.nix
+++ b/modules/blocks/authelia.nix
@@ -4,22 +4,12 @@ let
   cfg = config.shb.authelia;
 
   contracts = pkgs.callPackage ../contracts {};
+  shblib = pkgs.callPackage ../../lib {};
 
   fqdn = "${cfg.subdomain}.${cfg.domain}";
   fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}";
 
   autheliaCfg = config.services.authelia.instances.${fqdn};
-
-  template = file: newPath: replacements:
-    let
-      templatePath = newPath + ".template";
-      sedPatterns = lib.strings.concatStringsSep ";" (lib.attrsets.mapAttrsToList (from: to: "s|${from}|${to}|") replacements);
-    in
-      ''
-      ln -fs ${file} ${templatePath}
-      rm ${newPath} || :
-      sed "${sedPatterns}" ${templatePath} > ${newPath}
-      '';
 in
 {
   options.shb.authelia = {
@@ -307,7 +297,7 @@ in
           replace = client: {"%SECRET_${client.id}%" = "$(cat ${toString client.secretFile})";};
           replacements = lib.foldl (container: client: container // (replace client) ) {} clients;
         in
-          template tmplFile "/var/lib/authelia-${fqdn}/oidc_clients.yaml" replacements;
+          shblib.template tmplFile "/var/lib/authelia-${fqdn}/oidc_clients.yaml" replacements;
       in
         lib.mkBefore (mkCfg cfg.oidcClients);
 
diff --git a/modules/blocks/davfs.nix b/modules/blocks/davfs.nix
index 9292ef9..05fe6be 100644
--- a/modules/blocks/davfs.nix
+++ b/modules/blocks/davfs.nix
@@ -3,17 +3,7 @@
 let
   cfg = config.shb.davfs;
 
-  template = file: newPath: replacements:
-    let
-      templatePath = newPath + ".template";
-
-      sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "\"s|${from}|${to}|\"") replacements);
-    in
-      ''
-      ln -fs ${file} ${templatePath}
-      rm ${newPath} || :
-      sed ${sedPatterns} ${templatePath} > ${newPath}
-      '';
+  shblib = pkgs.callPackage ../../lib {};
 in
 {
   options.shb.davfs = {
diff --git a/modules/services/arr.nix b/modules/services/arr.nix
index 388cb26..c55f669 100644
--- a/modules/services/arr.nix
+++ b/modules/services/arr.nix
@@ -4,6 +4,7 @@ let
   cfg = config.shb.arr;
 
   contracts = pkgs.callPackage ../contracts {};
+  shblib = pkgs.callPackage ../../lib {};
 
   apps = {
     radarr = {
@@ -185,18 +186,6 @@ let
       } // (c.moreOptions or {});
     };
   });
-
-  template = file: newPath: replacements:
-    let
-      templatePath = newPath + ".template";
-
-      sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements);
-    in
-      ''
-      ln -fs ${file} ${templatePath}
-      rm ${newPath} || :
-      sed ${sedPatterns} ${templatePath} > ${newPath}
-      '';
 in
 {
   options.shb.arr = lib.listToAttrs (lib.mapAttrsToList appOption apps);
@@ -227,7 +216,7 @@ in
             };
           templatedSettings = (removeAttrs s [ "APIKeyFile" ]) // templatedfileSettings;
 
-          t = template (apps.radarr.settingsFormat.generate "
+          t = shblib.template (apps.radarr.settingsFormat.generate "
 config.xml" templatedSettings) "${config.services.radarr.dataDir}/config.xml" (
             lib.optionalAttrs (!(isNull s.APIKeyFile)) {
               "%APIKEY%" = "$(cat ${s.APIKeyFile})";
@@ -295,7 +284,7 @@ config.xml" templatedSettings) "${config.services.radarr.dataDir}/config.xml" (
             };
           templatedSettings = (removeAttrs s [ "APIKeyFile" "OmdbApiKeyFile" ]) // templatedfileSettings;
 
-          t = template (apps.jackett.settingsFormat.generate "jackett.json" templatedSettings) "${config.services.jackett.dataDir}/ServerConfig.json" (
+          t = shblib.template (apps.jackett.settingsFormat.generate "jackett.json" templatedSettings) "${config.services.jackett.dataDir}/ServerConfig.json" (
             lib.optionalAttrs (!(isNull s.APIKeyFile)) {
               "%APIKEY%" = "$(cat ${s.APIKeyFile})";
             } // lib.optionalAttrs (!(isNull s.OmdbApiKeyFile)) {
diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix
index 039a8ac..148418d 100644
--- a/modules/services/jellyfin.nix
+++ b/modules/services/jellyfin.nix
@@ -4,20 +4,9 @@ let
   cfg = config.shb.jellyfin;
 
   contracts = pkgs.callPackage ../contracts {};
+  shblib = pkgs.callPackage ../../lib {};
 
   fqdn = "${cfg.subdomain}.${cfg.domain}";
-
-  template = file: newPath: replacements:
-    let
-      templatePath = newPath + ".template";
-
-      sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "\"s|${from}|${to}|\"") replacements);
-    in
-      ''
-      ln -fs ${file} ${templatePath}
-      rm ${newPath} || :
-      sed ${sedPatterns} ${templatePath} > ${newPath}
-      '';
 in
 {
   options.shb.jellyfin = {
@@ -359,13 +348,13 @@ in
           </BrandingOptions>
         '';
       in
-        template ldapConfig "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml" {
+        shblib.template ldapConfig "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml" {
           "%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})";
         }
-        + template ssoConfig "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml" {
+        + shblib.template ssoConfig "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml" {
           "%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})";
         }
-        + template brandingConfig "/var/lib/jellyfin/config/branding.xml" {"%a%" = "%a%";};
+        + shblib.template brandingConfig "/var/lib/jellyfin/config/branding.xml" {"%a%" = "%a%";};
 
     shb.authelia.oidcClients = [
       {
diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix
index b4abbbd..3430960 100644
--- a/modules/services/vaultwarden.nix
+++ b/modules/services/vaultwarden.nix
@@ -4,20 +4,9 @@ let
   cfg = config.shb.vaultwarden;
 
   contracts = pkgs.callPackage ../contracts {};
+  shblib = pkgs.callPackage ../../lib {};
 
   fqdn = "${cfg.subdomain}.${cfg.domain}";
-
-  template = file: newPath: replacements:
-    let
-      templatePath = newPath + ".template";
-
-      sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements);
-    in
-      ''
-      ln -fs ${file} ${templatePath}
-      rm ${newPath} || :
-      sed ${sedPatterns} ${templatePath} > ${newPath}
-      '';
 in
 {
   options.shb.vaultwarden = {
@@ -165,7 +154,7 @@ in
         SMTP_PASSWORD=%SMTP_PASSWORD%
         '';
       in
-        template envFile "/var/lib/bitwarden_rs/vaultwarden.env" {
+        shblib.template envFile "/var/lib/bitwarden_rs/vaultwarden.env" {
           "%DB_PASSWORD%" = "$(cat ${cfg.databasePasswordFile})";
           "%SMTP_PASSWORD%" = "$(cat ${cfg.smtp.passwordFile})";
         };