Archive for October, 2026

Goal: Make OpenCloud available locally (behind a NAT-router) and from outside.
Use docker with Traefik; with ipv4 and ipv6.
Only OpenCloud is accessible from outside; other https services running as docker containers are only available from inside.

Required (using QTS 5.2.10):

  • cross compile linux kernel module ip6table_nat.ko (reference) and load it by autorun.sh
  • edit .qpkg/container-station/etc/docker.json and set
    “ip6tables”: true
  • split dns to access opencloud from inside and outside

Used:

  • Dockhand to manage docker containers and stacks
  • Filebrowser Quantum to edit config files
    (version 2.1.3-beta was used to write this blog post)
  • ssh access for qnap admin user
  • mc as text editor when using ssh
  • Entware to install mc
  • Let’s Encrypt wildcard certificates created by Traefik using DNS-01 Challenge
  • a DNS-Provider supported by lego (ACME client and ACME library used by Traefik)
  • configure Traefik with multiple https EntryPoints
  • a dynamic dns provider with support for ipv6 (AAAA records)
  • a DNS server with A records pointing to YOURDOMAIN.TLD and to *.YOURDOMAIN.TLD with the ipv4 address of qnap ts-216g

Detailed Steps

Warning: The configuration described below is meant for a local network with trusted users!

  • The Traefik dashboard and Dockhand, the docker management web interface run without password protection.

QNAP-Configuration

  • Enable ipv6 (controlpanel/network/adapter): stateless, slaac without secret key. (Make sure your router firewall is blocking ipv6 access from outside)
  • Enable ssh
  • Enable admin user; (this asks for admin password; keep a secure copy of this password.)
  • Create (non admin) user container
    • which gets uid:gid=1001:100, if this is the first user created after running setup
    • to display uid, gid run by ssh command:
      id container
  • Enable autorun.sh (controlpanel/hardware)
  • Install Entware:
    • Download Entware
    • App Center / “+” (upper right corner) / Install manually
    • login as admin by ssh: opkg update; opk install mc
  • Install ContainerStation with default shared folder /Container
    • configure ContainerStation with default settings
    • with QTS 5.2.10 (ipv6 is not available as builtin feature)
    • docker compose files are located in subfolders of
      /share/Container/container-station-data/application/
  • Move https qnap webadmin to port 4443 (controlpanel/system/general settings)

Docker ipv6 on QNAP

  • ssh into qnap and download the compiled version of ip6table_nat.ko
    (kernel version 5.10.60-qnap; md5 sum 8bca1a42748535c60a0ca0835a766b8c):
    wget https://spblinux.de/qnap/ts-216g/ip6table_nat.ko
    (If you prefer to compile this module yourself, see instructions at the bottom of this page.)
    • modprobe ip6tables.ko
    • insmod ./ip6table_nat.ko
  • If both modules load successfully docker ipv6 can be enabled.
    • use mc to navigate to
      /share/CACHEDEV1_DATA/.qpkg/container-station/etc/
    • and edit docker.json
      • replace: “ip6tables”: false
      • by: “ip6tables”: true
    • restart ContainerStation
      • /etc/init.d/container-station.sh stop
      • /etc/init.d/container-station.sh start
  • Create ipv6 enabled docker network traefik-net
    • docker network create –ipv6 traefik-net
    • (if command docker is not found, exit from ssh and login again.)
    • If traefik-net has been created successfully docker runs with ipv6 support:
      • docker network inspect traefik-net
  • Make docker ipv6 support permanent:
    • mount the config partition of qnap flash storage
      • /etc/init.d/init_disk.sh mount_flash_config
      • cd /tmp/nasconfig_tmp/
      • touch autorun.sh
      • chmod +x autorun.sh
      • copy ip6table_nas.ko to /tmp/nasconfig_tmp/
      • open autorun.sh with mc editor and paste the code below into this file
      • save autorun.sh
      • /etc/init.d/init_disk.sh umount_flash_config
      • make sure autorun.sh is enabled in controlpanel/hardware
      • reboot the qnap nas and check if ip6table_nat.ko has been loaded
        • lsmod | grep ip6
        • docker network inspect traefik-net
#!/bin/sh
#
# to edit this file use:
#   /etc/init.d/init_disk.sh mount_flash_config
#   edit /tmp/nasconfig_tmp/autorun.sh
#   /etc/init.d/init_disk.sh umount_flash_config
#
modprobe ip6_tables
[ -d /tmp/nasconfig_tmp2 ] || mkdir /tmp/nasconfig_tmp2
mount /dev/mmcblk0p5  /tmp/nasconfig_tmp2
[ -f /tmp/nasconfig_tmp2/ip6table_nat.ko ] && insmod /tmp/nasconfig_tmp2/ip6table_nat.ko
umount /dev/mmcblk0p5  /tmp/nasconfig_tmp2

Install Traefik with socketproxy

Use ssh as admin user:

docker-compose.yml
(replace 192.168.224.252 by your qnap ipv4 address)

services:
  traefik:
    env_file:
      - path: /share/Container/container-station-data/application/traefik/.env
    image: traefik:v3.7
    container_name: traefik
    restart: always
    ports:
      # web, webs is set as default
      - 192.168.224.252:80:80/tcp # web
      - 192.168.224.252:443:443/tcp # webs
      - 192.168.224.252:443:443/udp # https http3 quic: webs
      # label: - traefik.routers.ROUTERNAME.entrypoints=web2,webs2
      # - 192.168.226.252:80:81/tcp # web2
      - 192.168.224.252:65480:81/tcp # web2, limit to 1 network adapter
      #- 192.168.226.252:443:444/tcp # webs2
      - 192.168.224.252:65443:444/tcp # webs2, limit to 1 network adapter
       # label: - traefik.routers.ROUTERNAME.entrypoints=web2,webs2,webs3
    - "[::]:443:445/tcp" # webs3, limit to ipv6, all network adapters
      #- 192.168.226.252:443:444/udp # https http3 quic: webs2
      - 8081:8080 # http api dashboard; also at https://YOURDOMAIN.TLD
    volumes:
      #- /var/run/docker.sock:/var/run/docker.sock:ro # better use socket-proxy instead
      - /share/Container/container-station-data/application/traefik:/etc/traefik/ # put the provided traefik.yml and fileConfig.yml files at this location
      - /share/Container/container-station-data/application/traefik/logs:/logs
    environment:
      - TZ=Europe/Zurich
    labels:
      - traefik.enable=true
      - traefik.http.routers.api.rule=Host(`YOURDOMAIN.TLD`) # Define the (sub)domain for the traefik dashboard.
      - traefik.http.routers.api.service=api@internal # Enable Traefik API.
      - traefik.http.routers.traefik.middlewares=rate-limit@file
      #- traefik.http.routers.api.middlewares=local-ipwhitelist@file,basic-auth@file # protect dashboard with basic auth and restrict access to private class subnets only
      #- traefik.http.middlewares.basic-auth-global.basicauth.users=admin:$$apr1$$epoKf5li$$QfTMJZOCS/halv3CiIUEu0 # protect the traefik dashboard by basic auth (pw=password)
    # extra_hosts:
    #  - host.docker.internal:172.17.0.1
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik-net
      - socketproxy-net
  socket-proxy:
    image: lscr.io/linuxserver/socket-proxy:latest
    container_name: socket-proxy
    environment:
      - CONTAINERS=1
      - EVENTS=1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - socketproxy-net
    restart: always
    labels:
      - com.centurylinklabs.watchtower.enable=false
    read_only: true
    tmpfs:
      - /run
networks:
  traefik-net:
      external: true # docker network ls; docker network create --ipv6 traefik-net
  socketproxy-net:    
    internal: true

traefik.yml

# Traefik global configuration
global:
  checkNewVersion: true
  sendAnonymousUsage: false

# Enable traefik ui dashboard
api:
  dashboard: true
  insecure: true

# Log level INFO|DEBUG|ERROR
log:
  level: INFO

# crowdsec bouncer
#experimental:
#  plugins:
#    bouncer:
#      moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
#      version: v1.7.1

#metrics:
#  influxDB2:
#     address: http://influxdb2:8086
#     token: my-secure-token-secret
#     org: influx-org
#     bucket: influx-bucket
#     addEntryPointsLabels: true
#     addRoutersLabels: true
#     addServicesLabels: true
#     pushInterval: 60s  

# Configuring Multiple Filters
accessLog:
  filePath: "/logs/traefik.log"
  format: json
  filters:
    statusCodes:
      - "200-299" # log successful http requests
      - "300-399" # log redirect http requests
      - "400-599" # log failed http requests
    #retryAttempts: true
    #minDuration: "10ms"
  # collect logs as in-memory buffer before writing into log file
  bufferingSize: 0
  fields:
    headers:
      defaultMode: drop # drop all headers per default
      names:
          User-Agent: keep # log user agent strings

# The setting below is to allow insecure backend connections.  
serverTransport:
  insecureSkipVerify: true

# Traefik entrypoints (network ports) configuration
entryPoints:
#    asDefault: true # in use unless changed by label traefik.http.routers.ROUTERNAME.entrypoints=web,webs
  # Not used in apps, but redirect everything from HTTP to HTTPS
  web:
    asDefault: true
    address: :80
#    forwardedHeaders:
    http:
      # https://doc.traefik.io/traefik/migrate/v3/#underscoreheadersstrategy
      underscoreHeadersStrategy: delete
      redirections:
        entryPoint:
          to: webs
          scheme: https
  web2:
    address: :81
#    forwardedHeaders:
    http:
      # https://doc.traefik.io/traefik/migrate/v3/#underscoreheadersstrategy
      underscoreHeadersStrategy: delete
      redirections:
        entryPoint:
          to: webs2
          scheme: https
  web3:
    address: "[::]:82"
#    forwardedHeaders:
    http:
      # https://doc.traefik.io/traefik/migrate/v3/#underscoreheadersstrategy
      underscoreHeadersStrategy: delete
      redirections:
        entryPoint:
          to: webs3
          scheme: https

  # HTTPS endpoint, with domain wildcard
  webs:
    asDefault: true
    address: :443
    forwardedHeaders:
      # reuse list of Cloudflare Trusted IP's above for HTTPS requests
#      trustedIPs: *trustedIps
    # enable HTTP3 QUIC via UDP/443
    http3:
      advertisedPort: '443'      
    http:
      # https://doc.traefik.io/traefik/migrate/v3/#underscoreheadersstrategy
      aliasHeadersStrategy: delete
      # use dynamically generated self-signed certificate
      #tls: {}
      # or use a pre-defined certificate resolver from below
      # if so, remove above line with tls: {}
      tls:
        certResolver: myresolver-dns
        domains:
          - main: YOURDOMAIN.TLD # change this to your proxy domain
            sans:
              - '*.YOURDOMAIN.TLD' # change this to your proxy domain
      middlewares:
        - security-headers@file # reference to a dynamic middleware for setting http security headers per default
  webs2:
    address: :444
    forwardedHeaders:
      # reuse list of Cloudflare Trusted IP's above for HTTPS requests
#      trustedIPs: *trustedIps
    # enable HTTP3 QUIC via UDP/443
    #http3:
    #  advertisedPort: '444'      
    http:
      # https://doc.traefik.io/traefik/migrate/v3/#underscoreheadersstrategy
      aliasHeadersStrategy: delete
      # use dynamically generated self-signed certificate
      #tls: {}
      # or use a pre-defined certificate resolver from below
      # if so, remove above line with tls: {}
      tls:
        certResolver: myresolver-dns
        domains:
          - main: YOURDOMAIN.TLD # change this to your proxy domain
            sans:
              - '*.YOURDOMAIN.TLD' # change this to your proxy domain
      middlewares:
        - security-headers@file # reference to a dynamic middleware for setting http security headers per default
  webs3:
    address: "[::]:445"
    forwardedHeaders:
      # reuse list of Cloudflare Trusted IP's above for HTTPS requests
#      trustedIPs: *trustedIps
    # enable HTTP3 QUIC via UDP/443
    #http3:
    #  advertisedPort: '444'      
    http:
      # https://doc.traefik.io/traefik/migrate/v3/#underscoreheadersstrategy
      aliasHeadersStrategy: delete
      # use dynamically generated self-signed certificate
      #tls: {}
      # or use a pre-defined certificate resolver from below
      # if so, remove above line with tls: {}
      tls:
        certResolver: myresolver-dns
        domains:
          - main: YOURDOMAIN.TLD # change this to your proxy domain
            sans:
              - '*.YOURDOMAIN.TLD' # change this to your proxy domain
      middlewares:
        - security-headers@file # reference to a dynamic middleware for setting http security headers per default

providers:
  providersThrottleDuration: 2s

  # File provider for connecting things that are outside of docker / defining middleware
  file:
    filename: /etc/traefik/fileConfig.yml
    watch: true

  # Docker provider for connecting all apps that are inside of the docker network
  docker:
    watch: true
    network: traefik-net # add Your Docker Network Name Here
    endpoint: "tcp://socket-proxy:2375" # use socket-proxy for secure access to docker api
    defaultRule: "Host(`{{ index .Labels \"com.docker.compose.service\"}}.YOURDOMAIN.TLD`)" # subdomain==service name; change 'YOURDOMAIN.TLD' to your proxy domain
#    defaultRule: "Host(`{{ .Name }}.YOURDOMAIN.TLD`)" # subdomain==SERVICENAME-CONTAINERNAME; change 'YOURDOMAIN.TLD' to your proxy domain
    exposedByDefault: false # only containers with label traefik.enable=true are routed by traefik

# Use letsencrypt to generate ssl certificates
certificatesResolvers:
  # ACME HTTP CHALLENGE
  myresolver-http:
    acme:
      email: osth@sunrise.ch  # the email address used for ssl certificate registration
      storage: /etc/traefik/acme.json
      httpChallenge: # acme http challenge; requires port 80 and proper dns entries
        entryPoint: web # specify the entry point for the HTTP challenge (adjust if needed)
  # ACME DNS CHALLENGE
  myresolver-dns:
    acme:
      email: YOU@MAIL.ORG  # the email address used for ssl certificate registration
      storage: /etc/traefik/acme.json
      dnsChallenge: # acme dns challenge; requires api token of dns provider
#        provider: YOURDNSHOST2
        provider: desec
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

fileConfig.yml

http:

  ## EXTERNAL ROUTING EXAMPLE - Only use if you want to proxy something manually ##
#  routers:
#    test3:
#      entryPoints:
#        - https
#        - http
#      rule: 'Host(`test3.YOURDOMAIN.TLD`)'
#      service: test3
#      middlewares:
#        - "local-ipwhitelist@file"
  #routers:
  #  homeassistant:
  #    entryPoints:
  #      - https
  #      - http
  #    rule: 'Host(`ha.example.com`)'
  #    service: homeassistant
  #    middlewares:
  #      - "local-ipwhitelist@file"

  #  pve:
  #    entryPoints:
  #      - https
  #      - http
  #    rule: 'Host(`pve.example.com`)'
  #    service: pve
  #    middlewares:
  #      - "local-ipwhitelist@file"

  ## SERVICES EXAMPLE ##
#  services:
#     test3:
#       loadBalancer:
#         serversTransport: insecureTransport
#         servers:
#           - url: https://t4.YOURDOMAIN.TLD
  #  homeassistant:
  #    loadBalancer:
  #      serversTransport: insecureTransport
  #      servers:
  #        - url: http://192.168.1.10:8123

  #  pve:
  #    loadBalancer:
  #      serversTransport: insecureTransport
  #      servers:
  #        - url: https://192.168.1.20:8006

  # allow self-signed certificates for proxied web services
  serversTransports:
    insecureTransport:
      insecureSkipVerify: true

  ## MIDDLEWARES ##
  middlewares:
    # UNUSED remove X-Frame-Options header by plugin because of only office
    #custom-allow-iframe:
    #  plugin:
    #    htransformation:
    #      Rules:
    #        - Header: 'X-Frame-Options'
    #          Name: 'allow iframe'
    #          Type: 'Del'
    #          SetOnResponse: 'True'

    # Only Allow Local networks
    local-ipwhitelist:
      ipAllowList:
        sourceRange:
          - 127.0.0.1/32 # localhost
          - 10.0.0.0/8 # private class A
          - 172.16.0.0/12 # private class B
          - 192.168.0.0/16 # private class C
        #ipstrategy: # enable this when cloudflare proxy in use
        #  depth: 1 # enable this when cloudflare proxy in use

    # Security headers
    security-headers:
      headers:
        customResponseHeaders: # field names are case-insensitive
          #X-Robots-Tag: "all,noarchive,nosnippet,notranslate,noimageindex"
          Server: "" # prevent version disclosure
          X-Powered-By: "" # prevent version disclosure
          X-Forwarded-Proto: "https"
          #Permissions-Policy: "accelerometer=(), autoplay=(), camera=(), display-capture=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), storage-access=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()"
          #Cross-Origin-Embedder-Policy: "unsafe-none"
          #Cross-Origin-Opener-Policy: "same-origin"
          #Cross-Origin-Resource-Policy: "same-site"
          #X-Permitted-Cross-Domain-Policies: "none"
        sslProxyHeaders:
          X-Forwarded-Proto: "https"
        hostsProxyHeaders:
          - "X-Forwarded-Host"
        #customRequestHeaders:
        #  X-Forwarded-Proto: "https"
        contentTypeNosniff: true # X-Content-Type-Options
        customFrameOptionsValue: "SAMEORIGIN" # X-Frame-Options
        browserXssFilter: false # X-XSS-Protection; deprecated
        referrerPolicy: "strict-origin-when-cross-origin" # Referrer-Policy
        forceSTSHeader: true # HTTP-Strict-Transport-Security (HSTS)
        stsIncludeSubdomains: true # HTTP-Strict-Transport-Security (HSTS)
        stsSeconds: 63072000 # HTTP-Strict-Transport-Security (HSTS)
        stsPreload: true # HTTP-Strict-Transport-Security (HSTS)
        #contentSecurityPolicy: "default-src 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content" # Content-Security-Policy (CSP)

    # Authelia guard
    #authelia:
    #  forwardauth:
    #    address: http://authelia:9091/api/authz/forward-auth # replace example.com with your domain name
    #    trustForwardHeader: true
    #    authResponseHeaders:
    #      - Remote-User
    #      - Remote-Groups
    #      - Remote-Name
    #      - Remote-Email

    #crowdsec:
    #  plugin:
    #    bouncer:
    #      enabled: true
    #      defaultDecisionSeconds: 60
    #      crowdsecMode: live
    #      crowdsecAppsecEnabled: false
    #      crowdsecAppsecHost: crowdsec:7422
    #      crowdsecAppsecFailureBlock: true
    #      crowdsecAppsecUnreachableBlock: true
    #      crowdsecLapiKey: FIXME-LAPI-KEY # <--- replace this later
    #      crowdsecLapiHost: crowdsec:8080
    #      crowdsecLapiScheme: http
    #      crowdsecLapiTLSInsecureVerify: false
    #      banFilePath: /ban.html
    #      captchaProvider: hcaptcha
    #      captchaSiteKey: YourSiteKey
    #      captchaSecretKey: YourSecretKey
    #      captchaGracePeriodSeconds: 1800
    #      captchaFilePath: /captcha.html    
    #      forwardedHeadersTrustedIPs:
    #        # private class ranges
    #        - 10.0.0.0/8
    #        - 172.16.0.0/12
    #        - 192.168.0.0/16
    #        ## cloudflare ipv4
    #        #- 103.21.244.0/22
    #        #- 103.22.200.0/22
    #        #- 103.31.4.0/22
    #        #- 104.16.0.0/13
    #        #- 104.24.0.0/14
    #        #- 108.162.192.0/18
    #        #- 131.0.72.0/22
    #        #- 141.101.64.0/18
    #        #- 162.158.0.0/15
    #        #- 172.64.0.0/13
    #        #- 173.245.48.0/20
    #        #- 188.114.96.0/20
    #        #- 190.93.240.0/20
    #        #- 197.234.240.0/22
    #        #- 198.41.128.0/17
    #        ## cloudflare ipv6
    #        #- 2400:cb00::/32
    #        #- 2606:4700::/32
    #        #- 2803:f800::/32
    #        #- 2405:b500::/32
    #        #- 2405:8100::/32
    #        #- 2a06:98c0::/29
    #        #- 2c0f:f248::/32
    #      clientTrustedIPs:
    #        # private class ranges
    #        - 10.0.0.0/8
    #        - 172.16.0.0/12
    #        - 192.168.0.0/16

    # rate limiting
    rate-limit:
      rateLimit:
        average: 50
        period: 1s
        burst: 100
        # uncomment the next lines if behind cloudflare
        #sourceCriterion:
        #  requestHeaderName: CF-Connecting-IP        
    onlyoffice-ratelimit:
      rateLimit:
        average: 200
        period: 1s
        burst: 400


    # basic auth popup
    basic-auth:
      basicAuth:
        # https://hostingcanada.org/htpasswd-generator/
        users: "admin:$apr1$76boeqjp$tVQUCSSQky300AS.kgbZX/" # admin:password

# Only use secure ciphers - https://ssl-config.mozilla.org/#server=traefik&version=2.9&config=intermediate&guideline=5.6
tls:
  options:
    default:
      #sniStrict: true # prevents leaking default cert; see https://doc.traefik.io/traefik/v2.2/https/tls/#strict-sni-checking
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

  • use ssh and
    • cd /share/Container/container-station-data/application/traefik
    • touch .env
    • put your dns credentials into this file, e.g.
      DESEC_TOKEN=’123456789abcdefgh’
    • reference
  • try to start traefik
    • cd /share/Container/container-station-data/application/traefik
    • docker compose up -d
    • docker compose logs -f
      (ctrl + c to stop log viewer)
    • on the first run it will take a while until traefik gets certificates from Let’s Encrypt
  • open https://YOURDOMAIN.TLD

Install Dockhand

  • use ssh and
    • cd /share/Container/container-station-data/application/
    • mkdir dockhand
    • cd dockhand
    • touch .env
    • use mc editor to write into file .env
      DATA_DIR=/share/Container/container-station-data/application/dockhand
      STACKS_DIR=/share/Container/container-station-data/application
    • touch docker-compose.yml
      and enter the code below into file docker-compose.yml
    • docker compose up -d
    • docker compose logs -f
    • open https://dockhand.YOURDOMAIN.TLD and add environment “/var/run/docker.sock”
    • use Dockhand to create other (docker compose) Stacks
services:
  dockhand:
    env_file:
      - path: /share/Container/container-station-data/application/dockhand/.env
    image: fnsys/dockhand:latest
    container_name: dockhand
    restart: unless-stopped
    user: 0:0
    ports:
      - 127.0.0.1:3000:3000
    # use https://dockhand.YOURDOMAIN.TLD   
    networks:
      - traefik-net
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - dockhand_data:/app/data
      - /share/Container/container-station-data/application/dockhand:/share/Container/container-station-data/application/dockhand
      - /share/Container/container-station-data/application:/share/Container/container-station-data/application
    labels:
      - traefik.enable=true
      - traefik.http.services.dockhand.loadbalancer.server.port=3000
      # Assign middleware: default rate-limit,...
      - traefik.http.routers.dockhand.middlewares=rate-limit@file
volumes:
  dockhand_data: null
networks:
  traefik-net:
    external: true

First test case: hello world with nginx

  • logged in by ssh
    • cd /share/Container/container-station-data/application
    • mkdir test1
    • cd test1
    • mkdir html
    • echo “hello world” >html/index.html
    • mkdir etcnginxconfd
    • touch etcnginxconfd/test1.conf
    • use mc to edit test1.conf:
      server {
      listen 80;
      server_name: test1.YOURDOMAIN.TLD
      index index.html;
      root /usr/share/nginx/html;
      }
  • Use Dockhand / Stacks and create a new stack “test1”
    • the content of compose.yaml is shown below
  • “Create & Start” this stack
    and open https://test1.YOURDOMAIN.TLD
  • Debugging:
    • Dockhand / Stacks allows to view the logs
    • Dockhand / Containers allows to open a shell inside a running container
    • https://YOURDOMAIN.TLD: Traefik / HTTP Routers has to show
      Host(`test1.YOURDOMAIN.TLD`) with Entrypoint webs

compose.yaml:

services:
  test1: 
    image: nginx:alpine
    # ports:
    #  - "127.0.0.1:8083:80"
    # use https://test1.YOURDOMAIN.TLD
    networks:
      - traefik-net
    environment:
      - APP_ENV=${APP_ENV:-production}
    volumes:
#      - /share/YOURQNAPFOLDER/opt/test1/html:/usr/share/nginx/html:ro
      - /share/Container/container-station-data/application/test1/html:/usr/share/nginx/html:ro
      - /share/Container/container-station-data/application/test1/etcnginxconfd:/etc/nginx/conf.d
    restart: unless-stopped
    labels:
      - traefik.enable=true
      - traefik.http.services.test1.loadbalancer.server.port=80
      # - traefik.http.routers.test1.rule=Host(`t1.YOURDOMAIN.TLD`) # override default == SERVICENAME
      # Assign middleware: default rate-limit,...
      - traefik.http.routers.test1.middlewares=rate-limit@file
      - traefik.http.routers.test1.entrypoints=web,webs

networks:
   traefik-net:
     external: true

Testing external access with ipv4

  • ipv4 external access uses traefik entrypoint webs2 (traefik.yml)
    • which listens inside traefik-net on port 444
    • traefik/docker-compose.yml forwards this to QNAP_IP:65443
    • the nat-router has to forward internet ipv4 port 443 to QNAP_IP:65443
    • test1/compose.yaml defines if entrypoint webs2 is used by container test1:
      labels:
      – traefik.http.routers.test1.entrypoints=web,webs,webs2
    • click “Save & redeploy” if compose.yaml has been edited by Dockhand
    • traefik webinterface (https://YOURDOMAIN.TLD) shows in section routers, if test1.YOURDOMAIN.TLD is using webs2
  • test ipv4 external with linux:
    • required is a linux computer with internet access which is not connected to the local lan of QNAP_IPV4_ADDRESS
    • edit /etc/hosts and add the line:
      QNAP_IPV4_ADDRESS test1.YOURDOMAIN.TLD
    • wget -4 https:test1.YOURDOMAIN.TLD
    • this should fetch index.html
    • content of file index.html should be
      hello world
    • remove (or comment out with #) the line QNAP_IPV4_ADDRESS in /etc/hosts
  • do verify that https:test1.YOURDOMAIN.TLD is no longer accessible externally by ipv4 if webs2 is removed from labels
    • – traefik.http.routers.test1.entrypoints=web,webs

Testing external access with ipv6

  • ipv6 external access uses traefik entrypoint webs3 (traefik.yml)
    • which listens inside traefik-net on port 445
    • traefik/docker-compose.yml forwards this to ANY_IPV6_ADDRESS_ON_QNAP:443
      (TS-216G has 2 lan ports; the prefix part of the (non local) ipv6 addresses might change dynamically; so it should not be written into traefik/docker-compose.yml)
    • the (nat-)router has to allow external access by ipv6 to QNAP_IPV6_ADDRESS port 443
    • test1/compose.yaml defines if entrypoint webs3 is used by container test1:
      labels:
      – traefik.http.routers.test1.entrypoints=web,webs,webs3
    • click “Save & redeploy” if compose.yaml has been edited by Dockhand
    • traefik webinterface (https://YOURDOMAIN.TLD) shows in section routers, if test1.YOURDOMAIN.TLD is using webs3
  • test ipv6 external with linux:
    • required is a linux computer with internet access which is not connected to the local lan of QNAP_IP
    • edit /etc/hosts and add the line:
      QNAP_IPV6_ADDRESS test1.YOURDOMAIN.TLD
    • wget -6 https:test1.YOURDOMAIN.TLD
    • this should fetch index.html
    • content of file index.html should be
      hello world
    • remove (or comment out with #) the line QNAP_IPV6_ADDRESS in /etc/hosts
  • do verify that https:test1.YOURDOMAIN.TLD is no longer accessible externally by ipv6 if webs3 is removed from labels
    • – traefik.http.routers.test1.entrypoints=web,webs

DNS record for external access

  • if the domain name given by your dyndns provider is
    YOU.YOURDYNDNSPROVIDER.TLD
  • and if this name has both an ipv4 A record and an ipv6 AAAA record,
  • then to the existing A records of your dns provider for YOURDOMAIN.TLD
    • YOURDOMAIN.TLD QNAP_IPV4_ADRESS
    • *.YOURDOMAIN.TLD QNAP_IPV4_ADRESS
  • a CNAME record has to be added
    • test1.YOURDOMAIN.TLD YOU.YOURDYNDNSPROVIDER.TLD
  • use dig or nslookup to test these settings
    • dig test1.YOURDOMAIN.TLD
    • dig test1.YOURDOMAIN.TLD AAAA
  • set both entrypoints, webs2 and webs3 in test1/compose.yaml:
    labels:
    – traefik.http.routers.test1.entrypoints=web,webs,webs2,webs3
  • finally open https://test1.YOURDOMAIN.TLD by browser on a computer which is not connected to your local lan.

Install Quantum Filebrowser as editor for container config files

  • logged in by ssh
    • cd /share/Container/container-station-data/application
    • mkdir filebrowser
    • cd filebrowser
    • mkdir data
    • touch config.yaml
    • use mc to edit config.yaml:
      the content of this file is shown below
  • Use Dockhand / Stacks and create a new stack “filebrowser”
  • the content of compose.yaml is shown below
  • “Create & Start” this stack
  • Use Dockhand / Stacks to display the log file of filebrowser
    • one of the first log lines displays the initial password for user admin
  • open https://filebrowser.YOURDOMAIN.TLD
    • login as user admin with password from log file
      and set your personal password;
      (filebrowser has write access to all docker compose stacks!)
  • The “home” directory of filebrowser points to
    /share/Container/container-station-data/application

config.yaml:

server:
  cacheDir: /home/filebrowser/data/tmp # using the data volume so it can persist across restarts
  sources:
    - path: /Docker # Do not use a root "/" directory or include the "/var" folder
      config:
        defaultEnabled: true
  logging:
    - output: "stdout"
      levels: "info|warning|error"  # add debug to see detailed logs

http:
  externalUrl: "https://filebrowser.YOURDOMAIN.TLD"
  internalUrl: "http://filebrowser:80"
  trustProxyHeaders: true   # v2.0.0+; on v1.4.x–v1.5.x use trustedHeaders list instead

#integrations:
#  office:
#    url: "https://onlyoffice.YOURDOMAIN.TLD"
#    internalUrl: "http://onlyoffice:80" # (optional)

compose.yaml:

services:
  filebrowser:
    image: gtstef/filebrowser:beta
    container_name: filebrowser
    user: 0:0 # running as user admin (like dockhand)
    volumes:
      - /share/Container/container-station-data/application:/Docker # Do not use a root "/" directory or include the "/var" folder
      - /share/Container/container-station-data/application/filebrowser/data:/home/filebrowser/data 
# IF config.yml is not in data/ but in /share/Container/container-station-data/application/filebrowser
# IT NEEDS extra volume for config.yaml
# and this location of config.yaml is defined by environment variable FILEBROWSER_CONFIG
      - /share/Container/container-station-data/application/filebrowser/config.yaml:/share/Container/container-station-data/application/filebrowser/config.yaml
    ports:
      - 127.0.0.1:8080:80 # exposes port 80 to host, the left-side number can be changed without config.yaml changes.
    # use https://filebrowser.YOURDOMAIN.TLD    
    restart: unless-stopped
    networks:
      - traefik-net
    environment:
      - FILEBROWSER_CONFIG=/share/Container/container-station-data/application/filebrowser/config.yaml
#      - FILEBROWSER_ONLYOFFICE_SECRET=${COMMON_SECRET} # set in .env file; created by openssl rand -base64 32
    labels:
      - traefik.enable=true
      - traefik.http.services.filebrowser.loadbalancer.server.port=80
      # Assign middleware: default rate-limit,required headers for onlyoffice integration
      - traefik.http.routers.filebrowser.middlewares=rate-limit@file,filebrowser-headers
      - traefik.http.middlewares.filebrowser-headers.headers.accessControlAllowOriginList=https://onlyoffice.YOURDOMAIN.TLD
      - traefik.http.middlewares.filebrowser-headers.headers.contentSecurityPolicy=frame-ancestors 'self' https://onlyoffice.YOURDOMAIN.TLD
networks:
  traefik-net:
      external: true

Install OpenCloud 7.2 stable

For some reason the docker container of OpenCloud does not include these config files (source):

apps.yaml

maps:
  config:
    folderViewEnabled: false

csp.yaml

directives:
  child-src:
    - '''self'''
  connect-src:
    - '''self'''
    - 'blob:'
    - 'https://${COMPANION_DOMAIN|companion.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
    - 'wss://${COMPANION_DOMAIN|companion.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
    - 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
    - 'https://${IDP_DOMAIN|keycloak.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
    - 'https://update.opencloud.eu/'
    - 'https://tile.openstreetmap.org/'
  default-src:
    - '''none'''
  font-src:
    - '''self'''
  frame-ancestors:
    - '''self'''
  frame-src:
    - '''self'''
    - 'blob:'
    - 'https://embed.diagrams.net/'
    # In contrary to bash and docker the default is given after the | character
    - 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
    - 'https://${EURO_OFFICE_DOMAIN|euro-office.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
    # This is needed for the external-sites web extension when embedding sites
    - 'https://docs.opencloud.eu'
  img-src:
    - '''self'''
    - 'data:'
    - 'blob:'
    - 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
    - 'https://tile.openstreetmap.org/'
    # In contrary to bash and docker the default is given after the | character
    - 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
    - 'https://${EURO_OFFICE_DOMAIN|euro-office.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
  manifest-src:
    - '''self'''
  media-src:
    - '''self'''
  object-src:
    - '''self'''
    - 'blob:'
  script-src:
    - '''self'''
    - '''unsafe-inline'''
    - 'https://${IDP_DOMAIN|keycloak.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
  style-src:
    - '''self'''
    - '''unsafe-inline'''
  worker-src:
    - "'self'"
    - 'blob:'

banned-password-list.txt

password
12345678
123
OpenCloud
OpenCloud-1

(If you want to follow the official setup instructions to downloads these config files into directory docker-compose/config/opencloud you have to install opkg install git git-http)

  • with qnap webinterface
    • create a shared folder YOURFOLDER
    • verify by ssh that this folder can be accessed by path
      /share/YOURFOLDER
    • give qnap user container read write access to this folder
      (opencloud container runs with uid:gid=1001:100=container:everyone)
    • verify by ssh that this folder can be accessed by path
      /share/YOURFOLDER
    • inside YOURFOLDER create the directories
      • opt
      • opt/opencloud
  • logged in by ssh configure shared folder YOURFOLDER
    • cd /share/YOURFOLDER/opt/opencloud
    • mkdir config
      (bind mounted configuration volume of opencloud; defined in .env by OC_CONFIG_DIR)
    • touch config/apps.yaml
    • touch config/csp.yaml
    • touch config/banned-password-list.txt
    • mkdir data
      (bind mounted data volume of opencloud; defined in .env by OC_DATA_DIR)
    • mkdir apps
      (bind mounted volume to install apps in opencloud; defined in .env by OC_APPS_DIR)
    • chown -R 1001:100 config data apps
    • use mc editor to fill apps.yaml, csp.yaml, banned-passwors-list.txt with the content shown above
  • logged in by ssh create the docker configuration files of opencloud (reference1, reference2)
    • cd /share/Container/container-station-data/application
    • mkdir opencloud
    • cd opencloud
    • touch docker-compose.yml
    • touch .env
    • touch dockhand.yml
    • mkdir external-traefik
    • touch external-traefik/opencloud.yml
  • Use Filebrowser to edit docker-compose.yml and external-traefik/opencloud.yml
    (see contents below)
  • Then use Dockhand / Stacks to create a new stack named opencloud and open dockhand.yml as compose file for Dockhand.
    • edit .env and dockhand.yml with Dockhand
      (see contents below)
    • Usage of dockhand.yml is a workaround because Dockhand does not support to merge multiple compose files by environment variable COMPOSE_FILE
  • Below are the contents of the configuration files.

docker-compose.yml:

services:
  opencloud:
    # renovate: depName=opencloudeu/opencloud-rolling
    image: ${OC_DOCKER_IMAGE:-opencloudeu/opencloud-rolling}:${OC_DOCKER_TAG:-7.5.0}
    # changelog: https://github.com/opencloud-eu/opencloud/tree/main/changelog
    # release notes: https://docs.opencloud.eu/opencloud_release_notes.html
    user: ${OC_CONTAINER_UID_GID:-0:0}
    networks:
      traefik-net:
    entrypoint:
      - /bin/sh
    # run opencloud init to initialize a configuration file with random secrets
    # it will fail on subsequent runs, because the config file already exists
    # therefore we ignore the error and then start the opencloud server
    command: ["-c", "opencloud init || true; opencloud server"]
#    command: ["-c", "sleep 36000"] # console access by dockhand while opencloud is not running: opencloud idm resetpassword
    environment:
      # enable services that are not started automatically
      OC_ADD_RUN_SERVICES: ${START_ADDITIONAL_SERVICES}
      OC_URL: https://${OC_DOMAIN:-cloud.opencloud.test}${TRAEFIK_PORT_HTTPS:+:}${TRAEFIK_PORT_HTTPS:-}
      OC_LOG_LEVEL: ${LOG_LEVEL:-info}
      OC_LOG_COLOR: "${LOG_PRETTY:-false}"
      OC_LOG_PRETTY: "${LOG_PRETTY:-false}"
      # do not use SSL between the reverse proxy and OpenCloud
      PROXY_TLS: "false"
      # INSECURE: needed if OpenCloud / reverse proxy is using self generated certificates
      OC_INSECURE: "${INSECURE:-false}"
      # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
      PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
      # demo users
      IDM_CREATE_DEMO_USERS: "${DEMO_USERS:-false}"
      # admin password
      IDM_ADMIN_PASSWORD: "${INITIAL_ADMIN_PASSWORD}"
      # email server (if configured)
      NOTIFICATIONS_SMTP_HOST: "${SMTP_HOST}"
      NOTIFICATIONS_SMTP_PORT: "${SMTP_PORT}"
      NOTIFICATIONS_SMTP_SENDER: "${SMTP_SENDER:-OpenCloud Notifications <notifications@cloud.opencloud.test>}"
      NOTIFICATIONS_SMTP_USERNAME: "${SMTP_USERNAME}"
      NOTIFICATIONS_SMTP_PASSWORD: "${SMTP_PASSWORD}"
      NOTIFICATIONS_SMTP_INSECURE: "${SMTP_INSECURE:-false}"
      NOTIFICATIONS_SMTP_AUTHENTICATION: "${SMTP_AUTHENTICATION}"
      NOTIFICATIONS_SMTP_ENCRYPTION: "${SMTP_TRANSPORT_ENCRYPTION:-none}"
      FRONTEND_ARCHIVER_MAX_SIZE: "10000000000"
      FRONTEND_CHECK_FOR_UPDATES: "${CHECK_FOR_UPDATES:-true}"
      PROXY_CSP_CONFIG_FILE_LOCATION: /etc/opencloud/csp.yaml
      # enable to allow using the banned passwords list
      OC_PASSWORD_POLICY_BANNED_PASSWORDS_LIST: banned-password-list.txt
      # control the password enforcement and policy for public shares
      OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD: "${OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD:-true}"
      OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD: "${OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD:-false}"
      OC_PASSWORD_POLICY_DISABLED: "${OC_PASSWORD_POLICY_DISABLED:-false}"
      OC_PASSWORD_POLICY_MIN_CHARACTERS: "${OC_PASSWORD_POLICY_MIN_CHARACTERS:-8}"
      OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS: "${OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS:-1}"
      OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS: "${OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS:-1}"
      OC_PASSWORD_POLICY_MIN_DIGITS: "${OC_PASSWORD_POLICY_MIN_DIGITS:-1}"
      OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS: "${OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS:-1}"
      # default language for services/WebUI; defaults to English, language code (ISO 639-1, e.g. de, en, fr)
      OC_DEFAULT_LANGUAGE: ${DEFAULT_LANGUAGE}
    volumes:
    # - ./config/opencloud/csp.yaml:/etc/opencloud/csp.yaml
    #  - ./config/opencloud/apps.yaml:/etc/opencloud/apps.yaml
    #  - ./config/opencloud/banned-password-list.txt:/etc/opencloud/banned-password-list.txt
      # configure the .env file to use own paths instead of docker internal volumes
      - ${OC_CONFIG_DIR:-opencloud-config}:/etc/opencloud
      - ${OC_DATA_DIR:-opencloud-data}:/var/lib/opencloud
      - ${OC_APPS_DIR:-./config/opencloud/apps}:/var/lib/opencloud/web/assets/apps
    logging:
      driver: ${LOG_DRIVER:-local}
    restart: always

volumes:
  opencloud-config:
  opencloud-data:

networks:
  traefik-net:
    external: true

dockhand.yml:

# dockhand start does not merge files using COMPOSE_FILE
# workaround: use this file dockhand.yml as stack compose file inside dockhand (browse to select it instead of docker-compose.yml)
# it *merges* the files with include (using path list)
# COMPOSE_FILE=docker-compose.yml:weboffice/euro-office.yml:external-traefik/opencloud.yml:external-traefik/euro-office.yml
include:
  - path:
    - ./docker-compose.yml
#    - ./weboffice/euro-office.yml
    - ./external-traefik/opencloud.yml
#    - ./external-traefik/euro-office.yml

external-traefik/opencloud.yml:

services:
  opencloud:
    environment:
      # bind to all interfaces
      PROXY_HTTP_ADDR: "0.0.0.0:9200"
    ports:
      # expose the opencloud server on localhost
      - "127.0.0.1:9200:9200"
    networks:
      - traefik-net
    labels:
      - traefik.enable=true
      - traefik.http.services.opencloud.loadbalancer.server.port=9200
      - traefik.http.routers.opencloud.rule=Host(`oc.YOURDOMAIN.TLD`) # Define the subdomain (defaults to SERVICENAME).
      - traefik.http.routers.opencloud.entrypoints=web,webs # default entrypoints web, webs
#      - traefik.http.routers.opencloud.entrypoints=web,webs,webs2 # webs with split dns+hairpin nat; webs2 external access (src nat)
#      - traefik.http.routers.opencloud.entrypoints=web,webs,webs2,webs3 # webs with split dns+hairpin nat; webs2 external access (src nat); webs3 ipv6
      - "traefik.http.middlewares.hsts-header.headers.stsSeconds=31536000"
      - "traefik.http.middlewares.hsts-header.headers.stsIncludeSubdomains=true"
      - "traefik.http.middlewares.hsts-header.headers.stsPreload=true"
      - "traefik.http.middlewares.hsts-header.headers.forceSTSHeader=true"
      # Assign middleware: default rate-limit,required headers for onlyoffice integration
      - traefik.http.routers.opencloud.middlewares=rate-limit@file,opencloud-headers,hsts-header
#      - traefik.http.middlewares.opencloud-headers.headers.accessControlAllowOriginList=https://euro-office.YOURDOMAIN.TLD,https://oc.YOURDOMAIN.TLD
#      - "traefik.http.middlewares.opencloud-headers.headers.contentSecurityPolicy=frame-ancestors 'self' https://euro-office.YOURDOMAIN.TLD"
      - traefik.http.middlewares.opencloud-headers.headers.accessControlAllowOriginList=https://oc.YOURDOMAIN.TLD
      - "traefik.http.middlewares.opencloud-headers.headers.contentSecurityPolicy=frame-ancestors 'self'"
#      - traefik.http.middlewares.opencloud-headers.headers.accessControlAllowOriginList=https://euro-office.YOURDOMAIN.TLD
#      - traefik.http.middlewares.opencloud-headers.headers.accessControlAllowOriginList=https://oc.YOURDOMAIN.TLD
# try to match issuer in https://oc.YOURDOMAIN.TLD/.well-known/openid-configuration: "issuer": "https://oc.YOURDOMAIN.TLD:443"
networks:
    traefik-net:
       external: true

.env (click on this link to show the content of this file)

First start of OpenCloud

  • logged in by ssh
    • cd /share/Container/container-station-data/application/opencloud
    • docker compose up -d
    • docker compose logs -f
      (use ctrl + c to stop logging)
  • webbrowser
    • open oc.YOURDOMAIN.TLD
    • the login page of Opencloud should open
    • user: admin
      password as defined in .env file by
      INITIAL_ADMIN_PASSWORD=YOUR1-2-3password
  • troubleshooting, if login fails with error 500
    • look for red errors (docker compose logs -f)
    • docker compose down
    • try to fix missing files
    • use mc to remove
      • config/opencloud.yaml
      • data/*
        (remove all subdirectories of data/)
    • docker compose up -d
  • use Dockhand / Stacks to stop and start opencloud
  • edit file external-traefik/opencloud.yml with Filebrowser:
    • traefik.http.routers.opencloud.entrypoints=… defines on which ports OpenCloud listens
      • web,webs: ipv4: 80 and 443 (http and https)
      • webs2: ipv4: 65443 (https)
      • webs3: ipv6: 443 (https)
  • web,webs are endpoints used inside the local network (Traefik Dashboard, Dockhand, Filebrowser, OpenCloud)
  • webs2 is meant to be accessed from external networks (OpenCloud)
  • webs3 is meant to be accessed from external networks by ipv6 (OpenCloud)

Archiving OpenCloud volumes – xattr required

  • archiving the data volume of Opencloud (configured by OC_DATA_DIR in .env)
    • builtin tar or rsync fail because OpenCloud uses extended attributes
    • opkg install tar rsync
      this installs newer versions of tar and rsync which support –xattr
    • to copy the data volume of OpenCloud to another directory
      • tar c –xattr -C SOURCEDIR . | tar x –xattr -C TARGETDIR
    • log message if extended attributes are missing:
      • error=”node.Xattr
    • rsync –version displays “capabilities: … xattr” if extended attributes are supported
      • rsync -X … uses extended attributes

NAT-Router configuration

  • Most NAT-Routers do not have static external ip addresses.
    • dynamic dns is required
    • port 443 of the external ipv4 address has to be forwarded to port 66543 of the ipv4 address of qnap.
    • if ipv6 should be used the dynamic dns provider has to support AAAA records
    • usually a script running on the router is required which sets this AAAA record:
      The ipv6 address as a combination of
      • the ipv6 prefix given by your internet provider
      • the ipv6 suffix given by ipv6 address of qnap
    • the same script has to set the firewall rule which allows external access exclusively on port 443 to this ipv6 address.

Hints to compile linux kernel module ip6table_nat.ko

  • use an x64 installation of Ubuntu 26.04 to cross compile
  • follow this reference
  • cross compile kernel and module
    • apt-get install make build-essential libncurses-dev bison flex libssl-dev libelf-dev
    • export ARCH=arm64
    • export CROSS_COMPILE=/YOURPATH/aarch64-QNAP-linux-gnu/bin/aarch64-QNAP-linux-gnu-
    • cd /YOURPATH/GPL_QTS/src/linux-5.10/
    • cp ../../kernel_cfg/TS-X16/linux-5.10-arm64.config ./.config
    • make oldconfig
    • make menuconfig
      • Networking support > Networking options > Network packet filtering framework (Netfilter) > IPv6: Netfilter Configuration
        • <M>   ip6tables NAT support
    • make clean
    • make prepare
    • use the flags defined in GPL_QTS/src/linux-5.10/cflag_kernel_qnap.mk:
    • compile the kernel with CFLAGS=… make zImage -j8 (in case your cpu has 8 cores)
      • CFLAGS=”-DTSX55U -DQNAP -DX86_PARKER -DNAS_VIRTUAL -DNAS_VIRTUAL_EX -DQNAP_FNOTIFY -DQNAP_SEARCH_FILENAME_CASE_INSENSITIVE -DQNAP_HAL -DSUPPORT_VAAI -DQNAP_I2C_MV9235 -DSUPPORT_FAST_BLOCK_CLONE -DSUPPORT_LOGICAL_BLOCK_4KB_FROM_NAS_GUI -DSUPPORT_CONCURRENT_TASKS -DSUPPORT_SINGLE_INIT_LOGIN -DVIRTUAL_JBOD -DSUPPORT_VOLUME_BASED -DQTS_HA -DSUPPORT_TP -DNFS_VAAI -DNFS_VAAI_V3 -DNFS_QLOG -DQNAP_NFS_FORCE_UMASK -DQNAP_SNAPSHOT -DISCSI_MULTI_INIT_ACL -DUSE_BLKDEV_READPAGES -DUSE_BLKDEV_WRITEPAGES -DKSWAPD_FIX -DQNAP_KERNEL_STORAGE_V2″ make zImage -j8
    • WORKAROUND: comment out the compilation of modules of drivers/target because of compile errors
      • edit drivers/Makefile and comment out “target”:
        • #obj-$(CONFIG_TARGET_CORE)<—>+= target/
    • compile the modules with CFLAGS=… make modules -j8
      • CFLAGS=”-DTSX55U -DQNAP -DX86_PARKER -DNAS_VIRTUAL -DNAS_VIRTUAL_EX -DQNAP_FNOTIFY -DQNAP_SEARCH_FILENAME_CASE_INSENSITIVE -DQNAP_HAL -DSUPPORT_VAAI -DQNAP_I2C_MV9235 -DSUPPORT_FAST_BLOCK_CLONE -DSUPPORT_LOGICAL_BLOCK_4KB_FROM_NAS_GUI -DSUPPORT_CONCURRENT_TASKS -DSUPPORT_SINGLE_INIT_LOGIN -DVIRTUAL_JBOD -DSUPPORT_VOLUME_BASED -DQTS_HA -DSUPPORT_TP -DNFS_VAAI -DNFS_VAAI_V3 -DNFS_QLOG -DQNAP_NFS_FORCE_UMASK -DQNAP_SNAPSHOT -DISCSI_MULTI_INIT_ACL -DUSE_BLKDEV_READPAGES -DUSE_BLKDEV_WRITEPAGES -DKSWAPD_FIX -DQNAP_KERNEL_STORAGE_V2 -DMODULE” make modules -j8
    • module ip6table_nat.ko is in directory
      • net/ipv6/netfilter

finis