# Example YAML of IPFS-based node-to-node image sharing with ipfs-cluster

apiVersion: v1
kind: ConfigMap
metadata:
  name: ipfs-cluster-conf
data:
  # `replication_factor_max` and `replication_factor_max`
  # https://cluster.ipfs.io/documentation/reference/configuration/
  cluster-replication-factor: "2"

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ipfs-bootstrap
spec:
  selector:
    matchLabels:
      app: ipfs-bootstrap
  template:
    metadata:
      labels:
        app: ipfs-bootstrap
    spec:
      initContainers:
        - name: configure-ipfs
          image: "ghcr.io/stargz-containers/ipfs/kubo:v0.16.0"
          command: ["sh", "/custom/configure-ipfs.sh"]
          env:
            - name: LIBP2P_FORCE_PNET
              value: "1"
            - name: IPFS_SWARM_KEY
              valueFrom:
                secretKeyRef:
                  name: secret-config
                  key: ipfs-swarm-key
          volumeMounts:
            - name: ipfs-storage
              mountPath: /data/ipfs
            - name: configure-script
              mountPath: /custom
      containers:
        - name: id
          image: "ghcr.io/stargz-containers/ipfs/kubo:v0.16.0"
          command: ["sh", "/custom/id-server.sh"]
          ports:
            - name: id
              protocol: TCP
              containerPort: 8000
          volumeMounts:
            - name: ipfs-storage
              mountPath: /data/ipfs
            - name: configure-script
              mountPath: /custom
        - name: ipfs
          image: "ghcr.io/stargz-containers/ipfs/kubo:v0.16.0"
          command: ["ipfs", "daemon"]
          env:
            - name: LIBP2P_FORCE_PNET
              value: "1"
          ports:
            - name: swarm
              protocol: TCP
              containerPort: 4001
          volumeMounts:
            - name: ipfs-storage
              mountPath: /data/ipfs
            - name: configure-script
              mountPath: /custom
          livenessProbe:
            tcpSocket:
              port: swarm
            initialDelaySeconds: 30
            timeoutSeconds: 5
            periodSeconds: 15
        - name: ipfs-cluster
          image: "ghcr.io/stargz-containers/ipfs/ipfs-cluster:1.0.4"
          command: ["sh", "/custom/cluster-entrypoint.sh"]
          env:
            - name: CLUSTER_REPLICATIONFACTORMIN
              valueFrom:
                configMapKeyRef:
                  name: ipfs-cluster-conf
                  key: cluster-replication-factor
            - name: CLUSTER_REPLICATIONFACTORMAX
              valueFrom:
                configMapKeyRef:
                  name: ipfs-cluster-conf
                  key: cluster-replication-factor
            - name: CLUSTER_BOOTSTRAP_PEER_ID
              valueFrom:
                configMapKeyRef:
                  name: env-config
                  key: cluster-bootstrap-peer-id
            - name: CLUSTER_BOOTSTRAP_PEER_PRIV_KEY
              valueFrom:
                secretKeyRef:
                  name: secret-config
                  key: cluster-bootstrap-peer-priv-key
            - name: CLUSTER_SECRET
              valueFrom:
                secretKeyRef:
                  name: secret-config
                  key: cluster-secret
          ports:
            - name: api-http
              containerPort: 9094
              protocol: TCP
            - name: proxy-http
              containerPort: 9095
              protocol: TCP
            - name: cluster-swarm
              containerPort: 9096
              protocol: TCP
          volumeMounts:
            - name: cluster-storage
              mountPath: /data/ipfs-cluster
            - name: configure-script
              mountPath: /custom
          livenessProbe:
            tcpSocket:
              port: cluster-swarm
            initialDelaySeconds: 5
            timeoutSeconds: 5
            periodSeconds: 10
      volumes:
        - name: configure-script
          configMap:
            name: ipfs-bootstrap-conf
        - name: ipfs-storage
          emptyDir: {}
        - name: cluster-storage
          emptyDir: {}

---

apiVersion: v1
kind: Service
metadata:
  name: ipfs-bootstrap
  labels:
    app: ipfs-bootstrap
spec:
  type: ClusterIP
  ports:
    - name: id
      targetPort: id
      port: 8000
    - name: swarm
      targetPort: swarm
      port: 4001
    - name: cluster-swarm
      targetPort: cluster-swarm
      port: 9096
  selector:
    app: ipfs-bootstrap

---

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: ipfs
spec:
  selector:
    matchLabels:
      app: ipfs
  template:
    metadata:
      labels:
        app: ipfs
    spec:
      initContainers:
        - name: configure-ipfs
          image: "ghcr.io/stargz-containers/ipfs/kubo:v0.16.0"
          command: ["sh", "/custom/configure-ipfs.sh"]
          env:
            - name: BOOTSTRAP_SVC_NAME
              value: "ipfs-bootstrap"
            - name: LIBP2P_FORCE_PNET
              value: "1"
            - name: IPFS_SWARM_KEY
              valueFrom:
                secretKeyRef:
                  name: secret-config
                  key: ipfs-swarm-key
          volumeMounts:
            - name: ipfs-storage
              mountPath: /data/ipfs
            - name: configure-script
              mountPath: /custom
      containers:
        - name: ipfs
          image: "ghcr.io/stargz-containers/ipfs/kubo:v0.16.0"
          command: ["ipfs", "daemon"]
          env:
            - name: LIBP2P_FORCE_PNET
              value: "1"
          ports:
            - name: swarm
              protocol: TCP
              containerPort: 4001
            - name: api
              protocol: TCP
              containerPort: 5001
              hostPort: 5001
          volumeMounts:
            - name: ipfs-storage
              mountPath: /data/ipfs
            - name: configure-script
              mountPath: /custom
          livenessProbe:
            tcpSocket:
              port: swarm
            initialDelaySeconds: 30
            timeoutSeconds: 5
            periodSeconds: 15
        - name: ipfs-cluster
          image: "ghcr.io/stargz-containers/ipfs/ipfs-cluster:1.0.4"
          command: ["sh", "/custom/cluster-entrypoint.sh"]
          env:
            - name: BOOTSTRAP_SVC_NAME
              value: "ipfs-bootstrap"
            - name: CLUSTER_REPLICATIONFACTORMIN
              valueFrom:
                configMapKeyRef:
                  name: ipfs-cluster-conf
                  key: cluster-replication-factor
            - name: CLUSTER_REPLICATIONFACTORMAX
              valueFrom:
                configMapKeyRef:
                  name: ipfs-cluster-conf
                  key: cluster-replication-factor
            - name: CLUSTER_BOOTSTRAP_PEER_ID
              valueFrom:
                configMapKeyRef:
                  name: env-config
                  key: cluster-bootstrap-peer-id
            - name: CLUSTER_SECRET
              valueFrom:
                secretKeyRef:
                  name: secret-config
                  key: cluster-secret
          ports:
            - name: api-http
              containerPort: 9094
              protocol: TCP
            - name: proxy-http
              containerPort: 9095
              protocol: TCP
              hostPort: 9095
            - name: cluster-swarm
              containerPort: 9096
              protocol: TCP
          volumeMounts:
            - name: cluster-storage
              mountPath: /data/ipfs-cluster
            - name: configure-script
              mountPath: /custom
          livenessProbe:
            tcpSocket:
              port: cluster-swarm
            initialDelaySeconds: 5
            timeoutSeconds: 5
            periodSeconds: 10
        - name: nerdctl-ipfs-registry
          image: "ghcr.io/stargz-containers/nerdctl-ipfs-registry:v0.23.0"
          command: ["sh", "/custom/nerdctl-ipfs-registry-entrypoint.sh"]
          env:
            - name: IPFS_PATH
              value: "/data/ipfs"
          ports:
            - containerPort: 5050
              hostPort: 5050
          volumeMounts:
            - name: ipfs-storage
              mountPath: /data/ipfs
            - name: configure-script
              mountPath: /custom
      volumes:
        - name: configure-script
          configMap:
            name: ipfs-peer-conf
        - name: ipfs-storage
          hostPath:
            path: /var/ipfs/
        - name: cluster-storage
          hostPath:
            path: /var/ipfs-cluster/

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: ipfs-peer-conf
data:
  nerdctl-ipfs-registry-entrypoint.sh: |
    #!/bin/sh
    set -eu

    if ! command -v curl &> /dev/null
    then
        echo "curl not found. installing..."
        apt-get update -y && apt-get install -y curl
    fi

    # wait for ipfs daemon
    ok=false
    for i in $(seq 100) ; do
        if curl -fsSL localhost:9095/api/v0/id >/dev/null 2>&1 ; then
            ok=true
            break
        fi
        echo "Fail(${i}). Retrying..."
        sleep 3
    done
    if [ "$ok" != "true" ] ; then
      echo "failed to detect ipfs api"
      exit 1
    fi

    exec /usr/local/bin/nerdctl ipfs registry serve --listen-registry 0.0.0.0:5050 --ipfs-address /ip4/127.0.0.1/tcp/9095 --read-retry-num 3 --read-timeout 1s

  cluster-entrypoint.sh: |
    #!/bin/sh
    set -eu -o pipefail

    # wait for bootstrap node running
    ok=false
    for i in $(seq 100) ; do
        if nc -z ${BOOTSTRAP_SVC_NAME} 9096 ; then
            ok=true
            break
        fi
        echo "Fail(${i}). Retrying..."
        sleep 3
    done
    if [ "$ok" != "true" ] ; then
      echo "failed to detect bootstrap node"
      exit 1
    fi

    mkdir -p /data/ipfs-cluster
    if ! [ -z "$(ls -A /data/ipfs-cluster)" ]; then
      echo "IPFS cluster already configured on this node; destroying the current repo and refreshing..."
      rm -rf /data/ipfs-cluster/*
    fi
    ipfs-cluster-service init
    cat /data/ipfs-cluster/service.json | sed 's|/ip4/127.0.0.1/tcp/9095|/ip4/0.0.0.0/tcp/9095|' > /tmp/tmp.json
    mv /tmp/tmp.json /data/ipfs-cluster/service.json

    BOOTSTRAP_ADDR=/dns4/${BOOTSTRAP_SVC_NAME}/tcp/9096/ipfs/${CLUSTER_BOOTSTRAP_PEER_ID}
    exec ipfs-cluster-service daemon --upgrade --bootstrap $BOOTSTRAP_ADDR --leave

  configure-ipfs.sh: |
    #!/bin/sh
    set -eu -o pipefail

    # wait for bootstrap node running
    ok=false
    for i in $(seq 100) ; do
        if nc -z ${BOOTSTRAP_SVC_NAME} 4001 ; then
            ok=true
            break
        fi
        echo "Fail(${i}). Retrying..."
        sleep 3
    done
    if [ "$ok" != "true" ] ; then
      echo "failed to detect bootstrap node"
      exit 1
    fi

    BOOTSTRAP_ID=$(wget -O - ${BOOTSTRAP_SVC_NAME}:8000/id)
    if [ "${BOOTSTRAP_ID}" == "" ] ; then
      echo "failed to get bootstrap peer id"
      exit 1
    fi
    if [ "${IPFS_SWARM_KEY}" == "" ] || [ "${LIBP2P_FORCE_PNET}" != "1" ] ; then
      echo "must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})"
      exit 1
    fi

    mkdir -p /data/ipfs
    if ! [ -z "$(ls -A /data/ipfs)" ]; then
      echo "IPFS already configured on this node; destroying the current repo and refreshing..."
      rm -rf /data/ipfs/*
    fi

    ipfs init --profile=server
    ipfs bootstrap rm --all
    ipfs bootstrap add /dns4/${BOOTSTRAP_SVC_NAME}/tcp/4001/ipfs/${BOOTSTRAP_ID}
    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001
    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080
    ipfs config Datastore.StorageMax 100GB
    ipfs config Addresses.NoAnnounce --json '[]'
    ipfs config Swarm.AddrFilters --json '[]'
    echo -n "${IPFS_SWARM_KEY}" > /data/ipfs/swarm.key

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: ipfs-bootstrap-conf
data:
  id-server.sh: |
    #!/bin/sh
    set -eu -o pipefail

    if [ ! -f /doc/id ]; then
      mkdir /doc
      ipfs config show | grep "PeerID" | sed -E 's/.*"PeerID": "([a-zA-Z0-9]*)".*/\1/' > /doc/id
    fi
    exec httpd -f -p 8000 -h /doc

  cluster-entrypoint.sh: |
    #!/bin/sh
    set -eu -o pipefail

    mkdir -p /data/ipfs-cluster
    if ! [ -z "$(ls -A /data/ipfs-cluster)" ]; then
      echo "IPFS cluster already configured on this node; destroying the current repo and refreshing..."
      rm -rf /data/ipfs-cluster/*
    fi
    ipfs-cluster-service init

    CLUSTER_ID=${CLUSTER_BOOTSTRAP_PEER_ID} \
    CLUSTER_PRIVATEKEY=${CLUSTER_BOOTSTRAP_PEER_PRIV_KEY} \
    exec ipfs-cluster-service daemon --upgrade

  configure-ipfs.sh: |
    #!/bin/sh
    set -eu -o pipefail

    if [ "${IPFS_SWARM_KEY}" == "" ] || [ "${LIBP2P_FORCE_PNET}" != "1" ] ; then
      echo "must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})"
    fi

    mkdir -p /data/ipfs
    if ! [ -z "$(ls -A /data/ipfs)" ]; then
      echo "IPFS already configured on this node; destroying the current repo and refreshing..."
      rm -rf /data/ipfs/*
    fi

    ipfs init --profile=server
    ipfs bootstrap rm --all
    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001
    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080
    ipfs config Addresses.NoAnnounce --json '[]'
    ipfs config Swarm.AddrFilters --json '[]'
    ipfs config Datastore.StorageMax 1GB
    echo -n "${IPFS_SWARM_KEY}" > /data/ipfs/swarm.key
