diff --git a/flake.nix b/flake.nix index 34a4b16..bd65a98 100644 --- a/flake.nix +++ b/flake.nix @@ -76,6 +76,41 @@ "devvm-hemar|${system}" = import ./nixos/system/devvm-hemar/default.nix { inherit flake self inputs system; }; }; + + #nixosTests = let + # testLib = import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit pkgs; }; + #in { + # "hardware/lenovo-ideapad-15arh7" = testLib.makeTest { + # name = "hardware/lenovo-ideapad-15arh7"; + # nodes.machine = { ... }: { + # imports = [ self.nixosModules.hectic ]; + # services.hardware.lenovo-ideapad-15arh7.enable = true; + # }; + # testScript = '' + # start_all() + # machine.wait_for_unit("my-service.service") + # machine.succeed("journalctl -u my-service -b | grep -qi hello") + # ''; + # }; + #}; + + checks = let + mkSys = system: opts: + (nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + self.nixosModules.hectic + { services.hardware.lenovo-ideapad-15arh7 = opts; } + ]; + }); + + cases = { + enable = { enable = true; }; + disabled = { enable = false; }; + customFoo = { enable = true; foo = "bar"; }; + }; + in nixpkgs.lib.mapAttrs + (name: opts: (mkSys system opts).config.system.build.toplevel) cases; }) // { lib = self-lib; overlays.default = import ./overlay { inherit flake self inputs nixpkgs; }; diff --git a/nixos/module/hectic/archetype/base.nix b/nixos/module/hectic/archetype/base.nix index 33ddf74..9faf312 100644 --- a/nixos/module/hectic/archetype/base.nix +++ b/nixos/module/hectic/archetype/base.nix @@ -10,6 +10,10 @@ }: let cfg = config.hectic.archetype.base; in { + imports = [ + inputs.disko.nixosModules.default + ]; + options.hectic.archetype.base.enable = lib.mkEnableOption "Enable archetupe.dev"; config = lib.mkIf cfg.enable { diff --git a/nixos/module/hectic/archetype/explosive.nix b/nixos/module/hectic/archetype/explosive.nix new file mode 100644 index 0000000..0938e37 --- /dev/null +++ b/nixos/module/hectic/archetype/explosive.nix @@ -0,0 +1,3 @@ +{ ... }: { lib, ... }: { + options.hectic.archetype.explosive.enable = lib.mkEnableOption "Enable impermanence usage"; +} diff --git a/nixos/module/hectic/hardware/lenovo-ideapad-15arh7.nix b/nixos/module/hectic/hardware/lenovo-ideapad-15arh7.nix new file mode 100644 index 0000000..502e082 --- /dev/null +++ b/nixos/module/hectic/hardware/lenovo-ideapad-15arh7.nix @@ -0,0 +1,228 @@ +{ + inputs, + ... +}: +{ + lib, + config, + modulesPath, + pkgs, + ... +}: let + cfg = config.hectic.hardware.lenovo-ideapad-15arh7; + hasDisko = false; +in { + options.hectic.hardware.lenovo-ideapad-15arh7 = { + enable = lib.mkEnableOption "Enable lenovo-legion hardware configurations"; + swapSize = lib.mkOption { + type = lib.types.either (lib.types.enum [ "100%" ]) (lib.types.strMatching "[0-9]+[KMGTP]?"); + default = "0"; + description = '' + Size of the partition, in sgdisk format. + sets end automatically with the + prefix + can be 100% for the whole remaining disk, will be done last in that case. + ''; + }; + device = lib.mkOption { + type = lib.types.str; + default = "0"; + description = '' + Size of the partition, in sgdisk format. + sets end automatically with the + prefix + can be 100% for the whole remaining disk, will be done last in that case. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + zlaupa = 12; + imports = [ + "${inputs.nixos-hardware}/common/cpu/amd" + "${inputs.nixos-hardware}/common/cpu/amd/pstate.nix" + "${inputs.nixos-hardware}/common/gpu/amd" + "${inputs.nixos-hardware}/common/gpu/nvidia/prime-sync.nix" + "${inputs.nixos-hardware}/common/pc/laptop" + "${inputs.nixos-hardware}/common/pc/laptop/ssd" + ]; + + /* common */ + hardware.nvidia = { + modesetting.enable = true; + prime = { + amdgpuBusId = "PCI:5:0:0"; + nvidiaBusId = "PCI:1:0:0"; + }; + }; + + environment.systemPackages = with pkgs; [ + vulkan-tools + ]; + /* */ + + /* boot */ + boot.initrd.availableKernelModules = [ + "nvme" + "xhci_pci" + "usb_storage" + "usbhid" + "sd_mod" + ]; + boot.initrd.kernelModules = [ "dm-snapshot" "amdgpu" ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; + /* */ + + networking.useDHCP = lib.mkDefault true; + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + + /* cpu */ + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + + /* gpu */ + services.xserver.videoDrivers = [ + "nvidia" + #"amdgpu" # NOTE: probably useles with nvidia optimus prime + #"nouveau" # NOTE: open source nvidia + ]; + + hardware.opengl = { + enable = true; + driSupport = true; + driSupport32Bit = true; + extraPackages = with pkgs; [ + vulkan-loader + vulkan-validation-layers + vulkan-extension-layer + amdvlk + + ]; + extraPackages32 = with pkgs; [ + pkgsi686Linux.vulkan-loader + pkgsi686Linux.vulkan-validation-layers + pkgsi686Linux.vulkan-extension-layer + driversi686Linux.amdvlk + ]; + }; + + #environment.variables.VK_DRIVER_FILES=/run/opengl-driver/share/vulkan/icd.d/nvidia_icd.x86_64.json; + #environment.sessionVariables.VK_DRIVER_FILES = "/run/opengl-driver/share/vulkan/icd.d/nvidia_icd.x86_64.json"; + + #environment.sessionVariables = rec { + # VK_ICD_FILENAMES = + # "${config.hardware.nvidia.package}/share/vulkan/icd.d/nvidia_icd.x86_64.json"; + + # #:${config.environment.variables.VK_ICD_FILENAMES or ""}"; + #}; + + + hardware.nvidia = { + # Nvidia power management. Experimental, and can cause sleep/suspend to fail. + # Enable this if you have graphical corruption issues or application crashes after waking + # up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead + # of just the bare essentials. + powerManagement.enable = false; + + # Fine-grained power management. Turns off GPU when not in use. + # Experimental and only works on modern Nvidia GPUs (Turing or newer). + powerManagement.finegrained = false; + + # Use the NVidia open source kernel module (not to be confused with the + # independent third-party "nouveau" open source driver). + # Support is limited to the Turing and later architectures. Full list of + # supported GPUs is at: + # https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus + # Only available from driver 515.43.04+ + # Currently alpha-quality/buggy, so false is currently the recommended setting. + open = false; + + # Enable the Nvidia settings menu, + # accessible via `nvidia-settings`. + nvidiaSettings = true; + + # nvidia package overwrive + package = config.boot.kernelPackages.nvidiaPackages.stable; + + }; + /* */ + + /* sound */ + hardware.pulseaudio.enable = true; + hardware.pulseaudio.support32Bit = true; + /* */ + + /* disk */ + disko.devices = { + disk.main = { + inherit (cfg) device; + type = "disk"; + content = { + type = "gpt"; + partitions = { + boot = { + name = "boot"; + size = "1M"; + type = "EF02"; + }; + esp = { + name = "ESP"; + size = "500M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + swap = { + size = cfg.swapSize; + content = { + type = "swap"; + resumeDevice = true; + }; + }; + root = { + name = "root"; + size = "100%"; + content = { + type = "lvm_pv"; + vg = "root_vg"; + }; + }; + }; + }; + }; + lvm_vg = { + root_vg = { + type = "lvm_vg"; + lvs = { + root = { + size = "100%FREE"; + content = { + type = "btrfs"; + extraArgs = ["-f"]; + + subvolumes = lib.mkMerge [ + { + "/root" = { + mountpoint = "/"; + }; + "/nix" = { + mountOptions = ["subvol=nix" "noatime"]; + mountpoint = "/nix"; + }; + } + (if config.hectic.archetype.explosive.enable then { + "/persist" = { + mountOptions = ["subvol=persist" "noatime"]; + mountpoint = "/persist"; + }; + } else {}) + ]; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos/module/hectic/hardware/lenovo-legion.nix b/nixos/module/hectic/hardware/lenovo-legion.nix deleted file mode 100644 index 041909b..0000000 --- a/nixos/module/hectic/hardware/lenovo-legion.nix +++ /dev/null @@ -1,148 +0,0 @@ -{ - ... -}: -{ - inputs, - lib, - config, - modulesPath, - ... -}: let - cfg = config.hectic.hardware.lenovo-legion; - hasDisko = false; -in { - options.hectic.hardware.lenovo-legion = { - enable = lib.mkEnableOption "Enable lenovo-legion hardware configurations"; - swapSize = lib.mkOption { - type = lib.types.either (lib.types.enum [ "100%" ]) (lib.types.strMatching "[0-9]+[KMGTP]?"); - default = "0"; - description = '' - Size of the partition, in sgdisk format. - sets end automatically with the + prefix - can be 100% for the whole remaining disk, will be done last in that case. - ''; - }; - device = lib.mkOption { - type = lib.types.str; - default = "0"; - description = '' - Size of the partition, in sgdisk format. - sets end automatically with the + prefix - can be 100% for the whole remaining disk, will be done last in that case. - ''; - }; - }; - - config = lib.mkIf cfg.enable { - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - - "${inputs.nixos-hardware}/common/cpu/amd" - "${inputs.nixos-hardware}../../../common/cpu/amd/pstate.nix" - "${inputs.nixos-hardware}../../../common/gpu/amd" - "${inputs.nixos-hardware}../../../common/gpu/nvidia/prime-sync.nix" - "${inputs.nixos-hardware}../../../common/pc/laptop" - "${inputs.nixos-hardware}../../../common/pc/laptop/ssd" - ]; - - hardware.nvidia = { - modesetting.enable = true; - prime = { - amdgpuBusId = "PCI:5:0:0"; - nvidiaBusId = "PCI:1:0:0"; - }; - }; - - environment.systemPackages = with pkgs; [ - vulkan-tools - ]; - - /* boot */ - boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "usb_storage" "usbhid" "sd_mod" ]; - boot.initrd.kernelModules = [ "dm-snapshot" "amdgpu" ]; - boot.kernelModules = [ "kvm-amd" ]; - boot.extraModulePackages = [ ]; - - /* */ - networking.useDHCP = lib.mkDefault true; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - - /* cpu */ - hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - - /* nvidia */ - services.xserver.videoDrivers = [ - "nvidia" - #"amdgpu" # NOTE: probably useles with nvidia optimus prime - #"nouveau" # NOTE: open source nvidia - ]; - - hardware.opengl = { - enable = true; - driSupport = true; - driSupport32Bit = true; - extraPackages = with pkgs; [ - vulkan-loader - vulkan-validation-layers - vulkan-extension-layer - amdvlk - - ]; - extraPackages32 = with pkgs; [ - pkgsi686Linux.vulkan-loader - pkgsi686Linux.vulkan-validation-layers - pkgsi686Linux.vulkan-extension-layer - driversi686Linux.amdvlk - ]; - }; - - #environment.variables.VK_DRIVER_FILES=/run/opengl-driver/share/vulkan/icd.d/nvidia_icd.x86_64.json; - #environment.sessionVariables.VK_DRIVER_FILES = "/run/opengl-driver/share/vulkan/icd.d/nvidia_icd.x86_64.json"; - - #environment.sessionVariables = rec { - # VK_ICD_FILENAMES = - # "${config.hardware.nvidia.package}/share/vulkan/icd.d/nvidia_icd.x86_64.json"; - - # #:${config.environment.variables.VK_ICD_FILENAMES or ""}"; - #}; - - - hardware.nvidia = { - # Modesetting is required. - modesetting.enable = true; - - # Nvidia power management. Experimental, and can cause sleep/suspend to fail. - # Enable this if you have graphical corruption issues or application crashes after waking - # up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead - # of just the bare essentials. - powerManagement.enable = false; - - # Fine-grained power management. Turns off GPU when not in use. - # Experimental and only works on modern Nvidia GPUs (Turing or newer). - powerManagement.finegrained = false; - - # Use the NVidia open source kernel module (not to be confused with the - # independent third-party "nouveau" open source driver). - # Support is limited to the Turing and later architectures. Full list of - # supported GPUs is at: - # https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus - # Only available from driver 515.43.04+ - # Currently alpha-quality/buggy, so false is currently the recommended setting. - open = false; - - # Enable the Nvidia settings menu, - # accessible via `nvidia-settings`. - nvidiaSettings = true; - - # nvidia package overwrive - package = config.boot.kernelPackages.nvidiaPackages.stable; - - }; - - /* sound */ - hardware.pulseaudio.enable = true; - hardware.pulseaudio.support32Bit = true; - - }; -} diff --git a/nixos/system/devvm-manual/devvm-manual.nix b/nixos/system/devvm-manual/devvm-manual.nix index c7d6140..df17244 100644 --- a/nixos/system/devvm-manual/devvm-manual.nix +++ b/nixos/system/devvm-manual/devvm-manual.nix @@ -18,9 +18,10 @@ hectic = { archetype.dev.enable = true; hardware.hetzner-cloud.enable = true; + hardware.lenovo-ideapad-15arh7.enable = true; }; - environment.systemPackages = with pkgs.writers; [ + environment.systemPackages = with pkgs.hectic.writers; [ (writeMinCBin "minc-hello-world" [""] /*c*/ '' printf("hello world\n"); '') diff --git a/package/default.nix b/package/default.nix index 40aba2c..916c636 100644 --- a/package/default.nix +++ b/package/default.nix @@ -14,24 +14,6 @@ name = "extension-builder"; path = ./buildPostgresqlExtension.nix; })); - buildHemarExt = pkgs: versionSuffix: let - postgresql = pkgs."postgresql_${versionSuffix}"; - c-hectic = self.packages.${pkgs.system}.c-hectic; - in buildPostgresqlExtension pkgs { - stdenv = pkgs.clangStdenv; - inherit postgresql; - } { - pname = "hemar"; - version = "0.1"; - src = ./c/hemar; - nativeBuildInputs = (with pkgs; [pkg-config]) ++ [ c-hectic ]; - dontShrinkRPath = true; - postFixup = '' - echo ">>> postFixup running..." - ${pkgs.patchelf}/bin/patchelf --set-rpath ${c-hectic}/lib $out/lib/hemar.so - ''; - preInstall = ''mkdir $out''; - }; buildPgrxExtension = pkgs: pkgs.callPackage (import (builtins.path { name = "extension-builder"; @@ -260,17 +242,14 @@ in { shellplot = pkgs.callPackage ./shellplot {}; sops = pkgs.callPackage ./sops.nix {}; onlinepubs2man = pkgs.callPackage ./onlinepubs2man {}; - pg-17-ext-hemar = buildHemarExt pkgs "17"; pg-17-ext-http = buildHttpExt pkgs "17"; pg-17-ext-smtp-client = buildSmtpExt pkgs "17"; pg-17-ext-plhaskell = buildPlHaskellExt pkgs "17"; pg-17-ext-plsh = buildPlShExt pkgs "17"; - pg-16-ext-hemar = buildHemarExt pkgs "16"; pg-16-ext-http = buildHttpExt pkgs "16"; pg-16-ext-smtp-client = buildSmtpExt pkgs "16"; pg-16-ext-plhaskell = buildPlHaskellExt pkgs "16"; pg-16-ext-plsh = buildPlShExt pkgs "16"; - pg-15-ext-hemar = buildHemarExt pkgs "15"; pg-15-ext-http = buildHttpExt pkgs "15"; pg-15-ext-smtp-client = buildSmtpExt pkgs "15"; pg-15-ext-plhaskell = buildPlHaskellExt pkgs "15"; diff --git a/package/sentinèlla/colors.sh b/package/sentinèlla/colors.sh new file mode 100644 index 0000000..627aad5 --- /dev/null +++ b/package/sentinèlla/colors.sh @@ -0,0 +1,54 @@ +NC='\033[0m' + +# Regular text colors +BLACK='\033[30m' +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +MAGENTA='\033[35m' +CYAN='\033[36m' +WHITE='\033[37m' + +# Bright text colors +BBLACK='\033[90m' +BRED='\033[91m' +BGREEN='\033[92m' +BYELLOW='\033[93m' +BBLUE='\033[94m' +BMAGENTA='\033[95m' +BCYAN='\033[96m' +BWHITE='\033[97m' + +# Background colors +BG_BLACK='\033[40m' +BG_RED='\033[41m' +BG_GREEN='\033[42m' +BG_YELLOW='\033[43m' +BG_BLUE='\033[44m' +BG_MAGENTA='\033[45m' +BG_CYAN='\033[46m' +BG_WHITE='\033[47m' + +# Bright background colors +BG_BBLACK='\033[100m' +BG_BRED='\033[101m' +BG_BGREEN='\033[102m' +BG_BYELLOW='\033[103m' +BG_BBLUE='\033[104m' +BG_BMAGENTA='\033[105m' +BG_BCYAN='\033[106m' +BG_BWHITE='\033[107m' + +# Text effects +RESET='\033[0m' +BOLD='\033[1m' +DIM='\033[2m' +ITALIC='\033[3m' +UNDERLINE='\033[4m' +BLINK='\033[5m' +INVERSE='\033[7m' +HIDDEN='\033[8m' +STRIKE='\033[9m' + +: "$NC" "$BLACK" "$RED" "$GREEN" "$YELLOW" "$BLUE" "$MAGENTA" "$CYAN" "$WHITE" "$BBLACK" "$BRED" "$BGREEN" "$BYELLOW" "$BBLUE" "$BMAGENTA" "$BCYAN" "$BWHITE" "$BG_BLACK" "$BG_RED" "$BG_GREEN" "$BG_YELLOW" "$BG_BLUE" "$BG_MAGENTA" "$BG_CYAN" "$BG_WHITE" "$BG_BBLACK" "$BG_BRED" "$BG_BGREEN" "$BG_BYELLOW" "$BG_BBLUE" "$BG_BMAGENTA" "$BG_BCYAN" "$BG_BWHITE" "$RESET" "$BOLD" "$DIM" "$ITALIC" "$UNDERLINE" "$BLINK" "$INVERSE" "$HIDDEN" "$STRIKE" diff --git a/package/sentinèlla/default.nix b/package/sentinèlla/default.nix index fa6d8fb..4ffb446 100644 --- a/package/sentinèlla/default.nix +++ b/package/sentinèlla/default.nix @@ -1,4 +1,4 @@ -{ symlinkJoin, writeShellApplication, socat, dash, hectic, curl, gawk }: +{ symlinkJoin, writeTextFile, socat, dash, hectic, curl, gawk }: let shell = "${dash}/bin/dash"; bashOptions = [ @@ -31,7 +31,12 @@ let inherit shell bashOptions; name = "sentinel"; runtimeInputs = [ hectic.shellplot curl ]; - text = builtins.readFile ./sentinel.sh; + + text = '' + ${builtins.readFile ./log.sh} + ${builtins.readFile ./colors.sh} + ${builtins.readFile ./sentinel.sh} + ''; }; in symlinkJoin { diff --git a/package/sentinèlla/log.sh b/package/sentinèlla/log.sh new file mode 100644 index 0000000..96ca706 --- /dev/null +++ b/package/sentinèlla/log.sh @@ -0,0 +1,21 @@ +#!/bin/dash + +log() { + level="$1"; shift + case "$level" in + trace) color="$MAGENTA" ;; + debug) color="$BLUE" ;; + info) color="$GREEN" ;; + notice) color="$CYAN" ;; + warn) color="$YELLOW" ;; + error) color="$RED" ;; + *) color="$WHITE" ;; + esac + + + + # shellcheck disable=SC1003 + fmt="$(printf "%s" "$1" | sed 's/\\033\[0m/''\'"$color"'/g')" + shift + printf "%b\n" "$color$fmt$NC" "$@" +} diff --git a/package/sentinèlla/sentinel.sh b/package/sentinèlla/sentinel.sh index 726b149..683ea98 100644 --- a/package/sentinèlla/sentinel.sh +++ b/package/sentinèlla/sentinel.sh @@ -3,23 +3,35 @@ # Env: # SERVERS="http://host1:8080,http://host2:8080" # TOKENS="-,b64token2" # CSV aligned with SERVERS; "-" means no auth -# TOKEN="..." # Telegram bot token -# CHAT_ID="..." # Telegram chat id +# TG_TOKEN="..." # Telegram bot token +# TG_CHAT_ID="..." # Telegram chat id # TIMEOUT=5 # curl timeout seconds (default 5) # POLLING_INTERVAL_SEC=3 # default 3 -# STATE_DIR=/tmp/sentinel # default /tmp/sentinel +# STATE_DIR=/var/lib/sentinel # default /var/lib/sentinel +# SPAM=0 # if 1 will notify every poling, default 0 set -eu TIMEOUT=${TIMEOUT:-5} POLLING_INTERVAL_SEC=${POLLING_INTERVAL_SEC:-3} -STATE_DIR=${STATE_DIR:-$(mktemp -d)} SERVERS=${SERVERS:-} TOKENS=${TOKENS:-} TOKEN=${TOKEN:-} CHAT_ID=${CHAT_ID:-} +SPAM=${SPAM:-0} + +STATE_DIR=${STATE_DIR:-/var/lib/sentinel} +mkdir -p "$STATE_DIR" 2>/dev/null || { + # TODO: some sort of message? + STATE_DIR="$HOME/.local/$(basename "$STATE_DIR")" + mkdir -p "$STATE_DIR" +} + +mkdir -p "$STATE_DIR" 2>/dev/null || mkdir -p "$HOME/.local/$(basename "$STATE_DIR")" [ -n "$SERVERS" ] || { printf >&2 'SERVERS not set\n'; exit 1; } +[ -n "$TOKEN" ] || { printf >&2 'TOKEN not set\n'; exit 1; } +[ -n "$CHAT_ID" ] || { printf >&2 'CHAT_ID not set\n'; exit 1; } # If TOKENS unset, synthesize "-" for each server if [ -z "$TOKENS" ]; then @@ -27,13 +39,11 @@ if [ -z "$TOKENS" ]; then TOKENS=$(awk -v n="$n" 'BEGIN{for(i=1;i<=n;i++){printf("-"); if(i echo idx-th field (1-based) from CSV string VAR +# get_csv(csv_variable, index) +# echo idx-th field (1-based) from CSV string VAR get_csv() { - # shellcheck disable=SC2001 printf '%s' "$1" | sed 's/,/\n/g' | awk -v n="$2" 'NR==n{print; exit}' } @@ -48,6 +58,7 @@ notify() { fi } +# sid(text) sid() { printf '%s' "$1" | cksum | awk '{print $1}'; } parse_summary() { @@ -65,6 +76,7 @@ list_failures() { # --- main loop --- while :; do + log info "pooling ${WHITE}${POLLING_INTERVAL_SEC}${NC} sec" i=1 while :; do srv=$(get_csv "$SERVERS" "$i") || true @@ -79,6 +91,8 @@ while :; do code=$(sh -c "curl -sS -m \"$TIMEOUT\" -w '%{http_code}' -o \"$tmpb\" $auth_h \"$url\"") || code="000" body=$(cat "$tmpb"); rm -f "$tmpb" + log info "server ${WHITE}${srv}${NC}\ncode ${WHITE}${code}${NC}\nbody ${WHITE}${body}${NC}" + ok="down"; tot=0; good=0 if [ "$code" = "200" ]; then s=$(printf '%s' "$body" | parse_summary || true)