3dMjVEP?&P#JuFxE&d;yV5=-=%>RYRhvkqmb8H_bH2-H(Qjv6YWRfRM_pHSC~_k
zJYPG2eANO1y=t6W#w|U-T9zcCh>6HWh>Za{=yiZd{DfYQHFGm%CZtt=#64NNAs(s8
zG4i7uRfd38cCZfKsa`p=jp-4J&w-?m=E|QO!8Uv%m)fXpunI}L=7{RTYytZ(ZHyJ3HEBGV#qafT
zbl~_Udw}mwdBvtucvi6YCFNPui~MH*6FoAW$-GJ1#H{r7naKVUeMTl-T&*6`&;b$~
z8~_)wYch89g_R9~0z_K51)wP8Wjn8abC_6o+?Grvu4r5VPGQnZGW2rTswFO
z$c&9E3Rsq)BJU_RaLx5GIxq`@K3SNe6IgN(4zO~R;2)O|_G-oypNr*}bluMpgTpwI
z8s4D(+G$+MmN?*VJ(LIPQ7xZ}26N*jkoNn4vaJ(h*>epRi)&$@<`iDVP8{a?4CLFy
z%MPj?rd@x2(iW}+z!Pw8jig^>bfNXRZ}k8g
zq@fSqiIQl(4w4N*xZZ7-6`XUm{FoGKl{cu`i6)3Djw7MrL{V7m>xaWonAE{@X%X#A
zlGMbb5T!=oP6lZf!LT!$b2+$Sl>3d#?^#CB=kXdDV4&uR*$dXQaFSa{tVzcbT>upq
zfFg>A@+kOIp>}LfZZnQF#Rv5ye*)l3c-$05$pL%A%_QOfX$2hvS
z4b1T`43HytOmP8zt}RSCc32@|;3u&Fov|S=ecNU6U|HIf$a1{JH(G4Tf|&4AC5*uZ
z^B{!{U(}A3nf(1iS^(wC(dQcg-iSD(zR;|P@m<$C4{5pCP5G;jWSELPXU2MOpD_VG
zt0FK6-?U!thP|YnO_-o6@hKZ%uybF>FlpzL4{ZGW1}|o?J*GT-op_OZK6~P*z<~-r
zGaxb45*Cp#p>I_jOnz2+rXUeyT`#y#_drQgKewM*$uIuXJ7&e^Hd6S|#5`S|L~Qxf
zz-ljk;6}PtI#%3Lk~J=s2-TWtPAH;Wi(%1t*R_bH;35647I6N#!T}t2Jr~=wHVoIo
zb_P^NZ{Uxdj@}v%x~_*^pu8Qkmze(PZlq{E)s+=f$79^b4g^f-kK|-xFIH|S@?z}M
z-5L`C2pB-GE!t#wQu>y?zu0VZrd17KAHU)9Y=Fn$kj^Z$u!DWtjw>3!lX*CBB{8%l
z1)Iy2X06aoSD)=5mpOHIo9oXU&fN@e$Ng{H+zF+4bDs3@oL2-fOb9RT+%Kb9v@6d~
zl~4N)J-S=+z0?vrPdxWb{f{!1`mA0YrO)=|aM_i)cTDZCgitrMwpoZ;C`h7qM)(&H
zAeS`hFAmVJw%g3vYvfYu!JQev>TBmcDwq7$-Ij=P@Ym<;B{$~3W93Cw$N3Z&MO2ss
z%yyF^wk?XTyr3M98haI)nCZhF{Thm;$G!Cwsm@`Dv|#Utm}Z}B*Qt-gd~Rx|Gi~;h
zli4R}Z9X}~($`ihGqq>(@_4AE=6Aq@qH;9H2flDs6zc(szPWZs#|bWC*ehOt_t~>p
z5vf4%zY+P(w(w*(CC^_ks79|T)Ycp(zW#xmSKjOvt`oXFAt~xuQ}N}aK%Y(vQRj|S
zw(N_6MLv>Z0r_I@lo1uv!jgtt^q(Hc75v8Y=?IGkDMO*pj)YF6GR9bu)bXz=O6gvW
zNMb0^%AIv9;{K{2|j6@;Vn%cl*4gPDdqwj_Tuz*kD9NWR@e1~
z^OH_)&9)?cX)Y8sDxh0kr~5AdMW$`t&1^F@+C-%@8vDh05Ak&gLuxZ#|9*~~3Po?b
zcUsZ$R(#&)^qT~uP=D+CrIJz(K?;j5t=$Yhz^nl0h9V`_
z<60)E6RYs8^LdO_nLaXRng!qmhFy6+iCsQpFqJ0iZ&N+M@rrf$W#7cBg1RDoY!R~p
zQ}18hDMidP2G7)7dEK<1##)!Z)|OeyK=gG_CStH0I5smFJVM+iukLAfH#G?WQ1cc9
z$XE1Aoqn9u8O4yZo>dh>Kb))|_*(FN+)903Xm!IdI6(micIbxmthq7kg-3&}#^CdC
z*fQ^TQ|!GHd!;@%nh}UFnH<(Jn!>7Gg|*c!L0l|G3b`%~5#={!e6e0C2>PJ&t0%8H
z4?R;<0&{Fo2?*f&b%v2IO`icLuL;4$a&Hnm%>?RSy1imwH7w9nqp`D%l~bS(f?x=3KhfDVTvcZc$sdz8
zYZvr)xK$%SF1kK;p^Zeb9>2$5m3>87KcK@X{%KN+Y?_m6KZ+?1aZ1>z?iV?5G#Ed6
ztOYcsV3K(2BKylyodlsTr8|4cvQa9KS+ZY)ZVKJd5o0T62jlNTwE98+ZT_YV$wi&f;I1X(oZ
z&2bN}^nX^jb^n5gGQgiojc1AK!=cPj3~19Z=7&$ED=o&5nFPG_6$*7C>GidlOsP1$
zGI_z&jn827F8qVRcS-YWte*}OM-OboW%)UeokZNK{bJD=-s8=uGUv|*{So*
zf;=0KR}DVNt39%fM_ge9e6NlLH)D3LsoW@)zqn`)Mp&BLz+}%04O~PB0r=QyWdV|}
zt|t8>tL7P!jH+EN(texg8Y;tU@Yp7v6&qNC#m|2
zSTywE%*n+DrR3xfgGu%fX{|4cS7tfrM%J!O;&b$%qH1WCrKIZxE4G%C4RRLlMX&nI
z7uBWtYXRQF%^&ej)IymC?kNFO=T?OIR8qkK6mcWB{=7ye$;}E~{G2IEe!^m3Z&cuePR5UMj|lTxgWm;)3`f
zq%6;dQBc*VCRM_j%?#f&UUmq!DAO4qZ%FhiyfIu~fExws7
zI?vKxKLNCz>ntQ%SdL(+-fEu?CF2Ba8w_o3&|oZ>@hafG`;8gSsZfsn(1j4FNA74Hb#4v@Wup_P7yG+b(;aKRnL$p>gLy@s^zK0@meh2B>TO=3
zZtbcC0UroZmf{1C9>T7I!N_-EO6!S?oiN^7Ov17xXZe#+vxY1iEFoBgyO!%dwpm>4
zW}b%W#PH5nNf8DR>4;MH!ik?Nj-o!?+Cu$pgvlCdi$BR0d2#b7Yob@^V*Q=s9cD|V
zz!^cTBaZZl3mJMaZ605S(MR~|%x`55H9|7Zgt_@7?+ZwSsiD2-{t8}Ab0)peh6CkT
zC_K+_<62@{eCn->*H*QAyZs9QFP>ZTpP7nFIl{7mYK%z(QI9(E
zNaqZcBR)>X^0JEs!hTP?kev{*9jL^QHC+b
ziy7-`-GQS04{c>EP#Q2NF94sgDloxm2`KYJ*#BT;dkRxaEZ)pC$B1g$lN6sM9np5M
z5=?I2wA1jdG-tb}we4Mxk)D(3@d{O7`(aYm@vOQu>Tevfg$*9FgO*8XuC%c#(0Mo6
z!TTUd^o|?!Ts&sblMp?mmw3qNaxx|R@Ugjuo?q=p87M4;VC90J24{uQ1<+oJi7I6*
z2;}ZDkILq_dVTe6$(^p+b_A_)kHLK0DNSRwAt5J?nACBP
zIDnT$a1MXEVWc0Pp~LFQi__BxaXCFh>y;uv0aaW(Gf=u3dU0YkQMB+iF^w0=8YjB-
zaL2;Nj*xl3m@;G@HCSW;b^wo)niV_A=%J^%4aYZSWF*e#M!wTfqjL{J&@;Em`^@dfvO0s^=bpxW=v$Ah
za-{|7r%kQU`Zbg&+5yw!9XU40{Ya~7>IH17C7}apHdXweNF8QKR(BzJ)5>L2PfmVz
zGsbZ;{c7`frvU{JEM(!1!In)X-xo1&y@FZen)We;27ZG(OuUQ7OHbO%mYC1lBh!UL
z`+85fW|atJN+m2E>S0@|W3hrv0Imb)1O{`R=1wm$fRfAfc_FR^0f5xpnZ@p~Psqo%
z!4BsaGRM7KJ(|$r$0-l7EqHC|p&|_}_UZ#n9akOfhB1%m!(pJNEN*eQ&=3J!=k=L%
z_(v_7!w6(-;zIcy5^->T8xvNWQZsoH$!D^lF@Ql08(K0wrveWBUv{S3=SF~~2k7
zQ4=70zvVZ#9XTjV?Lre)195$3qQ7yTUi
zhaO@O=q=K|=6CQ7O-83m%uLsBWVJM4ir@Q(S;_V{46rVf;H-N{U1{2F^@|XTFwejP
zbah>#W2N#L6y-G>7z2SUQ5H$ST0CbolyuYNW;!9D#FocAB1o)?tb4LFcS%?mt;qm`9wDiQAH`&-fz!T%wmFdNg_J!2m%HtBNS6%zDd@xwNk_2-agR^eA34
zK`g6P;KSN(-u*TFwh84p(Oy4D`EJJIRl*{BBrxT1072Y@ME8}`In5zzV^ECVwtGW-
ztf1V<+n$Ci)_A;w*gRx*afH-{rQZ+PJ8RNbpYdo=2fDKiH!2!DP(^P<`L)hDtd=J5?^?gIcS
z=CiIg7B-E-n*_S(SFoYum^lqS{~z6ltTIUmEhZLHqZBs+Y^}68e$i8T;HtKbCB=&l>A_*+uYpqhoDz`aAHhy`VA){eGOqSRzx*7X$$4cWd
z#!hgjU^j-DQe?5m#nRqw99O-zoutz+1)|G>K#pcp8)+dG-p^|AVd>9IKMvqAUd3CFH#-K=&Zr=~
zb6fNyQIEtorC}*kkR@;1no@>8bkOC2jO@+Bx?d~stbcO|@Nod9+qx3itVuF`^Bbfe
z|2qGrju}lV0WXXcHQ9-n$0HN>sP1Lij6oX>paP4ZN@LuJ6Y>4(>pIy=(vM6$!Cj{C
zNg>y^SeeN=&L3)nwj7g3!(b?5v)E9?V
z4~8pnnsXV-2P}o~+PL{!HT5>0nW4*4M<-8c3)JA{u(~u<86XfAwhh&MC2YaFSOKGQ
zP*<(Rp&lB?DY7m+`9asp04!QCK)gtzuKt!k)n?aHO!z7fB*#*s9r>jX|Kn8
z=KxSh!3&2~D?WD4a{k39z3WLU6SC>;WIP=xa+B2B&Wu*!f{El4mQ(xuc@|_{^005w
zs_W(GBK
zlf1~e(y-NiN=YDYr+Sd8R#fp$(D3bpg=Qt!_lkzZXs!5yRGo5+XykfkF2oxY>C_{+b=9GKMeg=WKd(
zcn20S8Vx5cK)$v+LngGTk7&t+!omkl$~ceiESp0*(wA6r;YVfH=9)
zz$-)ZakkHCXn@LjmVD0U$b&I)^|W)v5)zP1=*O4qL$-w6El9lmgzs1mC7l55*x5
z$H>h@n`fY*#yMi~mBrB_kH "${keyfile}"
+ chmod 600 "${keyfile}"
+ export CODEX_ETH_PRIVATE_KEY="${keyfile}"
+ echo "Private key set"
+ fi
+done
+
+# Set arguments
+if [[ "${MODE}" == "codex-node-with-marketplace" ]]; then
+ set -- "$@" persistence
+ [[ -z "${CODEX_MARKETPLACE_ADDRESS}" ]] && unset CODEX_MARKETPLACE_ADDRESS
+elif [[ "${MODE}" == "codex-storage-node" ]]; then
+ set -- "$@" persistence prover
+else
+ unset CODEX_ETH_PROVIDER
+fi
+
+# Set network parameters
+if [[ "${NETWORK}" == "testnet" ]]; then
+ bootstrap_nodes=(
+ --bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P
+ --bootstrap-node=spr:CiUIAhIhAyUvcPkKoGE7-gh84RmKIPHJPdsX5Ugm_IHVJgF-Mmu_EgIDARo8CicAJQgCEiEDJS9w-QqgYTv6CHzhGYog8ck92xflSCb8gdUmAX4ya78QoemesAYaCwoJBES39Q2RAnVOKkYwRAIgLi3rouyaZFS_Uilx8k99ySdQCP1tsmLR21tDb9p8LcgCIG30o5YnEooQ1n6tgm9fCT7s53k6XlxyeSkD_uIO9mb3
+ --bootstrap-node=spr:CiUIAhIhA6_j28xa--PvvOUxH10wKEm9feXEKJIK3Z9JQ5xXgSD9EgIDARo8CicAJQgCEiEDr-PbzFr74--85TEfXTAoSb195cQokgrdn0lDnFeBIP0QzOGesAYaCwoJBK6Kf1-RAnVEKkcwRQIhAPUH5nQrqG4OW86JQWphdSdnPA98ErQ0hL9OZH9a4e5kAiBBZmUl9KnhSOiDgU3_hvjXrXZXoMxhGuZ92_rk30sNDA
+ --bootstrap-node=spr:CiUIAhIhA7E4DEMer8nUOIUSaNPA4z6x0n9Xaknd28Cfw9S2-cCeEgIDARo8CicAJQgCEiEDsTgMQx6vydQ4hRJo08DjPrHSf1dqSd3bwJ_D1Lb5wJ4Qt_CesAYaCwoJBEDhWZORAnVYKkYwRAIgFNzhnftocLlVHJl1onuhbSUM7MysXPV6dawHAA0DZNsCIDRVu9gnPTH5UkcRXLtt7MLHCo4-DL-RCMyTcMxYBXL0
+ --bootstrap-node=spr:CiUIAhIhAzZn3JmJab46BNjadVnLNQKbhnN3eYxwqpteKYY32SbOEgIDARo8CicAJQgCEiEDNmfcmYlpvjoE2Np1Wcs1ApuGc3d5jHCqm14phjfZJs4QrvWesAYaCwoJBKpA-TaRAnViKkcwRQIhANuMmZDD2c25xzTbKSirEpkZYoxbq-FU_lpI0K0e4mIVAiBfQX4yR47h1LCnHznXgDs6xx5DLO5q3lUcicqUeaqGeg
+ --bootstrap-node=spr:CiUIAhIhAgybmRwboqDdUJjeZrzh43sn5mp8jt6ENIb08tLn4x01EgIDARo8CicAJQgCEiECDJuZHBuioN1QmN5mvOHjeyfmanyO3oQ0hvTy0ufjHTUQh4ifsAYaCwoJBI_0zSiRAnVsKkcwRQIhAJCb_z0E3RsnQrEePdJzMSQrmn_ooHv6mbw1DOh5IbVNAiBbBJrWR8eBV6ftzMd6ofa5khNA2h88OBhMqHCIzSjCeA
+ --bootstrap-node=spr:CiUIAhIhAntGLadpfuBCD9XXfiN_43-V3L5VWgFCXxg4a8uhDdnYEgIDARo8CicAJQgCEiECe0Ytp2l-4EIP1dd-I3_jf5XcvlVaAUJfGDhry6EN2dgQsIufsAYaCwoJBNEmoCiRAnV2KkYwRAIgXO3bzd5VF8jLZG8r7dcLJ_FnQBYp1BcxrOvovEa40acCIDhQ14eJRoPwJ6GKgqOkXdaFAsoszl-HIRzYcXKeb7D9
+ )
+fi
+
+# Update arguments
+set -- "$@" ${bootstrap_nodes[@]} ${EXTRA_OPTS}
+
+# Check if the endpoint is synced
+if [[ -n "${CODEX_ETH_PROVIDER}" ]]; then
+ echo "Marketplace is enabled - Check if the endpoint is synced"
+
+ timeout=3
+ interval=5
+ endpoint="${CODEX_ETH_PROVIDER}"
+ while true; do
+ block=$(curl -m $timeout -X POST \
+ -s "${CODEX_ETH_PROVIDER}" \
+ -H 'Content-Type: application/json' \
+ -d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":0}' | jq -r '.result.number')
+ block=$(("${block}"))
+ sync=$(curl -m $timeout -X POST \
+ -s "${CODEX_ETH_PROVIDER}" \
+ -H 'Content-Type: application/json' \
+ -w %{time_total} \
+ -d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}')
+ sync=$(echo $sync | tr '\n' ' ')
+ if [[ $sync == *false* ]]; then
+ echo "$(date) - Endpoint ${endpoint} is Synced, go to run - block $block - $sync"
+ break
+ else
+ echo "$(date) - Endpoint ${endpoint} is Not synced, waiting - block $block - $sync"
+ fi
+ sleep $interval
+ done
+else
+ echo "Marketplace is disabled - Skip endpoint sync check"
+fi
+
+# Circuit downloader
+# cirdl [circuitPath] [rpcEndpoint] [marketplaceAddress]
+if [[ "$@" == *"prover"* ]]; then
+ echo "Prover is enabled - Run Circuit downloader"
+
+ # Set variables required by cirdl from command line arguments when passed
+ for arg in data-dir circuit-dir eth-provider marketplace-address; do
+ arg_value=$(grep -o "${arg}=[^ ,]\+" <<< $@ | awk -F '=' '{print $2}')
+ if [[ -n "${arg_value}" ]]; then
+ var_name=$(tr '[:lower:]' '[:upper:]' <<< "CODEX_${arg//-/_}")
+ export "${var_name}"="${arg_value}"
+ fi
+ done
+
+ # Set circuit dir from CODEX_CIRCUIT_DIR variables if set
+ if [[ -z "${CODEX_CIRCUIT_DIR}" ]]; then
+ export CODEX_CIRCUIT_DIR="${CODEX_DATA_DIR}/circuits"
+ fi
+
+ # Download circuit
+ mkdir -p "${CODEX_CIRCUIT_DIR}"
+ chmod 700 "${CODEX_CIRCUIT_DIR}"
+ download="cirdl ${CODEX_CIRCUIT_DIR} ${CODEX_ETH_PROVIDER} ${CODEX_MARKETPLACE_ADDRESS}"
+ echo "${download}"
+ eval "${download}"
+ [[ $? -ne 0 ]] && { echo "Failed to download circuit files"; exit 1; }
+fi
+
+# Show
+echo "Codex parameters:"
+vars=$(env | grep CODEX_)
+echo -e "${vars//CODEX_/ - CODEX_}"
+echo -e " $@"
+
+# Run
+echo "Run Codex node..."
+exec "$@"
diff --git a/dappnode_package.json b/dappnode_package.json
new file mode 100644
index 0000000..14b5c42
--- /dev/null
+++ b/dappnode_package.json
@@ -0,0 +1,34 @@
+{
+ "upstream": [
+ {
+ "repo": "codex-storage/nim-codex",
+ "version": "0.1.9",
+ "arg": "UPSTREAM_VERSION_CODEX_NODE"
+ },
+ {
+ "repo": "codexstorage/codex-marketplace-ui",
+ "version": "0.0.13",
+ "arg": "UPSTREAM_VERSION_CODEX_APP"
+ },
+ {
+ "repo": "ethereum/client-go",
+ "version": "v1.13.15",
+ "arg": "UPSTREAM_VERSION_GETH"
+ }
+ ],
+ "shortDescription": "Codex Storage - Decentralized Durability Engine",
+ "description": "Codex is a durable, decentralised storage protocol designed to safeguard the world's most valuable information. Join the testnet to help secure a resilient digital future.",
+ "type": "service",
+ "mainService": "codex-app",
+ "architectures": ["linux/amd64", "linux/arm64"],
+ "author": "Codex DevOps",
+ "categories": ["Storage"],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/codex-storage/DAppNodePackage-codex.git"
+ },
+ "bugs": {
+ "url": "https://github.com/codex-storage/nim-codex/issues"
+ },
+ "license": "Apache-2.0, MIT"
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..13716ff
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,59 @@
+version: "3.8"
+services:
+ codex-node:
+ build:
+ context: ./codex-node
+ dockerfile: Dockerfile
+ args:
+ UPSTREAM_VERSION_CODEX_NODE: 0.1.9
+ image: codex-node.public.dappnode.eth:0.1.0
+ restart: unless-stopped
+ environment:
+ MODE: codex-node-with-marketplace
+ NETWORK: testnet
+ IP_MODE: auto
+ EXTRA_OPTS: ""
+ CODEX_DATA_DIR: /data
+ CODEX_NAT: ""
+ # CODEX_METRICS: "false"
+ # CODEX_METRICS_ADDRESS: 0.0.0.0
+ # CODEX_METRICS_PORT: 8008
+ CODEX_API_BINDADDR: 0.0.0.0
+ CODEX_API_PORT: 8080
+ CODEX_LISTEN_ADDRS: /ip4/0.0.0.0/tcp/8070
+ CODEX_DISC_PORT: 8090
+ CODEX_LOG_LEVEL: info
+ CODEX_STORAGE_QUOTA: 8gb
+ CODEX_BLOCK_TTL: 24h
+ CODEX_API_CORS_ORIGIN: "*"
+ CODEX_MARKETPLACE_ADDRESS: ""
+ CODEX_ETH_PROVIDER: https://rpc.testnet.codex.storage
+ ETH_PRIVATE_KEY: ""
+ NAT_PUBLIC_IP_AUTO: https://ip.codex.storage
+ ports:
+ - 8070:8070/tcp # P2P transport
+ - 8090:8090/udp # P2P discovery
+ volumes:
+ - codex-node-data:/data
+ logging:
+ driver: json-file
+ options:
+ max-size: 100m
+ max-file: 5
+
+ codex-app:
+ build:
+ context: ./codex-app
+ dockerfile: Dockerfile
+ args:
+ UPSTREAM_VERSION_CODEX_APP: 0.0.13
+ image: codex-app.public.dappnode.eth:0.1.0
+ restart: unless-stopped
+ logging:
+ driver: json-file
+ options:
+ max-size: 100m
+ max-file: 5
+
+volumes:
+ codex-node-data: {}
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..288ce2c
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,210 @@
+# Dappnode package development
+
+ 1. [Description](#description)
+ 2. [Server](#server)
+ 3. [Install](#install)
+ 4. [Connect](#connect)
+ 5. [Considerations](#considerations)
+ 6. [Development](#development)
+ 7. [Publish](#publish)
+ 8. [Limitations](#limitations)
+ 9. [Known issues](#known-issues)
+
+
+## Description
+
+ Dappnode packages supports two types of [configuration](https://docs.dappnode.io/docs/dev/package-development/overview)
+ - [Single Configuration](https://docs.dappnode.io/docs/dev/package-development/single-configuration) - Used to generate a single package, tailored for a specific configuration.
+ - [Multi-Configuration](https://docs.dappnode.io/docs/dev/package-development/multi-configuration) - Used to generate multiple packages with varying configurations, such as different networks or client setups.
+
+ Provided guide is focused on Multi-Configuration variant because it provides more flexibility.
+
+ The easiest way to develop the package is to use a VM and in that guide we will use Hetzner Cloud.
+
+ - [Docs](https://docs.dappnode.io)
+ - [Package Development](https://docs.dappnode.io/docs/dev/package-development/overview)
+
+
+## Server
+
+ 1. Run an Ubuntu VM on Hetzner - `8vCPU/16GB RAM` (`cx42/cpx41`)
+ 2. Create firewall rules based on the [Cloud Providers / AWS](https://docs.dappnode.io/docs/user/dappnode-cloud/providers/aws/set-up-instance/) guide
+
+ | Protocol | Port | Service | Source | Comment |
+ | -------- | ------------ | ------------- | ----------- | ------------------------------------ |
+ | `TCP` | `22` | `SSH` | `0.0.0.0/0` | |
+ | `TCP` | `80` | `HTTP` | `0.0.0.0/0` | Required for services exposing only? |
+ | `TCP` | `443` | `HTTP` | `0.0.0.0/0` | Required for services exposing only? |
+ | `UDP` | `51820` | `Wireguard` | `0.0.0.0/0` | |
+ | `TCP` | `1024-65535` | `General TCP` | `0.0.0.0/0` | |
+ | `UDP` | `1024-65535` | `General UDP` | `0.0.0.0/0` | |
+
+
+## Install
+
+ 1. We can install Dappnode on Ubuntu VM using [Script installation](https://docs.dappnode.io/docs/user/install/script/)
+ ```shell
+ # Prerequisites
+ sudo wget -O - https://prerequisites.dappnode.io | sudo bash
+
+ # Dappnode
+ sudo wget -O - https://installer.dappnode.io | sudo bash
+
+ # Restart
+ sudo reboot
+ ```
+
+
+## Connect
+
+ > [!NOTE]
+ > Please wait for 1-3 minutes after node was started.
+
+ 1. Check Dappnode status
+ ```shell
+ dappnode_status
+ ```
+
+ 2. Run Dappnode if not started
+ ```shell
+ dappnode_start
+ ```
+
+ 3. Get wireguard credentials and connect to the Dappnode instance - [WireGuard Access to Dappnode](https://docs.dappnode.io/docs/user/access-your-dappnode/vpn/wireguard/)
+ ```shell
+ dappnode_wireguard
+ ```
+
+ 4. Open http://my.dappnode in the browser.
+
+
+## Considerations
+
+ 1. Users might run a lot of different packages, which can use some standard ports like `30303`, this is why we used different default ports
+ ```shell
+ 30303 --> 40303
+ ```
+ Just add 10000 to every port.
+
+
+## Development
+
+ 1. Clone GitHub repository on local machine
+ ```shell
+ git clone https://github.com/codex-storage/DAppNodePackage-codex
+
+ # For new package run 'init'
+ # npx @dappnode/dappnodesdk init --use-variants --dir DAppNodePackage-codex
+ ```
+ Add you changes to the code.
+
+ 2. Copy package files to Dappnode server
+ ```shell
+ local_dir="DAppNodePackage-codex"
+ remote_dir="/opt/DAppNodePackage-codex"
+ host="root@"
+
+ rsync -avze ssh --rsync-path='sudo rsync --mkpath' "${local_dir}/" "${host}:${remote_dir}/" --delete
+ ```
+
+ 3. Install [Node.js](Node.js) on Dappnode server using [nvm](https://github.com/nvm-sh/nvm)
+ ```shell
+ # nvm
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
+
+ # Load
+ export NVM_DIR="$HOME/.nvm"
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
+
+ # Node 20
+ nvm install 20
+
+ # Check
+ node --version
+ # v20.18.2
+ ```
+
+ 4. Install [DAppNodeSDK](https://github.com/dappnode/DAppNodeSDK) on remote Dappnode
+ ```shell
+ # Install
+ npx -y @dappnode/dappnodesdk
+
+ # Help
+ npx @dappnode/dappnodesdk --help
+ ```
+
+ 5. Get Dappnode IPFS IP: `Packages` --> `System packages` --> `Ipfs` --> `Network` --> `Container IP`
+
+ 6. Build the package
+ ```shell
+ # Code directory - multi-arch builds failed with --dir argument
+ cd /opt/DAppNodePackage-codex
+
+ # Use Ipfs node IP
+ npx @dappnode/dappnodesdk build --all-variants --provider=http://172.33.0.2:5001
+ ```
+ ```
+ Dappnode Package (codex.public.dappnode.eth) built and uploaded
+ DNP name : codex.public.dappnode.eth
+ Release hash : /ipfs/QmR7HVCpyWyDLGswF5Z1FXebrr3XjbWZeYQV2bhq5e4v1Q
+ http://my.dappnode/installer/public/%2Fipfs%2FQmR7HVCpyWyDLGswF5Z1FXebrr3XjbWZeYQV2bhq5e4v1Q
+ ```
+
+ 7. Install the package via DAppStore and using IPFS CID from previous step and check `Bypass only signed safe restriction`
+
+
+## Publish
+
+ - [Package Publishing](https://docs.dappnode.io/docs/dev/package-publishing/publish-packages-clients)
+
+
+## Limitations
+
+ 1. Dappnode packages are built on top of the [Docker Compose](https://docs.docker.com/compose/) which has limited configuration flexibility and DAppNodeSDK does not provide any workarounds.
+ 2. Docker Compose base imply the following limitations
+ - Variables
+ - If we need to pass an optional environment variable, it needs to be defined in Compose file with some default value and it anyway will be passed to the container
+ - If that optional variable can't accept a blank value, we should undefine it conditionally in the Docker entrypoint
+ - Ports
+ - If we need to define an optional port forwarding, it needs to be defined in Compose file with some default values and it anyway will be active and take the port on the node
+ - There is no way to configure "really optional" port forwarding
+ - A workaround would be use a separate package variant, but it is to big overhead
+ 3. We can't have a relation between variable and port forwarding, to setup same value using a single field. User have to fill separately two fields with the same value.
+ 4. Multi-Configuration package does not provide a real flexibility, it just generate multiple separate packages and it doesn't work like a single package with multiple options during the installation.
+ 5. [Using profiles with Compose](https://docs.docker.com/compose/how-tos/profiles/) is not supported.
+ 6. There is no way to setup a custom service name during package installation and it is predefined in the main `dappnode_package.json`
+ - We can set an alias like `codex.public.dappnode --> codex-app.codex.public.dappnode`
+ - That can be done for a single service in the package
+ 7. Is there a way to adjust container port for [Published ports](https://docs.docker.com/engine/network/#published-ports) or we can configure just host port?
+ 8. File [`setup-wizard.yml`]() is not supported in [Multi-Config Package Development]() which is very confusing. And same issue is with the `getting-started.md`.
+ 9. There is no way to setup custom service names for multiple services and they all namespaced under the `package name`
+ ```shell
+ # Public packages
+ geth.codex.public.dappnode
+ codex-app.codex.public.dappnode
+ codex-node.codex.public.dappnode
+ ```
+ 1. When we have Multi-Configuration package, we should define different package name for each variant, which imply different namespaces for services names and that looks not so nice, for example
+ ```shell
+ # Package codex
+ codex.public.dappnode --> codex-app.codex.public.dappnode
+ codex-app.codex.public.dappnode
+ codex-node.codex.public.dappnode
+
+ # Package codex-local-geth
+ codex-local-geth.public.dappnode --> codex-app.codex-local-geth.public.dappnode
+ codex-app.codex-local-geth.public.dappnode
+ codex-node.codex-local-geth.public.dappnode
+ geth.codex-local-geth.public.dappnode
+ ```
+ If we would like to have separate packages, which would permit to use same handy URL like `codex.public.dappnode` for main service, and other services under that namespace, it would be required to have separate repositories(package folders) with the same package name. It can be a cosmetic point, but it highlights a limitation we have.
+
+
+## Known issues
+
+ 1. Latest [Node.js LTS release 22](https://nodejs.org/en/about/previous-releases), is not supported and we should use version 20.
+ 2. During local package build it is uploaded to the local IPFS node, but in the Dappnode UI package avatar is loaded from the https://gateway.ipfs.dappnode.io, so most probably it will not be shown and it is not so clear what is the issue. Maybe something is wrong with avatar or something else? We can use default avatar, which is known by Dappnode IPFS gateway.
+ 3. File `getting-started.md` is not specified in the official documentation, but it exists and is very usefully.
+ 4. Dappnodesdk does not support `compose.yaml` file, [which is default and preferred](https://docs.docker.com/compose/intro/compose-application-model/).
+ 5. Often time it can be more effective to [explorer existing packages](https://github.com/dappnode?q=DAppNodePackage&type=all&language=&sort=) configuration, than to use a documentation.
+ 6. During the package build, Docker warn that ["the attribute `version` is obsolete"](https://docs.docker.com/reference/compose-file/version-and-name/#version-top-level-element-obsolete), but dappnodesdk will fail if we remove it - that is very confusing.
diff --git a/geth/Dockerfile b/geth/Dockerfile
new file mode 100644
index 0000000..ce8cdd0
--- /dev/null
+++ b/geth/Dockerfile
@@ -0,0 +1,17 @@
+# Variables
+ARG UPSTREAM_VERSION_GETH
+ARG GENESIS_DIR=/
+ARG GENESIS_PREFIX=genesis
+
+# Create
+FROM ethereum/client-go:${UPSTREAM_VERSION_GETH}
+ARG GENESIS_DIR
+ARG GENESIS_PREFIX
+
+COPY --chmod=0755 ${GENESIS_PREFIX}-*.json ${GENESIS_DIR}
+COPY --chmod=0755 docker-entrypoint.sh /docker-entrypoint.sh
+
+ENV GENESIS_DIR=${GENESIS_DIR}
+ENV GENESIS_PREFIX=${GENESIS_PREFIX}
+
+ENTRYPOINT ["/docker-entrypoint.sh"]
diff --git a/geth/docker-entrypoint.sh b/geth/docker-entrypoint.sh
new file mode 100644
index 0000000..1b9b86c
--- /dev/null
+++ b/geth/docker-entrypoint.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Network
+if [ "${NETWORK}" = "testnet" ]; then
+ export GETH_BOOTNODES=enode://cff0c44c62ecd6e00d72131f336bb4e4968f2c1c1abeca7d4be2d35f818608b6d8688b6b65a18f1d57796eaca32fd9d08f15908a88afe18c1748997235ea6fe7@159.223.243.50:40010,enode://ea331eaa8c5150a45b793b3d7c17db138b09f7c9dd7d881a1e2e17a053e0d2600e0a8419899188a87e6b91928d14267949a7e6ec18bfe972f3a14c5c2fe9aecb@68.183.245.13:40030,enode://4a7303b8a72db91c7c80c8fb69df0ffb06370d7f5fe951bcdc19107a686ba61432dc5397d073571433e8fc1f8295127cabbcbfd9d8464b242b7ad0dcd35e67fc@174.138.127.95:40020,enode://36f25e91385206300d04b95a2f8df7d7a792db0a76bd68f897ec7749241b5fdb549a4eecfab4a03c36955d1242b0316b47548b87ad8291794ab6d3fecda3e85b@64.225.89.147:40040,enode://2e14e4a8092b67db76c90b0a02d97d88fc2bb9df0e85df1e0a96472cdfa06b83d970ea503a9bc569c4112c4c447dbd1e1f03cf68471668ba31920ac1d05f85e3@170.64.249.54:40050,enode://6eeb3b3af8bef5634b47b573a17477ea2c4129ab3964210afe3b93774ce57da832eb110f90fbfcfa5f7adf18e55faaf2393d2e94710882d09d0204a9d7bc6dd2@143.244.205.40:40060,enode://6ba0e8b5d968ca8eb2650dd984cdcf50acc01e4ea182350e990191aadd79897801b79455a1186060aa3818a6bc4496af07f0912f7af53995a5ddb1e53d6f31b5@209.38.160.40:40070
+fi
+
+# Show
+echo "Geth parameters:"
+vars=$(env | grep GETH_)
+echo -e "${vars//GETH_/ - GETH_}"
+echo -e " $@"
+
+# Genesis
+echo "Create Genesis block"
+if [ -d "${GETH_DATADIR}/geth/chaindata" ]; then
+ echo "Genesis block was already created"
+else
+ echo "Creating Genesis block"
+ echo "geth init --datadir ${GETH_DATADIR} ${GENESIS_DIR}${GENESIS_PREFIX}-${NETWORK}.json"
+ geth init --datadir "${GETH_DATADIR}" "${GENESIS_DIR}${GENESIS_PREFIX}-${NETWORK}.json"
+fi
+
+# Run
+echo "Run Geth node..."
+geth
diff --git a/geth/genesis-testnet.json b/geth/genesis-testnet.json
new file mode 100644
index 0000000..05c5e51
--- /dev/null
+++ b/geth/genesis-testnet.json
@@ -0,0 +1,34 @@
+{
+ "config": {
+ "chainId": 789987,
+ "homesteadBlock": 0,
+ "eip150Block": 0,
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "muirGlacierBlock": 0,
+ "berlinBlock": 0,
+ "londonBlock": 0,
+ "arrowGlacierBlock": 0,
+ "grayGlacierBlock": 0,
+ "clique": {
+ "period": 10,
+ "epoch": 30000
+ }
+ },
+ "difficulty": "1",
+ "gasLimit": "8000000",
+ "extradata": "0x00000000000000000000000000000000000000000000000000000000000000003a39904b71595608524274bfd8c20fcfd9e772360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "alloc": {
+ "0x3a39904b71595608524274bfd8c20fcfd9e77236": { "balance": "100000000000000000000000000" },
+ "0x28840F2a7869E1dBe1Ef90Db08Bf50c74614c37e": { "balance": "100000000000000000000000000" },
+ "0x1fa74c3DD6050799BA74D4363D6036E22FAF05F6": { "balance": "100000000000000000000000000" },
+ "0x7f60982Ad4eF64FC42455Ba666fcFc8DBdDf2F1D": { "balance": "100000000000000000000000000" },
+ "0x7558f20595678c78961a61bE6F9BF3615C1b1C60": { "balance": "100000000000000000000000000" },
+ "0x4Fed456c1b4F1F4cA085c18475feE2E3cdE056f8": { "balance": "100000000000000000000000000" },
+ "0xa1671207A037a1133018eaaCb0DA51b2FE789d35": { "balance": "100000000000000000000000000" }
+ }
+}
diff --git a/getting-started.md b/getting-started.md
new file mode 100644
index 0000000..f0f6034
--- /dev/null
+++ b/getting-started.md
@@ -0,0 +1,28 @@
+## Welcome to your Codex Node
+
+Thanks for being a part of the Codex decentralized storage network.
+
+##### Codex package
+
+ * Codex App: [codex.public.dappnode](http://codex.public.dappnode)
+ * Codex Node API: `http://codex-node.codex.public.dappnode:8080`
+
+----
+
+##### Codex with local Geth package
+
+ After installation, please wait some time, until Geth will sync with the network. It might take up to 30 minutes.
+
+ During that time, Codex Node API is not available and you can't connect to it using Codex App.
+
+ * Codex App: [codex-local-geth.public.dappnode](http://codex-local-geth.public.dappnode)
+ * Codex Node API: `http://codex-node.codex-local-geth.public.dappnode:8080`
+ * Geth HTTP RPC: `http://geth.codex-local-geth.public.dappnode:8545`
+
+----
+
+#### Next steps
+
+ * Check the [documentation](https://docs.codex.storage)
+ * Want to stay up-date, or looking for further assistance? Try our [discord-server](https://discord.gg/codex-storage).
+ * Ready to explore Codex functionality? Please [Join Codex Testnet](https://docs.codex.storage/networks/testnet).
diff --git a/package_variants/codex-local-geth/dappnode_package.json b/package_variants/codex-local-geth/dappnode_package.json
new file mode 100644
index 0000000..7f55a36
--- /dev/null
+++ b/package_variants/codex-local-geth/dappnode_package.json
@@ -0,0 +1,9 @@
+{
+ "name": "codex-local-geth.public.dappnode.eth",
+ "version": "0.1.0",
+ "links": {
+ "homepage": "https://codex.storage",
+ "ui": "http://codex-local-geth.public.dappnode",
+ "api": "http://codex-node.codex-local-geth.public.dappnode:8080/api/codex/v1"
+ }
+}
diff --git a/package_variants/codex-local-geth/docker-compose.yml b/package_variants/codex-local-geth/docker-compose.yml
new file mode 100644
index 0000000..4e1e71a
--- /dev/null
+++ b/package_variants/codex-local-geth/docker-compose.yml
@@ -0,0 +1,48 @@
+version: "3.8"
+services:
+ codex-node:
+ environment:
+ CODEX_ETH_PROVIDER: http://geth.codex-local-geth.public.dappnode:8545
+ depends_on:
+ - geth
+
+ geth:
+ build:
+ context: ./geth
+ dockerfile: Dockerfile
+ args:
+ UPSTREAM_VERSION_GETH: v1.13.15
+ image: geth.codex.public.dappnode.eth:0.1.0
+ restart: unless-stopped
+ environment:
+ NETWORK: testnet
+ GETH_DATADIR: /data
+ GETH_NETWORKID: 789987
+ GETH_SYNCMODE: snap
+ GETH_GCMODE: full
+ GETH_NAT: auto
+ GETH_PORT: 40303
+ GETH_DISCOVERY_PORT: 40303
+ GETH_VERBOSITY: 3
+ GETH_HTTP: true
+ GETH_HTTP_PORT: 8545
+ GETH_HTTP_ADDR: 0.0.0.0
+ GETH_HTTP_VHOSTS: "*"
+ GETH_HTTP_CORSDOMAIN: "*"
+ GETH_WS: true
+ GETH_WS_PORT: 8546
+ GETH_WS_ADDR: 0.0.0.0
+ GETH_WS_ORIGINS: "*"
+ ports:
+ - 40303:40303/tcp # Network
+ - 40303:40303/udp # P2P discovery
+ volumes:
+ - geth-data:/data
+ logging:
+ driver: json-file
+ options:
+ max-size: 100m
+ max-file: 5
+
+volumes:
+ geth-data: {}
diff --git a/package_variants/codex/dappnode_package.json b/package_variants/codex/dappnode_package.json
new file mode 100644
index 0000000..166215e
--- /dev/null
+++ b/package_variants/codex/dappnode_package.json
@@ -0,0 +1,9 @@
+{
+ "name": "codex.public.dappnode.eth",
+ "version": "0.1.0",
+ "links": {
+ "homepage": "https://codex.storage",
+ "ui": "http://codex.public.dappnode",
+ "api": "http://codex-node.codex.public.dappnode:8080/api/codex/v1"
+ }
+}
diff --git a/package_variants/codex/docker-compose.yml b/package_variants/codex/docker-compose.yml
new file mode 100644
index 0000000..549512d
--- /dev/null
+++ b/package_variants/codex/docker-compose.yml
@@ -0,0 +1 @@
+version: "3.8"
diff --git a/setup-wizard.yml b/setup-wizard.yml
new file mode 100644
index 0000000..ba8569f
--- /dev/null
+++ b/setup-wizard.yml
@@ -0,0 +1,239 @@
+version: "2"
+fields:
+ - id: MODE
+ target:
+ type: environment
+ name: MODE
+ service: codex-node
+ title: Codex mode
+ description: >-
+ - **`Codex node`** - useful for local testing/development and basic/files sharing.
+
+ - **`Codex node with marketplace support`** (`Recommended`) - you can share files and buy the storage, this is the main mode and should be used by the end users.
+
+ - **`Codex storage node`** - should be used by storage providers or if you would like to sell your local storage.
+ enum:
+ - codex-node
+ - codex-node-with-marketplace
+ - codex-storage-node
+ required: true
+
+ - id: NETWORK
+ target:
+ type: environment
+ name: NETWORK
+ service: [codex-node, geth]
+ title: Codex network
+ description: >-
+ - **`Testnet`** - Codex Testnet network. Please check the [docs](https://docs.codex.storage/networks/networks) for available networks.
+ enum:
+ - testnet
+ required: true
+
+ - id: ETH_PRIVATE_KEY
+ target:
+ type: environment
+ name: ETH_PRIVATE_KEY
+ service: codex-node
+ title: ETH_PRIVATE_KEY
+ description: >-
+ Ethereum private key which will be saved to a file and passed as a value for `CODEX_ETH_PRIVATE_KEY` variable.
+ secret: true
+ pattern: "^0x[a-fA-F0-9]{64}$"
+ patternErrorMessage: Must be a valid private key (0x1fd16a...)
+ required: true
+ if: { "MODE": { "enum": ["codex-node-with-marketplace", "codex-storage-node"] } }
+
+ - id: CODEX_ETH_PROVIDER
+ target:
+ type: environment
+ name: CODEX_ETH_PROVIDER
+ service: codex-node
+ title: CODEX_ETH_PROVIDER
+ description: >-
+ The URL of the JSON-RPC API of the Ethereum node - use [Public RPC endpoint](https://docs.codex.storage/networks/networks) or local Geth node RPC.
+ pattern: >-
+ ^(http(s|)\:\/\/|)((([a-zA-Z0-9-_]{1,}\.){1,})([a-zA-Z]{1}[a-zA-Z0-9-]{1,}))(:[0-9]{1,}|)(\/[a-zA-Z0-9_~#?\+\&\.\/-=%-]{1,}|)$
+ patternErrorMessage: Must be a valid URL
+ required: true
+ if: { "MODE": { "enum": ["codex-node-with-marketplace", "codex-storage-node"] } }
+
+ # We can't make 'required' conditional and need to define save variable two times
+ - id: CODEX_MARKETPLACE_ADDRESS-node-with-marketplace-support
+ target:
+ type: environment
+ name: CODEX_MARKETPLACE_ADDRESS
+ service: codex-node
+ title: CODEX_MARKETPLACE_ADDRESS
+ description: >-
+ Address of deployed Marketplace contract.
+
+ We should set this variable **only if we would like to override auto-detected address**. Please check the address for the network you are using in the [docs](https://docs.codex.storage/networks/networks).
+ pattern: "^0x[a-fA-F0-9]{40}$"
+ patternErrorMessage: Must be a valid Ethereum address
+ required: false
+ if: { "MODE": { "enum": ["codex-node-with-marketplace"] } }
+
+ - id: CODEX_MARKETPLACE_ADDRESS-storage-node
+ target:
+ type: environment
+ name: CODEX_MARKETPLACE_ADDRESS
+ service: codex-node
+ title: CODEX_MARKETPLACE_ADDRESS
+ description: >-
+ Address of deployed Marketplace contract.
+
+ We should set this variable because we are running in **Codex `storage node`** mode. Please check the address for the network you are using in the [docs](https://docs.codex.storage/networks/networks).
+ pattern: "^0x[a-fA-F0-9]{40}$"
+ patternErrorMessage: Must be a valid Ethereum address
+ required: true
+ if: { "MODE": { "enum": ["codex-storage-node"] } }
+
+ - id: IP_MODE
+ target:
+ type: environment
+ name: IP_MODE
+ service: codex-node
+ title: IP detection mode
+ description: >-
+ Set your Public IP address manually or use autodetection.
+ enum:
+ - auto
+ - manual
+ required: true
+
+ - id: CODEX_NAT
+ target:
+ type: environment
+ name: CODEX_NAT
+ service: codex-node
+ title: CODEX_NAT
+ description: >-
+ Public IP address to announce behind a NAT.
+ pattern: "^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0).){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)$"
+ patternErrorMessage: Must be a valid IP address
+ required: true
+ if: { "IP_MODE": { "enum": ["manual"] } }
+
+ - id: NAT_PUBLIC_IP_AUTO
+ target:
+ type: environment
+ name: NAT_PUBLIC_IP_AUTO
+ service: codex-node
+ title: NAT_PUBLIC_IP_AUTO
+ description: >-
+ IP Lookup Service URL to be used to Public IP address autodetect, default = [`https://ip.codex.storage`](https://ip.codex.storage).
+ pattern: >-
+ ^(http(s|)\:\/\/|)((([a-zA-Z0-9-_]{1,}\.){1,})([a-zA-Z]{1}[a-zA-Z0-9-]{1,}))(:[0-9]{1,}|)(\/[a-zA-Z0-9_~#?\+\&\.\/-=%-]{1,}|)$
+ patternErrorMessage: Must be a valid URL
+ required: true
+ if: { "IP_MODE": { "enum": ["auto"] } }
+
+ # - id: CODEX_METRICS
+ # target:
+ # type: environment
+ # name: CODEX_METRICS
+ # service: codex-node
+ # title: CODEX_METRICS
+ # description: >-
+ # Enable the metrics server, default = `false`.
+ # enum:
+ # - "true"
+ # - "false"
+ # required: false
+
+ - id: CODEX_STORAGE_QUOTA
+ target:
+ type: environment
+ name: CODEX_STORAGE_QUOTA
+ service: codex-node
+ title: CODEX_STORAGE_QUOTA
+ description: >-
+ The size of the total storage quota dedicated to the node, default = `8gb`. Accept common units like kb, mb, gb, tb.
+ pattern: "^[0-9A-Za-z]+$"
+ patternErrorMessage: Must be a valid storage quota value - number with an optional unit
+ required: true
+
+ - id: CODEX_BLOCK_TTL
+ target:
+ type: environment
+ name: CODEX_BLOCK_TTL
+ service: codex-node
+ title: CODEX_BLOCK_TTL
+ description: >-
+ Default block timeout in seconds - 0 disables the ttl, default `24h`. Accept common units like s, h, d, w.
+ pattern: "^[0-9A-Za-z]+$"
+ patternErrorMessage: Must be a valid block timeout - number with an optional unit
+ required: true
+
+ - id: CODEX_LOG_LEVEL
+ target:
+ type: environment
+ name: CODEX_LOG_LEVEL
+ service: codex-node
+ title: CODEX_LOG_LEVEL
+ description: >-
+ Sets the log level, default = `info`. Available options: `info`, `warn`, `error`, `debug`, `trace`. Check the [docs](https://docs.codex.storage/learn/run#logging) for more details.
+ pattern: "^[A-Za-z-,;:]+$"
+ patternErrorMessage: Must be a valid log level
+ required: true
+
+ - id: CODEX_API_PORT
+ target:
+ type: environment
+ name: CODEX_API_PORT
+ service: codex-node
+ title: CODEX_API_PORT
+ description: >-
+ The REST API port, default = `8080`.
+ pattern: "^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0][0-9]{1,4})|([0-9]{1,4}))$"
+ patternErrorMessage: Must be a valid port number
+ required: false
+
+ - id: CODEX_DISC_PORT
+ target:
+ type: environment
+ name: CODEX_DISC_PORT
+ service: codex-node
+ title: CODEX_DISC_PORT
+ description: >-
+ Discovery (UDP) port, default = `8090`.
+ pattern: "^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0][0-9]{1,4})|([0-9]{1,4}))$"
+ patternErrorMessage: Must be a valid port number
+ required: false
+
+ - id: EXTRA_OPTS
+ target:
+ type: environment
+ name: EXTRA_OPTS
+ service: codex-node
+ title: EXTRA_OPTS
+ description: >-
+ Extra options to pass to the Codex node. Check [Codex CLI options](https://docs.codex.storage/learn/run#cli-options) for more details.
+ pattern: "^.*$"
+ patternErrorMessage: Must be a valid string
+ required: false
+
+ - id: portMapping_CODEX_LISTEN_ADDRS
+ target:
+ type: portMapping
+ containerPort: 8070/TCP
+ service: codex-node
+ title: Port mapping for Codex Node - CODEX_LISTEN_ADDRS
+ description: >-
+ Transport (TCP) port, default = `8070`.
+ pattern: "^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0][0-9]{1,4})|([0-9]{1,4}))$"
+ patternErrorMessage: Must be a valid port number
+ required: true
+
+ - id: portMapping_CODEX_DISC_PORT
+ target:
+ type: portMapping
+ containerPort: 8090/UDP
+ service: codex-node
+ title: Port mapping for Codex Node - CODEX_DISC_PORT
+ description: >-
+ Discovery (UDP) port, default = `8090`.
+ pattern: "^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0][0-9]{1,4})|([0-9]{1,4}))$"
+ patternErrorMessage: Must be a valid port number
+ required: true