~netlandish/links-wiki

ef143eb39a7cc088ff6f70640e6084c5ff689baa — Peter Sanchez 4 months ago 4c22507 master
Adding self-host document
2 files changed, 332 insertions(+), 1 deletions(-)

M build.md
A self-host.md
M build.md => build.md +1 -1
@@ 7,7 7,7 @@ description: 'How to build the LinkTaco (links) project'

Requirements:

- Go 1.20
- Go 1.21
- GNU Make
- PostgreSQL (any modern version should work)


A self-host.md => self-host.md +331 -0
@@ 0,0 1,331 @@
---
title: 'How to self-host the LinkTaco (links) project'
description: 'How to self-host the LinkTaco (links) project'
---

# How to self-host the LinkTaco (links) project

We don't have a Docker file and probably will never provide one. If you want to
create one, please do and share it with the community. We're happy to link to
it.

To self host we're going to assume that you have access to a server and at
least 4 IP addresses you can use. Why 4 IP's? Because since each service can
run for user's custom domains they each need to have their own IP address. The
4th IP is for the GraphQL server. This can share an IP with other hosts on the
same system if there are others.

Requirements:

- Root access to Server / VPS running Unix like operating system where you can
  install the application. Linux or BSD is fine.
- 4 IP addresses
- Go 1.21+
- PostgreSQL (any modern-ish version should work) installed and running.
- supervisord installed
- nginx installed
- Caddy installed
- Some basic system admin skills to get through this process.

Notes:

- The example commands in this document are based on the FreeBSD operating
  system (OS), so if you're installing to Linux or some other Unix like OS,
  please refer to your OS documentation for the equivalent commands or file
  locations.
- This is just one way to deploy this software. We are not saying this is the
  best way. Please review your own network, requirements, environments, etc.
  to determine your best deployment strategy.

## Initial setup

We will create a `links` user and then build and run this software as this
user.

```
root ~ # adduser
Username: links
Full name: LinkTaco.com
Uid (Leave empty for default):
Login group [links]:
Login group is links. Invite links into other groups? []:
Login class [default]:
Shell (sh csh tcsh bash rbash git-shell nologin) [sh]: bash
Home directory [/home/links]:
Home directory permissions (Leave empty for default):
Use password-based authentication? [yes]:
Use an empty password? (yes/no) [no]:
Use a random password? (yes/no) [no]: yes
Lock out the account after creation? [no]:
Username   : links
Password   : <random>
Full Name  : LinkTaco.com
Uid        : 1004
Class      :
Groups     : links
Home       : /home/links
Home Mode  :
Shell      : /usr/local/bin/bash
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (links) to the user database.
adduser: INFO: Password for (links) is: XXXXXXXXXXXX
Add another user? (yes/no): no
Goodbye!
root ~ #
```

Now let's create a new PostgreSQL user and database for the application.

```
root ~ # createuser -U postgres -W links
Password:
root ~ # createdb -U postgres -O links links
root ~ #
```

Great! Now we've got a new user. Let's go into this users home directory and
clone the repo.

```
root ~ # su - links
links ~ $ git clone https://git.code.netlandish.com/~netlandish/links
links ~ $ cd links
links ~/links $ make all links-admin links-domains
```

We won't go into the build steps here. Please see the [Build
documentation](build.md) for this step.

## nginx

We'll first setup the nginx configuration because it's pretty straight forward
and most people are familiar with it's syntax.

In this case, we have the following in our `/usr/local/etc/nginx/nginx.conf':

```
http {
    ...
    ...
    include /usr/local/etc/nginx/Includes/*.conf;
}
```

This allows us to place `links-api.conf` in the `/usr/local/etc/nginx/Includes`
directory (create it if it doesn't exist) and we can put the api service config
in this file.

Now create the `/usr/local/etc/nginx/Includes/links-api.conf` file and put the
following (obviously, change the relevant pieces):

```
    server {
        listen           123.456.789.100:443 ssl http2;
        server_name      api.YOURDOMAIN.com;

        ssl_certificate      /etc/letsencrypt/live/api.YOURDOMAIN.com/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/api.YOURDOMAIN.com/privkey.pem;
        ssl_protocols        TLSv1.2;  # don't use SSLv3 ref: POODLE
        ssl_ciphers          "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECD
HE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-
AES256-SHA384";
        ssl_ecdh_curve       X25519:prime256v1:secp521r1:secp384r1;

        # openssl dhparam -dsaparam -out /usr/local/etc/nginx/ssl/dhparams_4096.pem 4096
        ssl_dhparam          /usr/local/etc/nginx/ssl/dhparams_4096.pem;

        ssl_prefer_server_ciphers on;

        gzip off;

        add_header Strict-Transport-Security "max-age=63072000; includeSubdomains" always;
        #add_header Content-Security-Policy "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';" always;
        #add_header Referrer-Policy "no-referrer";
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Feature-Policy "geolocation none; midi none; notifications none; push none; sync-xhr none; microphone none; camera none; magnetometer none; gyroscope none; speaker none; vibrate none; fullscreen self; payment none; usb none;";

        add_header Allow "GET, POST, HEAD" always;
        if ($request_method !~ ^(GET|POST|HEAD)$) {
            return 405;
        }

        location / {
            # Run through api daemon, Change port to whatever you have the
            # api service configured to run on
            proxy_pass http://127.0.0.1:5001;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
```

Note: The nginx config above assumes the existence of a LetsEncrypt SSL cert
for your api domain. Please make sure the certificate exists at the path you
use in the config snippet above. Obviously you can change it to meet whatever
path you have on your system.

## Caddy

While links software itself supports auto TLS generation in this case we will
use Caddy to be the front for each of the other 3 services. So let's edit
the `/usr/local/etc/caddy/Caddyfile`:

```
{
        # This IP:Port should be where the "links-domains" service is configured to run 
        # locally. Generally this is localhost but your situation will vary
        on_demand_tls {
                ask http://localhost:5004/_check/domain
        }
}

# links service
https:// {
        bind 123.456.789.101
        tls {
                on_demand
        }
        reverse_proxy localhost:5000
        log {
                output file /var/log/caddy/links-access.log
                format console
        }
}

# short service
https:// {
        bind 123.456.789.102
        tls {
                on_demand
        }
        reverse_proxy localhost:5002
        log {
                output file /var/log/caddy/short-access.log
                format console
        }
}

# list service
https:// {
        bind 123.456.789.103
        tls {
                on_demand
        }
        reverse_proxy localhost:5003
        log {
                output file /var/log/caddy/list-access.log
                format console
        }
}
```

## supervisord

Finally let's setup the supervisord services. Edit your
`/usr/local/etc/supervisord.conf` file and place the following below in the
`[include]` section of the file:

```
files = supervisord/*.conf
```

If the `/usr/local/etc/supervisord` directory does not yet exist then create
it. Now let's create the `/usr/local/etc/supervisord/links.conf` file and add
the following:

```
[program:links]
command = /home/links/links/links
directory = /home/links/links
user = links
priority = 1
stopwaitsecs = 60
stopsignal = QUIT
stdout_logfile = /var/log/links.log
stderr_logfile = /var/log/links.log

[program:links-api]
command = /home/links/links/links-api
directory = /home/links/links
user = links
priority = 1
stopwaitsecs = 60
stopsignal = QUIT
stdout_logfile = /var/log/links_api.log
stderr_logfile = /var/log/links_api.log

[program:links-short]
command = /home/links/links/links-short
directory = /home/links/links
user = links
priority = 1
stopwaitsecs = 60
stopsignal = QUIT
stdout_logfile = /var/log/links_short.log
stderr_logfile = /var/log/links_short.log

[program:links-list]
command = /home/links/links/links-list
directory = /home/links/links
user = links
priority = 1
stopwaitsecs = 60
stopsignal = QUIT
stdout_logfile = /var/log/links_list.log
stderr_logfile = /var/log/links_list.log

[program:links-domains]
command = /home/links/links/links-domains
directory = /home/links/links
user = links
priority = 1
stopwaitsecs = 60
stopsignal = QUIT
stdout_logfile = /var/log/links_domains.log
stderr_logfile = /var/log/links_domains.log
```

## Starting services

Now we should be ready to restart (or start) all the services. You can run the
following:

```
root ~ # service nginx restart
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
Stopping nginx.
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
Starting nginx.
root ~ # service caddy restart
Stopping caddy... done
Starting caddy... done
Log: /var/log/caddy/caddy.log
root ~ # supervisorctl udpate
adding new process: links
links: started
adding new process: links-api
links-api: started
adding new process: links-short
links-short: started
adding new process: links-list
links-list: started
adding new process: links-domains
links-domains: started
root ~ #
```

Note: The supervisorctl `update` command is just to let the daemon know there
are new commands configured. In the future, to restart a running supervisord
service, you would use the `restart` command. For example: `supervisorctl
restart links-api`. See the supervisord docs for more info.

## Done.

You should be all setup and running now. Try loading your root domain in a
browser. It may have a slight delay as the SSL certificate is generated