├── .github └── workflows │ └── build-docker-image.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── config └── world.c ├── patch ├── PostgreSQL.cpp.patch ├── patch.py ├── planet.public ├── planet.secret └── planets.json ├── s6-files └── etc │ ├── conf-init.d │ ├── 10-zero-ui │ └── 10-zerotier │ └── services.d │ ├── backend │ └── run │ └── zerotier │ └── run └── schema ├── createDB.sql └── createTables.sql /.github/workflows/build-docker-image.yaml: -------------------------------------------------------------------------------- 1 | name: 'Build docker image' 2 | on: 3 | # Auto build on push to main branch 4 | push: 5 | paths-ignore: 6 | - “README.md” 7 | - ".gitignore" 8 | - "LICENSE" 9 | branches: 10 | - main 11 | # Auto build @ every Monday 6:00am 12 | schedule: 13 | - cron: '0 6 * * 1' 14 | jobs: 15 | build: 16 | name: 'Build docker image' 17 | runs-on: ubuntu-latest 18 | 19 | # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest 20 | defaults: 21 | run: 22 | shell: bash 23 | 24 | steps: 25 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 26 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 27 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 28 | 29 | # Checkout the repository to the GitHub Actions runner 30 | - name: 'Checkout the repository to the GitHub Actions runner' 31 | uses: actions/checkout@v2 32 | 33 | # Set up QEMU 34 | - name: 'Set up QEMU' 35 | uses: docker/setup-qemu-action@v1 36 | 37 | # Login to DockerHub with username and token 38 | - name: Login to DockerHub with username and token 39 | uses: docker/login-action@v1 40 | with: 41 | username: ${{ secrets.DOCKERHUB_USERNAME }} 42 | password: ${{ secrets.DOCKERHUB_TOKEN }} 43 | 44 | # Build image and push to DockerHub 45 | - name: "Build image and push to DockerHub" 46 | uses: docker/build-push-action@v2 47 | with: 48 | context: . 49 | push: true 50 | tags: sbilly/zerotier-controller:latest 51 | 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # macOS 132 | .DS_Store 133 | 134 | # vscode 135 | .history 136 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:8 as build-stage 2 | 3 | ENV NODE_OPTIONS=--openssl-legacy-provider 4 | ENV NODE_VERSION=17.x 5 | ENV ZEROTIER_ONE_VERSION=1.8.7 6 | ENV LIBPQXX_VERSION=7.6.1 7 | 8 | ENV PATCH_ALLOW=0 9 | 10 | RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Linux-* && \ 11 | sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://linuxsoft.cern.ch/centos-vault|g' /etc/yum.repos.d/CentOS-Linux-* && \ 12 | echo -e 'deltarpm=0\ntimeout=300\nminrate=100' >> /etc/yum.conf 13 | 14 | RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo && \ 15 | rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg && \ 16 | curl -fsSL https://rpm.nodesource.com/setup_${NODE_VERSION} | bash - && \ 17 | dnf install -y nodejs yarn python3 wget git bash jq postgresql-devel curl gcc-c++ glibc-headers tar make diffutils patch cargo openssl-devel 18 | 19 | WORKDIR /src 20 | 21 | # Prepaire Environment 22 | COPY ./patch /src/patch 23 | COPY ./config /src/config 24 | 25 | # Downloading and build latest libpqxx 26 | RUN curl https://codeload.github.com/jtv/libpqxx/tar.gz/refs/tags/${LIBPQXX_VERSION} --output /tmp/libpqxx.tar.gz && \ 27 | mkdir -p /src && \ 28 | cd /src && \ 29 | tar fxz /tmp/libpqxx.tar.gz && \ 30 | mv /src/libpqxx-* /src/libpqxx && \ 31 | rm -rf /tmp/libpqxx.tar.gz && \ 32 | cd /src/libpqxx && \ 33 | /src/libpqxx/configure --disable-documentation --with-pic && \ 34 | make && \ 35 | make install 36 | 37 | # Downloading and build latest version ZeroTierOne 38 | RUN curl https://codeload.github.com/zerotier/ZeroTierOne/tar.gz/refs/tags/${ZEROTIER_ONE_VERSION} --output /tmp/ZeroTierOne.tar.gz && \ 39 | mkdir -p /src && \ 40 | cd /src && \ 41 | tar fxz /tmp/ZeroTierOne.tar.gz && \ 42 | mv /src/ZeroTierOne-* /src/ZeroTierOne && \ 43 | rm -rf /tmp/ZeroTierOne.tar.gz 44 | 45 | RUN python3 /src/patch/patch.py 46 | 47 | RUN cd /src/ZeroTierOne && \ 48 | make central-controller CPPFLAGS+=-w && \ 49 | cd /src/ZeroTierOne/attic/world && \ 50 | bash build.sh 51 | 52 | # Downloading and build latest tagged zero-ui 53 | RUN ZERO_UI_VERSION=`curl --silent "https://api.github.com/repos/dec0dOS/zero-ui/tags" | jq -r '.[0].name'` && \ 54 | curl https://codeload.github.com/dec0dOS/zero-ui/tar.gz/refs/tags/${ZERO_UI_VERSION} --output /tmp/zero-ui.tar.gz && \ 55 | mkdir -p /src/ && \ 56 | cd /src && \ 57 | tar fxz /tmp/zero-ui.tar.gz && \ 58 | mv /src/zero-ui-* /src/zero-ui && \ 59 | rm -rf /tmp/zero-ui.tar.gz && \ 60 | cd /src/zero-ui && \ 61 | yarn install && \ 62 | yarn installDeps && \ 63 | yarn build 64 | 65 | FROM centos:8 66 | 67 | WORKDIR /app/ZeroTierOne 68 | 69 | # libpqxx 70 | COPY --from=build-stage /usr/local/lib/libpqxx.la /usr/local/lib/libpqxx.la 71 | COPY --from=build-stage /usr/local/lib/libpqxx.a /usr/local/lib/libpqxx.a 72 | 73 | # ZeroTierOne 74 | COPY --from=build-stage /src/ZeroTierOne/zerotier-one /app/ZeroTierOne/zerotier-one 75 | RUN cd /app/ZeroTierOne && \ 76 | ln -s zerotier-one zerotier-cli && \ 77 | ln -s zerotier-one zerotier-idtool 78 | 79 | # mkworld @ ZeroTierOne 80 | COPY --from=build-stage /src/ZeroTierOne/attic/world/mkworld /app/ZeroTierOne/mkworld 81 | COPY --from=build-stage /src/ZeroTierOne/attic/world/world.bin /app/config/world.bin 82 | COPY --from=build-stage /src/config/world.c /app/config/world.c 83 | 84 | # Envirment 85 | RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Linux-* && \ 86 | sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://linuxsoft.cern.ch/centos-vault|g' /etc/yum.repos.d/CentOS-Linux-* && \ 87 | echo -e 'deltarpm=0\ntimeout=300\nminrate=100' >> /etc/yum.conf 88 | 89 | RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo && \ 90 | rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg && \ 91 | curl -fsSL https://rpm.nodesource.com/setup_${NODE_VERSION} | bash - && \ 92 | dnf update -y && \ 93 | dnf module enable -y postgresql:10 && \ 94 | dnf install -y nodejs yarn postgresql-server libpq wget git bash jq postgresql-devel tar gcc-c++ make xz openssl && \ 95 | mkdir -p /var/lib/zerotier-one/ && \ 96 | ln -s /app/config/authtoken.secret /var/lib/zerotier-one/authtoken.secret 97 | 98 | # Installing s6-overlay 99 | RUN S6_OVERLAY_VERSION=`curl --silent "https://api.github.com/repos/just-containers/s6-overlay/releases/latest" | jq -r .tag_name | sed 's/^v//'` && \ 100 | cd /tmp && \ 101 | curl --silent --location https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz --output s6-overlay-noarch-${S6_OVERLAY_VERSION}.tar.xz && \ 102 | curl --silent --location https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz --output s6-overlay-x86_64-${S6_OVERLAY_VERSION}.tar.xz && \ 103 | tar -C / -Jxpf /tmp/s6-overlay-noarch-${S6_OVERLAY_VERSION}.tar.xz && \ 104 | tar -C / -Jxpf /tmp/s6-overlay-x86_64-${S6_OVERLAY_VERSION}.tar.xz && \ 105 | rm -f /tmp/*.xz 106 | 107 | # Frontend @ zero-ui 108 | COPY --from=build-stage /src/zero-ui/frontend/build /app/frontend/build/ 109 | 110 | # Backend @ zero-ui 111 | WORKDIR /app/backend 112 | COPY --from=build-stage /src/zero-ui/backend/package*.json /app/backend 113 | RUN yarn install && \ 114 | ln -s /app/config/world.bin /app/frontend/build/static/planet 115 | COPY --from=build-stage /src/zero-ui/backend /app/backend 116 | 117 | # s6-overlay 118 | COPY ./s6-files/etc /etc/ 119 | RUN chmod +x /etc/services.d/*/run 120 | 121 | # schema 122 | COPY ./schema /app/schema/ 123 | 124 | EXPOSE 3000 4000 9993 9993/UDP 125 | ENV S6_KEEP_ENV=1 126 | 127 | ENTRYPOINT ["/init"] 128 | CMD [] 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 sbilly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-zerotier-controller 2 | 3 | Dockernized ZeroTierOne controller with zero-ui web interface. [中文讨论](https://v2ex.com/t/799623) 4 | 5 | ## Customize ZeroTierOne's controller planets 6 | 7 | Modify `patch/planets.json` as you needed, then build the docker image. I've put the `patch/planet.public` and `patch/planet.private` files in this repo. 8 | 9 | ```json 10 | { 11 | "planets": [ 12 | { 13 | "Location": "Beijing", // Where this planet located 14 | "Identity": "a4de2130c2:0:ab5257bb05cd2fb8044fe26483f6d27b57124ca7b350fb3e0f07d405c68c4416094dbc836bf62ed483072501aa3384dff3c74ac50050c1bfbb1dc657001ef6a1", // The planet's public key, ex: identity.public 15 | "Endpoints": ["127.0.0.1/9993"] // The list of endpoints in 'ip/port' format. IPv6 is supportted 16 | } 17 | ] 18 | } 19 | ``` 20 | 21 | ## Build 22 | 23 | ```bash 24 | docker build --force-rm . -t sbilly/zerotier-controller:latest 25 | ``` 26 | 27 | ## Run 28 | 29 | ### Controller 30 | 31 | ```bash 32 | # Run with default settings 33 | docker run --rm -ti -p 4000:4000 -p 9993:9993 -p 9993:9993/udp sbilly/zerotier-controller:latest 34 | 35 | # Run with custom envirments settings 36 | docker run --rm -ti -e ZU_SECURE_HEADERS=false -e ZU_CONTROLLER_ENDPOINT=http://127.0.0.1:9993/ -e ZU_DEFAULT_USERNAME=admin -e ZU_DEFAULT_PASSWORD=zero-ui -p 4000:4000 -p 9993:9993 -p 9993:9993/udp sbilly/zerotier-controller:latest 37 | 38 | # Run with docker volumes 39 | docker run --rm -ti -v `pwd`/config/identity.public:/app/config/identity.public -v `pwd`/config/identity.secret:/app/config/identity.secret -v `pwd`/config/authtoken.secret:/app/config/authtoken.secret -p 3000:3000 -p 4000:4000 -p 9993:9993 -p 9993:9993/udp sbilly/zerotier-controller:latest 40 | ``` 41 | 42 | ### Peer 43 | 44 | Download `planet` from controller WEB interface to peer configuration directory. For example, `/var/lib/zerotier-one/planet`, Then start `zerotier-one`. 45 | 46 | ```bash 47 | # Download planet 48 | wget http://[IP_OF_CONTROLLER]:[PORT_OF_CONTROLLER]/app/static/planet -O /var/lib/zerotier-one/planet 49 | 50 | # Start ZeroTierOne 51 | zerotier-one /var/lib/zerotier-one 52 | ``` 53 | 54 | ## Environment Variables 55 | 56 | - The default username/password (`admin`/`zero-ui`) is defined by `ZU_DEFAULT_USERNAME` and `ZU_DEFAULT_PASSWORD`. 57 | - The environment variable `ZT_PRIMARY_PORT` is ZeroTierOne's `primaryPort` in `local.conf`. 58 | - Please check [zero-ui](https://github.com/dec0dOS/zero-ui/blob/main/README.md) for other environment variables. 59 | 60 | ## Files in Docker Image 61 | 62 | ```bash 63 | /app/ 64 | ├── config/ 65 | ├── backend/ 66 | ├── frontend/ 67 | └── ZeroTierOne/ 68 | ``` 69 | 70 | - `config`: The configurations of ZeroTierOne, such as `identity.*`, `authtoken.secret`, etc. 71 | - `backend`: zero-ui backend. 72 | - `frontend`: The static files of zero-ui frontend. 73 | - `ZeroTierOne`: The binaries of ZeroTierOne, such as `zerotier-*`, `mkworld`. 74 | 75 | ## FAQ 76 | 77 | - **What's the difference from the official docker image of [zero-ui](https://github.com/dec0dOS/zero-ui)/[ztncui](https://github.com/key-networks/ztncui)** 78 | 79 | The offical docker images of [zero-ui](https://github.com/dec0dOS/zero-ui) and [ztncui](https://github.com/key-networks/ztncui) are controller‘s interface. And we provide full operational functions of planet/controller/controller-ui of ZeroTier. 80 | 81 | 82 | ## Change Log 83 | - 20220215 - Update software versions and Readme 84 | - 20211206 - Add FAQ section. 85 | - 20210904 - Update peer's instructions. 86 | - 20210902 - First Release. 87 | -------------------------------------------------------------------------------- /config/world.c: -------------------------------------------------------------------------------- 1 | 2 | #define ZT_DEFAULT_WORLD_LENGTH 257 3 | static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0xea, 0xc9, 0x0a, 0x00, 0x00, 0x01, 0x7b, 0xa5, 0xc2, 0xc7, 0x78, 0x6a, 0xc8, 0x17, 0xd9, 0x8f, 0xea, 0xe4, 0x33, 0x8a, 0xec, 0x31, 0xdc, 0xb9, 0x69, 0xb7, 0xb2, 0x3f, 0xd7, 0xc8, 0x36, 0x02, 0xb3, 0x9b, 0x59, 0x65, 0x5d, 0x3d, 0xc8, 0xca, 0xa1, 0x9b, 0x1d, 0xd3, 0x46, 0xf2, 0xd1, 0xaf, 0x86, 0x4e, 0xcb, 0xd8, 0xed, 0xfb, 0xa4, 0x34, 0x51, 0xaa, 0xa0, 0xcf, 0x8f, 0x63, 0x77, 0x83, 0xcb, 0x3b, 0xe1, 0x6d, 0x43, 0x9d, 0xb6, 0x1a, 0x17, 0x5a, 0x92, 0x37, 0xad, 0x54, 0xf5, 0xda, 0x99, 0x47, 0x42, 0x70, 0xa3, 0xe7, 0xf8, 0xf5, 0x44, 0x60, 0xa9, 0x10, 0xdb, 0xd6, 0x0a, 0xba, 0xc8, 0x2a, 0x6a, 0x96, 0x20, 0x89, 0x8a, 0x12, 0xd9, 0xfc, 0xf3, 0x5d, 0xee, 0x38, 0x5d, 0x1f, 0xa7, 0xe3, 0x2f, 0xaf, 0xdc, 0xc2, 0x9d, 0x0b, 0xbd, 0x00, 0xcf, 0x2c, 0xfc, 0x93, 0x4f, 0x03, 0x92, 0xe4, 0xc9, 0x2b, 0x11, 0xa4, 0x06, 0x99, 0x4b, 0x31, 0x03, 0x48, 0x54, 0x1a, 0x0a, 0xb6, 0x5d, 0xb2, 0xd9, 0x7c, 0x0d, 0x11, 0xba, 0x2e, 0xea, 0xfb, 0x48, 0x0a, 0x13, 0x73, 0x2b, 0xa1, 0xa2, 0x39, 0x24, 0x9f, 0xd7, 0xb3, 0xfc, 0x32, 0xbc, 0x32, 0x3f, 0x01, 0xa4, 0xde, 0x21, 0x30, 0xc2, 0x00, 0xab, 0x52, 0x57, 0xbb, 0x05, 0xcd, 0x2f, 0xb8, 0x04, 0x4f, 0xe2, 0x64, 0x83, 0xf6, 0xd2, 0x7b, 0x57, 0x12, 0x4c, 0xa7, 0xb3, 0x50, 0xfb, 0x3e, 0x0f, 0x07, 0xd4, 0x05, 0xc6, 0x8c, 0x44, 0x16, 0x09, 0x4d, 0xbc, 0x83, 0x6b, 0xf6, 0x2e, 0xd4, 0x83, 0x07, 0x25, 0x01, 0xaa, 0x33, 0x84, 0xdf, 0xf3, 0xc7, 0x4a, 0xc5, 0x00, 0x50, 0xc1, 0xbf, 0xbb, 0x1d, 0xc6, 0x57, 0x00, 0x1e, 0xf6, 0xa1, 0x00, 0x01, 0x04, 0x7f, 0x00, 0x00, 0x01, 0x27, 0x09}; -------------------------------------------------------------------------------- /patch/PostgreSQL.cpp.patch: -------------------------------------------------------------------------------- 1 | --- controller/PostgreSQL.cpp 2021-08-20 16:27:38.599930650 +0800 2 | +++ controller/PostgreSQL.cpp.new 2021-09-05 19:48:55.925215931 +0800 3 | @@ -582,6 +582,7 @@ void PostgreSQL::initializeMembers(PGcon 4 | 5 | std::string ctime = PQgetvalue(res, i, 5); 6 | config["id"] = memberId; 7 | + config["address"] = memberId; 8 | config["nwid"] = networkId; 9 | config["activeBridge"] = (strcmp(PQgetvalue(res, i, 2), "t") == 0); 10 | config["authorized"] = (strcmp(PQgetvalue(res, i, 3), "t") == 0); 11 | @@ -1080,7 +1081,10 @@ void PostgreSQL::commitThread() 12 | try { 13 | std::string memberId = (*config)["id"]; 14 | std::string networkId = (*config)["nwid"]; 15 | - std::string identity = (*config)["identity"]; 16 | + std::string identity = ""; 17 | + if (!(*config)["identity"].is_null()) { 18 | + std::string identity = (*config)["identity"]; 19 | + } 20 | std::string target = "NULL"; 21 | 22 | if (!(*config)["remoteTraceTarget"].is_null()) { 23 | @@ -1476,38 +1480,40 @@ void PostgreSQL::commitThread() 24 | continue; 25 | } 26 | auto dns = (*config)["dns"]; 27 | - std::string domain = dns["domain"]; 28 | - std::stringstream servers; 29 | - servers << "{"; 30 | - for (auto j = dns["servers"].begin(); j < dns["servers"].end(); ++j) { 31 | - servers << *j; 32 | - if ( (j+1) != dns["servers"].end()) { 33 | - servers << ","; 34 | + if (dns.is_object()) { 35 | + std::string domain = dns["domain"]; 36 | + std::stringstream servers; 37 | + servers << "{"; 38 | + for (auto j = dns["servers"].begin(); j < dns["servers"].end(); ++j) { 39 | + servers << *j; 40 | + if ( (j+1) != dns["servers"].end()) { 41 | + servers << ","; 42 | + } 43 | } 44 | - } 45 | - servers << "}"; 46 | + servers << "}"; 47 | 48 | - const char *p[3] = { 49 | - id.c_str(), 50 | - domain.c_str(), 51 | - servers.str().c_str() 52 | - }; 53 | + const char *p[3] = { 54 | + id.c_str(), 55 | + domain.c_str(), 56 | + servers.str().c_str() 57 | + }; 58 | 59 | - res = PQexecParams(conn, "INSERT INTO ztc_network_dns (network_id, domain, servers) VALUES ($1, $2, $3) ON CONFLICT (network_id) DO UPDATE SET domain = EXCLUDED.domain, servers = EXCLUDED.servers", 60 | - 3, 61 | - NULL, 62 | - p, 63 | - NULL, 64 | - NULL, 65 | - 0); 66 | - if (PQresultStatus(res) != PGRES_COMMAND_OK) { 67 | - fprintf(stderr, "ERROR: Error updating DNS: %s\n", PQresultErrorMessage(res)); 68 | + res = PQexecParams(conn, "INSERT INTO ztc_network_dns (network_id, domain, servers) VALUES ($1, $2, $3) ON CONFLICT (network_id) DO UPDATE SET domain = EXCLUDED.domain, servers = EXCLUDED.servers", 69 | + 3, 70 | + NULL, 71 | + p, 72 | + NULL, 73 | + NULL, 74 | + 0); 75 | + if (PQresultStatus(res) != PGRES_COMMAND_OK) { 76 | + fprintf(stderr, "ERROR: Error updating DNS: %s\n", PQresultErrorMessage(res)); 77 | + PQclear(res); 78 | + PQclear(PQexec(conn, "ROLLBACK")); 79 | + err = true; 80 | + break; 81 | + } 82 | PQclear(res); 83 | - PQclear(PQexec(conn, "ROLLBACK")); 84 | - err = true; 85 | - break; 86 | } 87 | - PQclear(res); 88 | 89 | res = PQexec(conn, "COMMIT"); 90 | if (PQresultStatus(res) != PGRES_COMMAND_OK) { 91 | @@ -1533,7 +1539,7 @@ void PostgreSQL::commitThread() 92 | } 93 | 94 | } catch (std::exception &e) { 95 | - fprintf(stderr, "ERROR: Error updating member: %s\n", e.what()); 96 | + fprintf(stderr, "ERROR: Error updating network: %s\n", e.what()); 97 | } 98 | // if (_rc != NULL) { 99 | // try { 100 | @@ -1551,7 +1557,7 @@ void PostgreSQL::commitThread() 101 | // } 102 | } else if (objtype == "_delete_network") { 103 | try { 104 | - std::string networkId = (*config)["nwid"]; 105 | + std::string networkId = (*config)["id"]; 106 | const char *values[1] = { 107 | networkId.c_str() 108 | }; 109 | -------------------------------------------------------------------------------- /patch/patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import os 5 | import json 6 | import time 7 | from datetime import datetime 8 | 9 | # Check patch allow variable 10 | if os.environ.get('PATCH_ALLOW', '0') != '1': 11 | print("PATCH: Nothing to do") 12 | exit() 13 | 14 | # Load data from planets.json 15 | # @pFile, location of planets.json 16 | def loadPlanets(pFile): 17 | with open(pFile, 'r') as f: 18 | data = json.load(f) 19 | f.close() 20 | return data["planets"] 21 | 22 | 23 | # Find string in lines 24 | def findString(lines, s): 25 | i = 0 26 | s = s.strip() 27 | for line in lines: 28 | i = i + 1 29 | l = line.strip() 30 | if l[:len(s)] == s: 31 | return i 32 | 33 | 34 | # Modify mkworld.cpp of ZeroTierOne 35 | # @mFile, location of mkworld.cpp 36 | # @pFile, location of planets.json 37 | def modifyMKWORLD(mFile, pFile): 38 | with open(mFile, 'r') as file: 39 | lines = file.read().splitlines() 40 | file.close() 41 | 42 | roots = loadPlanets(pFile) 43 | 44 | worldStartLineNum = findString(lines, "// EDIT BELOW HERE") 45 | worldEndLineNum = findString(lines, "// END WORLD DEFINITION") 46 | 47 | planets = [] 48 | for p in roots: 49 | planets.append("") 50 | planets.append(" // {}".format(p["Location"])) 51 | planets.append(" roots.push_back(World::Root());") 52 | planets.append( 53 | " roots.back().identity = Identity(\"{}\");".format(p["Identity"])) 54 | for ep in p["Endpoints"]: 55 | planets.append( 56 | " roots.back().stableEndpoints.push_back(InetAddress(\"{}\"));".format(ep)) 57 | 58 | ts = int(round(time.time() * 1000)) 59 | fileContent = [] 60 | fileContent.extend(lines[0:worldStartLineNum + 4]) 61 | fileContent.append( 62 | " const uint64_t ts = {}ULL; // {}".format( 63 | ts, datetime.utcfromtimestamp(int(ts/1000)).strftime('%Y-%m-%d %H:%M:%S')) 64 | ) 65 | fileContent.extend(planets) 66 | fileContent.extend(lines[worldEndLineNum - 2:]) 67 | 68 | with open(mFile, 'w') as file: 69 | for l in fileContent: 70 | file.write(l+"\n") 71 | file.close() 72 | 73 | 74 | # Build mkworld 75 | # @mFile, location of mkworld.cpp 76 | # @wFile, location of world.c 77 | def buildMKWORLD(mFile, wFile): 78 | os.system( 79 | "cd {} && g++ -I../../ -o mkworld ../../node/C25519.cpp ../../node/Salsa20.cpp ../../node/SHA512.cpp ../../node/Identity.cpp ../../node/Utils.cpp ../../node/InetAddress.cpp ../../osdep/OSUtils.cpp mkworld.cpp -std=c++11 -w".format(os.path.dirname(mFile))) 80 | os.system( 81 | "cd {} && {} > {}".format(os.path.dirname(mFile), os.path.join(os.path.dirname(mFile), "mkworld"), os.path.join(os.path.dirname(wFile), "world.c"))) 82 | 83 | 84 | # Modify node/Topology.cpp 85 | # @tFile, location of Topology.cpp 86 | # @wFile, location of world.c 87 | def modifyTOPOLOGY(tFile, wFile): 88 | with open(tFile, 'r') as file: 89 | lines = file.read().splitlines() 90 | file.close() 91 | 92 | with open(wFile, 'r') as worldFile: 93 | world = worldFile.read().splitlines() 94 | worldFile.close() 95 | 96 | worldStartLineNum = findString( 97 | lines, "#define ZT_DEFAULT_WORLD_LENGTH ") 98 | worldEndLineNum = findString( 99 | lines, "static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = ") 100 | 101 | fileContent = [] 102 | fileContent.extend(lines[:worldStartLineNum - 1]) 103 | fileContent.extend(world) 104 | fileContent.extend(lines[worldEndLineNum:]) 105 | 106 | with open(tFile, 'w') as file: 107 | for l in fileContent: 108 | file.write(l+"\n") 109 | file.close() 110 | 111 | 112 | # Patch controller/PostgreSQL.cpp 113 | # @pgFile, location of PostgreSQL.cpp 114 | def patchPOSTGRESQL(pgFile, patchFile): 115 | os.system( 116 | "cd {}/.. && patch -p 0 < {}".format(os.path.dirname(pgFile), os.path.abspath(patchFile))) 117 | 118 | 119 | def main(): 120 | mFile = os.path.abspath("./ZeroTierOne/attic/world/mkworld.cpp") 121 | tFile = os.path.abspath("./ZeroTierOne/node/Topology.cpp") 122 | pgFile = os.path.abspath("./ZeroTierOne/controller/PostgreSQL.cpp") 123 | pFile = os.path.abspath("./patch/planets.json") 124 | patchFile = os.path.abspath("./patch/PostgreSQL.cpp.patch") 125 | wFile = os.path.abspath("./config/world.c") 126 | 127 | # Modify mkworld.cpp with planets.json 128 | modifyMKWORLD(mFile, pFile) 129 | # Compile mkworld.cpp 130 | buildMKWORLD(mFile, wFile) 131 | # Modify node/Topology.cpp with world.c 132 | modifyTOPOLOGY(tFile, wFile) 133 | # Patch controller/PostgreSQL.cpp 134 | # patchPOSTGRESQL(pgFile, patchFile) 135 | 136 | 137 | main() 138 | -------------------------------------------------------------------------------- /patch/planet.public: -------------------------------------------------------------------------------- 1 | a4de2130c2:0:ab5257bb05cd2fb8044fe26483f6d27b57124ca7b350fb3e0f07d405c68c4416094dbc836bf62ed483072501aa3384dff3c74ac50050c1bfbb1dc657001ef6a1 -------------------------------------------------------------------------------- /patch/planet.secret: -------------------------------------------------------------------------------- 1 | a4de2130c2:0:ab5257bb05cd2fb8044fe26483f6d27b57124ca7b350fb3e0f07d405c68c4416094dbc836bf62ed483072501aa3384dff3c74ac50050c1bfbb1dc657001ef6a1:4829a9f2eb9c40ab5a9d33536ad1b7eb5930d5d329aff566fb9a6cb7810fc5f6c4d1489fd3d21ee80c12a71e706c1dd9f2a389f5df584618c7f0068d28537626 -------------------------------------------------------------------------------- /patch/planets.json: -------------------------------------------------------------------------------- 1 | { 2 | "planets": [{ 3 | "Location": "Beijing", 4 | "Identity": "a4de2130c2:0:ab5257bb05cd2fb8044fe26483f6d27b57124ca7b350fb3e0f07d405c68c4416094dbc836bf62ed483072501aa3384dff3c74ac50050c1bfbb1dc657001ef6a1", 5 | "Endpoints": [ 6 | "127.0.0.1/9993" 7 | ] 8 | }] 9 | } -------------------------------------------------------------------------------- /s6-files/etc/conf-init.d/10-zero-ui: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv sh 2 | 3 | mkdir -p /var/run/s6/container_environment 4 | 5 | if [ -z "$NODE_ENV" ]; then 6 | printf "production" > /var/run/s6/container_environment/NODE_ENV 7 | fi 8 | 9 | if [ -z "$LISTEN_ADDRESS" ]; then 10 | printf "0.0.0.0" > /var/run/s6/container_environment/LISTEN_ADDRESS 11 | fi 12 | 13 | if [ -z "$ZU_SERVE_FRONTEND" ]; then 14 | printf "true" > /var/run/s6/container_environment/ZU_SERVE_FRONTEND 15 | fi 16 | 17 | if [ -z "$ZU_SECURE_HEADERS" ]; then 18 | printf "false" > /var/run/s6/container_environment/ZU_SECURE_HEADERS 19 | fi 20 | 21 | if [ -z "$ZU_CONTROLLER_ENDPOINT" ]; then 22 | printf "http://localhost:9993/" > /var/run/s6/container_environment/ZU_CONTROLLER_ENDPOINT 23 | fi 24 | 25 | if [ -z "$ZU_CONTROLLER_TOKEN" ]; then 26 | if [ -f /app/config/authtoken.secret ]; then 27 | cat /app/config/authtoken.secret > /var/run/s6/container_environment/ZU_CONTROLLER_TOKEN 28 | fi 29 | fi 30 | 31 | if [ -z "$ZU_DEFAULT_USERNAME" ]; then 32 | printf "admin" > /var/run/s6/container_environment/ZU_DEFAULT_USERNAME 33 | fi 34 | 35 | if [ -z "$ZU_DEFAULT_PASSWORD" ]; then 36 | printf "zero-ui" > /var/run/s6/container_environment/ZU_DEFAULT_PASSWORD 37 | fi 38 | 39 | if [ -z "$ZU_DATAPATH" ]; then 40 | printf "data/db.json" > /var/run/s6/container_environment/ZU_DATAPATH 41 | fi 42 | -------------------------------------------------------------------------------- /s6-files/etc/conf-init.d/10-zerotier: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv sh 2 | 3 | mkdir -p /var/run/s6/container_environment 4 | 5 | if [ -z "$ZT_PRIMARY_PORT" ]; then 6 | printf "9993" > /var/run/s6/container_environment/ZT_PRIMARY_PORT 7 | fi 8 | 9 | if [ -z "$ZT_PGSQL" ]; then 10 | printf "false" > /var/run/s6/container_environment/ZT_PGSQL 11 | fi 12 | 13 | if [ -z "$ZT_PGSQL_INIT" ]; then 14 | printf "false" > /var/run/s6/container_environment/ZT_PGSQL_INIT 15 | fi 16 | 17 | if [ -z "$ZT_PGSQL_HOST" ]; then 18 | printf "127.0.0.1" > /var/run/s6/container_environment/ZT_PGSQL_HOST 19 | fi 20 | 21 | if [ -z "$ZT_PGSQL_PORT" ]; then 22 | printf "5432" > /var/run/s6/container_environment/ZT_PGSQL_PORT 23 | fi 24 | 25 | if [ -z "$ZT_PGSQL_DB" ]; then 26 | printf "zt" > /var/run/s6/container_environment/ZT_PGSQL_DB 27 | fi 28 | 29 | if [ -z "$ZT_PGSQL_USER" ]; then 30 | printf "zt" > /var/run/s6/container_environment/ZT_PGSQL_USER 31 | fi 32 | 33 | if [ -z "$ZT_PGSQL_PASS" ]; then 34 | printf "zt" > /var/run/s6/container_environment/ZT_PGSQL_PASS 35 | fi 36 | 37 | if [ -z "$ZT_REDIS" ]; then 38 | printf "false" > /var/run/s6/container_environment/ZT_REDIS 39 | fi 40 | -------------------------------------------------------------------------------- /s6-files/etc/services.d/backend/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | 3 | if [ -z "$NODE_ENV" ]; then 4 | NODE_ENV=production 5 | fi 6 | 7 | if [ -z "$LISTEN_ADDRESS" ]; then 8 | LISTEN_ADDRESS=0.0.0.0 9 | fi 10 | 11 | if [ -z "$ZU_SERVE_FRONTEND" ]; then 12 | ZU_SERVE_FRONTEND=true 13 | fi 14 | 15 | if [ -z "$ZU_SECURE_HEADERS" ]; then 16 | ZU_SECURE_HEADERS=false 17 | fi 18 | 19 | if [ -z "$ZU_CONTROLLER_ENDPOINT" ]; then 20 | # check local.conf 21 | while [ ! -f /app/config/local.conf ]; do 22 | sleep 1 23 | done 24 | ZT_PRIMARY_PORT=`/bin/cat /app/config/local.conf | /usr/bin/jq -r '.settings.primaryPort'` 25 | ZU_CONTROLLER_ENDPOINT=http://127.0.0.1:$ZT_PRIMARY_PORT/ 26 | fi 27 | 28 | if [ -z "$ZU_CONTROLLER_TOKEN" ]; then 29 | # check authtoken.secret 30 | while [ ! -f /app/config/authtoken.secret ]; do 31 | sleep 1 32 | done 33 | ZU_CONTROLLER_TOKEN=`/bin/cat /app/config/authtoken.secret` 34 | fi 35 | 36 | if [ -z "$ZU_DEFAULT_USERNAME" ]; then 37 | ZU_DEFAULT_USERNAME=admin 38 | fi 39 | 40 | if [ -z "$ZU_DEFAULT_PASSWORD" ]; then 41 | ZU_DEFAULT_PASSWORD=zero-ui 42 | fi 43 | 44 | if [ -z "$ZU_DATAPATH" ]; then 45 | ZU_DATAPATH=data/db.json 46 | fi 47 | 48 | NODE_ENV=$NODE_ENV 49 | LISTEN_ADDRESS=$LISTEN_ADDRESS 50 | ZU_SERVE_FRONTEND=$ZU_SERVE_FRONTEND 51 | ZU_SECURE_HEADERS=$ZU_SECURE_HEADERS 52 | ZU_CONTROLLER_ENDPOINT=$ZU_CONTROLLER_ENDPOINT 53 | ZU_CONTROLLER_TOKEN=$ZU_CONTROLLER_TOKEN 54 | ZU_DEFAULT_USERNAME=$ZU_DEFAULT_USERNAME 55 | ZU_DEFAULT_PASSWORD=$ZU_DEFAULT_PASSWORD 56 | ZU_DATAPATH=$ZU_DATAPATH 57 | 58 | cd /app/backend 59 | NODE_ENV=${NODE_ENV} LISTEN_ADDRESS=${LISTEN_ADDRESS} ZU_SERVE_FRONTEND=${ZU_SERVE_FRONTEND} ZU_SECURE_HEADERS=${ZU_SECURE_HEADERS} ZU_CONTROLLER_ENDPOINT=${ZU_CONTROLLER_ENDPOINT} ZU_CONTROLLER_TOKEN=${ZU_CONTROLLER_TOKEN} ZU_DEFAULT_USERNAME=${ZU_DEFAULT_USERNAME} ZU_DEFAULT_PASSWORD=${ZU_DEFAULT_PASSWORD} /usr/bin/node /app/backend/bin/www 60 | -------------------------------------------------------------------------------- /s6-files/etc/services.d/zerotier/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | 3 | if [ -z "$ZT_PRIMARY_PORT" ]; then 4 | ZT_PRIMARY_PORT=9993 5 | mkdir -p /var/run/s6/container_environment/ 6 | printf "9993" > /var/run/s6/container_environment/ZT_PRIMARY_PORT 7 | fi 8 | 9 | while [ ! -f /app/config/local.conf ]; do 10 | if [ "${ZT_PGSQL}" = "true" ]; then 11 | # PGSQL 12 | echo "{\"settings\":{\"primaryPort\":${ZT_PRIMARY_PORT},\"allowManagementFrom\":[\"0.0.0.0/0\"],\"controllerDbPath\":\"postgres:host=${ZT_PGSQL_HOST} port=${ZT_PGSQL_PORT} dbname=${ZT_PGSQL_DB} user=${ZT_PGSQL_USER} password=${ZT_PGSQL_PASS} sslmode=prefer sslcert= sslkey= sslrootcert=\",\"portMappingEnabled\":true,\"softwareUpdate\":\"disable\",\"interfacePrefixBlacklist\":[\"inot\",\"nat64\"]}}" > /app/config/local.conf 13 | else 14 | # NO PGSQL 15 | echo "{\"settings\": {\"primaryPort\": ${ZT_PRIMARY_PORT}, \"allowManagementFrom\": [\"0.0.0.0/0\"]}}" > /app/config/local.conf 16 | fi 17 | done 18 | 19 | if [ -z "$ZT_PGSQL_INIT" ]; then 20 | if [ "${ZT_PGSQL_INIT}" = "true" ]; then 21 | # init postgresql database 22 | echo "Init PostgreSQL database ..." 23 | echo "DROP DATABASE ${ZT_PGSQL_DB};\nCREATE DATABASE ${ZT_PGSQL_DB};\n" > /app/schema/createDB.sql 24 | /usr/bin/psql postgresql://${ZT_PGSQL_USER}:${ZT_PGSQL_PASS}@${ZT_PGSQL_HOST}:${ZT_PGSQL_PORT}/ -f /app/schema/createDB.sql 25 | /usr/bin/psql postgresql://${ZT_PGSQL_USER}:${ZT_PGSQL_PASS}@${ZT_PGSQL_HOST}:${ZT_PGSQL_PORT}/${ZT_PGSQL_DB} -f /app/schema/createTables.sql 26 | fi 27 | fi 28 | 29 | ZT_PRIMARY_PORT=`/bin/cat /app/config/local.conf | /usr/bin/jq -r '.settings.primaryPort'` 30 | /app/ZeroTierOne/zerotier-one -p${ZT_PRIMARY_PORT} /app/config 31 | -------------------------------------------------------------------------------- /schema/createDB.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE zt; 2 | CREATE DATABASE zt; 3 | -------------------------------------------------------------------------------- /schema/createTables.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE ztc_database; 2 | CREATE TABLE ztc_database ( 3 | version integer 4 | ); 5 | INSERT INTO ztc_database VALUES (5); 6 | SELECT * FROM ztc_database; 7 | 8 | DROP TABLE ztc_network; 9 | CREATE TABLE ztc_network ( 10 | id text UNIQUE, 11 | creation_time timestamp, 12 | owner_id text, 13 | capabilities text, 14 | enable_broadcast BOOLEAN NOT NULL DEFAULT TRUE, 15 | last_modified timestamp, 16 | mtu text, 17 | multicast_limit text, 18 | name text, 19 | private BOOLEAN NOT NULL DEFAULT TRUE, 20 | remote_trace_level text, 21 | remote_trace_target text, 22 | revision integer, 23 | rules text, 24 | rules_source text, 25 | tags text, 26 | v4_assign_mode text, 27 | v6_assign_mode text, 28 | deleted BOOLEAN NOT NULL DEFAULT FALSE, 29 | controller_id text 30 | ); 31 | SELECT * FROM ztc_network; 32 | 33 | DROP TABLE ztc_member; 34 | CREATE TABLE ztc_member ( 35 | id text, 36 | network_id text, 37 | active_bridge BOOLEAN NOT NULL DEFAULT FALSE, 38 | authorized BOOLEAN NOT NULL DEFAULT FALSE, 39 | capabilities text, 40 | identity text, 41 | last_authorized_time timestamp, 42 | last_deauthorized_time timestamp, 43 | no_auto_assign_ips BOOLEAN NOT NULL DEFAULT FALSE, 44 | remote_trace_level text, 45 | remote_trace_target text, 46 | revision integer, 47 | tags text, 48 | v_major text, 49 | v_minor text, 50 | v_rev text, 51 | v_proto text, 52 | creation_time timestamp, 53 | deleted BOOLEAN NOT NULL DEFAULT FALSE, 54 | hidden BOOLEAN NOT NULL DEFAULT FALSE 55 | ); 56 | CREATE UNIQUE INDEX on ztc_member (network_id, id); 57 | SELECT * FROM ztc_member; 58 | 59 | DROP TABLE ztc_controller; 60 | CREATE TABLE ztc_controller ( 61 | id text UNIQUE, 62 | cluster_host text, 63 | last_alive timestamp, 64 | public_identity text, 65 | v_major text, 66 | v_minor text, 67 | v_rev text, 68 | v_proto text, 69 | v_build text, 70 | host_port text, 71 | use_redis text 72 | ); 73 | SELECT * FROM ztc_controller; 74 | 75 | DROP TABLE ztc_global_permissions; 76 | CREATE TABLE ztc_global_permissions ( 77 | user_id text, 78 | authorize boolean, 79 | del boolean, 80 | modify boolean, 81 | read boolean 82 | ); 83 | SELECT * FROM ztc_global_permissions; 84 | 85 | DROP TABLE ztc_network_assignment_pool; 86 | CREATE TABLE ztc_network_assignment_pool ( 87 | network_id text, 88 | ip_range_start inet, 89 | ip_range_end inet 90 | ); 91 | SELECT * FROM ztc_network_assignment_pool; 92 | 93 | DROP TABLE ztc_network_route; 94 | CREATE TABLE ztc_network_route ( 95 | network_id text, 96 | address inet, 97 | bits text, 98 | via inet 99 | ); 100 | SELECT * FROM ztc_network_route; 101 | 102 | DROP TABLE ztc_network_dns; 103 | CREATE TABLE ztc_network_dns ( 104 | network_id text, 105 | domain text, 106 | servers text 107 | ); 108 | SELECT * FROM ztc_network_dns; 109 | 110 | 111 | DROP TABLE ztc_member_ip_assignment; 112 | CREATE TABLE ztc_member_ip_assignment ( 113 | network_id text, 114 | member_id text, 115 | address inet 116 | ); 117 | CREATE UNIQUE INDEX on ztc_member_ip_assignment (network_id, member_id, address); 118 | SELECT * FROM ztc_member_ip_assignment; 119 | 120 | DROP TABLE ztc_member_status; 121 | CREATE TABLE ztc_member_status ( 122 | network_id text, 123 | member_id text, 124 | address inet, 125 | last_updated text 126 | ); 127 | CREATE UNIQUE INDEX on ztc_member_status (network_id, member_id); 128 | SELECT * FROM ztc_member_status; 129 | --------------------------------------------------------------------------------