This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

Merge remote-tracking branch 'origin/master'

+475 -11
+10 -9
2configs/consul.nix
··· 12 12 # interface.bind = "retiolum"; 13 13 extraConfig = { 14 14 bind_addr = config.krebs.build.host.nets.retiolum.ip4.addr; 15 - bootstrap_expect = 1; 15 + bootstrap_expect = 3; 16 16 server = true; 17 17 # retry_join = config.services.consul.extraConfig.start_join; 18 18 retry_join = lib.mapAttrsToList (_n: h: lib.head h.nets.retiolum.aliases) ( ··· 29 29 cleanup_dead_servers = true; 30 30 # How long a server must be stable before promoting 31 31 server_stabilization_time = "10s"; 32 - # Only requires 3 servers minimum instead of all configured servers 33 - min_quorum = 3; 32 + # Allow cluster to operate with 2 of 3 nodes (majority quorum) 33 + min_quorum = 2; 34 34 }; 35 35 36 - # Performance tuning for faster leader elections 36 + # Performance tuning - slightly relaxed for network tolerance 37 37 performance = { 38 - # Reduce raft timing for faster recovery 39 - raft_multiplier = 1; 38 + # 2 = default, gives more time for slow/unstable networks 39 + # 1 was too aggressive and caused leader flapping 40 + raft_multiplier = 2; 40 41 }; 42 + 43 + # Reconnect timeout for failed nodes (default 72h, reduce for faster cleanup) 44 + reconnect_timeout = "24h"; 41 45 42 46 # Leave on terminate for cleaner shutdowns 43 47 leave_on_terminate = true; ··· 48 52 systemd.services.consul = { 49 53 # Add pre-start script to clean up potential issues 50 54 preStart = '' 51 - # Remove any stale peers.json from failed recovery attempts 52 - rm -f /var/lib/consul/raft/peers.json 53 - 54 55 # Ensure proper permissions 55 56 chown -R consul:consul /var/lib/consul 56 57 '';
-2
2configs/paste.nix
··· 11 11 self.inputs.stockholm.nixosModules.htgen 12 12 ]; 13 13 14 - security.acme.certs."cyberlocker".server = config.krebs.ssl.acmeURL; 15 - security.acme.certs."paste".server = config.krebs.ssl.acmeURL; 16 14 services.nginx.virtualHosts.cyberlocker = { 17 15 enableACME = true; 18 16 addSSL = true;
+57
5pkgs/system-audio-dump/package.nix
··· 1 + { 2 + lib, 3 + fetchFromGitHub, 4 + swift, 5 + swiftpm, 6 + swiftPackages, 7 + }: 8 + 9 + swiftPackages.stdenv.mkDerivation { 10 + pname = "system-audio-dump"; 11 + version = "0-unstable-2025-09-02"; 12 + 13 + src = fetchFromGitHub { 14 + owner = "sohzm"; 15 + repo = "systemAudioDump"; 16 + rev = "19caa4f6c0661c03a10d1f08c79a11f0b00f251a"; 17 + hash = "sha256-H2TeB7/Meq7EvfW7zLZn5GGFrKwCyZuJvafv61ZWdrA="; 18 + }; 19 + 20 + nativeBuildInputs = [ 21 + swift 22 + swiftpm 23 + ]; 24 + 25 + postPatch = '' 26 + # Downgrade swift-tools-version from 6.0 to 5.10 and macOS from v15 to v14 27 + sed -i 's/swift-tools-version:6.0/swift-tools-version:5.10/' Package.swift 28 + sed -i 's/.macOS(.v15)/.macOS(.v14)/' Package.swift 29 + 30 + # Patch Swift 6 / macOS 15 features in source 31 + # Remove @retroactive attribute (Swift 6 feature for silencing conformance warnings) 32 + sed -i 's/@retroactive //' Sources/SystemAudioDump/main.swift 33 + # Remove captureMicrophone (macOS 15+ only, defaults to false anyway) 34 + sed -i '/captureMicrophone/d' Sources/SystemAudioDump/main.swift 35 + ''; 36 + 37 + buildPhase = '' 38 + runHook preBuild 39 + swift build -c release 40 + runHook postBuild 41 + ''; 42 + 43 + installPhase = '' 44 + runHook preInstall 45 + mkdir -p $out/bin 46 + cp .build/release/SystemAudioDump $out/bin/system-audio-dump 47 + runHook postInstall 48 + ''; 49 + 50 + meta = { 51 + description = "macOS CLI that captures system audio and outputs raw PCM to stdout"; 52 + homepage = "https://github.com/sohzm/systemAudioDump"; 53 + license = lib.licenses.mit; 54 + platforms = lib.platforms.darwin; 55 + mainProgram = "system-audio-dump"; 56 + }; 57 + }
+62
5pkgs/vosk/package.nix
··· 1 + { 2 + lib, 3 + python3Packages, 4 + fetchurl, 5 + stdenv, 6 + }: 7 + let 8 + version = "0.3.44"; 9 + 10 + # Platform-specific wheels 11 + wheels = { 12 + "x86_64-linux" = { 13 + url = "https://files.pythonhosted.org/packages/py3/v/vosk/vosk-${version}-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl"; 14 + hash = "sha256-JlLvITxMeAfcQK8eeqV43Mz8TcQZKCKia8RalZG8v3Y="; 15 + }; 16 + "aarch64-linux" = { 17 + url = "https://files.pythonhosted.org/packages/py3/v/vosk/vosk-${version}-py3-none-manylinux2014_aarch64.whl"; 18 + hash = "sha256-mZW3+lQ0sTS0vQmJfiSQ28VhFcKtKUPAMGIeEMcDezQ="; 19 + }; 20 + "x86_64-darwin" = { 21 + url = "https://files.pythonhosted.org/packages/py3/v/vosk/vosk-${version}-py3-none-macosx_10_6_universal2.whl"; 22 + hash = "sha256-Ap0LPWpc/4dMV1uKWBSkzM+zdgjP9S6EbAKoxnqIKAE="; 23 + }; 24 + "aarch64-darwin" = { 25 + url = "https://files.pythonhosted.org/packages/py3/v/vosk/vosk-${version}-py3-none-macosx_10_6_universal2.whl"; 26 + hash = "sha256-Ap0LPWpc/4dMV1uKWBSkzM+zdgjP9S6EbAKoxnqIKAE="; 27 + }; 28 + }; 29 + 30 + wheel = 31 + wheels.${stdenv.hostPlatform.system} 32 + or (throw "Unsupported platform: ${stdenv.hostPlatform.system}"); 33 + in 34 + python3Packages.buildPythonPackage { 35 + pname = "vosk"; 36 + inherit version; 37 + format = "wheel"; 38 + 39 + src = fetchurl { 40 + inherit (wheel) url hash; 41 + }; 42 + 43 + dependencies = with python3Packages; [ 44 + cffi 45 + requests 46 + tqdm 47 + srt 48 + websockets 49 + ]; 50 + 51 + # Skip tests as they require audio files and models 52 + doCheck = false; 53 + 54 + pythonImportsCheck = [ "vosk" ]; 55 + 56 + meta = { 57 + description = "Offline speech recognition API based on Kaldi and Vosk"; 58 + homepage = "https://alphacephei.com/vosk/"; 59 + license = lib.licenses.asl20; 60 + platforms = lib.attrNames wheels; 61 + }; 62 + }
+3
machines/shodan/physical.nix
··· 6 6 self.inputs.nixos-hardware.nixosModules.lenovo-thinkpad-x220 7 7 ]; 8 8 9 + # Disable hdapsd - not needed for SSD, and the package is broken with newer GCC 10 + services.hdapsd.enable = false; 11 + 9 12 boot.loader.grub = { 10 13 enable = true; 11 14 device = "/dev/disk/by-id/ata-Samsung_SSD_850_EVO_500GB_S2RBNX0H662201F";
+17
tools/record/flake-module.nix
··· 1 + { ... }: 2 + { 3 + perSystem = 4 + { pkgs, self', ... }: 5 + { 6 + packages.record = pkgs.writeShellApplication { 7 + name = "record"; 8 + runtimeInputs = [ 9 + pkgs.ffmpeg 10 + pkgs.gum 11 + ] 12 + ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.pulseaudio ] 13 + ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [ self'.packages.system-audio-dump ]; 14 + text = builtins.readFile ./rec.sh; 15 + }; 16 + }; 17 + }
+97
tools/record/rec.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + list_sources() { 5 + if [[ "$OSTYPE" == "darwin"* ]]; then 6 + # macOS: list audio devices via ffmpeg avfoundation 7 + echo "system: System Audio (via ScreenCaptureKit)" 8 + ffmpeg -f avfoundation -list_devices true -i "" 2>&1 | \ 9 + sed -n '/AVFoundation audio devices:/,/^[^[]/p' | \ 10 + grep -E '^\[AVFoundation.*\[[0-9]+\]' | \ 11 + sed 's/.*\[\([0-9]*\)\] \(.*\)/\1: \2/' 12 + else 13 + # Linux: list PulseAudio/PipeWire sources including monitors (for system audio) 14 + # Sources ending in .monitor capture the output of that sink (system audio) 15 + pactl list sources short | awk '{print $2}' | while read -r source; do 16 + if [[ "$source" == *.monitor ]]; then 17 + echo "$source (system audio)" 18 + else 19 + echo "$source" 20 + fi 21 + done 22 + fi 23 + } 24 + 25 + get_source_name() { 26 + # Strip the description suffix for actual recording 27 + local name="$1" 28 + echo "${name% (system audio)}" 29 + } 30 + 31 + record_source() { 32 + local source="$1" 33 + 34 + if [[ "$OSTYPE" == "darwin"* ]]; then 35 + if [[ "$source" == "system" ]]; then 36 + # Use system-audio-dump for system audio (ScreenCaptureKit) 37 + # Output is raw PCM 24kHz 16-bit stereo, convert to WAV for compatibility 38 + system-audio-dump | ffmpeg -f s16le -ar 24000 -ac 2 -i - -f wav -acodec pcm_s16le - 2>/dev/null 39 + else 40 + # Use avfoundation for mic input, source is the device index 41 + ffmpeg -f avfoundation -i ":${source}" -f wav -acodec pcm_s16le - 2>/dev/null 42 + fi 43 + else 44 + # Linux: use pulse 45 + ffmpeg -f pulse -i "$source" -f wav -acodec pcm_s16le - 2>/dev/null 46 + fi 47 + } 48 + 49 + # Show help 50 + if [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then 51 + echo "Usage: record [SOURCE]" 52 + echo "" 53 + echo "Record audio from a source and output WAV to stdout." 54 + echo "If no source is provided, prompts interactively with gum." 55 + echo "" 56 + echo "Options:" 57 + echo " -l, --list List available audio sources" 58 + echo " -h, --help Show this help" 59 + echo "" 60 + echo "System audio:" 61 + echo " Linux: Select a .monitor source (captures sink output)" 62 + echo " macOS: Use 'system' source (requires Screen Recording permission)" 63 + echo "" 64 + echo "Examples:" 65 + echo " record | mpv -" 66 + echo " record system | mpv - # macOS system audio" 67 + echo " record 0 | mpv - # macOS device index" 68 + echo " record alsa_output.pci-xxx.monitor | mpv - # Linux system audio" 69 + exit 0 70 + fi 71 + 72 + # List sources 73 + if [[ "${1:-}" == "-l" ]] || [[ "${1:-}" == "--list" ]]; then 74 + list_sources 75 + exit 0 76 + fi 77 + 78 + # Get source from argument or prompt 79 + if [[ -n "${1:-}" ]]; then 80 + SOURCE="$1" 81 + else 82 + # Use gum to select a source 83 + if [[ "$OSTYPE" == "darwin"* ]]; then 84 + SOURCE=$(list_sources | gum choose --header "Select audio source:" | cut -d: -f1) 85 + else 86 + SELECTION=$(list_sources | gum choose --header "Select audio source:") 87 + SOURCE=$(get_source_name "$SELECTION") 88 + fi 89 + fi 90 + 91 + if [[ -z "$SOURCE" ]]; then 92 + echo "Error: No source selected" >&2 93 + exit 1 94 + fi 95 + 96 + echo "Recording from: $SOURCE (Ctrl+C to stop)" >&2 97 + record_source "$SOURCE"
+29
tools/stt/flake-module.nix
··· 1 + { ... }: 2 + { 3 + perSystem = 4 + { pkgs, self', ... }: 5 + let 6 + python = pkgs.python3.withPackages (_ps: [ 7 + self'.packages.vosk 8 + ]); 9 + in 10 + { 11 + packages.stt = pkgs.stdenv.mkDerivation { 12 + pname = "stt"; 13 + version = "0.4.0"; 14 + src = ./.; 15 + nativeBuildInputs = [ pkgs.makeWrapper ]; 16 + installPhase = '' 17 + runHook preInstall 18 + mkdir -p $out/bin 19 + cp stt.py $out/bin/stt 20 + chmod +x $out/bin/stt 21 + wrapProgram $out/bin/stt \ 22 + --prefix PATH : ${python}/bin \ 23 + --set PYTHONPATH ${python}/${python.sitePackages} 24 + runHook postInstall 25 + ''; 26 + meta.mainProgram = "stt"; 27 + }; 28 + }; 29 + }
+200
tools/stt/stt.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Real-time streaming speech-to-text using Vosk. 4 + Reads audio from stdin and outputs words as they're recognized. 5 + """ 6 + 7 + import argparse 8 + import json 9 + import os 10 + import struct 11 + import sys 12 + import zipfile 13 + from pathlib import Path 14 + from urllib.request import urlretrieve 15 + 16 + from vosk import KaldiRecognizer, Model, SetLogLevel 17 + 18 + MODEL_DIR = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local/share")) / "vosk" 19 + 20 + # Small, fast models for real-time streaming 21 + MODELS = { 22 + "en": "vosk-model-small-en-us-0.15", 23 + "de": "vosk-model-small-de-0.15", 24 + "fr": "vosk-model-small-fr-0.22", 25 + "es": "vosk-model-small-es-0.42", 26 + "ru": "vosk-model-small-ru-0.22", 27 + "cn": "vosk-model-small-cn-0.22", 28 + "ja": "vosk-model-small-ja-0.22", 29 + } 30 + 31 + 32 + def download_model(lang: str) -> Path: 33 + """Download vosk model if not present.""" 34 + model_name = MODELS.get(lang) 35 + if not model_name: 36 + # Fall back to English if language not available 37 + print(f"No model for '{lang}', using English", file=sys.stderr) 38 + model_name = MODELS["en"] 39 + lang = "en" 40 + 41 + model_path = MODEL_DIR / model_name 42 + if model_path.exists(): 43 + return model_path 44 + 45 + print(f"Downloading model {model_name}...", file=sys.stderr) 46 + MODEL_DIR.mkdir(parents=True, exist_ok=True) 47 + 48 + url = f"https://alphacephei.com/vosk/models/{model_name}.zip" 49 + zip_path = MODEL_DIR / f"{model_name}.zip" 50 + 51 + urlretrieve(url, zip_path) 52 + 53 + print("Extracting...", file=sys.stderr) 54 + with zipfile.ZipFile(zip_path, "r") as z: 55 + z.extractall(MODEL_DIR) 56 + zip_path.unlink() 57 + 58 + print("Model ready", file=sys.stderr) 59 + return model_path 60 + 61 + 62 + def parse_wav_header(data: bytes) -> tuple[int, int, int, int]: 63 + """Parse WAV header, return (sample_rate, channels, bits_per_sample, data_offset).""" 64 + if len(data) < 44 or data[:4] != b"RIFF" or data[8:12] != b"WAVE": 65 + raise ValueError("Invalid WAV header") 66 + 67 + pos = 12 68 + sample_rate = channels = bits_per_sample = 0 69 + while pos < len(data) - 8: 70 + chunk_id = data[pos : pos + 4] 71 + chunk_size = struct.unpack("<I", data[pos + 4 : pos + 8])[0] 72 + if chunk_id == b"fmt ": 73 + channels = struct.unpack("<H", data[pos + 10 : pos + 12])[0] 74 + sample_rate = struct.unpack("<I", data[pos + 12 : pos + 16])[0] 75 + bits_per_sample = struct.unpack("<H", data[pos + 22 : pos + 24])[0] 76 + elif chunk_id == b"data": 77 + return sample_rate, channels, bits_per_sample, pos + 8 78 + pos += 8 + chunk_size 79 + 80 + raise ValueError("No data chunk found") 81 + 82 + 83 + def convert_audio(data: bytes, in_rate: int, in_channels: int, in_bits: int) -> bytes: 84 + """Convert audio to 16kHz mono 16-bit for Vosk.""" 85 + import array 86 + 87 + # Align data to sample boundary 88 + bytes_per_sample = in_bits // 8 89 + frame_size = in_channels * bytes_per_sample 90 + aligned_len = (len(data) // frame_size) * frame_size 91 + if aligned_len == 0: 92 + return b"" 93 + data = data[:aligned_len] 94 + 95 + # Convert to samples 96 + if in_bits == 16: 97 + samples = array.array("h", data) 98 + elif in_bits == 8: 99 + samples = array.array("h", [(s - 128) * 256 for s in data]) 100 + else: 101 + raise ValueError(f"Unsupported bit depth: {in_bits}") 102 + 103 + # Convert to mono 104 + if in_channels == 2: 105 + mono = array.array("h") 106 + for i in range(0, len(samples) - 1, 2): 107 + mono.append((samples[i] + samples[i + 1]) // 2) 108 + samples = mono 109 + 110 + # Resample to 16kHz if needed 111 + if in_rate != 16000: 112 + ratio = 16000 / in_rate 113 + new_len = int(len(samples) * ratio) 114 + resampled = array.array("h") 115 + for i in range(new_len): 116 + src_idx = min(int(i / ratio), len(samples) - 1) 117 + resampled.append(samples[src_idx]) 118 + samples = resampled 119 + 120 + return samples.tobytes() 121 + 122 + 123 + def main(): 124 + parser = argparse.ArgumentParser(description="Real-time STT using Vosk") 125 + parser.add_argument( 126 + "-l", 127 + "--language", 128 + default=os.environ.get("VOSK_LANG", "en"), 129 + help="Language: en, de, fr, es, ru, cn, ja (default: en)", 130 + ) 131 + parser.add_argument( 132 + "-w", 133 + "--words", 134 + action="store_true", 135 + help="Output individual words as they're recognized", 136 + ) 137 + args = parser.parse_args() 138 + 139 + # Suppress Vosk logging 140 + SetLogLevel(-1) 141 + 142 + model_path = download_model(args.language) 143 + model = Model(str(model_path)) 144 + 145 + # Read WAV header 146 + stdin = sys.stdin.buffer 147 + header_buf = stdin.read(4096) 148 + sample_rate, channels, bits, data_offset = parse_wav_header(header_buf) 149 + 150 + print(f"Audio: {sample_rate}Hz, {channels}ch, {bits}bit", file=sys.stderr) 151 + print("Listening...", file=sys.stderr) 152 + 153 + # Create recognizer for 16kHz audio 154 + rec = KaldiRecognizer(model, 16000) 155 + rec.SetWords(True) 156 + 157 + # Process initial audio from header buffer 158 + initial_audio = header_buf[data_offset:] 159 + if initial_audio: 160 + converted = convert_audio(initial_audio, sample_rate, channels, bits) 161 + rec.AcceptWaveform(converted) 162 + 163 + # Process audio stream 164 + chunk_size = int(sample_rate * channels * (bits // 8) * 0.1) # 100ms chunks 165 + 166 + while True: 167 + data = stdin.read(chunk_size) 168 + if not data: 169 + break 170 + 171 + converted = convert_audio(data, sample_rate, channels, bits) 172 + 173 + if rec.AcceptWaveform(converted): 174 + result = json.loads(rec.Result()) 175 + text = result.get("text", "") 176 + if text: 177 + if args.words: 178 + # Show final sentence on stderr with period and newline 179 + print(f"\r\033[K{text}.", file=sys.stderr, flush=True) 180 + print(text, flush=True) 181 + elif args.words: 182 + partial = json.loads(rec.PartialResult()) 183 + text = partial.get("partial", "") 184 + if text: 185 + # Show partial with corrections on stderr 186 + print(f"\r\033[K{text}", end="", file=sys.stderr, flush=True) 187 + 188 + # Final result 189 + result = json.loads(rec.FinalResult()) 190 + text = result.get("text", "") 191 + if text: 192 + if args.words: 193 + print(f"\r\033[K{text}.", file=sys.stderr, flush=True) 194 + print(text, flush=True) 195 + 196 + print("Stream ended", file=sys.stderr) 197 + 198 + 199 + if __name__ == "__main__": 200 + main()