├── LICENSE ├── README.md ├── anchor.yml ├── docker-compose.yml ├── install.sh └── settings.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Native Planet 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 | ![](https://nyc3.digitaloceanspaces.com/startram/anchor.png) 2 | 3 | # Native Planet self-hosted Anchor 4 | 5 | This is a self-hostable version of the NativePlanet anchor service, a single-tenant version of the subscription StarTram service. Anchor is meant to host a single [GroundSeg](https://nativeplanet.io/software) client, but there are no limits on the number of piers it can host. The installer script is meant to be run on Debian/Ubuntu, but it should work on other distros if you have Python 3 & Ansible installed. 6 | 7 | Credit for initial concept due to [odyslam](https://github.com/odyslam) for the [home-urbit](https://github.com/odyslam/home-urbit) project, implemented by Native Planet 8 | 9 | ### How to use 10 | 11 | You'll need set a few things up ahead of time: 12 | 13 | - Spin up a VPS (e.g. [DigitalOcean](https://m.do.co/c/94f7fdc03fad) or [Vultr](https://www.vultr.com/?ref=9235764)) and make note of the IP address 14 | - This package is designed to run on Ubuntu 22.04; it works well with typical ~$5 1GB/1CPU instances though could work reasonably on a 512MB/1CPU instance. 15 | - The playbook will automatically provision 2GB swap -- this doesn't work on some hosts, so if it breaks it, just comment out those lines and try again. 16 | - Register a domain (you can re-use an existing one, you'll only be using subdomains) 17 | 18 | Create the following 5 `A` records for your domain, assigned to your VPS's IP address: 19 | - `anchor.yourdomain.com` -- the anchor URL your NP will connect to 20 | - `ship-name.yourdomain.com` -- The URL of your ship's name 21 | - `s3.ship-name.yourdomain.com` -- These 3 subdomains for Minio 22 | - `console.s3.ship-name.yourdomain.com` 23 | - `bucket.s3.ship-name.yourdomain.com` 24 | 25 | If you want to run multiple ships, create new subdomains for them (you only need one `anchor` record). 26 | 27 | Make sure you can connect to the VPS over SSH as root with an SSH key. 28 | - If you don't have a key, you can generate one with `ssh-keygen -t ed25519 -m PEM -f id_ed25519` 29 | - Copy the contents of your public key from `id_ed25519.pub` 30 | - Get the public key for your keyfile with `ssh-keygen -f ./id_ed25519 -y` if you don't see one 31 | - Connect to your VPS, and use `sudo su -` to switch to `root` 32 | - Open `/root/.ssh/authorized_keys` in a text editor and paste in the public key for your key 33 | - Run `systemctl restart sshd` 34 | 35 | Clone this git repository on your local computer: 36 | 37 | ``` 38 | git clone https://github.com/Native-Planet/anchor.git 39 | cd anchor 40 | ``` 41 | 42 | Open the `settings.sh` file in a text editor and edit the variables, with the root of your domain (`yourdomain.com`), `REG_CODE` as the code you'll use to register your device (🚨 **change this!** 🚨), and the path to the pem file you just generated. 43 | 44 | Run `./install.sh` to execute the installer script; it will connect to your VPS (using the `anchor.yourdomain.com` IP address) and download and configure all of the software automatically. Wait until the ansible playbook has completed -- this might take a few minutes on low-spec servers. 45 | 46 | On your NativePlanet, go to the settings menu and under the Anchor submenu, select 'custom endpoint' and enter `anchor.yourdomain.com`. 47 | -------------------------------------------------------------------------------- /anchor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: anchor 3 | become: yes 4 | environment: 5 | ROOT_DOMAIN: "{{ ROOT_DOMAIN }}" 6 | HEADER_AUTH: "{{ HEADER_AUTH }}" 7 | REG_CODE: "{{ REG_CODE }}" 8 | DEBUG_DB: "{{ DEBUG_DB }}" 9 | vars: 10 | swap_configure: true 11 | swap_enable: true 12 | swap_file_path: "/swapfile" 13 | swap_file_size_mb: 2048 14 | swappiness: 1 15 | gather_facts: true 16 | tasks: 17 | 18 | - name: Install packages 19 | remote_user: root 20 | apt: 21 | name: ['apt-transport-https', 'ca-certificates', 'curl', 'software-properties-common', 'git', 'iptables', 'resolvconf', 'linux-headers-generic', 'wireguard', 'debian-keyring', 'debian-archive-keyring', 'apt-transport-https'] 22 | state: present 23 | update_cache: yes 24 | tags: 25 | - docker 26 | 27 | - name: Configure swap 28 | block: 29 | 30 | - name: Check if swap file exists 31 | stat: 32 | path: "{{swap_file_path}}" 33 | get_checksum: false 34 | get_md5: false 35 | register: swap_file_check 36 | changed_when: false 37 | 38 | - name: Set variable for existing swap file size 39 | set_fact: 40 | swap_file_existing_size_mb: "{{ (swap_file_check.stat.size / 1024 / 1024) | int }}" 41 | when: swap_file_check.stat.exists 42 | 43 | - name: Check if swap is on 44 | shell: swapon --show | grep {{swap_file_path}} 45 | register: swap_is_enabled 46 | changed_when: false 47 | failed_when: false 48 | 49 | - name: Disable swap 50 | command: swapoff {{swap_file_path}} 51 | register: swap_disabled 52 | when: > 53 | swap_file_check.stat.exists 54 | and 'rc' in swap_is_enabled and swap_is_enabled.rc == 0 55 | and (not swap_enable or (swap_enable and swap_file_existing_size_mb != swap_file_size_mb)) 56 | 57 | - name: Configure swap 58 | block: 59 | 60 | - name: Create or change the size of swap file 61 | command: dd if=/dev/zero of={{swap_file_path}} count={{swap_file_size_mb}} bs=1MiB 62 | register: swap_file_created 63 | when: > 64 | not swap_file_check.stat.exists 65 | or swap_file_existing_size_mb != swap_file_size_mb 66 | 67 | - name: Change swap file permissions 68 | file: 69 | path: "{{swap_file_path}}" 70 | mode: 0600 71 | 72 | - name: Check if swap is formatted 73 | shell: file {{swap_file_path}} | grep 'swap file' 74 | register: swap_file_is_formatted 75 | changed_when: false 76 | failed_when: false 77 | 78 | - name: Format swap file if it's not formatted 79 | command: mkswap {{swap_file_path}} 80 | when: > 81 | ('rc' in swap_file_is_formatted and swap_file_is_formatted.rc > 0) 82 | or swap_file_created.changed 83 | 84 | - name: Add swap entry to fstab 85 | mount: 86 | name: none 87 | src: "{{swap_file_path}}" 88 | fstype: swap 89 | opts: sw 90 | passno: '0' 91 | dump: '0' 92 | state: present 93 | 94 | - name: Turn on swap 95 | shell: swapon -a 96 | when: > 97 | ('rc' in swap_is_enabled and swap_is_enabled.rc != 0) 98 | or swap_disabled.changed 99 | 100 | - name: Configure swappiness 101 | sysctl: 102 | name: vm.swappiness 103 | value: "{{ swappiness|string }}" 104 | state: present 105 | 106 | - name: Add Docker PGP 107 | remote_user: root 108 | apt_key: 109 | url: https://download.docker.com/linux/ubuntu/gpg 110 | state: present 111 | tags: 112 | - docker 113 | 114 | - name: Verify fingerprint 115 | remote_user: root 116 | apt_key: 117 | id: 0EBFCD88 118 | state: present 119 | tags: 120 | - docker 121 | 122 | - name: Add Docker repo 123 | remote_user: root 124 | apt_repository: 125 | repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable 126 | state: present 127 | update_cache: yes 128 | tags: 129 | - docker 130 | 131 | - name: Update apt 132 | remote_user: root 133 | apt: 134 | update_cache: yes 135 | tags: 136 | - docker 137 | 138 | - name: Install docker 139 | remote_user: root 140 | apt: 141 | name: docker-ce 142 | state: present 143 | update_cache: yes 144 | tags: 145 | - docker 146 | 147 | - name: Add remote user to "docker" group 148 | remote_user: root 149 | user: 150 | name: root 151 | groups: "docker" 152 | append: yes 153 | tags: 154 | - docker 155 | 156 | - name: Install docker-compose 157 | remote_user: root 158 | get_url: 159 | url : https://github.com/docker/compose/releases/download/v2.9.0/docker-compose-linux-x86_64 160 | dest: /usr/local/bin/docker-compose 161 | mode: 'u+x,g+x' 162 | 163 | - name: Clone anchor git 164 | remote_user: root 165 | git: 166 | repo: "https://github.com/Native-Planet/anchor.git" 167 | dest: "{{ ansible_env.HOME }}/anchor" 168 | version: master 169 | 170 | - name: Write env vars 171 | remote_user: root 172 | shell: printf 'HEADER_AUTH={{ HEADER_AUTH }}\n 173 | ROOT_DOMAIN={{ ROOT_DOMAIN }}\n 174 | REG_CODE={{ REG_CODE }}' > "{{ ansible_env.HOME }}/anchor/.env" 175 | 176 | - name: Remove leading spaces from env file 177 | remote_user: root 178 | ansible.builtin.replace: 179 | path: "{{ ansible_env.HOME }}/anchor/.env" 180 | regexp: '^[ \t]*' 181 | replace: '' 182 | 183 | - name: Write rebuild script 184 | remote_user: root 185 | shell: printf '#!/bin/bash\n 186 | export HEADER_AUTH={{ HEADER_AUTH }}\n 187 | export ROOT_DOMAIN={{ ROOT_DOMAIN }}\n 188 | export REG_CODE={{ REG_CODE }}\n 189 | docker-compose down\n 190 | git pull\n 191 | docker-compose up --build -d\n 192 | docker logs api -f' > "{{ ansible_env.HOME }}/anchor/rebuild" 193 | 194 | - name: Remove leading spaces from rebuild script 195 | remote_user: root 196 | ansible.builtin.replace: 197 | path: "{{ ansible_env.HOME }}/anchor/rebuild" 198 | regexp: '^[ \t]*' 199 | replace: '' 200 | 201 | - name: Write to bashrc 202 | remote_user: root 203 | shell: printf 'docker logs api -f\n' >> "{{ ansible_env.HOME }}/.bashrc" 204 | 205 | - name: +x rebuild 206 | file: 207 | dest: "{{ ansible_env.HOME }}/anchor/rebuild" 208 | mode: a+x 209 | 210 | - name: Execute composition 211 | command: docker-compose up -d 212 | become: yes 213 | args: 214 | chdir: "{{ ansible_env.HOME }}/anchor" 215 | 216 | - name: Allow HTTP 217 | ufw: 218 | rule: allow 219 | port: '80' 220 | proto: tcp 221 | 222 | - name: Allow HTTPS 223 | ufw: 224 | rule: allow 225 | port: '443' 226 | proto: tcp 227 | 228 | - name: Allow UDP 229 | ufw: 230 | rule: allow 231 | port: 10000:65535 232 | proto: udp 233 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "3.3" 3 | services: 4 | wireguard: 5 | image: nativeplanet/anchor-wg:latest 6 | container_name: wg 7 | cap_add: 8 | - NET_ADMIN 9 | - SYS_MODULE 10 | environment: 11 | - PUID=1000 12 | - PGID=1000 13 | - TZ=Europe/London 14 | - SERVERURL=anchor.${ROOT_DOMAIN} 15 | - SERVERPORT=51820 16 | - PEERS=1 17 | - PEERDNS=1.1.1.1 18 | - INTERNAL_SUBNET=10.13.13.0 19 | - ALLOWEDIPS=0.0.0.0/0 20 | - LOG_CONFS=true 21 | - HEADER_AUTH 22 | volumes: 23 | - ./wg/config:/config 24 | - /lib/modules:/lib/modules 25 | ports: 26 | - 51820:51820/udp 27 | sysctls: 28 | - net.ipv4.conf.all.src_valid_mark=1 29 | networks: 30 | wgnet: 31 | ipv4_address: 172.20.0.2 32 | restart: unless-stopped 33 | labels: 34 | - "com.centurylinklabs.watchtower.enable=true" 35 | caddy: 36 | image: nativeplanet/anchor-caddy:latest 37 | cap_add: 38 | - NET_ADMIN 39 | container_name: caddy 40 | ports: 41 | - 80:80 42 | - 443:443 43 | volumes: 44 | - ./caddy/data:/data 45 | - ./caddy/config:/config/caddy 46 | networks: 47 | wgnet: 48 | ipv4_address: 172.20.0.4 49 | restart: unless-stopped 50 | labels: 51 | - "com.centurylinklabs.watchtower.enable=true" 52 | api: 53 | image: nativeplanet/anchor-api:latest 54 | depends_on: 55 | - wireguard 56 | - caddy 57 | container_name: api 58 | environment: 59 | - HEADER_AUTH 60 | - ROOT_DOMAIN 61 | - REG_CODE 62 | - DB_PATH=/api/db/anchors.db 63 | - DEBUG_DB 64 | volumes: 65 | - ./wg/config:/etc/wireguard/ 66 | - ./db:/api/db 67 | networks: 68 | wgnet: 69 | ipv4_address: 172.20.0.3 70 | restart: unless-stopped 71 | labels: 72 | - "com.centurylinklabs.watchtower.enable=true" 73 | watchtower: 74 | image: containrrr/watchtower 75 | restart: always 76 | volumes: 77 | - /var/run/docker.sock:/var/run/docker.sock 78 | - /etc/timezone:/etc/timezone:ro 79 | environment: 80 | - WATCHTOWER_CLEANUP=true 81 | - WATCHTOWER_LABEL_ENABLE=true 82 | - WATCHTOWER_INCLUDE_RESTARTING=true 83 | labels: 84 | - "com.centurylinklabs.watchtower.enable=true" 85 | 86 | # dbweb: 87 | # image: coleifer/sqlite-web@sha256:b0d4094b883ee274d2242d8e5b4173f40e56a1d137660cf78d67c87164db9490 88 | # container_name: dbweb 89 | # environment: 90 | # - SQLITE_DATABASE=anchors.db 91 | # volumes: 92 | # - ./db:/data 93 | # depends_on: 94 | # - api 95 | 96 | networks: 97 | wgnet: 98 | driver: bridge 99 | ipam: 100 | driver: default 101 | config: 102 | - subnet: 172.20.0.0/24 103 | gateway: 172.20.0.1 -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ###### 3 | # Don't edit below 4 | ###### 5 | source ./settings.sh 6 | HEADER_AUTH=$(openssl rand -hex 20) 7 | if grep -Fxq "CHANGE_ME!" settings.sh 8 | then 9 | echo "Please edit settings.sh with your settings" 10 | exit 1 11 | fi 12 | if command -v apt &> /dev/null 13 | then 14 | sudo apt update && sudo apt install -y python3 ansible 15 | elif ! command -v apt &> /dev/null 16 | then 17 | if ! command -v python3 &> /dev/null 18 | then 19 | echo "Please install Python 3" 20 | exit 1 21 | elif ! command -v ansible-playbook &> /dev/null 22 | then 23 | echo "Please install Ansible" 24 | exit 1 25 | fi 26 | fi 27 | host -t A anchor.${ROOT_DOMAIN} | grep "has address" >/dev/null || { 28 | echo "no DNS records for anchor.${ROOT_DOMAIN}!" 29 | exit 1 30 | } 31 | chmod 600 ${SSH_KEY} 32 | mkdir -p ~/.ssh 33 | SSH_PUB=`ssh-keygen -f ${SSH_KEY} -y` 34 | printf "[anchor]\nanchor.${ROOT_DOMAIN}\n\n[anchor:vars]\nansible_user=\"root\"\nansible_ssh_private_key_file=${SSH_KEY}" > hosts 35 | ssh-keyscan -H anchor.${ROOT_DOMAIN} >> ~/.ssh/known_hosts 36 | ssh -i ${SSH_KEY} -o "StrictHostKeyChecking=no" \ 37 | root@anchor.${ROOT_DOMAIN} "sed -i 's@PasswordAuthentication yes@PasswordAuthentication no@g' /etc/ssh/sshd_config" 38 | ssh -i ${SSH_KEY} -o "StrictHostKeyChecking=no" \ 39 | root@anchor.${ROOT_DOMAIN} "systemctl restart ssh" 40 | ssh -i ${SSH_KEY} root@anchor.${ROOT_DOMAIN} \ 41 | "echo \"${SSH_PUB}\" >> /root/.ssh/authorized_keys && echo \"${SSH_PUB}\" >> ~/.ssh/authorized_keys" 42 | ansible-playbook --key-file ${SSH_KEY} \ 43 | -i ./hosts -e "ROOT_DOMAIN=${ROOT_DOMAIN} \ 44 | HEADER_AUTH=${HEADER_AUTH} REG_CODE=${REG_CODE} DEBUG_DB=${DEBUG_DB} \ 45 | ansible_python_interpreter=$(which python3)" \ 46 | ./anchor.yml 47 | -------------------------------------------------------------------------------- /settings.sh: -------------------------------------------------------------------------------- 1 | # The main part of your domain (mydomain.com) 2 | ROOT_DOMAIN=test.nativeplanet.live 3 | # A strong random password (you will need to remember it) 4 | REG_CODE=🚨CHANGE_ME!🚨 5 | # Path to the SSH key for your VPS 6 | SSH_KEY="/home/reid/gits/relay/key.pem" 7 | --------------------------------------------------------------------------------