Zhenga

Private P2P network

Step-by-step manual

Every node in Zhenga network has independent configuration stored in a single file.

1. Configuration file

Configuration is a JSON file with arbitrary name in UTF-8 encoding.

2. Node IDs

Every network node is assigned a unique 8-byte ID. The easiest is to use random bytes for each node ID.
Random bytes can be taken at https://www.random.org/cgi-bin/randbyte?nbytes=8&format=h:

af 59 b9 c1 26 b1 3d ff

Removing all the whitespace gives node id in hexadecimal format:

{
  "id": "af59b9c126b13dff"
}

Known peer nodes are enumerated in the peers array. Each peer is a separate entity with id parameter:

{
  "id": "af59b9c126b13dff",
  "peers":
  [
    {
      "id": "df606b11d84b26fc"
    },
    {
      "id": "6ec39bb261b52d81"
    }
  ]
}

Peers can be given a optional name:

{
  "id": "af59b9c126b13dff",
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc"
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81"
    }
  ]
}

3. Secrets

Pre-shared secret is specified for each peer. It is used for encryption keys generation and should be kept in secret. Configuration file access must be limited accordingly.

{
  "id": "af59b9c126b13dff",
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc",
      "secret": "jackdaws love my big sphinx of quartz"
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81",
      "secret": "the quick brown fox jumps over the lazy dog"
    }
  ]
}

4. Listening

Zhenga network is not bound to any specific port and nodes can listen on any available port:

{
  "id": "af59b9c126b13dff",
  "listen": 42001,
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc",
      "secret": "jackdaws love my big sphinx of quartz"
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81",
      "secret": "the quick brown fox jumps over the lazy dog"
    }
  ]
}

5. Connections

Peer addresses are specified in the addrs array in the order of preference. host:port and ip:port formats are supported:

{
  "id": "af59b9c126b13dff",
  "listen": 42001,
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc",
      "secret": "jackdaws love my big sphinx of quartz",
      "addrs":
      [
        "localhost:42002",
        "bob:42002",
        "bob.mydomain.net:54321"
      ]
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81",
      "secret": "the quick brown fox jumps over the lazy dog",
      "addrs":
      [
        "localhost:42003"
      ]
    }
  ]
}

It doesn’t matter who connects and who listens. Any established connection will be used in both directions.
Cross-links are allowed. Zhenga will keep it’s operation to one of the successfull connections.
Port forwarding might be necessary to allow external connections.

Short hostnames may be beneficial when moving between local networks. Devices will keep connecting to each other provided they both are on the same network and hostnames are valid.

6. Shares

Array of shares is specified for each peer. Paths must be absolute. Names are optional. Default name is the last directory of the path. Names must be unique across the node:

{
  "id": "af59b9c126b13dff",
  "listen": 42001,
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc",
      "secret": "jackdaws love my big sphinx of quartz",
      "addrs":
      [
        "localhost:42002",
        "bob:42002",
        "bob.mydomain.net:54123"
      ],
      "shares":
      [
        {
          "name": "ForBob",
          "path": "path/to/for_bob"
        }
      ]
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81",
      "secret": "the quick brown fox jumps over the lazy dog",
      "addrs":
      [
        "localhost:42003"
      ],
      "shares":
      [
        {
          "name": "ForCarol",
          "path": "path/to/for_carol"
        }
      ]
    }
  ]
}

7. Templates

Templates is an array of named shares:

"templates":
[
]
"templates":
[
  {
    "name": "ForEveryone",
    "shares":
    [
      {
        "name": "One",
        "path": "path/to/first"
      }
    ]
  }
]
"templates":
[
  {
    "name": "ForEveryone",
    "shares":
    [
      {
        "name": "One",
        "path": "path/to/first"
      },
      {
        "name": "Two",
        "path": "path/to/second"
      }
    ]
  }
]
"templates":
[
  {
    "name": "ForEveryone",
    "shares":
    [
      {
        "name": "One",
        "path": "path/to/first"
      },
      {
        "name": "Two",
        "path": "path/to/second"
      }
    ]
  },
  {
    "name": "ForFriends",
    "shares":
    [
      {
        "name": "ForFriends",
        "path": "path/to/for_friends"
      }
    ]
  }
]

Templates can include other templates. ForFriends template includes all shares from ForEveryone template:

"templates":
[
  {
    "name": "ForEveryone",
    "shares":
    [
      {
        "name": "One",
        "path": "path/to/first"
      },
      {
        "name": "Two",
        "path": "path/to/second"
      }
    ]
  },
  {
    "name": "ForFriends",
    "shares":
    [
      "ForEveryone",
      {
        "name": "ForFriends",
        "path": "path/to/for_friends"
      }
    ]
  }
]

Adding templates to configuration allows peers to include them in shares:

{
  "id": "af59b9c126b13dff",
  "listen": 42001,
  "templates":
  [
    {
      "name": "ForEveryone",
      "shares":
      [
        {
          "name": "One",
          "path": "path/to/first"
        },
        {
          "name": "Two",
          "path": "path/to/second"
        }
      ]
    },
    {
      "name": "ForFriends",
      "shares":
      [
        "ForEveryone",
        {
          "name": "ForFriends",
          "path": "path/to/for_friends"
        }
      ]
    }
  ],
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc",
      "secret": "jackdaws love my big sphinx of quartz",
      "addrs":
      [
        "localhost:42002",
        "bob:42002",
        "bob.mydomain.net:54123"
      ],
      "shares":
      [
        "ForFriends",
        {
          "name": "ForBob",
          "path": "path/to/for_bob"
        }
      ]
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81",
      "secret": "the quick brown fox jumps over the lazy dog",
      "addrs":
      [
        "localhost:42003"
      ],
      "shares":
      [
        "ForFriends",
        {
          "name": "ForCarol",
          "path": "path/to/for_carol"
        }
      ]
    }
  ]
}

8. Write access

Writable shares are enumerated in the writes array of each peer:

{
  "id": "af59b9c126b13dff",
  "listen": 42001,
  "templates":
  [
    {
      "name": "ForEveryone",
      "shares":
      [
        {
          "name": "One",
          "path": "path/to/first"
        },
        {
          "name": "Two",
          "path": "path/to/second"
        }
      ]
    },
    {
      "name": "ForFriends",
      "shares":
      [
        "ForEveryone",
        {
          "name": "ForFriends",
          "path": "path/to/for_friends"
        }
      ]
    }
  ],
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc",
      "secret": "jackdaws love my big sphinx of quartz",
      "addrs":
      [
        "localhost:42002",
        "bob:42002",
        "bob.mydomain.net:54123"
      ],
      "shares":
      [
        "ForFriends",
        {
          "name": "ForBob",
          "path": "path/to/for_bob"
        }
      ],
      "writes":
      [
        "One",
        "ForBob"
      ]
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81",
      "secret": "the quick brown fox jumps over the lazy dog",
      "addrs":
      [
        "localhost:42003"
      ],
      "shares":
      [
        "ForFriends",
        {
          "name": "ForCarol",
          "path": "path/to/for_carol"
        }
      ],
      "writes":
      [
        "Two"
      ]
    }
  ]
}

Shares and templates can be made writable directly:

{
  "id": "af59b9c126b13dff",
  "listen": 42001,
  "templates":
  [
    {
      "name": "ForEveryone",
      "shares":
      [
        {
          "name": "One",
          "path": "path/to/first"
        },
        {
          "name": "Two",
          "path": "path/to/second"
        }
      ]
    },
    {
      "name": "ForFriends",
      "shares":
      [
        "ForEveryone",
        {
          "name": "ForFriends",
          "path": "path/to/for_friends",
          "write": true
        }
      ]
    }
  ],
  "peers":
  [
    {
      "name": "Bob",
      "id": "df606b11d84b26fc",
      "secret": "jackdaws love my big sphinx of quartz",
      "addrs":
      [
        "localhost:42002",
        "bob:42002",
        "bob.mydomain.net:54123"
      ],
      "shares":
      [
        "ForFriends",
        {
          "name": "ForBob",
          "path": "path/to/for_bob"
        }
      ],
      "writes":
      [
        "One",
        "ForBob"
      ]
    },
    {
      "name": "Carol",
      "id": "6ec39bb261b52d81",
      "secret": "the quick brown fox jumps over the lazy dog",
      "addrs":
      [
        "localhost:42003"
      ],
      "shares":
      [
        "ForFriends",
        {
          "name": "ForCarol",
          "path": "path/to/for_carol",
          "write": true
        }
      ],
      "writes":
      [
        "Two"
      ]
    }
  ]
}

Conclusion

The resulting configuration is the alice.cfg example file that can be found along the downloaded Zhenga binary.