This repository has no description
4.8 kB
182 lines
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
25 log() {
26 echo "[$(date -Iseconds)] $*"
27 }
28
29 add_file() {
30 local path="$1"
31 [ -f "$path" ] || return 0
32 case "$(basename "$path")" in
33 .* | *.part | *.tmp | *.synced.*) return 0 ;;
34 esac
35
36 log "Adding $path"
37 cid=$($IPFS add --nocopy --pin --quieter "$path" 2>/dev/null) || {
38 log "Failed to add $path"
39 return 0
40 }
41 log "Pinned $path -> $cid"
42 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP"
43 echo "$cid $path" >> "$CID_MAP"
44 }
45
46 remove_file() {
47 local path="$1"
48 cid=$(${pkgs.gawk}/bin/awk -v p="$path" '$0 ~ p {print $1; exit}' "$CID_MAP")
49 if [ -n "$cid" ]; then
50 log "Unpinning $cid ($path)"
51 $IPFS pin rm "$cid" 2>/dev/null || true
52 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP"
53 else
54 log "No CID found for removed file: $path"
55 fi
56 }
57
58 # initial sync
59 log "Starting initial sync..."
60 for dir in ${lib.escapeShellArgs watchDirs}; do
61 if [ -d "$dir" ]; then
62 ${pkgs.findutils}/bin/find "$dir" -type f | while read -r f; do
63 if ! ${pkgs.gnugrep}/bin/grep -q " $f$" "$CID_MAP" 2>/dev/null; then
64 add_file "$f"
65 fi
66 done
67 fi
68 done
69 log "Initial sync complete"
70
71 # clean stale entries
72 log "Cleaning stale entries..."
73 while IFS=' ' read -r cid path; do
74 if [ ! -f "$path" ]; then
75 log "Stale entry: $path (CID: $cid)"
76 $IPFS pin rm "$cid" 2>/dev/null || true
77 ${pkgs.gnused}/bin/sed -i "\|^[^ ]* $path$|d" "$CID_MAP"
78 fi
79 done < "$CID_MAP"
80 log "Cleanup complete"
81
82 # add all files in a newly appeared directory
83 scan_new_dir() {
84 local dir="$1"
85 log "Scanning new directory: $dir"
86 ${pkgs.findutils}/bin/find "$dir" -type f | while read -r f; do
87 add_file "$f"
88 done
89 }
90
91 # watch for changes
92 log "Watching directories: ${lib.concatStringsSep ", " watchDirs}"
93 $INOTIFYWAIT -m -r --format $'%w\t%e\t%f' \
94 -e close_write \
95 -e moved_to \
96 -e moved_from \
97 -e delete \
98 -e create \
99 ${lib.escapeShellArgs watchDirs} |
100 while IFS=$'\t' read -r dir event file; do
101 path="''${dir}''${file}"
102 case "$event" in
103 CREATE,ISDIR*|MOVED_TO,ISDIR*)
104 scan_new_dir "$path"
105 ;;
106 CLOSE_WRITE*|MOVED_TO*)
107 add_file "$path"
108 ;;
109 DELETE*|MOVED_FROM*)
110 remove_file "$path"
111 ;;
112 esac
113 done
114 '';
115in
116{
117 # symlink /var/download into IPFS root so --nocopy works
118 systemd.tmpfiles.rules = [
119 "L+ /var/lib/ipfs/download - - - - /var/download"
120 ];
121
122 services.kubo = {
123 enable = true;
124 settings = {
125 Experimental.FilestoreEnabled = true;
126 Addresses = {
127 API = [
128 "/ip4/127.0.0.1/tcp/5001"
129 "/ip6/::1/tcp/5001"
130 ];
131 Gateway = [
132 "/ip4/0.0.0.0/tcp/8089"
133 "/ip6/::/tcp/8089"
134 ];
135 };
136 Datastore.StorageMax = "100GB";
137 # limit bandwidth: 5MB/s out, 5MB/s in
138 Swarm.ConnMgr = {
139 LowWater = 20;
140 HighWater = 50;
141 GracePeriod = "10s";
142 };
143 Swarm.Transports.Network.TCP = true;
144 Swarm.Transports.Network.QUIC = false;
145 Swarm.ResourceMgr = {
146 Enabled = true;
147 MaxMemory = "256MB";
148 };
149 # disable relay to reduce overhead
150 Swarm.RelayClient.Enabled = false;
151 Swarm.RelayService.Enabled = false;
152 # reduce DHT overhead - client mode only announces, doesn't route for others
153 Routing.Type = "autoclient";
154 };
155 };
156
157 boot.kernel.sysctl."fs.inotify.max_user_watches" = 1048576;
158
159 systemd.services.ipfs-pin-watcher = {
160 description = "Auto-pin files to IPFS using inotify";
161 after = [ "ipfs.service" ];
162 requires = [ "ipfs.service" ];
163 wantedBy = [ "multi-user.target" ];
164 serviceConfig = {
165 Environment = [ "IPFS_PATH=/var/lib/ipfs" ];
166 ExecStart = pinWatcherScript;
167 Restart = "always";
168 RestartSec = "10s";
169 User = config.services.kubo.user;
170 Group = config.services.kubo.group;
171 SupplementaryGroups = [ "radio_container" ];
172 };
173 };
174
175 networking.firewall.allowedTCPPorts = [
176 4001 # IPFS swarm
177 8089 # IPFS gateway
178 ];
179 networking.firewall.allowedUDPPorts = [
180 4001 # IPFS swarm (QUIC)
181 ];
182}