This repository has no description
0

Configure Feed

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

1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 watchDirs = [ 9 "/var/lib/ipfs/download/movies" 10 "/var/lib/ipfs/download/shows" 11 "/var/lib/ipfs/download/games" 12 ]; 13 14 cidMapFile = "/var/lib/ipfs/cid-map.txt"; 15 downloadRoot = "/var/lib/ipfs/download"; 16 flixIndexMfs = "/flix-index"; 17 dirtyFlag = "/var/lib/ipfs/flix-index.dirty"; 18 ipnsNameFile = "/var/lib/ipfs/flix-index.name"; 19 20 pinWatcherScript = pkgs.writers.writeBash "ipfs-pin-watcher" '' 21 set -efu 22 23 IPFS="${pkgs.kubo}/bin/ipfs" 24 INOTIFYWAIT="${pkgs.inotify-tools}/bin/inotifywait" 25 CID_MAP="${cidMapFile}" 26 DOWNLOAD_ROOT="${downloadRoot}" 27 FLIX_INDEX="${flixIndexMfs}" 28 DIRTY_FLAG="${dirtyFlag}" 29 IPNS_NAME_FILE="${ipnsNameFile}" 30 31 touch "$CID_MAP" 32 33 log() { 34 echo "[$(date -Iseconds)] $*" 35 } 36 37 mark_dirty() { 38 touch "$DIRTY_FLAG" 39 } 40 41 # strip $DOWNLOAD_ROOT/ prefix to get the relative path used inside MFS 42 relpath() { 43 echo "''${1#$DOWNLOAD_ROOT/}" 44 } 45 46 mfs_add() { 47 local cid="$1" path="$2" 48 local rel dir 49 rel=$(relpath "$path") 50 dir=$(${pkgs.coreutils}/bin/dirname "$rel") 51 # kubo's `files rm/cp/mkdir` write detailed errors to stdout, not 52 # stderr, so both FDs need redirection to keep backfill quiet. 53 $IPFS files mkdir -p "$FLIX_INDEX/$dir" >/dev/null 2>&1 || true 54 $IPFS files rm "$FLIX_INDEX/$rel" >/dev/null 2>&1 || true 55 $IPFS files cp "/ipfs/$cid" "$FLIX_INDEX/$rel" >/dev/null 2>&1 || true 56 mark_dirty 57 } 58 59 mfs_rm() { 60 local path="$1" 61 local rel 62 rel=$(relpath "$path") 63 $IPFS files rm "$FLIX_INDEX/$rel" >/dev/null 2>&1 || true 64 mark_dirty 65 } 66 67 # ensure IPNS key + MFS root exist (idempotent) 68 if ! $IPFS key list | ${pkgs.gnugrep}/bin/grep -qx flix-index; then 69 log "Generating flix-index IPNS key" 70 $IPFS key gen --type=ed25519 flix-index > "$IPNS_NAME_FILE" || true 71 fi 72 if [ -f "$IPNS_NAME_FILE" ]; then 73 log "flix-index IPNS name: $(cat "$IPNS_NAME_FILE")" 74 fi 75 $IPFS files mkdir -p "$FLIX_INDEX" >/dev/null 2>&1 || true 76 77 add_file() { 78 local path="$1" 79 [ -f "$path" ] || return 0 80 case "$(basename "$path")" in 81 .* | *.part | *.tmp | *.synced.*) return 0 ;; 82 esac 83 84 log "Adding $path" 85 cid=$($IPFS add --nocopy --pin --quieter "$path" 2>/dev/null) || { 86 log "Failed to add $path" 87 return 0 88 } 89 log "Pinned $path -> $cid" 90 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP" 91 echo "$cid $path" >> "$CID_MAP" 92 mfs_add "$cid" "$path" 93 } 94 95 remove_file() { 96 local path="$1" 97 cid=$(${pkgs.gawk}/bin/awk -v p="$path" '$0 ~ p {print $1; exit}' "$CID_MAP") 98 if [ -n "$cid" ]; then 99 log "Unpinning $cid ($path)" 100 # remove from MFS first so the implicit MFS pin doesn't keep it alive 101 mfs_rm "$path" 102 $IPFS pin rm "$cid" 2>/dev/null || true 103 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP" 104 else 105 log "No CID found for removed file: $path" 106 fi 107 } 108 109 # initial sync 110 log "Starting initial sync..." 111 for dir in ${lib.escapeShellArgs watchDirs}; do 112 if [ -d "$dir" ]; then 113 ${pkgs.findutils}/bin/find "$dir" -type f | while read -r f; do 114 if ! ${pkgs.gnugrep}/bin/grep -q " $f$" "$CID_MAP" 2>/dev/null; then 115 add_file "$f" 116 fi 117 done 118 fi 119 done 120 log "Initial sync complete" 121 122 # clean stale entries 123 log "Cleaning stale entries..." 124 while IFS=' ' read -r cid path; do 125 if [ ! -f "$path" ]; then 126 log "Stale entry: $path (CID: $cid)" 127 mfs_rm "$path" 128 $IPFS pin rm "$cid" 2>/dev/null || true 129 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP" 130 fi 131 done < "$CID_MAP" 132 log "Cleanup complete" 133 134 # backfill MFS index from cid-map.txt once (sentinel-guarded so we run 135 # exactly once per machine, independently of whether the initial sync 136 # happened to create any MFS entries incrementally). 137 BACKFILL_SENTINEL="/var/lib/ipfs/flix-index.backfilled" 138 if [ ! -f "$BACKFILL_SENTINEL" ]; then 139 log "Backfilling MFS index from cid-map.txt..." 140 n=0 141 while IFS=' ' read -r cid path; do 142 [ -f "$path" ] || continue 143 # skip entries already present (idempotent re-runs) 144 rel=$(relpath "$path") 145 if $IPFS files stat "$FLIX_INDEX/$rel" >/dev/null 2>&1; then 146 continue 147 fi 148 mfs_add "$cid" "$path" 149 n=$((n + 1)) 150 done < "$CID_MAP" 151 touch "$BACKFILL_SENTINEL" 152 log "Backfill complete ($n entries added)" 153 fi 154 155 # add all files in a newly appeared directory 156 scan_new_dir() { 157 local dir="$1" 158 log "Scanning new directory: $dir" 159 ${pkgs.findutils}/bin/find "$dir" -type f | while read -r f; do 160 add_file "$f" 161 done 162 } 163 164 # watch for changes 165 log "Watching directories: ${lib.concatStringsSep ", " watchDirs}" 166 $INOTIFYWAIT -m -r --format $'%w\t%e\t%f' \ 167 -e close_write \ 168 -e moved_to \ 169 -e moved_from \ 170 -e delete \ 171 -e create \ 172 ${lib.escapeShellArgs watchDirs} | 173 while IFS=$'\t' read -r dir event file; do 174 path="''${dir}''${file}" 175 case "$event" in 176 CREATE,ISDIR*|MOVED_TO,ISDIR*) 177 scan_new_dir "$path" 178 ;; 179 CLOSE_WRITE*|MOVED_TO*) 180 add_file "$path" 181 ;; 182 DELETE*|MOVED_FROM*) 183 remove_file "$path" 184 ;; 185 esac 186 done 187 ''; 188 189 publishScript = pkgs.writers.writeBash "flix-index-publish" '' 190 set -efu 191 192 IPFS="${pkgs.kubo}/bin/ipfs" 193 CID_MAP="${cidMapFile}" 194 DOWNLOAD_ROOT="${downloadRoot}" 195 FLIX_INDEX="${flixIndexMfs}" 196 DIRTY_FLAG="${dirtyFlag}" 197 198 [ -f "$DIRTY_FLAG" ] || exit 0 199 200 # regenerate index.tsv from cid-map.txt 201 tmp=$(${pkgs.coreutils}/bin/mktemp) 202 printf 'cid\tpath\tsize\n' > "$tmp" 203 while IFS=' ' read -r cid path; do 204 [ -f "$path" ] || continue 205 rel="''${path#$DOWNLOAD_ROOT/}" 206 size=$(${pkgs.coreutils}/bin/stat -c %s "$path" 2>/dev/null || echo 0) 207 printf '%s\t%s\t%s\n' "$cid" "$rel" "$size" >> "$tmp" 208 done < "$CID_MAP" 209 210 $IPFS files rm "$FLIX_INDEX/index.tsv" >/dev/null 2>&1 || true 211 $IPFS files write --create --truncate "$FLIX_INDEX/index.tsv" < "$tmp" 212 ${pkgs.coreutils}/bin/rm -f "$tmp" 213 214 root=$($IPFS files stat --hash "$FLIX_INDEX") 215 echo "Publishing $root to IPNS (flix-index)" 216 $IPFS name publish --key=flix-index --lifetime=48h --ttl=1m "/ipfs/$root" 217 # Announce the MFS root to the DHT so public gateways can find us. 218 # MFS directory blocks are only kept alive by an implicit pin that 219 # Provide.Strategy="pinned" ignores, so we provide them explicitly here. 220 echo "Announcing $root to DHT" 221 $IPFS routing provide "$root" >/dev/null 2>&1 || true 222 ${pkgs.coreutils}/bin/rm -f "$DIRTY_FLAG" 223 ''; 224 225 # Periodic reconciliation: walks cid-map.txt and repairs stale state for 226 # files that no longer exist on disk. Protects against inotify event 227 # drops (e.g. queue overflow during bulk renames/deletes) and any other 228 # path that leaves cid-map/MFS out of sync with the filesystem. 229 reconcileScript = pkgs.writers.writeBash "flix-index-reconcile" '' 230 set -efu 231 232 IPFS="${pkgs.kubo}/bin/ipfs" 233 CID_MAP="${cidMapFile}" 234 DOWNLOAD_ROOT="${downloadRoot}" 235 FLIX_INDEX="${flixIndexMfs}" 236 DIRTY_FLAG="${dirtyFlag}" 237 238 [ -f "$CID_MAP" ] || exit 0 239 240 # Rebuild cid-map in one pass, collecting stale entries to clean up. 241 # Avoids sed regex issues with special chars like [ ] in paths. 242 # Small race with pin-watcher's concurrent appends — any line the 243 # watcher writes during this loop may be lost, but will be re-added 244 # on the next inotify event or watcher restart. 245 tmp=$(${pkgs.coreutils}/bin/mktemp) 246 removed=0 247 while IFS=' ' read -r cid path; do 248 [ -n "$cid" ] || continue 249 if [ -e "$path" ]; then 250 echo "$cid $path" >> "$tmp" 251 else 252 rel="''${path#$DOWNLOAD_ROOT/}" 253 $IPFS files rm "$FLIX_INDEX/$rel" >/dev/null 2>&1 || true 254 $IPFS pin rm "$cid" >/dev/null 2>&1 || true 255 removed=$((removed + 1)) 256 fi 257 done < "$CID_MAP" 258 259 if [ "$removed" -gt 0 ]; then 260 ${pkgs.coreutils}/bin/mv "$tmp" "$CID_MAP" 261 echo "reconciled: removed $removed stale entries" 262 touch "$DIRTY_FLAG" 263 else 264 ${pkgs.coreutils}/bin/rm -f "$tmp" 265 fi 266 ''; 267in 268{ 269 # symlink /var/download into IPFS root so --nocopy works 270 systemd.tmpfiles.rules = [ 271 "L+ /var/lib/ipfs/download - - - - /var/download" 272 ]; 273 274 services.kubo = { 275 enable = true; 276 settings = { 277 # kubo 0.40 deprecated Reprovider in favor of Provide; the nixpkgs 278 # module still injects a default Reprovider block, so null it out 279 # explicitly or kubo refuses to start. 280 Reprovider = null; 281 Experimental.FilestoreEnabled = true; 282 Addresses = { 283 API = [ 284 "/ip4/127.0.0.1/tcp/5001" 285 "/ip6/::1/tcp/5001" 286 ]; 287 Gateway = [ 288 "/ip4/0.0.0.0/tcp/8089" 289 "/ip6/::/tcp/8089" 290 ]; 291 }; 292 Datastore.StorageMax = "100GB"; 293 # limit bandwidth: 5MB/s out, 5MB/s in 294 Swarm.ConnMgr = { 295 LowWater = 100; 296 HighWater = 400; 297 GracePeriod = "20s"; 298 }; 299 Swarm.Transports.Network.TCP = true; 300 Swarm.Transports.Network.QUIC = true; 301 Swarm.ResourceMgr = { 302 Enabled = true; 303 MaxMemory = "2GB"; 304 }; 305 # disable relay to reduce overhead 306 Swarm.RelayClient.Enabled = false; 307 Swarm.RelayService.Enabled = false; 308 # dhtclient: announces our provider records (so peers can find content 309 # we host by CID) but does not serve DHT routing queries for others. 310 # autoclient would skip provide announcements and make our content 311 # invisible to bitswap. 312 Routing.Type = "dhtclient"; 313 Provide = { 314 Strategy = "pinned"; 315 DHT.Interval = "12h"; 316 }; 317 }; 318 }; 319 320 boot.kernel.sysctl."fs.inotify.max_user_watches" = 1048576; 321 322 systemd.services.ipfs-pin-watcher = { 323 description = "Auto-pin files to IPFS using inotify"; 324 after = [ "ipfs.service" ]; 325 requires = [ "ipfs.service" ]; 326 wantedBy = [ "multi-user.target" ]; 327 serviceConfig = { 328 Environment = [ "IPFS_PATH=/var/lib/ipfs" ]; 329 ExecStart = pinWatcherScript; 330 Restart = "always"; 331 RestartSec = "10s"; 332 User = config.services.kubo.user; 333 Group = config.services.kubo.group; 334 SupplementaryGroups = [ "radio_container" ]; 335 }; 336 }; 337 338 systemd.services.flix-index-publish = { 339 description = "Publish flix MFS index to IPNS"; 340 after = [ "ipfs.service" ]; 341 requires = [ "ipfs.service" ]; 342 serviceConfig = { 343 Type = "oneshot"; 344 Environment = [ "IPFS_PATH=/var/lib/ipfs" ]; 345 ExecStart = publishScript; 346 User = config.services.kubo.user; 347 Group = config.services.kubo.group; 348 }; 349 }; 350 351 systemd.timers.flix-index-publish = { 352 description = "Publish flix MFS index to IPNS periodically"; 353 wantedBy = [ "timers.target" ]; 354 timerConfig = { 355 OnBootSec = "5min"; 356 OnUnitActiveSec = "2min"; 357 Unit = "flix-index-publish.service"; 358 }; 359 }; 360 361 systemd.services.flix-index-reconcile = { 362 description = "Reconcile flix MFS index / cid-map against filesystem"; 363 after = [ "ipfs.service" ]; 364 requires = [ "ipfs.service" ]; 365 serviceConfig = { 366 Type = "oneshot"; 367 Environment = [ "IPFS_PATH=/var/lib/ipfs" ]; 368 ExecStart = reconcileScript; 369 User = config.services.kubo.user; 370 Group = config.services.kubo.group; 371 }; 372 }; 373 374 systemd.timers.flix-index-reconcile = { 375 description = "Reconcile flix MFS index hourly"; 376 wantedBy = [ "timers.target" ]; 377 timerConfig = { 378 OnBootSec = "15min"; 379 OnUnitActiveSec = "1h"; 380 Unit = "flix-index-reconcile.service"; 381 }; 382 }; 383 384 networking.firewall.allowedTCPPorts = [ 385 4001 # IPFS swarm 386 8089 # IPFS gateway 387 ]; 388 networking.firewall.allowedUDPPorts = [ 389 4001 # IPFS swarm (QUIC) 390 ]; 391}