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 16 pinWatcherScript = pkgs.writers.writeBash "ipfs-pin-watcher" '' 17 set -efu 18 19 IPFS="${pkgs.kubo}/bin/ipfs" 20 INOTIFYWAIT="${pkgs.inotify-tools}/bin/inotifywait" 21 CID_MAP="${cidMapFile}" 22 23 touch "$CID_MAP" 24 ${pkgs.coreutils}/bin/chmod 0644 "$CID_MAP" 25 26 log() { 27 echo "[$(date -Iseconds)] $*" 28 } 29 30 add_file() { 31 local path="$1" 32 [ -f "$path" ] || return 0 33 case "$(basename "$path")" in 34 .* | *.part | *.tmp | *.synced.* | *.aria2) return 0 ;; 35 esac 36 37 log "Adding $path" 38 cid=$($IPFS add --nocopy --pin --quieter "$path" 2>/dev/null) || { 39 log "Failed to add $path" 40 return 0 41 } 42 log "Pinned $path -> $cid" 43 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP" 44 echo "$cid $path" >> "$CID_MAP" 45 } 46 47 remove_file() { 48 local path="$1" 49 cid=$(${pkgs.gawk}/bin/awk -v p="$path" '$0 ~ p {print $1; exit}' "$CID_MAP") 50 if [ -n "$cid" ]; then 51 log "Unpinning $cid ($path)" 52 $IPFS pin rm "$cid" 2>/dev/null || true 53 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP" 54 else 55 log "No CID found for removed file: $path" 56 fi 57 } 58 59 # initial sync: pin any file in the watch dirs not already in cid-map 60 log "Starting initial sync..." 61 for dir in ${lib.escapeShellArgs watchDirs}; do 62 if [ -d "$dir" ]; then 63 ${pkgs.findutils}/bin/find "$dir" -type f | while read -r f; do 64 if ! ${pkgs.gnugrep}/bin/grep -q " $f$" "$CID_MAP" 2>/dev/null; then 65 add_file "$f" 66 fi 67 done 68 fi 69 done 70 log "Initial sync complete" 71 72 # clean stale entries: unpin files that no longer exist on disk 73 log "Cleaning stale entries..." 74 while IFS=' ' read -r cid path; do 75 if [ ! -f "$path" ]; then 76 log "Stale entry: $path (CID: $cid)" 77 $IPFS pin rm "$cid" 2>/dev/null || true 78 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP" 79 fi 80 done < "$CID_MAP" 81 log "Cleanup complete" 82 83 # pin all files in a newly appeared directory (covers the inotify 84 # recursive-watch race when a brand-new subdir is created + filled fast) 85 scan_new_dir() { 86 local dir="$1" 87 log "Scanning new directory: $dir" 88 ${pkgs.findutils}/bin/find "$dir" -type f | while read -r f; do 89 add_file "$f" 90 done 91 } 92 93 # watch for changes 94 log "Watching directories: ${lib.concatStringsSep ", " watchDirs}" 95 $INOTIFYWAIT -m -r --format $'%w\t%e\t%f' \ 96 -e close_write \ 97 -e moved_to \ 98 -e moved_from \ 99 -e delete \ 100 -e create \ 101 ${lib.escapeShellArgs watchDirs} | 102 while IFS=$'\t' read -r dir event file; do 103 path="''${dir}''${file}" 104 case "$event" in 105 CREATE,ISDIR*|MOVED_TO,ISDIR*) 106 scan_new_dir "$path" 107 ;; 108 CLOSE_WRITE*|MOVED_TO*) 109 add_file "$path" 110 ;; 111 DELETE*|MOVED_FROM*) 112 remove_file "$path" 113 ;; 114 esac 115 done 116 ''; 117 118 uploadServer = pkgs.writers.writePython3Bin "ipfs-upload-server" { 119 flakeIgnore = [ "E501" ]; 120 } ./ipfs_upload.py; 121 122 # Periodic reconciliation so cid-map/pins stay in sync with the filesystem 123 # even when the inotify watcher drops events: 124 # reverse - unpin + drop cid-map entries for files no longer on disk. 125 # forward - pin files present on disk but missing from cid-map. The watcher 126 # loop is single-threaded and blocks 1-2 min on each multi-GB 127 # `ipfs add`; while blocked, a concurrent upload's CREATE,ISDIR + 128 # MOVED_TO for a brand-new subdir can overflow the inotify queue 129 # and be dropped, leaving a fully-written file never pinned. The 130 # forward sweep heals that within an hour. 131 reconcileScript = pkgs.writers.writeBash "ipfs-pin-reconcile" '' 132 set -efu 133 134 IPFS="${pkgs.kubo}/bin/ipfs" 135 CID_MAP="${cidMapFile}" 136 137 [ -f "$CID_MAP" ] || exit 0 138 139 # Reverse: rebuild cid-map, unpinning entries whose file is gone. One pass 140 # avoids sed regex issues with special chars like [ ] in paths. Small race 141 # with the watcher's concurrent appends -- a line written during this loop 142 # may be lost, but is re-added on the next inotify event or watcher restart. 143 tmp=$(${pkgs.coreutils}/bin/mktemp) 144 removed=0 145 while IFS=' ' read -r cid path; do 146 [ -n "$cid" ] || continue 147 if [ -e "$path" ]; then 148 echo "$cid $path" >> "$tmp" 149 else 150 $IPFS pin rm "$cid" >/dev/null 2>&1 || true 151 removed=$((removed + 1)) 152 fi 153 done < "$CID_MAP" 154 155 if [ "$removed" -gt 0 ]; then 156 ${pkgs.coreutils}/bin/chmod 0644 "$tmp" 157 ${pkgs.coreutils}/bin/mv "$tmp" "$CID_MAP" 158 echo "reconciled: removed $removed stale entries" 159 else 160 ${pkgs.coreutils}/bin/rm -f "$tmp" 161 fi 162 163 # Forward: pin on-disk files missing from cid-map. Only files older than 164 # 10 min -- a file that old and still absent from cid-map was dropped by the 165 # watcher, not in-flight. No lock against the watcher: concurrent 166 # `add --nocopy` of the same file is idempotent (verified), so a rare 167 # double-add is harmless. 168 for dir in ${lib.escapeShellArgs watchDirs}; do 169 [ -d "$dir" ] || continue 170 ${pkgs.findutils}/bin/find "$dir" -type f -mmin +10 | while read -r f; do 171 case "$(${pkgs.coreutils}/bin/basename "$f")" in 172 .* | *.part | *.tmp | *.synced.* | *.aria2) continue ;; 173 esac 174 if ${pkgs.gnugrep}/bin/grep -q " $f$" "$CID_MAP" 2>/dev/null; then 175 continue 176 fi 177 cid=$($IPFS add --nocopy --pin --quieter "$f" 2>/dev/null) || { 178 echo "reconcile: failed to add $f" 179 continue 180 } 181 echo "$cid $f" >> "$CID_MAP" 182 echo "reconcile: pinned missed file $f -> $cid" 183 done 184 done 185 ''; 186in 187{ 188 # symlink /var/download into IPFS root so --nocopy works 189 systemd.tmpfiles.rules = [ 190 "L+ /var/lib/ipfs/download - - - - /var/download" 191 # Staging area for ipfs-upload: same filesystem as the destination, but 192 # outside the pin-watcher's watch dirs (movies/shows/games) so inotify 193 # never fires on in-flight uploads. 194 "d /var/download/incoming 0755 ${config.services.kubo.user} ${config.services.kubo.group} -" 195 "d /var/download/incoming/uploader 0755 ${config.services.kubo.user} ${config.services.kubo.group} -" 196 # Let the ipfs-upload service write into /var/download/games. Owner 197 # = ipfs so the service has write; group = users + setgid so existing 198 # CLI users (lass) keep write via group and new uploads inherit 199 # group=users. 200 "z /var/download/games 2775 ${config.services.kubo.user} users -" 201 ]; 202 203 services.kubo = { 204 enable = true; 205 settings = { 206 # kubo 0.40 deprecated Reprovider in favor of Provide; the nixpkgs 207 # module still injects a default Reprovider block, so null it out 208 # explicitly or kubo refuses to start. 209 Reprovider = null; 210 Experimental.FilestoreEnabled = true; 211 Addresses = { 212 API = [ 213 "/ip4/127.0.0.1/tcp/5001" 214 "/ip6/::1/tcp/5001" 215 ]; 216 Gateway = [ 217 "/ip4/0.0.0.0/tcp/8089" 218 "/ip6/::/tcp/8089" 219 ]; 220 }; 221 Datastore.StorageMax = "100GB"; 222 # limit bandwidth: 5MB/s out, 5MB/s in 223 Swarm.ConnMgr = { 224 LowWater = 100; 225 HighWater = 400; 226 GracePeriod = "20s"; 227 }; 228 Swarm.Transports.Network.TCP = true; 229 Swarm.Transports.Network.QUIC = true; 230 Swarm.ResourceMgr = { 231 Enabled = true; 232 MaxMemory = "2GB"; 233 }; 234 # disable relay to reduce overhead 235 Swarm.RelayClient.Enabled = false; 236 Swarm.RelayService.Enabled = false; 237 # autoclient: DHT client (no server queries) + IPNI/cid.contact HTTP 238 # announcements. Content is discoverable by both DHT peers and lassie 239 # (which uses IPNI for provider discovery). 240 Routing.Type = "autoclient"; 241 Provide = { 242 Strategy = "pinned"; 243 DHT.Interval = "12h"; 244 }; 245 }; 246 }; 247 248 boot.kernel.sysctl."fs.inotify.max_user_watches" = 1048576; 249 250 systemd.services.ipfs-pin-watcher = { 251 description = "Auto-pin files to IPFS using inotify"; 252 after = [ "ipfs.service" ]; 253 requires = [ "ipfs.service" ]; 254 wantedBy = [ "multi-user.target" ]; 255 serviceConfig = { 256 Environment = [ "IPFS_PATH=/var/lib/ipfs" ]; 257 ExecStart = pinWatcherScript; 258 Restart = "always"; 259 RestartSec = "10s"; 260 User = config.services.kubo.user; 261 Group = config.services.kubo.group; 262 SupplementaryGroups = [ "radio_container" ]; 263 }; 264 }; 265 266 systemd.services.ipfs-upload = { 267 description = "HTTP upload endpoint that writes files under /var/lib/ipfs/download and pins them"; 268 after = [ "ipfs.service" ]; 269 requires = [ "ipfs.service" ]; 270 wantedBy = [ "multi-user.target" ]; 271 serviceConfig = { 272 Environment = [ 273 "IPFS_PATH=/var/lib/ipfs" 274 "IPFS_BIN=${pkgs.kubo}/bin/ipfs" 275 "DOWNLOAD_ROOT=/var/lib/ipfs/download" 276 "BIND=127.0.0.1" 277 "PORT=8090" 278 ]; 279 ExecStart = "${uploadServer}/bin/ipfs-upload-server"; 280 Restart = "always"; 281 RestartSec = "10s"; 282 User = config.services.kubo.user; 283 Group = config.services.kubo.group; 284 # /var/download is owned by ipfs:ipfs on neoprism, so no extra 285 # groups needed. Existing subdirs owned by other users (e.g. games 286 # → lass:users) are not writable by this service; uploads to those 287 # paths will get a 500 unless ownership is adjusted out-of-band. 288 }; 289 }; 290 291 systemd.services.ipfs-pin-reconcile = { 292 description = "Reconcile pins / cid-map against the filesystem"; 293 after = [ "ipfs.service" ]; 294 requires = [ "ipfs.service" ]; 295 serviceConfig = { 296 Type = "oneshot"; 297 Environment = [ "IPFS_PATH=/var/lib/ipfs" ]; 298 ExecStart = reconcileScript; 299 User = config.services.kubo.user; 300 Group = config.services.kubo.group; 301 }; 302 }; 303 304 systemd.timers.ipfs-pin-reconcile = { 305 description = "Reconcile pins / cid-map hourly"; 306 wantedBy = [ "timers.target" ]; 307 timerConfig = { 308 OnBootSec = "15min"; 309 OnUnitActiveSec = "1h"; 310 Unit = "ipfs-pin-reconcile.service"; 311 }; 312 }; 313 314 networking.firewall.allowedTCPPorts = [ 315 4001 # IPFS swarm 316 8089 # IPFS gateway 317 ]; 318 networking.firewall.allowedUDPPorts = [ 319 4001 # IPFS swarm (QUIC) 320 ]; 321 322 # Shadow `ipfs` in PATH with a wrapper that rejects `ipfs add` without 323 # --nocopy. The kubo daemon and pin-watcher use absolute store paths, so 324 # they are unaffected. This blocks confused agents/users from creating 325 # blockstore-only duplicates of files that already live in 326 # /var/lib/ipfs/download. 327 environment.systemPackages = [ 328 (lib.hiPrio ( 329 pkgs.writeShellScriptBin "ipfs" '' 330 if [ "''${1-}" = "add" ]; then 331 for a in "$@"; do 332 case "$a" in 333 --nocopy|--nocopy=true) exec ${pkgs.kubo}/bin/ipfs "$@" ;; 334 esac 335 done 336 echo "refused: 'ipfs add' without --nocopy is forbidden on this host." >&2 337 echo " Use 'ipfs add --nocopy <path>' so the file is referenced via filestore" >&2 338 echo " instead of duplicated into the blockstore." >&2 339 exit 1 340 fi 341 exec ${pkgs.kubo}/bin/ipfs "$@" 342 '' 343 )) 344 ]; 345}