bunkerweb/docs/web-ui.md
2024-08-19 19:05:06 +02:00

85 KiB

Web UI

Overview

The "Web UI" is a web application that helps you manage your BunkerWeb instance using a user-friendly interface instead of the command-line one.

Features

  • Start, stop, restart and reload your BunkerWeb instance
  • Add, edit and delete settings for your web applications
  • Add, edit and delete custom configurations for NGINX and ModSecurity
  • Install and uninstall external plugins
  • Explore the cached files
  • Monitor jobs execution
  • View the logs and search pattern

Prerequisites

Because the web UI is a web application, the recommended installation procedure is to use BunkerWeb in front of it as a reverse proxy.

!!! warning "Security considerations"

The security of the web UI is really important. If someone manages to gain access to the application, not only he will be able to edit your configurations but he could execute some code in the context of BunkerWeb (with a custom configuration containing LUA code for example). We highly recommend you to follow minimal security best practices like :

* Choose a strong password for the login (**at least 8 chars with 1 lower case letter, 1 upper case letter, 1 digit and 1 special char is required**)
* Put the web UI under a "hard to guess" URI
* Do not open the web UI on the Internet without any further restrictions
* Apply settings listed in the [security tuning section](security-tuning.md) of the documentation

**Please note that using HTTPS in front the web UI is mandatory since version 1.5.8 of BunkerWeb.**

!!! info "Multisite mode"

The usage of the web UI implies enabling the [multisite mode](concepts.md#multisite-mode).

!!! tip "web UI specific environment variables"

The web UI uses the following environment variables :

- `OVERRIDE_ADMIN_CREDS` : set it to `yes` to enable the override even if the admin credentials are already set (default is `no`).
- `ADMIN_USERNAME` : username to access the web UI.
- `ADMIN_PASSWORD` : password to access the web UI.
- `FLASK_SECRET` : a secret key used to encrypt the session cookie (if not set, a random key will be generated).
- `TOTP_SECRETS` : a list of TOTP secrets separated by spaces or a dictionary (e.g. : `{"1": "mysecretkey"}` or `mysecretkey` or `mysecretkey mysecretkey1`). **We strongly recommend you to set this variable if you want to use 2FA, as it will be used to encrypt the TOTP secret keys** (if not set, a random number of secret keys will be generated). Check out the [passlib documentation](https://passlib.readthedocs.io/en/stable/narr/totp-tutorial.html#application-secrets) for more information.

The web UI will use these variables to authenticate you and handle the 2FA feature.

Setup wizard

!!! info "Wizard"

The setup wizard is a feature that helps you to **configure** and **install the web UI** using a **user-friendly interface**. You will need to set the `UI_HOST` setting (`http://hostname-of-web-ui:7000`) and browse the `/setup` URI of your server to access the setup wizard.
![Overview](assets/img/ui-wizard-account.webp){ align=center, width="350" }
Account section of the setup wizard

Choose your administrator username and password. Please note that password must have at least 8 chars with 1 lower case letter, 1 upper case letter, 1 digit and 1 special char.

![Overview](assets/img/ui-wizard-settings.webp){ align=center, width="350" }
Settings section of the setup wizard

Configure the following settings :

  • UI Host : internal http endpoint of your web UI
  • UI URL : the public path where your web UI will be accessible
  • Server name : the public server name where your web UI will be accessible
  • Auto let's encrypt : enable/disable HTTPS using automated let's encrypt

Please note that your server name must have a valid DNS entry pointing to your BunkerWeb instance (or load balancer if you use Swarm/Kubernetes). You can check the DNS validity by clicking the Check button.

Review your final BunkerWeb UI URL and then click on the Setup button. Once the setup is finished, you will be redirected to your web UI login page.

=== "Docker"

If you want to use the setup wizard, you will need to set the `UI_HOST` setting to the HTTP endpoint of your web UI container. For example, if your web UI container is named `bw-ui` and is listening on the `7000` port, you will need to set the `UI_HOST` setting to `http://bw-ui:7000`.

!!! tip "Accessing the setup wizard"

    You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.


Here is the docker-compose boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
x-ui-env: &bw-ui-env
  # We anchor the environment variables to avoid duplication
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - "80:8080/tcp"
      - "443:8443/tcp"
      - "443:8443/udp" # For QUIC / HTTP3 support
    environment:
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24" # Make sure to set the correct IP range so the scheduler can send the configuration to the instance
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-services

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *bw-ui-env
      BUNKERWEB_INSTANCES: "bunkerweb" # Make sure to set the correct instance name
      SERVER_NAME: ""
      MULTISITE: "yes"
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24" # We mirror the API_WHITELIST_IP from the bunkerweb service
      UI_HOST: "http://bw-ui:7000" # Change it if needed
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *bw-ui-env
      TOTP_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    restart: "unless-stopped"
    networks:
      - bw-db

volumes:
  bw-data:
  bw-db:

networks:
  bw-universe:
    name: bw-universe
    ipam:
      driver: default
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
  bw-db:
    name: bw-db
```

=== "Docker autoconf"

If you want to use the setup wizard, you will need to set the `UI_HOST` setting to the HTTP endpoint of your web UI container. For example, if your web UI container is named `bw-ui` and is listening on the `7000` port, you will need to set the `UI_HOST` setting to `http://bw-ui:7000`.

!!! tip "Accessing the setup wizard"

    You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.

Here is the docker-compose boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
x-ui-env: &bw-ui-env
  # We anchor the environment variables to avoid duplication
  AUTOCONF_MODE: "yes"
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - "80:8080/tcp"
      - "443:8443/tcp"
      - "443:8443/udp" # For QUIC / HTTP3 support
    labels:
      - "bunkerweb.INSTANCE=yes" # We set the instance label to allow the autoconf to detect the instance
    environment:
      AUTOCONF_MODE: "yes"
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-services

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *bw-ui-env
      BUNKERWEB_INSTANCES: ""
      SERVER_NAME: ""
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
      MULTISITE: "yes"
      UI_HOST: "http://bw-ui:7000" # Change it if needed
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db

  bw-autoconf:
    image: bunkerity/bunkerweb-autoconf:1.6.0-beta
    depends_on:
      - bw-docker
    environment:
      <<: *bw-ui-env
      DOCKER_HOST: "tcp://bw-docker:2375"
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-docker
      - bw-db

  bw-docker:
    image: tecnativa/docker-socket-proxy:nightly
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      CONTAINERS: "1"
      LOG_LEVEL: "warning"
    networks:
      - bw-docker

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *bw-ui-env
      TOTP_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    restart: "unless-stopped"
    networks:
      - bw-db

volumes:
  bw-data:
  bw-db:

networks:
  bw-universe:
    name: bw-universe
    ipam:
      driver: default
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
  bw-docker:
    name: bw-docker
  bw-db:
    name: bw-db
```

=== "Swarm"

If you want to use the setup wizard, you will need to set the `UI_HOST` setting to the HTTP endpoint of your web UI container. For example, if your web UI container is named `bw-ui` and is listening on the `7000` port, you will need to set the `UI_HOST` setting to `http://bw-ui:7000`.

!!! tip "Accessing the setup wizard"

    You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.

Here is the stack boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
x-ui-env: &bw-ui-env
  # We anchor the environment variables to avoid duplication
  SWARM_MODE: "yes"
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - published: 80
        target: 8080
        mode: host
        protocol: tcp
      - published: 443
        target: 8443
        mode: host
        protocol: tcp
      - published: 443
        target: 8443
        mode: host
        protocol: udp # For QUIC / HTTP3 support
    environment:
      SWARM_MODE: "yes"
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-services
    deploy:
      mode: global
      placement:
        constraints:
          - "node.role == worker"
      labels:
        - "bunkerweb.INSTANCE=yes"

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *bw-ui-env
      BUNKERWEB_INSTANCES: ""
      SERVER_NAME: ""
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
      MULTISITE: "yes"
      USE_REDIS: "yes"
      REDIS_HOST: "bw-redis"
      UI_HOST: "http://bw-ui:7000" # Change it if needed
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db

  bw-autoconf:
    image: bunkerity/bunkerweb-autoconf:1.6.0-beta
    environment:
      <<: *bw-ui-env
      DOCKER_HOST: "tcp://bw-docker:2375"
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-docker
      - bw-db

  bw-docker:
    image: tecnativa/docker-socket-proxy:nightly
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      CONFIGS: "1"
      CONTAINERS: "1"
      SERVICES: "1"
      SWARM: "1"
      TASKS: "1"
      LOG_LEVEL: "warning"
    networks:
      - bw-docker
    deploy:
      placement:
        constraints:
          - "node.role == manager"

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *bw-ui-env
      TOTP_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    restart: "unless-stopped"
    networks:
      - bw-db

  bw-redis:
    image: redis:7-alpine
    networks:
      - bw-universe

volumes:
  bw-db:
  bw-data:

networks:
  bw-universe:
    name: bw-universe
    driver: overlay
    attachable: true
    ipam:
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
    driver: overlay
    attachable: true
  bw-docker:
    name: bw-docker
    driver: overlay
    attachable: true
  bw-db:
    name: bw-db
    driver: overlay
    attachable: true
```

=== "Kubernetes"

If you want to use the setup wizard, you will need to set the `UI_HOST` setting to the HTTP endpoint of your web UI SERVICE. For example, if your web UI service is named `svc-bunkerweb-ui` and is listening on the `7000` port, you will need to set the `UI_HOST` setting to `http://svc-bunkerweb-ui.default.svc.cluster.local:7000` (don't forget to replace the namespace and the service name accordingly).

!!! tip "Accessing the setup wizard"

    You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.

Here is the yaml boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cr-bunkerweb
rules:
  - apiGroups: [""]
    resources: ["services", "pods", "configmaps", "secrets"]
    verbs: ["get", "watch", "list"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "watch", "list"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-bunkerweb
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: crb-bunkerweb
subjects:
  - kind: ServiceAccount
    name: sa-bunkerweb
    namespace: default
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: cr-bunkerweb
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: bunkerweb
spec:
  selector:
    matchLabels:
      app: bunkerweb
  template:
    metadata:
      labels:
        app: bunkerweb
      # mandatory annotation
      annotations:
        bunkerweb.io/INSTANCE: "yes"
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        # using bunkerweb as name is mandatory
        - name: bunkerweb
          image: bunkerity/bunkerweb:1.6.0-beta
          imagePullPolicy: Always
          securityContext:
            runAsUser: 101
            runAsGroup: 101
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
          ports:
            - containerPort: 8080
              hostPort: 80
            - containerPort: 8443
              hostPort: 443
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            # replace with your DNS resolvers
            # e.g. : kube-dns.kube-system.svc.cluster.local
            - name: DNS_RESOLVERS
              value: "coredns.kube-system.svc.cluster.local"
            # 10.0.0.0/8 is the cluster internal subnet
            - name: API_WHITELIST_IP
              value: "127.0.0.0/8 10.0.0.0/8"
          livenessProbe:
            exec:
              command:
                - /usr/share/bunkerweb/helpers/healthcheck.sh
            initialDelaySeconds: 30
            periodSeconds: 5
            timeoutSeconds: 1
            failureThreshold: 3
          readinessProbe:
            exec:
              command:
                - /usr/share/bunkerweb/helpers/healthcheck.sh
            initialDelaySeconds: 30
            periodSeconds: 1
            timeoutSeconds: 1
            failureThreshold: 3
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-controller
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-controller
  template:
    metadata:
      labels:
        app: bunkerweb-controller
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        - name: bunkerweb-controller
          image: bunkerity/bunkerweb-autoconf:1.6.0-beta
          imagePullPolicy: Always
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            - name: DATABASE_URI
              value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" # Remember to set a stronger password for the database
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-scheduler
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-scheduler
  template:
    metadata:
      labels:
        app: bunkerweb-scheduler
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        - name: bunkerweb-scheduler
          image: bunkerity/bunkerweb-scheduler:1.6.0-beta
          imagePullPolicy: Always
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            - name: DATABASE_URI
              value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" # Remember to set a stronger password for the database
            # replace with your DNS resolvers
            # e.g. : kube-dns.kube-system.svc.cluster.local
            - name: DNS_RESOLVERS
              value: "coredns.kube-system.svc.cluster.local"
            # 10.0.0.0/8 is the cluster internal subnet
            - name: API_WHITELIST_IP
              value: "127.0.0.0/8 10.0.0.0/8"
            - name: BUNKERWEB_INSTANCES
              value: ""
            - name: SERVER_NAME
              value: ""
            - name: MULTISITE
              value: "yes"
            - name: USE_REDIS
              value: "yes"
            # replace with your Redis host
            - name: REDIS_HOST
              value: "svc-bunkerweb-redis.default.svc.cluster.local"
            - name: UI_HOST
              value: "http://svc-bunkerweb-ui.default.svc.cluster.local:7000" # Change it if needed
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-redis
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-redis
  template:
    metadata:
      labels:
        app: bunkerweb-redis
    spec:
      containers:
        - name: bunkerweb-redis
          image: redis:7-alpine
          imagePullPolicy: Always
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-db
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-db
  template:
    metadata:
      labels:
        app: bunkerweb-db
    spec:
      containers:
        - name: bunkerweb-db
          image: mariadb:11
          imagePullPolicy: Always
          env:
            - name: MYSQL_RANDOM_ROOT_PASSWORD
              value: "yes"
            - name: MYSQL_DATABASE
              value: "db"
            - name: MYSQL_USER
              value: "bunkerweb"
            - name: MYSQL_PASSWORD
              value: "changeme" # Remember to set a stronger password for the database
          volumeMounts:
            - mountPath: "/var/lib/mysql"
              name: vol-db
      volumes:
        - name: vol-db
          persistentVolumeClaim:
            claimName: pvc-bunkerweb
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-ui
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-ui
  template:
    metadata:
      labels:
        app: bunkerweb-ui
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        - name: bunkerweb-ui
          image: bunkerity/bunkerweb-ui:1.6.0-beta
          imagePullPolicy: Always
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            - name: DATABASE_URI
              value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" # Remember to set a stronger password for the database
            - name: TOTP_SECRETS
              value: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb
spec:
  clusterIP: None
  selector:
    app: bunkerweb
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb-db
spec:
  type: ClusterIP
  selector:
    app: bunkerweb-db
  ports:
    - name: sql
      protocol: TCP
      port: 3306
      targetPort: 3306
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb-redis
spec:
  type: ClusterIP
  selector:
    app: bunkerweb-redis
  ports:
    - name: redis
      protocol: TCP
      port: 6379
      targetPort: 6379
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb-ui
spec:
  type: ClusterIP
  selector:
    app: bunkerweb-ui
  ports:
    - name: http
      protocol: TCP
      port: 7000
      targetPort: 7000
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-bunkerweb
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
```

=== "Linux"

If you want to use the setup wizard, you will need to run the following command `export UI_WIZARD=1` before installing BunkerWeb as described in [integrations section](integrations.md#linux) of the documentation (this is an optional step).

!!! tip "Accessing the setup wizard"

    You can access the setup wizard by browsing the `https://your-ip-address/setup` URI of your server.

Accessing logs

Beginning with version 1.6.0-beta, the method of accessing logs has changed. This update specifically impacts Docker, Autoconf, and Swarm Integrations. Logs are now exclusively accessed from the /var/log/bunkerweb directory.

To keep the logs accessible from the web UI, you will need to use syslog-ng to forward the logs to a file in the /var/log/bunkerweb directory.

=== "Docker"

To forward the logs correctly to the `/var/log/bunkerweb` directory on the Docker integration, you will need to stream the logs to a file using `syslog-ng`. Here is an example of how to do this :

```yaml
x-bw-env: &bw-env
  # We anchor the environment variables to avoid duplication
  API_WHITELIST_IP: "127.0.0.0/24 10.20.30.0/24"

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - "80:8080/tcp"
      - "443:8443/tcp"
      - "443:8443/udp" # QUIC
    environment:
      <<: *bw-env
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-services
    logging:
      driver: syslog
      options:
        tag: "bunkerweb" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *bw-env
      BUNKERWEB_INSTANCES: "bunkerweb" # Make sure to set the correct instance name
      SERVER_NAME: "www.example.com"
      MULTISITE: "yes"
      DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database
      SERVE_FILES: "no"
      DISABLE_DEFAULT_SERVER: "yes"
      USE_CLIENT_CACHE: "yes"
      USE_GZIP: "yes"
      www.example.com_USE_UI: "yes"
      www.example.com_USE_REVERSE_PROXY: "yes"
      www.example.com_REVERSE_PROXY_URL: "/changeme" # Change it to a hard to guess URI
      www.example.com_REVERSE_PROXY_HOST: "http://bw-ui:7000"
      www.example.com_INTERCEPTED_ERROR_CODES: "400 404 405 413 429 500 501 502 503 504"
      www.example.com_GENERATE_SELF_SIGNED_SSL: "yes"
      www.example.com_MAX_CLIENT_SIZE: "50m"
      www.example.com_ALLOWED_METHODS: "GET|POST|PUT|DELETE"
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db
    logging:
      driver: syslog
      options:
        tag: "bw-scheduler" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database
      ADMIN_USERNAME: "changeme"
      ADMIN_PASSWORD: "changeme" # Remember to set a stronger password for the admin user
      TOPT_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    volumes:
      - bw-logs:/var/log/bunkerweb # This is the volume used to store the logs
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db
    logging:
      driver: syslog
      options:
        tag: "bw-ui" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    restart: "unless-stopped"
    networks:
      - bw-db

  bw-syslog:
    image: balabit/syslog-ng:4.7.1
    # image: lscr.io/linuxserver/syslog-ng:4.7.1-r1-ls116 # For aarch64 architecture
    volumes:
      - bw-logs:/var/log/bunkerweb # This is the volume used to store the logs
      - ./syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf # This is the syslog-ng configuration file
    networks:
      bw-universe:
        ipv4_address: 10.20.30.254 # Make sure to set the correct IP address

volumes:
  bw-data:
  bw-db:
  bw-logs:

networks:
  bw-universe:
    name: bw-universe
    ipam:
      driver: default
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
  bw-db:
    name: bw-db
```

=== "Docker Autoconf"

To forward the logs correctly to the `/var/log/bunkerweb` directory on the Autoconf integration, you will need to stream the logs to a file using `syslog-ng`. Here is an example of how to do this :

```yaml
x-ui-env: &bw-ui-env
  # We anchor the environment variables to avoid duplication
  AUTOCONF_MODE: "yes"
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - "80:8080/tcp"
      - "443:8443/tcp"
      - "443:8443/udp" # QUIC
    environment:
      AUTOCONF_MODE: "yes"
      API_WHITELIST_IP: "127.0.0.0/24 10.20.30.0/24"
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-services
    logging:
      driver: syslog
      options:
        tag: "bunkerweb" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *bw-ui-env
      BUNKERWEB_INSTANCES: "" # We don't need to specify the BunkerWeb instance here as they are automatically detected by the autoconf service
      SERVER_NAME: "" # The server name will be filled with services labels
      MULTISITE: "yes" # Mandatory setting for autoconf / ui
      API_WHITELIST_IP: "127.0.0.0/24 10.20.30.0/24"
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db
    logging:
      driver: syslog
      options:
        tag: "bw-scheduler" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-autoconf:
    image: bunkerity/bunkerweb-autoconf:1.6.0-beta
    depends_on:
      - bunkerweb
      - bw-docker
    environment:
      <<: *bw-ui-env
      DOCKER_HOST: "tcp://bw-docker:2375" # This is the Docker socket address
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-docker
      - bw-db
    logging:
      driver: syslog
      options:
        tag: "bw-autoconf" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *bw-ui-env
      ADMIN_USERNAME: "changeme"
      ADMIN_PASSWORD: "changeme" # Remember to set a stronger password for the admin user
      TOPT_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    volumes:
      - bw-logs:/var/log/bunkerweb
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db
    labels:
      - "bunkerweb.SERVER_NAME=www.example.com"
      - "bunkerweb.USE_UI=yes"
      - "bunkerweb.USE_REVERSE_PROXY=yes"
      - "bunkerweb.REVERSE_PROXY_URL=/changeme" # Change it to a hard to guess URI
      - "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
      - "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
      - "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
      - "bunkerweb.MAX_CLIENT_SIZE=50m"
      - "bunkerweb.ALLOWED_METHODS=GET|POST|PUT|DELETE"
    logging:
      driver: syslog
      options:
        tag: "bw-ui" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    restart: "unless-stopped"
    networks:
      - bw-db

  bw-docker:
    image: tecnativa/docker-socket-proxy:nightly
    environment:
      CONTAINERS: "1"
      LOG_LEVEL: "warning"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: "unless-stopped"
    networks:
      - bw-docker

  bw-syslog:
    image: balabit/syslog-ng:4.7.1
    # image: lscr.io/linuxserver/syslog-ng:4.7.1-r1-ls116 # For aarch64 architecture
    volumes:
      - bw-logs:/var/log/bunkerweb # This is the volume used to store the logs
      - ./syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf # This is the syslog-ng configuration file
    networks:
      bw-universe:
        ipv4_address: 10.20.30.254 # Make sure to set the correct IP address

volumes:
  bw-data:
  bw-db:
  bw-logs:

networks:
  bw-universe:
    name: bw-universe
    ipam:
      driver: default
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
  bw-db:
    name: bw-db
  bw-docker:
    name: bw-docker
```

=== "Swarm"

To forward the logs correctly to the `/var/log/bunkerweb` directory on the Swarm integration, you will need to stream the logs to a file using `syslog-ng`. Here is an example of how to do this :

```yaml
x-ui-env: &ui-env
  # We anchor the environment variables to avoid duplication
  SWARM_MODE: "yes"
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - published: 80
        target: 8080
        mode: host
        protocol: tcp
      - published: 443
        target: 8443
        mode: host
        protocol: tcp
      - published: 443
        target: 8443
        mode: host
        protocol: udp # For QUIC / HTTP3 support
    environment:
      SWARM_MODE: "yes"
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-services
    deploy:
      mode: global
      placement:
        constraints:
          - "node.role == worker"
      labels:
        - "bunkerweb.INSTANCE=yes"
    logging:
      driver: syslog
      options:
        tag: "bunkerweb" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *ui-env
      BUNKERWEB_INSTANCES: ""
      SERVER_NAME: ""
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
      MULTISITE: "yes"
      USE_REDIS: "yes"
      REDIS_HOST: "bw-redis"
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db
    logging:
      driver: syslog
      options:
        tag: "bw-scheduler" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-autoconf:
    image: bunkerity/bunkerweb-autoconf:1.6.0-beta
    environment:
      <<: *ui-env
      DOCKER_HOST: "tcp://bw-docker:2375"
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-docker
      - bw-db
    logging:
      driver: syslog
      options:
        tag: "bw-autoconf" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-docker:
    image: tecnativa/docker-socket-proxy:nightly
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      CONFIGS: "1"
      CONTAINERS: "1"
      SERVICES: "1"
      SWARM: "1"
      TASKS: "1"
      LOG_LEVEL: "warning"
    restart: "unless-stopped"
    networks:
      - bw-docker
    deploy:
      placement:
        constraints:
          - "node.role == manager"

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *ui-env
      ADMIN_USERNAME: "changeme"
      ADMIN_PASSWORD: "changeme" # Remember to set a stronger password for the changeme user
      TOTP_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    volumes:
      - bw-logs:/var/log/bunkerweb # This is the volume used to store the logs
    restart: "unless-stopped"
    networks:
      - bw-universe
      - bw-db
    deploy:
      labels:
        - "bunkerweb.SERVER_NAME=www.example.com"
        - "bunkerweb.USE_UI=yes"
        - "bunkerweb.USE_REVERSE_PROXY=yes"
        - "bunkerweb.REVERSE_PROXY_URL=/changeme"
        - "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
        - "bunkerweb.REVERSE_PROXY_INTERCEPT_ERRORS=no"
        - "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
        - "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
        - "bunkerweb.MAX_CLIENT_SIZE=50m"
        - "bunkerweb.ALLOWED_METHODS=GET|POST|PUT|DELETE"
    logging:
      driver: syslog
      options:
        tag: "bw-ui" # This will be the tag used by syslog-ng to create the log file
        syslog-address: "udp://10.20.30.254:514" # This is the syslog-ng container address

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    networks:
      - bw-db

  bw-redis:
    image: redis:7-alpine
    networks:
      - bw-universe

  bw-syslog:
    image: balabit/syslog-ng:4.7.1
    # image: lscr.io/linuxserver/syslog-ng:4.7.1-r1-ls116 # For aarch64 architecture
    volumes:
      - bw-logs:/var/log/bunkerweb # This is the volume used to store the logs
      - ./syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf # This is the syslog-ng configuration file
    networks:
      bw-universe:
        ipv4_address: 10.20.30.254 # Make sure to set the correct IP address

volumes:
  bw-db:
  bw-data:

networks:
  bw-universe:
    name: bw-universe
    driver: overlay
    attachable: true
    ipam:
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
    driver: overlay
    attachable: true
  bw-docker:
    name: bw-docker
    driver: overlay
    attachable: true
  bw-db:
    name: bw-db
    driver: overlay
    attachable: true
```

=== "Kubernetes"

Kubernetes does not support the `syslog` logging driver. If you want to access the logs you will have to use some other way like Loki, Fluentd, or any other log management system.

=== "Linux"

For Linux this is the simplest way as the logs files are directly accessible from the filesystem.

Syslog-ng configuration

Here is an example of a syslog-ng.conf file that you can use to forward the logs to a file :

@version: 4.7

# Source configuration to receive logs from Docker containers
source s_net {
  udp(
    ip("0.0.0.0")
  );
};

# Template to format log messages
template t_imp {
  template("$MSG\n");
  template_escape(no);
};

# Destination configuration to write logs to dynamically named files
destination d_dyna_file {
  file(
    "/var/log/bunkerweb/${PROGRAM}.log"
    template(t_imp)
    owner("101")
    group("101")
    dir_owner("root")
    dir_group("101")
    perm(0440)
    dir_perm(0770)
    create_dirs(yes)
  );
};

# Log path to direct logs to dynamically named files
log {
  source(s_net);
  destination(d_dyna_file);
};

Account management

You can access the account management page by clicking on manage account inside the sidebar menu :

![Overview](assets/img/manage-account.webp){ align=center, width="350" }
Account page access from menu

Upgrade to PRO

!!! info "What is BunkerWeb PRO ?" BunkerWeb PRO is an enhanced version of BunkerWeb open-source. Whether it's enhanced security, an enriched user experience, or technical monitoring, the BunkerWeb PRO version will allow you to fully benefit from BunkerWeb and respond to your professional needs. Do not hesitate to visit the BunkerWeb panel or contact us if you have any question regarding the PRO version.

Once you have your PRO license key from the BunkerWeb panel, you can paste it into the PRO section of the account management page.

![PRO upgrade](assets/img/pro-ui-upgrade.webp){ align=center, width="550" }
Upgrade to PRO from the web UI

!!! warning "Upgrade time" The PRO version is downloaded in the background by the scheduler, it may take some time to upgrade.

When your BunkerWeb instance has upgraded to the PRO version, you will see your license expiration date and the maximum number of services you can protect.

![PRO upgrade](assets/img/ui-pro.webp){ align=center, width="550" }
PRO license information

Username / Password

!!! tip "Overriding admin credentials from environment variables"

If you want to override the admin credentials from environment variables, you can set the following variables :

- `OVERRIDE_ADMIN_CREDS` : set it to `yes` to enable the override even if the admin credentials are already set (default is `no`)
- `ADMIN_USERNAME` : username to access the web UI
- `ADMIN_PASSWORD` : password to access the web UI

The web UI will use these variables to authenticate you.

!!! tip "Generating recommended secrets"

To generate a valid password, we recommend you to use a password manager or a password generator.

You can generate a valid totp secrets dictionary using the following command (you will need the `passlib` package) :

```shell
python3 -c "from passlib import totp; import random; print(' '.join(totp.generate_secret() for _ in range(random.randint(1, 5))))"
```

!!! warning "Lost password/username"

In case you forgot your UI credentials, you can reset them from the CLI following [the steps described in the troubleshooting section](troubleshooting.md#web-ui).

You can update your username or password by filling the dedicated forms. For security reason, you need to enter your current password even if you are connected.

Please note that when your username or password is updated, you will be logout from the web UI to log in again.

![Overview](assets/img/profile-username-password.webp){ align=center, width="800" }
Username / Password forms

Two-Factor Authentication

!!! warning "Lost secret key"

In case you lost your secret key, two options are available :

- You can recover your account using one of the provided recovery codes when you enabled 2FA (a recovery code can only be used once).
- You can disable 2FA from the CLI following [the steps described in the troubleshooting section](troubleshooting.md#web-ui).

You can power-up your login security by adding Two-Factor Authentication (2FA) to your account. By doing so, an extra code will be needed in addition to your password.

The web UI uses Time based One Time Password (TOTP) as 2FA implementation : using a secret key, the algorithm will generate one time passwords only valid for a short period of time.

Any TOTP client such as Google Authenticator, Authy, FreeOTP, ... can be used to store the secret key and generate the codes. Please note that once TOTP is enabled, you won't be able to retrieve it from the web UI.

The following steps are needed to enable the TOTP feature from the web UI :

  • Copy the secret key or use the QR code on your authenticator app
  • Enter the current TOTP code in the 2FA input
  • Enter your current password

!!! info "Secret key refresh" A new secret key is generated each time you visit the page or submit the form. In case something went wrong (e.g. : expired TOTP code), you will need to copy the new secret key to your authenticator app until 2FA is successfully enabled.

!!! tip "Recovery codes"

When you enable 2FA, you will be provided with **5 recovery codes**. These codes can be used to recover your account in case you have lost your TOTP secret key. Each code can only be used once. **These codes will only be shown once so make sure to store them in a safe place**.

If you ever lose your recovery codes, **you can refresh them via the TOTP section of the account management page**. Please note that the old recovery codes will be invalidated.

Once enabled, 2FA authentication can be disabled at the same place.

![Overview](assets/img/profile-totp.webp){ align=center, width="800" }
TOTP enable / disable forms

After a successful login/password combination, you will be prompted to enter your TOTP code :

![Overview](assets/img/profile-2fa.webp){ align=center, width="400" }
Additional TOTP page

Advanced installation

=== "Docker"

The web UI can be deployed using a dedicated container which is available on [Docker Hub](https://hub.docker.com/r/bunkerity/bunkerweb-ui) :

```shell
docker pull bunkerity/bunkerweb-ui
```

Alternatively, you can also build it yourself :

```shell
git clone https://github.com/bunkerity/bunkerweb.git && \
cd bunkerweb && \
docker build -t my-bunkerweb-ui -f src/ui/Dockerfile .
```

!!! tip "Environment variables"

    Please read the [Prerequisites](#prerequisites) section to check out all the environment variables you can set to customize the web UI.

Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). We recommend you to connect BunkerWeb and web UI using a dedicated network (like `bw-universe` also used by the scheduler) so it won't be on the same network of your web services for obvious security reasons. Please note that the web UI container is listening on the `7000` port.

!!! info "Database backend"

    If you want another Database backend than MariaDB please refer to the docker-compose files in the [misc/integrations folder](https://github.com/bunkerity/bunkerweb/tree/v1.6.0-beta/misc/integrations) of the repository.

Here is the docker-compose boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
x-ui-env: &ui-env
  # We anchor the environment variables to avoid duplication
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - "80:8080/tcp"
      - "443:8443/tcp"
      - "443:8443/udp" # For QUIC / HTTP3 support
    environment:
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24" # Make sure to set the correct IP range so the scheduler can send the configuration to the instance
    networks:
      - bw-universe
      - bw-services

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *ui-env
      BUNKERWEB_INSTANCES: "bunkerweb" # Make sure to set the correct instance name
      SERVER_NAME: "www.example.com"
      MULTISITE: "yes"
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24" # We mirror the API_WHITELIST_IP from the bunkerweb service
      SERVE_FILES: "no"
      DISABLE_DEFAULT_SERVER: "yes"
      USE_CLIENT_CACHE: "yes"
      USE_GZIP: "yes"
      www.example.com_USE_UI: "yes"
      www.example.com_USE_REVERSE_PROXY: "yes"
      www.example.com_REVERSE_PROXY_URL: "/changeme" # Remember to set a stronger URI
      www.example.com_REVERSE_PROXY_HOST: "http://bw-ui:7000" # The web UI container is listening on the 7000 port by default
      www.example.com_INTERCEPTED_ERROR_CODES: "400 404 405 413 429 500 501 502 503 504"
      www.example.com_GENERATE_SELF_SIGNED_SSL: "yes"
      www.example.com_MAX_CLIENT_SIZE: "50m"
      www.example.com_ALLOWED_METHODS: "GET|POST|PUT|DELETE"
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    networks:
      - bw-universe
      - bw-db

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *ui-env
      ADMIN_USERNAME: "changeme"
      ADMIN_PASSWORD: "changeme" # Remember to set a stronger password for the changeme user
      TOTP_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    networks:
      - bw-universe
      - bw-db

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    networks:
      - bw-db

volumes:
  bw-data:
  bw-db:

networks:
  bw-universe:
    name: bw-universe
    ipam:
      driver: default
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
  bw-db:
    name: bw-db
```

=== "Docker autoconf"

The web UI can be deployed using a dedicated container which is available on [Docker Hub](https://hub.docker.com/r/bunkerity/bunkerweb-ui) :

```shell
docker pull bunkerity/bunkerweb-ui
```

Alternatively, you can also build it yourself :

```shell
git clone https://github.com/bunkerity/bunkerweb.git && \
cd bunkerweb && \
docker build -t my-bunkerweb-ui -f src/ui/Dockerfile .
```

!!! tip "Environment variables"

    Please read the [Prerequisites](#prerequisites) section to check out all the environment variables you can set to customize the web UI.

Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). We recommend you to connect BunkerWeb and web UI using a dedicated network (like `bw-universe` also used by the scheduler and autoconf) so it won't be on the same network of your web services for obvious security reasons. Please note that the web UI container is listening on the `7000` port.

!!! info "Database backend"

    If you want another Database backend than MariaDB please refer to the docker-compose files in the [misc/integrations folder](https://github.com/bunkerity/bunkerweb/tree/v1.6.0-beta/misc/integrations) of the repository.

Here is the docker-compose boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
x-ui-env: &ui-env
  # We anchor the environment variables to avoid duplication
  AUTOCONF_MODE: "yes"
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - "80:8080/tcp"
      - "443:8443/tcp"
      - "443:8443/udp" # For QUIC / HTTP3 support
    labels:
      - "bunkerweb.INSTANCE=yes" # We set the instance label to allow the autoconf to detect the instance
    environment:
      AUTOCONF_MODE: "yes"
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
    networks:
      - bw-universe
      - bw-services

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *ui-env
      BUNKERWEB_INSTANCES: ""
      SERVER_NAME: ""
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
      MULTISITE: "yes"
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    networks:
      - bw-universe
      - bw-db

  bw-autoconf:
    image: bunkerity/bunkerweb-autoconf:1.6.0-beta
    depends_on:
      - bw-docker
    environment:
      <<: *ui-env
      DOCKER_HOST: "tcp://bw-docker:2375"
    networks:
      - bw-universe
      - bw-docker
      - bw-db

  bw-docker:
    image: tecnativa/docker-socket-proxy:nightly
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      CONTAINERS: "1"
      LOG_LEVEL: "warning"
    networks:
      - bw-docker

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    networks:
      - bw-db

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *ui-env
      ADMIN_USERNAME: "changeme"
      ADMIN_PASSWORD: "changeme" # Remember to set a stronger password for the changeme user
      TOTP_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    labels:
      - "bunkerweb.SERVER_NAME=www.example.com"
      - "bunkerweb.USE_UI=yes"
      - "bunkerweb.USE_REVERSE_PROXY=yes"
      - "bunkerweb.REVERSE_PROXY_URL=/changeme"
      - "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
      - "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
      - "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
      - "bunkerweb.MAX_CLIENT_SIZE=50m"
      - "bunkerweb.ALLOWED_METHODS=GET|POST|PUT|DELETE"
    networks:
      - bw-universe
      - bw-db

volumes:
  bw-data:
  bw-db:

networks:
  bw-universe:
    name: bw-universe
    ipam:
      driver: default
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
  bw-docker:
    name: bw-docker
  bw-db:
    name: bw-db
```

=== "Swarm"

The web UI can be deployed using a dedicated container which is available on [Docker Hub](https://hub.docker.com/r/bunkerity/bunkerweb-ui) :

```shell
docker pull bunkerity/bunkerweb-ui
```

Alternatively, you can also build it yourself :

```shell
git clone https://github.com/bunkerity/bunkerweb.git && \
cd bunkerweb && \
docker build -t my-bunkerweb-ui -f src/ui/Dockerfile .
```

!!! tip "Environment variables"

    Please read the [Prerequisites](#prerequisites) section to check out all the environment variables you can set to customize the web UI.

Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). We recommend you to connect BunkerWeb and web UI using a dedicated network (like `bw-universe` also used by the scheduler and autoconf) so it won't be on the same network of your web services for obvious security reasons. Please note that the web UI container is listening on the `7000` port.

!!! info "Database backend"

    If you want another Database backend than MariaDB please refer to the stack files in the [misc/integrations folder](https://github.com/bunkerity/bunkerweb/tree/v1.6.0-beta/misc/integrations) of the repository.

Here is the stack boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
x-ui-env: &ui-env
  # We anchor the environment variables to avoid duplication
  SWARM_MODE: "yes"
  DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database

services:
  bunkerweb:
    image: bunkerity/bunkerweb:1.6.0-beta
    ports:
      - published: 80
        target: 8080
        mode: host
        protocol: tcp
      - published: 443
        target: 8443
        mode: host
        protocol: tcp
      - published: 443
        target: 8443
        mode: host
        protocol: udp # For QUIC / HTTP3 support
    environment:
      SWARM_MODE: "yes"
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
    networks:
      - bw-universe
      - bw-services
    deploy:
      mode: global
      placement:
        constraints:
          - "node.role == worker"
      labels:
        - "bunkerweb.INSTANCE=yes"

  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.0-beta
    environment:
      <<: *ui-env
      BUNKERWEB_INSTANCES: ""
      SERVER_NAME: ""
      API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
      MULTISITE: "yes"
      USE_REDIS: "yes"
      REDIS_HOST: "bw-redis"
    volumes:
      - bw-data:/data # This is used to persist the cache and other data like the backups
    networks:
      - bw-universe
      - bw-db

  bw-autoconf:
    image: bunkerity/bunkerweb-autoconf:1.6.0-beta
    environment:
      <<: *ui-env
      DOCKER_HOST: "tcp://bw-docker:2375"
    networks:
      - bw-universe
      - bw-docker
      - bw-db

  bw-docker:
    image: tecnativa/docker-socket-proxy:nightly
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      CONFIGS: "1"
      CONTAINERS: "1"
      SERVICES: "1"
      SWARM: "1"
      TASKS: "1"
      LOG_LEVEL: "warning"
    networks:
      - bw-docker
    deploy:
      placement:
        constraints:
          - "node.role == manager"

  bw-db:
    image: mariadb:11
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "db"
      MYSQL_USER: "bunkerweb"
      MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
    volumes:
      - bw-db:/var/lib/mysql
    networks:
      - bw-db

  bw-redis:
    image: redis:7-alpine
    networks:
      - bw-universe

  bw-ui:
    image: bunkerity/bunkerweb-ui:1.6.0-beta
    environment:
      <<: *ui-env
      ADMIN_USERNAME: "changeme"
      ADMIN_PASSWORD: "changeme" # Remember to set a stronger password for the changeme user
      TOTP_SECRETS: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
    networks:
      - bw-universe
      - bw-db
    deploy:
      labels:
        - "bunkerweb.SERVER_NAME=www.example.com"
        - "bunkerweb.USE_UI=yes"
        - "bunkerweb.USE_REVERSE_PROXY=yes"
        - "bunkerweb.REVERSE_PROXY_URL=/changeme"
        - "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
        - "bunkerweb.REVERSE_PROXY_INTERCEPT_ERRORS=no"
        - "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
        - "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
        - "bunkerweb.MAX_CLIENT_SIZE=50m"
        - "bunkerweb.ALLOWED_METHODS=GET|POST|PUT|DELETE"

volumes:
  bw-db:
  bw-data:

networks:
  bw-universe:
    name: bw-universe
    driver: overlay
    attachable: true
    ipam:
      config:
        - subnet: 10.20.30.0/24
  bw-services:
    name: bw-services
    driver: overlay
    attachable: true
  bw-docker:
    name: bw-docker
    driver: overlay
    attachable: true
  bw-db:
    name: bw-db
    driver: overlay
    attachable: true
```

=== "Kubernetes"

The web UI can be deployed using a dedicated container which is available on [Docker Hub](https://hub.docker.com/r/bunkerity/bunkerweb-ui) as a standard [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/).

!!! tip "Environment variables"

    Please read the [Prerequisites](#prerequisites) section to check out all the environment variables you can set to customize the web UI.

Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). Network segmentation between web UI and web services is not covered in this documentation. Please note that the web UI container is listening on the `7000` port.

!!! info "Database backend"

    If you want another Database backend than MariaDB please refer to the yaml files in the [misc/integrations folder](https://github.com/bunkerity/bunkerweb/tree/v1.6.0-beta/misc/integrations) of the repository.

Here is the yaml boilerplate that you can use (don't forget to edit the `changeme` data) :

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cr-bunkerweb
rules:
  - apiGroups: [""]
    resources: ["services", "pods", "configmaps", "secrets"]
    verbs: ["get", "watch", "list"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "watch", "list"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-bunkerweb
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: crb-bunkerweb
subjects:
  - kind: ServiceAccount
    name: sa-bunkerweb
    namespace: default
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: cr-bunkerweb
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: bunkerweb
spec:
  selector:
    matchLabels:
      app: bunkerweb
  template:
    metadata:
      labels:
        app: bunkerweb
      # mandatory annotation
      annotations:
        bunkerweb.io/INSTANCE: "yes"
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        # using bunkerweb as name is mandatory
        - name: bunkerweb
          image: bunkerity/bunkerweb:1.6.0-beta
          imagePullPolicy: Always
          securityContext:
            runAsUser: 101
            runAsGroup: 101
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
          ports:
            - containerPort: 8080
              hostPort: 80
            - containerPort: 8443
              hostPort: 443
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            # replace with your DNS resolvers
            # e.g. : kube-dns.kube-system.svc.cluster.local
            - name: DNS_RESOLVERS
              value: "coredns.kube-system.svc.cluster.local"
            # 10.0.0.0/8 is the cluster internal subnet
            - name: API_WHITELIST_IP
              value: "127.0.0.0/8 10.0.0.0/8"
          livenessProbe:
            exec:
              command:
                - /usr/share/bunkerweb/helpers/healthcheck.sh
            initialDelaySeconds: 30
            periodSeconds: 5
            timeoutSeconds: 1
            failureThreshold: 3
          readinessProbe:
            exec:
              command:
                - /usr/share/bunkerweb/helpers/healthcheck.sh
            initialDelaySeconds: 30
            periodSeconds: 1
            timeoutSeconds: 1
            failureThreshold: 3
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-controller
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-controller
  template:
    metadata:
      labels:
        app: bunkerweb-controller
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        - name: bunkerweb-controller
          image: bunkerity/bunkerweb-autoconf:1.6.0-beta
          imagePullPolicy: Always
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            - name: DATABASE_URI
              value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" # Remember to set a stronger password for the database
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-scheduler
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-scheduler
  template:
    metadata:
      labels:
        app: bunkerweb-scheduler
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        - name: bunkerweb-scheduler
          image: bunkerity/bunkerweb-scheduler:1.6.0-beta
          imagePullPolicy: Always
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            - name: DATABASE_URI
              value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" # Remember to set a stronger password for the database
            # replace with your DNS resolvers
            # e.g. : kube-dns.kube-system.svc.cluster.local
            - name: DNS_RESOLVERS
              value: "coredns.kube-system.svc.cluster.local"
            # 10.0.0.0/8 is the cluster internal subnet
            - name: API_WHITELIST_IP
              value: "127.0.0.0/8 10.0.0.0/8"
            - name: BUNKERWEB_INSTANCES
              value: ""
            - name: SERVER_NAME
              value: ""
            - name: MULTISITE
              value: "yes"
            - name: USE_REDIS
              value: "yes"
            # replace with your Redis host
            - name: REDIS_HOST
              value: "svc-bunkerweb-redis.default.svc.cluster.local"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-redis
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-redis
  template:
    metadata:
      labels:
        app: bunkerweb-redis
    spec:
      containers:
        - name: bunkerweb-redis
          image: redis:7-alpine
          imagePullPolicy: Always
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-db
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-db
  template:
    metadata:
      labels:
        app: bunkerweb-db
    spec:
      containers:
        - name: bunkerweb-db
          image: mariadb:11
          imagePullPolicy: Always
          env:
            - name: MYSQL_RANDOM_ROOT_PASSWORD
              value: "yes"
            - name: MYSQL_DATABASE
              value: "db"
            - name: MYSQL_USER
              value: "bunkerweb"
            - name: MYSQL_PASSWORD
              value: "changeme" # Remember to set a stronger password for the database
          volumeMounts:
            - mountPath: "/var/lib/mysql"
              name: vol-db
      volumes:
        - name: vol-db
          persistentVolumeClaim:
            claimName: pvc-bunkerweb
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-ui
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-ui
  template:
    metadata:
      labels:
        app: bunkerweb-ui
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        - name: bunkerweb-ui
          image: bunkerity/bunkerweb-ui:1.6.0-beta
          imagePullPolicy: Always
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            - name: DATABASE_URI
              value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" # Remember to set a stronger password for the database
            - name: ADMIN_USERNAME
              value: changeme
            - name: ADMIN_PASSWORD
              value: "changeme" # Remember to set a stronger password for the ui user
            - name: TOTP_SECRETS
              value: "mysecret" # Remember to set a stronger secret key (see the Prerequisites section)
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb
spec:
  clusterIP: None
  selector:
    app: bunkerweb
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb-db
spec:
  type: ClusterIP
  selector:
    app: bunkerweb-db
  ports:
    - name: sql
      protocol: TCP
      port: 3306
      targetPort: 3306
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb-redis
spec:
  type: ClusterIP
  selector:
    app: bunkerweb-redis
  ports:
    - name: redis
      protocol: TCP
      port: 6379
      targetPort: 6379
---
apiVersion: v1
kind: Service
metadata:
  name: svc-bunkerweb-ui
spec:
  type: ClusterIP
  selector:
    app: bunkerweb-ui
  ports:
    - name: http
      protocol: TCP
      port: 7000
      targetPort: 7000
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-bunkerweb
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  annotations:
    bunkerweb.io/www.example.com_SERVE_FILES: "no"
    bunkerweb.io/www.example.com_USE_CLIENT_CACHE: "yes"
    bunkerweb.io/www.example.com_USE_GZIP: "yes"
    bunkerweb.io/www.example.com_USE_UI: "yes"
    bunkerweb.io/www.example.com_INTERCEPTED_ERROR_CODES: "400 404 405 413 429 500 501 502 503 504"
    bunkerweb.io/www.example.com_GENERATE_SELF_SIGNED_SSL: "yes"
    bunkerweb.io/www.example.com_MAX_CLIENT_SIZE: "50m"
    bunkerweb.io/www.example.com_ALLOWED_METHODS: "GET|POST|PUT|DELETE"
spec:
  rules:
    - host: www.example.com
      http:
        paths:
          - path: /changeme
            pathType: Prefix
            backend:
              service:
                name: svc-bunkerweb-ui
                port:
                  number: 7000
```

=== "Linux"

The installation of the web UI using the [Linux integration](integrations.md#linux) is pretty straightforward because it is installed with BunkerWeb.

The web UI comes as systemd service named `bunkerweb-ui` which is not enabled by default. If you want to start the web UI when on startup you can run the following command :

```shell
systemctl enable bunkerweb-ui
```

A dedicated environment file located at `/etc/bunkerweb/ui.env` is used to configure the web UI :

```conf
ADMIN_USERNAME=changeme
ADMIN_PASSWORD=changeme
TOTP_SECRETS=mysecret
```

Replace the `changeme` data with your own values.

Remember to set a stronger secret key for the `TOTP_SECRETS` variable, check the [Prerequisites](#prerequisites) section for more information.

Each time you edit the `/etc/bunkerweb/ui.env` file, you will need to restart the service :

```shell
systemctl restart bunkerweb-ui
```

Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). Please note that the web UI is listening on the `7000` port and only on the loopback interface.

Here is the `/etc/bunkerweb/variables.env` boilerplate you can use :

```conf
HTTP_PORT=80
HTTPS_PORT=443
DNS_RESOLVERS=9.9.9.9 8.8.8.8 8.8.4.4
API_LISTEN_IP=127.0.0.1
SERVER_NAME=www.example.com
MULTISITE=yes
www.example.com_USE_UI=yes
www.example.com_USE_REVERSE_PROXY=yes
www.example.com_REVERSE_PROXY_URL=/changeme
www.example.com_REVERSE_PROXY_HOST=http://127.0.0.1:7000
www.example.com_INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504
www.example.com_GENERATE_SELF_SIGNED_SSL=yes
www.example.com_MAX_CLIENT_SIZE=50m
www.example.com_ALLOWED_METHODS=GET|POST|PUT|DELETE
```

Don't forget to reload the `bunkerweb` service :

```shell
systemctl reload bunkerweb
```

UI development

The web UI is moving from Flask to Vue.js using a builder approach. I will detail some steps and parts of the UI development process.

Create a dashboard page

A dashboard page is a page that will have at build time a separate html and js file. Furthermore, the main.py file will need to be updated to include the new page.

1. Create page folder

For the example, we will create a page named example.

First, you need to go to the src/ui/client/dashboard/pages folder and copy an existing page folder like home folder and rename it to example.

Don't forget to rename the Home.vue file to Example.vue, and home.js to example.js.

2. Update page folder files

Open example.js file, you get something like this :

import { createApp } from "vue"; // Utils
import { createPinia } from "pinia"; // Global Vue.js store
import { getI18n } from "@utils/lang.js"; // Get i18n
import Home from "./Home.vue"; // Vue page app

const pinia = createPinia();

// We set the page as app, add pinia plugin, and we add the i18n module with wanted prefix key and mount the app
// The i18n keys are in the src/ui/client/dashboard/lang folder
createApp(Home)
  .use(pinia)
  .use(getI18n(["dashboard", "action", "inp", "icons", "home"]))
  .mount("#app");

You need to update it like this :

import { createApp } from "vue";
import { createPinia } from "pinia";
import { getI18n } from "@utils/lang.js";
import Example from "./Example.vue";

const pinia = createPinia();

createApp(Example)
  .use(pinia)
  .use(getI18n(["dashboard", "action", "inp", "icons", "example"])) // get example prefix keys from lang folder
  .mount("#app");

Open Home.vue file, you get something like this :

<script setup>
import { reactive, onBeforeMount, onMounted } from "vue"; // built-in vue functions
import DashboardLayout from "@components/Dashboard/Layout.vue"; // Dashboard base layout
// BuilderHome is where we define the component to use for the current page
// We will update it later
import BuilderHome from "@components/Builder/Home.vue";
// Global is utils for buttons or link actions
import { useGlobal } from "@utils/global";


// This is JSDOC
/**
*  @name Page/Home.vue
*  @description This component is the home page.
  This page displays an overview of multiple stats related to BunkerWeb.
*/

// Get data for builder logic
const home = reactive({
  builder: "",
});

onBeforeMount(() => {
  // Get builder data
  const dataAtt = "data-server-builder";
  const dataEl = document.querySelector(`[${dataAtt}]`);
  const data =
    dataEl && !dataEl.getAttribute(dataAtt).includes(dataAtt)
      ? JSON.parse(atob(dataEl.getAttribute(dataAtt)))
      : {};
  home.builder = data;
});

// Use utils when app is built
onMounted(() => {
  useGlobal();
});
</script>

<template>
<!-- Template part -->
  <DashboardLayout>
    <BuilderHome v-if="home.builder" :builder="home.builder" />
  </DashboardLayout>
</template>

You need to update it like this :

<script setup>
import { reactive, onBeforeMount, onMounted } from "vue"; 
import DashboardLayout from "@components/Dashboard/Layout.vue";
// We will create it, or we can use
// import BuilderCollection from "@components/Builder/Collection.vue";
// to get all components but performance will be impacted
import BuilderExample from "@components/Builder/Example.vue";
import { useGlobal } from "@utils/global";

/**
*  @name Page/Example.vue
*  @description This component is the Example page.
*/

// Get data for builder logic
const example = reactive({
  builder: "",
});

onBeforeMount(() => {
  // Get builder data
  const dataAtt = "data-server-builder";
  const dataEl = document.querySelector(`[${dataAtt}]`);
  const data =
    dataEl && !dataEl.getAttribute(dataAtt).includes(dataAtt)
      ? JSON.parse(atob(dataEl.getAttribute(dataAtt)))
      : {};
  example.builder = data;
});

// Use utils when app is built
onMounted(() => {
  useGlobal();
});
</script>

<template>
<!-- Template part -->
  <DashboardLayout>
    <BuilderExample v-if="example.builder" :builder="example.builder" />
    <!-- alternative -->
    <!-- <BuilderCollection v-if="example.builder" :builder="example.builder" /> -->
  </DashboardLayout>
</template>

Open index.html file, you get something like this :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/x-icon" href="/img/favicon.ico" />
    <link rel="stylesheet" href="/css/style.css" />
    <link rel="stylesheet" href="/css/flag-icons.min.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BunkerWeb | Home</title>
  </head>
  <body>
    <div
      class="hidden"
      data-server-global='{"username" : "admin", "plugins_page": [{"id" : "antibot", "name": "Antibot"}, {"id": "backup", "name" : "backup"}]}'
    ></div>
    <div
      class="hidden"
      data-server-flash='[{"type" : "success", "title" : "success", "message" : "Success feedback"}, {"type" : "error", "title" : "error", "message" : "Error feedback"}, {"type" : "warning", "title" : "warning", "message" : "Warning feedback"}, {"type" : "info", "title" : "info", "message" : "Info feedback"}]'
    ></div>
    <div
      class="hidden"
      data-server-builder=""
    ></div>
    <div id="app"></div>
    <script type="module" src="home.js"></script>
  </body>
</html>

You need to change title and the script part like this :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/x-icon" href="/img/favicon.ico" />
    <link rel="stylesheet" href="/css/style.css" />
    <link rel="stylesheet" href="/css/flag-icons.min.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BunkerWeb | Example</title>
  </head>
  <body>
    <div
      class="hidden"
      data-server-global='{"username" : "admin", "plugins_page": [{"id" : "antibot", "name": "Antibot"}, {"id": "backup", "name" : "backup"}]}'
    ></div>
    <div
      class="hidden"
      data-server-flash='[{"type" : "success", "title" : "success", "message" : "Success feedback"}, {"type" : "error", "title" : "error", "message" : "Error feedback"}, {"type" : "warning", "title" : "warning", "message" : "Warning feedback"}, {"type" : "info", "title" : "info", "message" : "Info feedback"}]'
    ></div>
    <div
      class="hidden"
      data-server-builder=""
    ></div>
    <div id="app"></div>
    <script type="module" src="example.js"></script>
  </body>
</html>

2.1 Create custom builder (optional)

In case you don't want to use the BuilderCollection component but a custom and optimized one for your page, you need to create a new component in the src/ui/client/dashboard/components/Builder folder.

You can copy an existing component like Collection.vue and rename it to Example.vue, and inside it, you can remove useless components (and add new ones if needed).

3. Access page on dev

Now you can start dev your page, go to src/ui/client and run the following command :

npm install &&
npm run dev-dashboard

This will prepare vite and run a dev server, you can access your page on http://localhost:3000/dashboard/pages/example/index.html

4. Create page builder

The UI is using the JSdoc from components to generate a widgets.py that allow to create a component using python in a builder approach. In order to create a builder for the new page, you need to go to src/ui/client/builder/pages and you can start by copying an existing page folder like home.py and rename it to example.py.

4.1 Create test_builder (optional)

In case you want to test your page and the result of the builder, you can create a test file in the src/ui/client/builder folder. For example, you can copy the test_bans.py file and rename it to test_example.py.

You can import your builder, and run the builder with raw data to see the result. You can also directly update the data on your dev page using the save_builder utils.

5. Add page to build

By default, the new pages will not be added to built app. We need to update the vite.config.dashboard.js file and inclue the new page on build :

import { resolve } from "path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import { comment } from "postcss";

// https://vitejs.dev/config/
export default defineConfig({
  // ...
  build: {
    // ...
    rollupOptions: {
      input: {
        // ...
        // Add new page
        example: resolve(__dirname, "./dashboard/pages/example/index.html"),
      },
    },
  },
});

6. Update flask part

You need to know that pages and utils from src/ui/client/builder/ will be accessible on built from the main.py file. In order to add the page, you can use the existing src/ui/pages folder and follow how they are import in the main.py file.

After that, you will get the new page on the built app.

Create a standalone page

Standalone page is an all-in-one html file with js, css and any resources directly in the file. This is useful for pages that don't need to be part of the dashboard, or for pages that can't access to external resources like the setup page.

A standalone is similar to a dashboard page, but the differences is that we need a specifig vite config file like vite.config.standalone.js that is using viteSingleFile plugin to enable a single file build.

If you want to create your own standalone page, you can copy the standalone folder in src/ui/client/standalone and rename it to your page name. You need to create a vite config file based on the vite.config.standalone.js updating the build settings.

In case you want to add the standalone page in built app, you need to update the build.py file, and add the output file on the template folder AFTER the other logic.

Notice that standalone page is useful for custom plugins pages.

Utils

Because the UI is using a builder approach, we need to create utils that will allow to interact with components or to allow actions without components interaction.

The utils are located in the src/ui/client/dashboard/utils folder, you have the following utils :

  • global.js : You can find attributes based utils, this is utils that will listen to component attributes. It works well with some components and the attrs props. You can attach a link to a button to be redirect, or create a static value form to submit on click...
  • filter.js : Filter component utils only.
  • lang.js : All logic that is related to i18n, like getting the i18n instance, getting only needed keys, or getting the current language.
  • etc

You have specific logic in src/ui/client/dashboard/store too :

  • form.js : Allow to share and execute specific logic for advanced, raw or easy mode.
  • global.js : Others share data and state. For example the displayStore is useful because it allows to show/hide an element after a button click.