NextCloud and Cloudflare Tunnels

Believe me! This his good, wholesome fun!

This write up are instructions on how to successfully set up Nextcloud and cloudflare tunnels resulting, hopefully, in

  • working Cockpit

  • working NextCloud

  • well behaved iOS and Android apps.

  • persistent, authorized tunnels

  • a pod that will start automagically after a reboot

First, lets start with Fedora server 39. yeah.. I've got the exact same setup on Fedora server 38 - with the exception that SELinux works perfectly on 38. Not so much on 39. Is it fixable? Yes, yes it is. It's just annoying.

📣
Set your SELinux to permissive for this installation. You can always turn it back on when and if you have gotten it right.

Anyways. Why Fedora server? Because of Cockpit and podman. Not much to it. I happen to like fedora. Get acquainted with Cockpit. It will take some time and will be well worth it. And no matter what anyone may or may not tell you. Cockpit works, it works in production. It is not particularly fragile. It's a good piece of tech. Use it.

This installation guide requires you to get a free Cloudflare account and a domain. You might not need a domain, but this tutorial assumes that you've got one.

Prepare Cloudflare by adding your domain to the Cloudflare dashboard -> Websites. If you haven't got a domain I'm certain Cloudflare can provide you with one. The docs should be able to tell you how to do this.

Cloudflared

Download cloudflared from this page https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/

I'd recommend downloading the binary version and stuffing it in ~/.local/bin with a chmod +x to make cloudflared executable. No one but you should need to have access to it.

$ cloudflared login # follw the instructions.
# after successfull authentication you will get a certificate,
# and an ID. Take note of both
$ cloudflared tunnel create nextcloud # tunnel name can be anything
$ cloudflared tunnel route dns <your-tunnel-id> <subdomain>
# You did set up Cloudflare to host your domain.. right.
# chose a subdomain.. cloud, for example. That's imaginative.

Create a new file config.yml - this can be placed anywhere. Your home folder is a good choice (It will eventually get copied to /etc/cloudflared/config.yml)

# contents of config.yml
tunnel: <your-tunnel-id>
credentials-file: /home/<you>/.cloudflared/<your-tunnel-id>.json

Ingress:
    - hostname: subdomain.yourdomain.com
      service: http://localhost:8080
    - hostname: cockpit.yourdomain.com
      service: http://localhost:9090
      originRequest:
        noTLSVerify: true

Let's detour and fix Cockpit. As root, create the file /etc/cockpit/cockpit.conf

[WebService]
Origins = https://cockpit.yourdomain.com wss://cockpit.yourdomain.com
ProtocolHeader = X-Forwarded-Proto
AllowUnencrypted = true

This will let cockpit be proxied without error through the cloudflared tunnel. Remember to restart cockpit.

$ sudo systemctl restart cockpit.service

We're mostly done with the cloudflared stuff.. just one more thing.

$ sudo /home/<you>/.local/bin/cloudflared \
--config /path/to/config.yml service install # as mentioned, this will
# copy your config.yml to /etc/cloudflared/config.yml
$ sudo systemctl start cloudflared
$ sudo systemctl status cloudflared
$ sudo systemctl enable cloudflared

Now our cloudflared tunnel configuration is installed and enabled. It will survive a reboot. Excellent!

On to NextCloud.

This is the Kube file that will create the pod with all the containers.

Be sure to change out the various passwords and correct the paths, exchanging <you> with your actual account name (I'm trying to make this as beginner friendly as possible, seriously).

# nextcloud.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nextcloud
  name: nextcloud
spec:
  containers:
    - name: db
      args:
        - --transaction-isolation=READ-COMMITTED
        - --binlog-format=ROW
      command:
        - docker-entrypoint.sh
      env:
        - name: MYSQL_ROOT_PASSWORD
          value: <secret password>
        - name: MYSQL_DATABASE
          value: nextcloud
        - name: MYSQL_USER
          value: nextcloud
        - name: MYSQL_PASSWORD
          value: <secret password>
      image: docker.io/library/mariadb:latest
      securityContext:
        allowPrivilegeEscalation: true
        capabilities:
          drop:
            - CAP_MKNOD
            - CAP_NET_RAW
            - CAP_AUDIT_WRITE
        privileged: false
        readOnlyRootFilesystem: false
        seLinuxOptions: {}
      volumeMounts:
        - mountPath: /var/lib/mysql:Z
          name: home-<you>-containers-nextcloud-data-db
      workingDir: /
    - name: app
      env:
        - name: REDIS_HOST_PASSWORD
          value: <secret password>
        - name: MYSQL_HOST
          value: 127.0.0.1
        - name: MYSQL_DATABASE
          value: nextcloud
        - name: REDIS_HOST
          value: 127.0.0.1
        - name: MYSQL_USER
          value: nextcloud
        - name: MYSQL_PASSWORD
          value: <the mysql password>
      image: docker.io/library/nextcloud:latest
      resources: {}
      ports:
        - containerPort: 80
          hostPort: 8080
          protocol: TCP
      securityContext:
        allowPrivilegeEscalation: true
        capabilities:
          drop:
            - CAP_MKNOD
            - CAP_NET_RAW
            - CAP_AUDIT_WRITE
        privileged: false
        readOnlyRootFilesystem: false
        seLinuxOptions: {}
      volumeMounts:
        - mountPath: /var/www/html:Z
          name: home-<you>-containers-nextcloud-data-html
      workingDir: /var/www/html
    - name: redis
      command:
        - redis-server
        - --requirepass
        - <secret password>
      image: docker.io/library/redis:alpine
      resources: {}
      securityContext:
        allowPrivilegeEscalation: true
        capabilities:
          drop:
            - CAP_MKNOD
            - CAP_NET_RAW
            - CAP_AUDIT_WRITE
        privileged: false
        readOnlyRootFilesystem: true
        seLinuxOptions: {}
      volumeMounts:
        - mountPath: /tmp:Z
          name: tmpfs
        - mountPath: /var/tmp:Z
          name: tmpfs
        - mountPath: /run:Z
          name: tmpfs
      workingDir: /data
    - name: cron
      image: docker.io/library/nextcloud:latest
      command: ["/cron.sh"]
      resources: {}
      securityContext:
        allowPrivilegeEscalation: true
        capabilities:
          drop:
            - CAP_MKNOD
            - CAP_NET_RAW
            - CAP_AUDIT_WRITE
        privileged: false
        readOnlyRootFilesystem: false
        seLinuxOptions: {}
      volumeMounts:
        - mountPath: /var/www/html:Z
          name: home-<you>-containers-nextcloud-data-html
      workingDir: /var/www/html
  restartPolicy: Always
  volumes:
    - hostPath:
        path: /home/<you>/containers/nextcloud/data/db
        type: Directory
      name: home-<you>-containers-nextcloud-data-db
    - hostPath:
        path: /home/<you>/containers/nextcloud/data/html
        type: Directory
      name: home-<you>-containers-nextcloud-data-html
    - hostPath:
        path: tmpfs
        type: DirectoryOrCreate
      name: tmpfs

Now, create the necessary directories.

$ cd $HOME; \
mkdir -p containers/nextcloud/data/html ;\
mkdir -p containers/nextcloud/data/db

Great, nearly there. We will be running this as rootless containers, ie. as an unprivileged user (you).. and we will run into trouble for not being allowed to create ports below 1024. So we need to fix that.

# edit /etc/sysctl.conf as root
# add this line at the end of the file.
net.ipv4.ip_unprivileged_port_start=80

You can now either chose to reboot or

$ sudo sysctl net.ipv4.ip_unprivileged_port_start=80

Which will temporarily let you use ports below 1024 without rebooting.

It's time to create the pod.

$ # remember, do this a you, the user. Not root!
$ podman kube play path/to/nexcloud.yaml

This will churn along for quite some time depending on your hardware and internet connection. With fair winds and Odins luck (which you really shouldn't put to much faith in) everything just works and

$ podman pod ps
POD ID        NAME        STATUS      CREATED            INFRA ID      # OF CONTAINERS
96exxxxxxxxx  nextcloud   Running     About an hour ago  6adxxxxxxxxx  5
$ podman ps
6ade3c848dc4  localhost/podman-pause:4.9.0-1706090847                        About an hour ago  Up About an hour  0.0.0.0:8080->80/tcp  96e88382ac54-infra
59865dbccd75  docker.io/library/mariadb:latest         --transaction-iso...  About an hour ago  Up About an hour  0.0.0.0:8080->80/tcp  nextcloud-db
f015ddcdb8db  docker.io/library/nextcloud:latest       apache2-foregroun...  About an hour ago  Up About an hour  0.0.0.0:8080->80/tcp  nextcloud-app
1a22384699bf  docker.io/library/redis:alpine                                 About an hour ago  Up About an hour  0.0.0.0:8080->80/tcp  nextcloud-redis
12b1bb3f2e0e  docker.io/library/nextcloud:latest                             About an hour ago  Up About an hour  0.0.0.0:8080->80/tcp  nextcloud-cron

Yeah.. Now, most stuff has configured it self. But there are .. rough edges.

You'll notice that the IOS and Android app won't be able to connect to your server, so lets fix that.

As root, edit this file /home/<you>/containers/nextcloud/data/html/config/config.php

/* Add the following structures to your file, 
*  update the scructures  if they allready exists
*/
  'trusted_proxies' => 
  array (
    0 => '<the ip address of your machine>',
  ),
  'overwriteprotocol' => 'https',
  'trusted_domains' => 
  array (
    0 => 'localhost',
    1 => '<the ip address of your machine>',
    2 => 'subdomain.yourdomain.com',
  ),

/* Also, find and update this variable */
'overwrite.cli.url' => 'http://subdomain.yourdomain.com'

Now, restart your pod.

Did you think we were done? We're not. We need to get the pod to start up again in case of catastrophic failure.. like a reboot or what ever. Podman switched to something called Quadlets .. not long ago. It's not difficult, but there's scarce information about how to use it with pods. Well.. not exactly - as long as you realize that pods are Kubes. In either case..

Create a directory

$ cd $HOME; mkdir -p .config/containers/systemd

In this directory you will create one file named nexcloud.kube

# $HOME/.config/containers/systemd/nextcloud.kube
[Unit]
Description=A kubernetes yaml based service
Before=local-fs.target
[Kube]
Yaml=/path/to/nextcloud.yaml
[Install]
WantedBy=multi-user.target default.target

Now run

$ loginctl enable-linger <you>  # for good measure
$ /usr/libexec/podman/quadlets -dryrun -user  # to test the .kube
$ systemctl --user daemon-reload  # this creates the nextcloud.service
$ systemctl --user start nextcloud.service

And, we're done.

Now you can enjoy the glory of Cockpit and NextCloud even after a reboot.

'till next time.