-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathipfs-link
executable file
·204 lines (185 loc) · 8.57 KB
/
ipfs-link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/usr/bin/env bash
export LANGUAGE=C LANG=C LC_ALL=C
# Info: See `usage` funtion below
export version=0.0.4 IPFS_PATH="${IPFS_PATH:="${HOME}"/.ipfs}"
usage(){
echo -e '\nBrief:
1. Publish dynamic multiaddress of isolated or private node using IPNS
2. Get multiaddress to such a node'
echo -e '\nUsage: ipfs-link [option] [<peerID>]
Provide peerID only when seeking multiaddress (or swarm key) to the corresponding node'
echo -e "\nOption:
-k hash | rand
For swarm key generation.
$(tput smul)hash$(tput rmul) implies key derived from peerID. $(tput smul)rand$(tput rmul) implies random key.
-c IPFS_PATH
Provide the path to the local IPFS repository. Can also use IPFS_PATH env variable instead.
-v
Version
-h
Show help"
echo -e '\nExamples & Details: https://github.com/SomajitDey/ipfs-link'
exit
} >&2; export -f usage
dep_check(){
# Brief: Check major dependencies
local ipfs_version quit
if ipfs_version=$(ipfs version --number 2>/dev/null); then
local req_ipfs_ver=0.9.0 # Minimum ipfs-cli version required
[[ "$(echo -e "${ipfs_version}\n${req_ipfs_ver}" | sort -V | head -n1)" == "${req_ipfs_ver}" ]] || \
{ echo -e "Required: go-ipfs-cli version >= ${req_ipfs_ver}\nSuggestion: ipfs update install latest\n"; quit="yes";}
else
echo -e "Required: go-ipfs-cli\nSource: https://docs.ipfs.io/install/command-line/#linux\n"
quit="yes"
fi
if ! command -v jq &>/dev/null; then
echo -e "Required: jq\nSource: Native package repository"
echo -e "Ref: https://stedolan.github.io/jq/download/\n"
quit="yes"
fi
[[ -v quit ]] && exit 1
} >&2; export -f dep_check
dep_check
keygen(){
# Brief: Generate swarm key on stdout according to keygen_mode
local peerID="${1:-"${main_node_ID}"}"
echo -e "/key/swarm/psk/1.0.0/\n/base16/"
case "${keygen_mode}" in
'rand') echo "$(head -c32 /dev/urandom | xxd -p -c32)";;
'hash') echo "$(echo -n "${peerID}" | sha256sum | head -c64)";;
esac
}; export -f keygen
parse_cmdline(){
local OPTIND=1 opt
while getopts hvc:k: opt; do
case "${opt}" in
v) echo "${version}"; exit;;
c) export IPFS_PATH="${OPTARG}";;
k) [[ "${OPTARG}" =~ ^(rand|hash)$ ]] || usage; export keygen_mode="${OPTARG}";;
h|*) usage;;
esac
done
local serverID="${!OPTIND}"
if [[ -n "${serverID}" ]]; then
if [[ -v keygen_mode ]]; then
keygen "${serverID}"; exit
else
# Ref: https://ipfs.github.io/public-gateway-checker/
curl -sf https://ipfs.io/ipns/"${serverID}" 2>/dev/null || \
curl -sf https://gateway.ipfs.io/ipns/"${serverID}" 2>/dev/null || \
curl -sf https://cloudfare-ipfs.com/ipns/"${serverID}" 2>/dev/null || \
curl -sf https://gateway.pinata.cloud/ipns/"${serverID}" 2>/dev/null || \
curl -sf https://hub.textile.io/ipns/"${serverID}" 2>/dev/null || \
curl -sf https://ipfs.fleek.co/ipns/"${serverID}" 2>/dev/null
exit "${?}"
fi
fi
}; export -f parse_cmdline
parse_cmdline "${@}"
main_node_config(){
# Brief: Config the isolated / private node if not already online
ipfs swarm peers && return 0
[[ -v keygen_mode ]] && keygen > "${IPFS_PATH}"/swarm.key
if [[ -f "${IPFS_PATH}"/swarm.key ]]; then # Remove default bootstrap nodes in presence of swarm key
local curr_bootstrap="$(ipfs bootstrap list)"
local default_bootstrap="$(ipfs bootstrap rm all; ipfs bootstrap add default; ipfs bootstrap list)"
local pnet_boostrap="$(echo "${curr_bootstrap}" | grep -vf <(echo "${default_bootstrap}"))"
ipfs bootstrap rm all; ipfs bootstrap add <<< "${pnet_boostrap}"
else
ipfs bootstrap add default
local daemon_opts=(--routing=none) # Don't touch the config file
fi
local daemon_opts+=(--enable-pubsub-experiment --enable-namesys-pubsub --enable-gc)
[[ "$(ipfs daemon "${daemon_opts[@]}" | grep -iom1 'Daemon is ready' & tail -f --pid=$! /dev/null)" ]]
} &>/dev/null; export main_node_config
export main_node_ID="$(ipfs id -f='<id>')"
echo -e "Status: Getting node online...\nID: ${main_node_ID}\nRepo: ${IPFS_PATH}" >&2
if main_node_config; then
echo -e "Status: Node is online"
[[ -f "${IPFS_PATH}"/swarm.key ]] && echo -e "Swarm key file: ${IPFS_PATH}/swarm.key\n" || echo
[[ "$(ipfs name pubsub state)" == enabled ]] || \
{ echo "Error: IPNS over pubsub not enabled. Run your ipfs daemon with --enable-namesys-pubsub"; exit 1;}
else
echo "Error: Failed to launch IPFS daemon. Exiting..."; exit 1
fi >&2
aux_node_config(){
# Brief: Config the auxilliary node that reprovides the IPNS records of the main node at reg intervals
export aux_IPFS_PATH="${HOME}"/.ipfs-link
( # Subshell provides isolation from main environment so that we can export IPFS_PATH
export IPFS_PATH="${aux_IPFS_PATH}"
if ! ipfs id; then
ipfs init --profile server # Disallow local discovery & private IPs
ipfs config --bool Swarm.EnableRelayHop false
ipfs config Swarm.ConnMgr.Type none # Because this node stays online for seconds only
# NAT-traversal, if needed
ipfs config --bool Swarm.DisableNatPortMap false
ipfs config --bool Swarm.Transports.Network.Relay true
ipfs config --bool Swarm.EnableAutoRelay true
mkdir "${aux_IPFS_PATH}/ipns-records"
fi
)
} &>/dev/null; export -f aux_node_config
aux_node_config
launch_aux_node(){
# Brief: Get aux node online, if not already running
# Note: Random ports necessary for multiple nodes (main + aux) to coexist on same machine
ipfs -c "${aux_IPFS_PATH}" swarm peers || \
( export IPFS_PATH="${aux_IPFS_PATH}"
ipfs config profile apply randomports
rm -f "${IPFS_PATH}/config-pre-"*
pscan(){
# Brief: Give an unused, random, local TCP port except the one passed as argument
local port except="${1}"
while port="$(( 0x"$(dd if=/dev/urandom bs=2 count=1 2> /dev/null | xxd -p)" - 1 ))";do # Max port number=2^16-1
((port!=except)) || continue
nc -z localhost "${port}" || break
done
echo "${port}"
} 2>/dev/null; export -f pscan
local API_port="$(pscan)"; ipfs config Addresses.API "/ip4/127.0.0.1/tcp/${API_port}"
local gateway_port="$(pscan "${API_port}")"; ipfs config Addresses.Gateway "/ip4/127.0.0.1/tcp/${gateway_port}"
[[ "$(ipfs daemon --enable-namesys-pubsub | grep -iom1 'Daemon is ready' & tail -f --pid=$! /dev/null)" ]]
)
} &>/dev/null; export -f launch_aux_node
timestamp(){
date +%D\ %H:%M:%S
}; export -f timestamp
main(){
# Brief: The main loop
local buffer="$(mktemp)" multiaddress
trap "rm -f ${aux_IPFS_PATH}/ipns-records/${main_node_ID} ${buffer}" return
while ipfs swarm peers &>/dev/null; do
# Choose only internet accessible multiaddress, including p2p-circuit, if any
until multiaddress="$(ipfs id -f="<addrs>\n" | grep -vf <(ipfs diag sys | jq -r .net.interface_addresses[]))"; do
echo -en "$(timestamp): Seeking public multiaddress - circuit-relay or public IP...\r"
sleep 1
done && echo
until launch_aux_node; do
echo -ne "$(timestamp): Failed to start aux node. Trying again in 2s...\r"
sleep 2
done && echo -e "\n\n$(timestamp): Aux node resumed"
local cid="$(echo "${multiaddress}" | ipfs add --pin=false --inline --inline-limit=1000 -Q)"
if [[ "${cid}" != "${last_cid}" ]]; then # No need to expire old IPNS records & create same but new ones unnecessarily
ipfs name publish --ipns-base=b58mh -Q /ipfs/"${cid}" &>/dev/null
ipfs dht get /ipns/"${main_node_ID}" > "${buffer}" && \
mv "${buffer}" "${aux_IPFS_PATH}"/ipns-records/"${main_node_ID}"
local last_cid="${cid}"
fi
( if flock -n 3; then
local records=("${aux_IPFS_PATH}"/ipns-records/*)
# To publish all records, bcoz why not, the other instances might be sleeping now
else # Another instance running
local records=("${aux_IPFS_PATH}"/ipns-records/"${main_node_ID}") # To publish only main node record
flock 3 # Wait for the other instance to finish
[[ "$(ipfs name resolve /ipns/"${main_node_ID}")" != /ipfs/"${cid}" ]] || exit
# The other instance has already published seconds ago. So no need to republish just now.
fi
export IPFS_PATH="${aux_IPFS_PATH}"
echo "${records[@]}" | xargs -n1 -P2 ipfs dht put /ipns/"${main_node_ID}" # Use max 2 threads
) 3< "${aux_IPFS_PATH}"/config &>/dev/null
flock -n "${aux_IPFS_PATH}/config" ipfs -c "${aux_IPFS_PATH}" shutdown &>/dev/null
[[ "$("${BASH_SOURCE}" "${main_node_ID}")" == "${multiaddress}" ]] || continue # Continue if public gateway can't resolve
echo -e "\n$(timestamp): Published multiaddress over IPNS. Aux node pausing for 15m..."; sleep 15m
done
} >&2; export -f main
main