diff --git a/api.go b/api.go index 5343f68..27f6402 100644 --- a/api.go +++ b/api.go @@ -89,8 +89,8 @@ func StartAPIServer(config *Config, }) router.GET("/blockcache/personal", func(c *gin.Context) { - filePath := filepath.FromSlash("sources/personal.list") - f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_RDONLY, 0600) + personalListPath := filepath.Clean(filepath.FromSlash(config.Blocking.SourcesStore + "/personal.list")) + f, err := os.OpenFile(personalListPath, os.O_APPEND|os.O_CREATE|os.O_RDONLY, 0600) if err != nil { logger.Critical(err) } @@ -112,8 +112,8 @@ func StartAPIServer(config *Config, }) router.GET("/blockcache/set/:key", func(c *gin.Context) { - filePath := filepath.FromSlash("sources/personal.list") - f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600) + personalListPath := filepath.Clean(filepath.FromSlash(config.Blocking.SourcesStore + "/personal.list")) + f, err := os.OpenFile(personalListPath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600) if err != nil { logger.Critical(err) } diff --git a/config.go b/config.go index a77f2f6..b876dee 100644 --- a/config.go +++ b/config.go @@ -14,10 +14,10 @@ import ( ) // BuildVersion returns the build version of leng, this should be incremented every new release -var BuildVersion = "1.3.0" +var BuildVersion = "1.4.0" // ConfigVersion returns the version of leng, this should be incremented every time the config changes so leng presents a warning -var ConfigVersion = "1.3.0" +var ConfigVersion = "1.4.0" // Config holds the configuration parameters type Config struct { @@ -39,13 +39,14 @@ type Config struct { } type Blocking struct { - Sources []string - SourceDirs []string - Blocklist []string - Whitelist []string - NXDomain bool - Nullroute string - Nullroutev6 string + Sources []string + SourcesStore string + SourceDirs []string + Blocklist []string + Whitelist []string + NXDomain bool + Nullroute string + Nullroutev6 string } type Upstream struct { @@ -146,7 +147,8 @@ followCnameDepth = 12 "https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt" ] # list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list) - sourcedirs = ["sources"] + sourcedirs = ["./sources"] + sourcesStore = "./sources" [Upstream] diff --git a/doc/src/Configuration.md b/doc/src/Configuration.md index 1a6aa9e..8c368a2 100644 --- a/doc/src/Configuration.md +++ b/doc/src/Configuration.md @@ -81,7 +81,8 @@ customdnsrecords = [ "https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt" ] # list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list) - sourcedirs = ["sources"] + sourcedirs = ["./sources"] + sourcesStore = "./sources" # manual whitelist entries - comments for reference whitelist = [ # "getsentry.com", diff --git a/doc/src/Nix.md b/doc/src/Nix.md index 107bb82..2cbfa54 100644 --- a/doc/src/Nix.md +++ b/doc/src/Nix.md @@ -6,22 +6,66 @@ Leng is also packaged as [a Nix flake](../../flake.nix). You can simply run `nix run github:cottand/leng` to run latest `master`. -## Installing +## Installing in NixOS via a Module + +The leng flake also exports a NixOS module for easy deployment on NixOS machines. ### In your flake ```nix { - # pinned version for safety - inputs.grimn.url = "github:cottand/leng/v1.3.1"; + inputs = { + # pinned version for safety + leng.url = "github:cottand/leng/v1.4.0"; + leng.nixpkgs.follows = "nixpkgs"; + }; - outputs = { self, leng }: { + outputs = { self, leng, ... }: { # Use in your outputs + nixosConfigurations."this-is-a-server-innit" = nixpkgs.lib.nixosSystem { + modules = [ + ./configuration.nix + leng.nixosModules.default # <- import leng module + { + services.leng = { # <-- now you can use services.leng! + enable = true; + configuration = { + api = "127.0.0.1:8080"; + metrics.enabled = true; + blocking.sourcesStore = "/var/lib/leng-sources"; + }; + }; + } + ]; + }; }; } ``` +### Legacy Nix + +Add the following inside your configuration.nix: +```nix +{pkgs, lib, ... }: { + imports = [ + # import leng module + (builtins.getFlake "github:cottand/leng/1.4.0").nixosModules.default + ]; + + # now you can use services.leng! + services.leng = { + enable = true; + configuration = { + api = "127.0.0.1:8080"; + metrics.enabled = true; + blocking.sourcesStore = "/var/lib/leng-sources"; + }; + }; + +} +``` + ## Developing The flake's development shell simply includes Go 1.21+ and a [fish](https://fishshell.com/) shell. You can enter it with `nix develop`. diff --git a/flake.lock b/flake.lock index 7bdd046..c3d658f 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1699099776, - "narHash": "sha256-X09iKJ27mGsGambGfkKzqvw5esP1L/Rf8H3u3fCqIiU=", + "lastModified": 1701718080, + "narHash": "sha256-6ovz0pG76dE0P170pmmZex1wWcQoeiomUZGggfH9XPs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "85f1ba3e51676fa8cc604a3d863d729026a6b8eb", + "rev": "2c7f3c0fb7c08a0814627611d9d7d45ab6d75335", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index a70f87d..11b63f8 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,7 @@ packages = rec { leng = pkgs.buildGo121Module { inherit system; - vendorSha256 = "sha256-5dIZzqaw88lKuh1JHJurRZCPgrNzDHK/53bXKNGQBvQ="; + vendorHash = "sha256-5dIZzqaw88lKuh1JHJurRZCPgrNzDHK/53bXKNGQBvQ="; pname = "leng"; version = "1.4.0"; src = ./.; @@ -35,7 +35,7 @@ # shell with dependencies to build docs only ci-doc = with pkgs; mkShell { - packages = [ mdbook mdbook-mermaid ]; + packages = [ mdbook mdbook-mermaid ]; }; default = leng; @@ -49,5 +49,86 @@ default = leng; }; - })); + })) // + + { + nixosModules.default = { pkgs, lib, config, ... }: + with lib; + let + cfg = config.services.leng; + toml = pkgs.formats.toml {}; + in + { + ## interface + options.services.leng = { + enable = mkOption { + type = types.bool; + default = false; + }; + enableSeaweedFsVolume = mkOption { + type = types.bool; + description = "Whether to make this nomad client capable of hosting a SeaweedFS volume"; + }; + package = mkOption { + type = types.package; + default = self.packages.${pkgs.system}.leng; + }; + configuration = mkOption { + type = toml.type; + default = {}; + description = "Configuration as Nix attrSet"; + example = '' + { + api = "127.0.0.1:8080"; + metrics.enabled = true; + blocking.sourcesStore = "/var/lib/leng-sources"; + } + ''; + }; + + }; + + ## implementation + config = mkIf cfg.enable { + environment = { + etc."leng.toml".source = toml.generate "leng.toml" cfg.configuration; + systemPackages = [ cfg.package ]; + }; + + systemd.services.leng = { + description = "leng"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + restartTriggers = [ config.environment.etc."leng.toml".source ]; + + serviceConfig = { + DynamicUser = true; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStart = "${cfg.package}/bin/leng" + " --config=/etc/leng.toml"; + KillMode = "process"; + KillSignal = "SIGINT"; + Restart = "on-failure"; + RestartSec = 2; + TasksMax = "infinity"; + StateDirectory = "leng-sources"; + }; + + unitConfig = { + StartLimitIntervalSec = 10; + StartLimitBurst = 3; + }; + }; + assertions = [ + { + assertion = cfg.configuration.blocking.sourcesStore == "/var/lib/leng-sources"; + message = '' + `services.leng.configuration.blocking.sourcesStore` should be set to `var/lib/leng-sources`, but it is set to ${cfg.configuration.blocking.sourcesStore}. + ''; + } + ]; + }; + }; + }; } + diff --git a/updater.go b/updater.go index 6e2a56a..63e60e7 100644 --- a/updater.go +++ b/updater.go @@ -17,10 +17,11 @@ var timesSeen = make(map[string]int) var whitelist = make(map[string]bool) // Update downloads all the blocklists and imports them into the database -func update(blockCache *MemoryBlockCache, wlist []string, blist []string, sources []string) error { - if _, err := os.Stat("sources"); os.IsNotExist(err) { - if err := os.Mkdir("sources", 0700); err != nil { - return fmt.Errorf("error creating sources directory: %s", err) +func update(blockCache *MemoryBlockCache, wlist []string, blist []string, sources []string, sourcesStore string) error { + sourcesStore = filepath.Clean(sourcesStore) + if _, err := os.Stat(sourcesStore); os.IsNotExist(err) { + if err := os.Mkdir(sourcesStore, 0700); err != nil { + return fmt.Errorf("error creating sources directory (at %s): %s", err, sourcesStore) } } @@ -35,15 +36,15 @@ func update(blockCache *MemoryBlockCache, wlist []string, blist []string, source } } - if err := fetchSources(sources); err != nil { + if err := fetchSources(sources, sourcesStore); err != nil { return fmt.Errorf("error fetching sources: %s", err) } return nil } -func downloadFile(uri string, name string) error { - filePath := filepath.FromSlash(fmt.Sprintf("sources/%s", name)) +func downloadFile(uri string, name string, sourcesStore string) error { + filePath := filepath.Clean(filepath.FromSlash(fmt.Sprintf("%s/%s", sourcesStore, name))) output, err := os.Create(filePath) if err != nil { @@ -73,7 +74,7 @@ func downloadFile(uri string, name string) error { return nil } -func fetchSources(sources []string) error { +func fetchSources(sources []string, sourcesStore string) error { var wg sync.WaitGroup for _, uri := range sources { @@ -86,7 +87,7 @@ func fetchSources(sources []string) error { go func(uri string, name string) { logger.Debugf("fetching source %s\n", uri) - if err := downloadFile(uri, name); err != nil { + if err := downloadFile(uri, name, sourcesStore); err != nil { fmt.Println(err) } @@ -179,7 +180,7 @@ func parseHostFile(fileName string, blockCache *MemoryBlockCache) error { func PerformUpdate(config *Config, forceUpdate bool) *MemoryBlockCache { newBlockCache := &MemoryBlockCache{Backend: make(map[string]bool), Special: make(map[string]*regexp.Regexp)} if _, err := os.Stat("lists"); os.IsNotExist(err) || forceUpdate { - if err := update(newBlockCache, config.Blocking.Whitelist, config.Blocking.Blocklist, config.Blocking.Sources); err != nil { + if err := update(newBlockCache, config.Blocking.Whitelist, config.Blocking.Blocklist, config.Blocking.Sources, config.Blocking.SourcesStore); err != nil { logger.Fatal(err) } }