diff options
Diffstat (limited to '')
-rw-r--r-- | modules/common/documentation.nix | 17 | ||||
-rw-r--r-- | modules/piracy/default.nix | 3 | ||||
-rw-r--r-- | modules/profiles/headful.nix | 1 | ||||
-rw-r--r-- | modules/soju.nix | 183 | ||||
-rw-r--r-- | modules/throttled.nix | 105 | ||||
-rw-r--r-- | overlays.nix | 21 | ||||
-rw-r--r-- | packages/soju-upload.patch | 222 |
7 files changed, 360 insertions, 192 deletions
diff --git a/modules/common/documentation.nix b/modules/common/documentation.nix index f9e0fcb..b28988d 100644 --- a/modules/common/documentation.nix +++ b/modules/common/documentation.nix @@ -7,23 +7,28 @@ }: { config = { - hm.manual = { - manpages.enable = this.isHeadful; - html.enable = false; - json.enable = false; + hm = { + manual = { + manpages.enable = this.isHeadful; + html.enable = false; + json.enable = false; + }; + + # Fixes a wierd issue with `direnv` and an unbound value. + home.sessionVariables.MANPATH = ""; }; documentation = { enable = this.isHeadful; dev.enable = true; doc.enable = false; - info.enable = false; + info.enable = true; nixos.enable = true; man.man-db.manualPages = (pkgs.buildEnv { name = "man-paths"; - paths = with config; environment.systemPackages ++ hm.home.packages; + paths = config.environment.systemPackages ++ config.hm.home.packages; pathsToLink = [ "/share/man" ]; extraOutputsToInstall = [ "man" ]; ignoreCollisions = true; diff --git a/modules/piracy/default.nix b/modules/piracy/default.nix index 7682356..3554a02 100644 --- a/modules/piracy/default.nix +++ b/modules/piracy/default.nix @@ -55,6 +55,9 @@ in user = "rtorrent"; inherit (cfg) group; + port = 1337; + openFirewall = true; + rpcSocket = socket; configText = with config.services.rtorrent; diff --git a/modules/profiles/headful.nix b/modules/profiles/headful.nix index 186d97d..a315af4 100644 --- a/modules/profiles/headful.nix +++ b/modules/profiles/headful.nix @@ -54,6 +54,7 @@ in packages = with pkgs; [ anki audacity + ayugram-desktop byedpi eaglemode easyeffects diff --git a/modules/soju.nix b/modules/soju.nix index 2060eca..dbf069d 100644 --- a/modules/soju.nix +++ b/modules/soju.nix @@ -12,24 +12,30 @@ in options.nixfiles.modules.soju = { enable = mkEnableOption "soju"; - address = mkOption { - description = "Address."; - type = with types; str; - default = ""; - }; - port = mkOption { description = "Port."; type = with types; port; default = 6697; }; + httpPort = mkOption { + description = "HTTP Port."; + type = with types; port; + default = 9981; + }; + domain = mkOption { description = "Domain."; type = with types; str; default = config.networking.fqdn; }; + uploadsDir = mkOption { + description = "Uploads directory."; + type = with types; str; + default = "/srv/soju/uploads"; + }; + prometheus = { enable = mkEnableOption "Prometheus exporter" // { default = true; @@ -50,7 +56,25 @@ in mkIf cfg.enable { nixfiles.modules = { acme.enable = true; - nginx.enable = true; + nginx = { + enable = true; + upstreams.soju.servers."127.0.0.1:${toString cfg.httpPort}" = { }; + virtualHosts.${cfg.domain}.locations = { + "/_irc" = { + proxyPass = "http://soju"; + proxyWebsockets = true; + extraConfig = '' + rewrite ^/_irc/(.*)$ /$1 break; + ''; + }; + "/_irc/uploads" = { + root = "/srv/soju"; + extraConfig = '' + rewrite ^/_irc/(.*)$ /$1 break; + ''; + }; + }; + }; postgresql = { enable = true; extraPostStart = [ @@ -71,76 +95,87 @@ in ]; }; - systemd.services.soju = { - description = "soju IRC bouncer"; - wantedBy = [ "multi-user.target" ]; - wants = [ "network-online.target" ]; - requires = [ "postgresql.service" ]; - after = [ - "network-online.target" - "postgresql.service" - ]; - serviceConfig = { - ExecStart = - let - # https://soju.im/doc/soju.1.html - configFile = pkgs.writeText "soju.conf" '' - listen ircs://${cfg.address}:${toString cfg.port} - tls ${with config.certs.${cfg.domain}; "${directory}/fullchain.pem ${directory}/key.pem"} - ${with cfg.prometheus; optionalString enable "listen http+prometheus://localhost:${toString port}"} - db postgres "${ - concatStringsSep " " [ - "host=/run/postgresql" - "user=${db}" - "dbname=${db}" - "sslmode=disable" - ] - }" - message-store db - hostname ${cfg.domain} - title ${cfg.domain} - ''; - in - concatStringsSep " " [ - (getExe' pkgs.soju "soju") - "-config ${configFile}" - ]; - DynamicUser = true; - SupplementaryGroups = [ config.services.nginx.group ]; - AmbientCapabilities = [ "" ]; - CapabilityBoundingSet = [ "" ]; - UMask = "0077"; - LockPersonality = true; - MemoryDenyWriteExecute = true; - NoNewPrivileges = true; - PrivateDevices = true; - PrivateTmp = true; - PrivateUsers = true; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectSystem = "strict"; - ProtectProc = "invisible"; - ProcSubset = "pid"; - RemoveIPC = true; - RestrictAddressFamilies = [ - "AF_UNIX" - "AF_INET" - "AF_INET6" + systemd = { + services.soju = { + description = "soju IRC bouncer"; + documentation = [ + "https://soju.im/" + "man:soju(1)" + "man:sojuctl(1)" ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - "~@privileged" + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + requires = [ "postgresql.service" ]; + after = [ + "network-online.target" + "postgresql.service" ]; + serviceConfig = { + ExecStart = + let + # https://soju.im/doc/soju.1.html + configFile = pkgs.writeText "soju.conf" '' + listen ircs://:${toString cfg.port} + listen http://localhost:${toString cfg.httpPort} + tls ${with config.certs.${cfg.domain}; "${directory}/fullchain.pem ${directory}/key.pem"} + ${with cfg.prometheus; optionalString enable "listen http+prometheus://localhost:${toString port}"} + db postgres "${ + concatStringsSep " " [ + "host=/run/postgresql" + "user=${db}" + "dbname=${db}" + "sslmode=disable" + ] + }" + message-store db + file-upload fs ${cfg.uploadsDir} + hostname ${cfg.domain} + http-ingress https://${cfg.domain}/_irc + ''; + in + "${pkgs.soju}/bin/soju -config ${configFile}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + DynamicUser = true; + ReadWritePaths = [ cfg.uploadsDir ]; + SupplementaryGroups = [ config.services.nginx.group ]; + AmbientCapabilities = [ "" ]; + CapabilityBoundingSet = [ "" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + ProtectProc = "invisible"; + ProcSubset = "pid"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + }; }; + + tmpfiles.rules = [ + "d ${cfg.uploadsDir} 0755 soju soju" + ]; }; }; } diff --git a/modules/throttled.nix b/modules/throttled.nix deleted file mode 100644 index 7d37cd4..0000000 --- a/modules/throttled.nix +++ /dev/null @@ -1,105 +0,0 @@ -{ config, lib, ... }: -with lib; -let - cfg = config.nixfiles.modules.throttled; -in -{ - options.nixfiles.modules.throttled.enable = mkEnableOption "Throttled"; - - config = mkIf cfg.enable { - services.throttled.enable = true; - - environment.etc."throttled.conf".text = mkDefault '' - [GENERAL] - # Enable or disable the script execution - Enabled: True - # SYSFS path for checking if the system is running on AC power - Sysfs_Power_Path: /sys/class/power_supply/AC*/online - # Auto reload config on changes - Autoreload: True - - ## Settings to apply while connected to Battery power - [BATTERY] - # Update the registers every this many seconds - Update_Rate_s: 30 - # Max package power for time window #1 - PL1_Tdp_W: 29 - # Time window #1 duration - PL1_Duration_s: 28 - # Max package power for time window #2 - PL2_Tdp_W: 44 - # Time window #2 duration - PL2_Duration_S: 0.002 - # Max allowed temperature before throttling - Trip_Temp_C: 85 - # Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL) - cTDP: 0 - # Disable BDPROCHOT (EXPERIMENTAL) - Disable_BDPROCHOT: False - - ## Settings to apply while connected to AC power - [AC] - # Update the registers every this many seconds - Update_Rate_s: 5 - # Max package power for time window #1 - PL1_Tdp_W: 44 - # Time window #1 duration - PL1_Duration_s: 28 - # Max package power for time window #2 - PL2_Tdp_W: 44 - # Time window #2 duration - PL2_Duration_S: 0.002 - # Max allowed temperature before throttling - Trip_Temp_C: 95 - # Set HWP energy performance hints to 'performance' on high load (EXPERIMENTAL) - # Uncomment only if you really want to use it - # HWP_Mode: False - # Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL) - cTDP: 0 - # Disable BDPROCHOT (EXPERIMENTAL) - Disable_BDPROCHOT: False - - # All voltage values are expressed in mV and *MUST* be negative (i.e. undervolt)! - [UNDERVOLT.BATTERY] - # CPU core voltage offset (mV) - CORE: 0 - # Integrated GPU voltage offset (mV) - GPU: 0 - # CPU cache voltage offset (mV) - CACHE: 0 - # System Agent voltage offset (mV) - UNCORE: 0 - # Analog I/O voltage offset (mV) - ANALOGIO: 0 - - # All voltage values are expressed in mV and *MUST* be negative (i.e. undervolt)! - [UNDERVOLT.AC] - # CPU core voltage offset (mV) - CORE: 0 - # Integrated GPU voltage offset (mV) - GPU: 0 - # CPU cache voltage offset (mV) - CACHE: 0 - # System Agent voltage offset (mV) - UNCORE: 0 - # Analog I/O voltage offset (mV) - ANALOGIO: 0 - - # [ICCMAX.AC] - # # CPU core max current (A) - # CORE: - # # Integrated GPU max current (A) - # GPU: - # # CPU cache max current (A) - # CACHE: - - # [ICCMAX.BATTERY] - # # CPU core max current (A) - # CORE: - # # Integrated GPU max current (A) - # GPU: - # # CPU cache max current (A) - # CACHE: - ''; - }; -} diff --git a/overlays.nix b/overlays.nix index f5c3aac..3c64def 100644 --- a/overlays.nix +++ b/overlays.nix @@ -16,6 +16,7 @@ ''; grc.__output = { + version.__assign = "nixfiles"; src.__assign = final.fetchFromGitHub { owner = "garabik"; repo = "grc"; @@ -63,13 +64,6 @@ openssl_1_0_0.__assign = prev.callPackage ./packages/openssl_1_0_0.nix { }; - telegram-desktop = { - __input.stdenv = final.useMoldLinker; - # __output.patches.__append = [ - # ./packages/telegram-no-ads.patch - # ]; - }; - vesktop = { __input = { withMiddleClickScroll.__assign = true; @@ -92,5 +86,18 @@ dendrite = _: (lib.packages.fromPR 366129 "sha256-oI9Afm3azJyEz4SJJIwuzeyuH7IaiGNTSA442vFlfv4=").dendrite; + + soju.__output = { + version.__assign = "nixfiles"; + src.__assign = final.fetchFromGitea { + domain = "codeberg.org"; + owner = "emersion"; + repo = "soju"; + rev = "5f68e69529d8da21b22d073f07278722488d507d"; + hash = "sha256-oJDw2AzUBfU2KkG4YzzAXiE5UZj0m0/zw/GuHVrqddM="; + }; + vendorHash.__assign = "sha256-tq9FI8A3pi3ztcLYF6sZ4wmwTD0HWq4g2EAl7eLo+po="; + patches.__append = [ ./packages/soju-upload.patch ]; + }; }; } diff --git a/packages/soju-upload.patch b/packages/soju-upload.patch new file mode 100644 index 0000000..03efe76 --- /dev/null +++ b/packages/soju-upload.patch @@ -0,0 +1,222 @@ +diff --git i/fileupload/fileupload.go w/fileupload/fileupload.go +index 07fccf1..55104f8 100644 +--- i/fileupload/fileupload.go ++++ w/fileupload/fileupload.go +@@ -7,6 +7,7 @@ import ( + "errors" + "fmt" + "io" ++ "log" + "mime" + "net/http" + "net/url" +@@ -153,30 +154,35 @@ func (h *Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + return + } + ++ var err error + switch req.Method { + case http.MethodOptions: + resp.WriteHeader(http.StatusNoContent) + case http.MethodHead, http.MethodGet: +- h.fetch(resp, req) ++ err = h.fetch(resp, req) + case http.MethodPost: +- h.store(resp, req) ++ err = h.store(resp, req) + default: + http.Error(resp, "only OPTIONS, HEAD, GET and POST are allowed", http.StatusMethodNotAllowed) ++ err = errors.New(http.StatusText(http.StatusMethodNotAllowed)) ++ } ++ if err != nil { ++ log.Printf("upload: %s", err.Error()) + } + } + +-func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { ++func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) error { + prefix := "/uploads/" + if !strings.HasPrefix(req.URL.Path, prefix) { + http.Error(resp, "invalid path", http.StatusNotFound) +- return ++ return fmt.Errorf("invalid path") + } + + filename := strings.TrimPrefix(req.URL.Path, prefix) + filename = path.Join("/", filename)[1:] // prevent directory traversal + if filename == "" { + http.Error(resp, "invalid path", http.StatusNotFound) +- return ++ return fmt.Errorf("invalid path") + } + + basename, modTime, content, err := h.Uploader.load(req.Context(), filename) +@@ -184,10 +190,10 @@ func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { + var httpErr *httpError + if errors.As(err, &httpErr) { + httpErr.Write(resp) +- return ++ return err + } + http.Error(resp, "failed to open file", http.StatusNotFound) +- return ++ return fmt.Errorf("failed to open file: %v", err) + } + defer content.Close() + +@@ -200,7 +206,7 @@ func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { + _, err := content.Seek(0, io.SeekStart) // rewind to output whole file + if err != nil { + http.Error(resp, "failed to seek file", http.StatusInternalServerError) +- return ++ return fmt.Errorf("failed to seek file: %v", err) + } + } + +@@ -219,18 +225,20 @@ func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { + resp.Header().Set("Content-Disposition", contentDisp) + + http.ServeContent(resp, req, basename, modTime, content) ++ ++ return nil + } + +-func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { ++func (h *Handler) store(resp http.ResponseWriter, req *http.Request) error { + if req.URL.Path != "/uploads" { + http.Error(resp, "invalid path", http.StatusNotFound) +- return ++ return fmt.Errorf("invalid path") + } + + authz := req.Header.Get("Authorization") + if authz == "" { + http.Error(resp, "missing Authorization header", http.StatusUnauthorized) +- return ++ return fmt.Errorf("missing Authorization header") + } + + var ( +@@ -243,26 +251,26 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { + plainAuth := h.Auth.Plain + if plainAuth == nil { + http.Error(resp, "Basic scheme in Authorization header not supported", http.StatusBadRequest) +- return ++ return fmt.Errorf("Basic scheme in Authorization header not supported") + } + var password string + var ok bool + username, password, ok = req.BasicAuth() + if !ok { + http.Error(resp, "invalid Authorization header", http.StatusBadRequest) +- return ++ return fmt.Errorf("invalid Authorization header") + } + err = plainAuth.AuthPlain(req.Context(), h.DB, username, password) + case "bearer": + oauthAuth := h.Auth.OAuthBearer + if oauthAuth == nil { + http.Error(resp, "Bearer scheme in Authorization header not supported", http.StatusBadRequest) +- return ++ return fmt.Errorf("Bearer scheme in Authorization header not supported") + } + username, err = oauthAuth.AuthOAuthBearer(req.Context(), h.DB, param) + default: + http.Error(resp, "unsupported Authorization header scheme", http.StatusBadRequest) +- return ++ return fmt.Errorf("unsupported Authorization header scheme") + } + if err != nil { + var msg string +@@ -272,7 +280,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { + msg = "authentication failed" + } + http.Error(resp, msg, http.StatusForbidden) +- return ++ return fmt.Errorf("%s: %v", msg, err) + } + + var mimeType string +@@ -284,7 +292,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { + mimeType, params, err = mime.ParseMediaType(contentType) + if err != nil { + http.Error(resp, "failed to parse Content-Type", http.StatusBadRequest) +- return ++ return fmt.Errorf("failed to parse Content-Type: %v", err) + } + if mimeType == "application/octet-stream" { + mimeType = "" +@@ -295,7 +303,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { + // OK + default: + http.Error(resp, "unsupported charset", http.StatusUnsupportedMediaType) +- return ++ return fmt.Errorf("unsupported charset") + } + } + +@@ -304,7 +312,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { + _, params, err := mime.ParseMediaType(contentDisp) + if err != nil { + http.Error(resp, "failed to parse Content-Disposition", http.StatusBadRequest) +- return ++ return fmt.Errorf("failed to parse Content-Disposition: %v", err) + } + basename = path.Base(params["filename"]) + } +@@ -325,7 +333,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { + var httpErr *httpError + if errors.As(err, &httpErr) { + httpErr.Write(resp) +- return ++ return err + } + status := http.StatusInternalServerError + var maxBytesErr *http.MaxBytesError +@@ -335,19 +343,21 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { + resp.Header().Set("Upload-Limit", fmt.Sprintf("maxsize=%v", maxBytesErr.Limit)) + } + http.Error(resp, "failed to write file", status) +- return ++ return fmt.Errorf("failed to write file: %v", err) + } + + u, err := url.Parse(out) + if err != nil { + http.Error(resp, "failed to write file", http.StatusInternalServerError) +- return ++ return fmt.Errorf("failed to write file: %v", err) + } +- baseURL := url.URL{Path: "/uploads/"} ++ baseURL := url.URL{Path: "/_irc/uploads/"} + out = baseURL.ResolveReference(u).String() + + resp.Header().Set("Location", out) + resp.WriteHeader(http.StatusCreated) ++ ++ return nil + } + + func generateToken(n int) (string, error) { +diff --git i/fileupload/fs.go w/fileupload/fs.go +index c0c401d..66337a1 100644 +--- i/fileupload/fs.go ++++ w/fileupload/fs.go +@@ -56,7 +56,7 @@ func (fs *fileuploadFS) store(ctx context.Context, r io.Reader, username, mimeTy + } + + dir := filepath.Join(fs.dir, username) +- if err := os.MkdirAll(dir, 0700); err != nil { ++ if err := os.MkdirAll(dir, 0755); err != nil { + return "", fmt.Errorf("failed to create user upload directory: %w", err) + } + +@@ -77,7 +77,7 @@ func (fs *fileuploadFS) store(ctx context.Context, r io.Reader, username, mimeTy + } + basename += suffix + +- f, err = os.OpenFile(filepath.Join(dir, basename), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) ++ f, err = os.OpenFile(filepath.Join(dir, basename), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) + if err == nil { + break + } else if !os.IsExist(err) { |