├── .env ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── bug.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── docker.yml │ └── stale.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── README.md ├── Side-Projects.md ├── ci.sh ├── docker-build.sh ├── docker-compose.yml ├── docker-entrypoint.sh ├── docker-healthcheck.sh ├── examples ├── fixing_backup_errors │ ├── README.md │ └── unifi-backup-1.png └── traefikv2 │ ├── .env.example │ ├── README.md │ └── docker-compose.yml ├── functions ├── hotfixes └── README.md ├── import_cert ├── license └── pre_build ├── README.md └── armhf └── mongodb.sh /.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=unifi -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | /functions text eol=lf 3 | /import_cert text eol=lf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["bug"] 4 | assignees: 5 | - jacobalberty 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Before opening a bug report, please search for the behaviour in the existing issues. 11 | 12 | --- 13 | 14 | Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information. 15 | - type: input 16 | id: os 17 | attributes: 18 | label: Operating system 19 | description: "Which operating system do you use? Please provide the version as well." 20 | placeholder: "Ubuntu Linux 20.04.3 LTS" 21 | validations: 22 | required: true 23 | - type: input 24 | id: unifi 25 | attributes: 26 | label: UniFi Tag 27 | description: "Please specify the tag of the Docker image you are using" 28 | placeholder: "stable" 29 | validations: 30 | required: true 31 | - type: input 32 | id: docker 33 | attributes: 34 | label: Docker run 35 | description: "Please specify the full Docker command or the docker-compose.yml file you use to start the container (omit sensitive values)" 36 | placeholder: "docker run --rm --init -p 8080:8080 -p 8443:8443 -p 3478:3478/udp -p 10001:10001/udp -e TZ='Africa/Johannesburg' -v ~/unifi/data:/var/lib/unifi -v ~/unifi/logs:/var/log/unifi --name unifi jacobalberty/unifi:stable" 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: bug-description 41 | attributes: 42 | label: Bug description 43 | description: What happened? 44 | validations: 45 | required: true 46 | - type: textarea 47 | id: steps 48 | attributes: 49 | label: Steps to reproduce 50 | description: Which steps do we need to take to reproduce this error? 51 | - type: textarea 52 | id: logs 53 | attributes: 54 | label: Relevant log output 55 | description: If applicable, provide relevant log output. No need for backticks here. 56 | render: shell -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Use a prefix like [TASK], [BUGFIX], [DOC] or [CGL] and provide a general summary of your changes in the Title above. 2 | 3 | **IMPORTANT: Please do not create a Pull Request without creating an issue first.** 4 | 5 | *Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request.* 6 | 7 | Please provide enough information so that others can review your pull request: 8 | 9 | ## Description 10 | Explain the **details** for making this change. What existing problem does the pull request solve? 11 | 12 | ## Motivation and Context 13 | Why is this change required? What problem does it solve? 14 | 15 | ## How Has This Been Tested? 16 | 17 | Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. 18 | 19 | ## Closing issues 20 | 21 | Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if such). 22 | 23 | ## Checklist: 24 | Go over all the following points, and put an `x` in all the boxes that apply. 25 | If you're unsure about any of these, don't hesitate to ask. We're here to help! 26 | - [ ] My code follows the code style of this project. (CI will test it anyway and also needs approval) 27 | - [ ] My change requires a change to the documentation. 28 | - [ ] I have updated the documentation accordingly. 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Docs: 2 | 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: {interval: monthly} 9 | reviewers: [jacobalberty] 10 | assignees: [jacobalberty] 11 | 12 | - package-ecosystem: docker 13 | directory: / 14 | schedule: {interval: monthly} 15 | reviewers: [jacobalberty] 16 | assignees: [jacobalberty] 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker image 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | env: 8 | TEST_TAG: jacobalberty/unifi:test 9 | PKGURL: https://dl.ui.com/unifi/6.5.55/unifi_sysvinit_all.deb 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Prepare 18 | id: prep 19 | run: | 20 | DOCKER_IMAGE=jacobalberty/unifi 21 | VERSION=noop 22 | if [ "${{ github.event_name }}" = "schedule" ]; then 23 | VERSION=nightly 24 | elif [[ $GITHUB_REF == refs/tags/* ]]; then 25 | VERSION=${GITHUB_REF#refs/tags/} 26 | elif [[ $GITHUB_REF == refs/heads/* ]]; then 27 | VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g') 28 | if [ "${{ github.event.repository.default_branch }}" = "$VERSION" ]; then 29 | VERSION=latest 30 | fi 31 | elif [[ $GITHUB_REF == refs/pull/* ]]; then 32 | VERSION=pr-${{ github.event.number }} 33 | fi 34 | TAGS="${DOCKER_IMAGE}:${VERSION}" 35 | if [[ $VERSION =~ ^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 36 | MINOR=${VERSION%.*} 37 | MAJOR=${MINOR%.*} 38 | TAGS="$TAGS,${DOCKER_IMAGE}:${MINOR},${DOCKER_IMAGE}:${MAJOR}" 39 | elif [ "${{ github.event_name }}" = "push" ]; then 40 | TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}" 41 | fi 42 | echo ::set-output name=version::${VERSION} 43 | echo ::set-output name=tags::${TAGS} 44 | echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') 45 | - name: Setup QEMU 46 | uses: docker/setup-qemu-action@v2 47 | - name: Setup Docker buildx 48 | uses: docker/setup-buildx-action@v2 49 | - name: Build and export to Docker 50 | uses: docker/build-push-action@v4 51 | with: 52 | context: . 53 | load: true 54 | tags: ${{ env.TEST_TAG }} 55 | - name: Test image 56 | run: | 57 | export PKGURL=${{env.PKGURL}} && 58 | docker run -d --env PKGURL --rm --name unifitest ${{env.TEST_TAG}} && 59 | sleep 35 && 60 | for i in $(seq 1 10); do [ $(docker inspect --format='{{json .State.Health.Status}}' unifitest ) == \"healthy\" ] && break || sleep 35; done && 61 | [ $(docker inspect --format='{{json .State.Health.Status}}' unifitest ) == \"healthy\" ] && 62 | docker stop unifitest 63 | - name: Build Docker image 64 | uses: docker/build-push-action@v4 65 | with: 66 | context: . 67 | platforms: linux/arm64/v8,linux/amd64 68 | push: false 69 | load: false 70 | cache-from: type=gha 71 | cache-to: type=gha,mode=max 72 | tags: ${{ steps.prep.outputs.tags }} 73 | labels: | 74 | org.opencontainers.image.title=${{ github.event.repository.name }} 75 | org.opencontainers.image.description=${{ github.event.repository.description }} 76 | org.opencontainers.image.url=${{ github.event.repository.html_url }} 77 | org.opencontainers.image.source=${{ github.event.repository.clone_url }} 78 | org.opencontainers.image.version=${{ steps.prep.outputs.version }} 79 | org.opencontainers.image.created=${{ steps.prep.outputs.created }} 80 | org.opencontainers.image.revision=${{ github.sha }} 81 | org.opencontainers.image.licenses=${{ github.event.repository.license.spdx_id }} 82 | - name: Image digest 83 | run: echo ${{ steps.docker_build.outputs.digest }} 84 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | - 'beta' 7 | - 'hotfix/*' 8 | tags: 9 | - 'v*.*.*' 10 | 11 | env: 12 | TEST_TAG: jacobalberty/unifi:test 13 | PKGURL: https://dl.ui.com/unifi/6.5.55/unifi_sysvinit_all.deb 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | - name: Set up QEMU 22 | uses: docker/setup-qemu-action@v2 23 | with: 24 | platforms: all 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v2 27 | - name: Login to GitHub Container Resgistry 28 | uses: docker/login-action@v2 29 | with: 30 | registry: ghcr.io 31 | username: ${{ github.actor }} 32 | password: ${{ secrets.GITHUB_TOKEN }} 33 | - name: Login to DockerHub 34 | uses: docker/login-action@v2 35 | with: 36 | username: ${{ secrets.DOCKERHUB_USERNAME }} 37 | password: ${{ secrets.DOCKERHUB_TOKEN }} 38 | - name: Build and export to Docker 39 | uses: docker/build-push-action@v4 40 | with: 41 | context: . 42 | load: true 43 | tags: ${{ env.TEST_TAG }} 44 | - name: Test image 45 | run: | 46 | export PKGURL=${{env.PKGURL}} && 47 | docker run -d --env PKGURL --rm --name unifitest ${{env.TEST_TAG}} && 48 | sleep 35 && 49 | for i in $(seq 1 10); do [ $(docker inspect --format='{{json .State.Health.Status}}' unifitest ) == \"healthy\" ] && break || sleep 35; done && 50 | [ $(docker inspect --format='{{json .State.Health.Status}}' unifitest ) == \"healthy\" ] && 51 | docker stop unifitest 52 | - name: Extract metadata (tags, labels) for Docker 53 | id: meta 54 | uses: docker/metadata-action@v4 55 | with: 56 | tags: | 57 | type=raw,value=latest,enable={{is_default_branch}} 58 | type=semver,pattern=v{{version}} 59 | type=semver,pattern=v{{major}} 60 | type=semver,pattern=v{{major}}.{{minor}} 61 | type=ref,event=branch 62 | images: | 63 | jacobalberty/unifi 64 | ghcr.io/${{ github.repository }} 65 | - name: Build container image 66 | uses: docker/build-push-action@v4 67 | with: 68 | context: . 69 | provenance: false 70 | platforms: linux/arm64/v8,linux/amd64 71 | push: true 72 | cache-from: type=gha 73 | cache-to: type=gha,mode=max 74 | tags: ${{ steps.meta.outputs.tags }} 75 | labels: ${{ steps.meta.outputs.labels }} 76 | - name: Image digest 77 | run: echo ${{ steps.docker_build.outputs.digest }} 78 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v8 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 13 | stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 14 | days-before-stale: 30 15 | days-before-close: 5 16 | stale-issue-label: 'no-issue-activity' 17 | stale-pr-label: 'no-pr-activity' 18 | exempt-issue-labels: 'awaiting-approval,work-in-progress' 19 | exempt-pr-labels: 'awaiting-approval,work-in-progress' 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .DS_Store 4 | backup/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | dist: bionic 3 | env: 4 | global: 5 | - DOCKER_REPO=jacobalberty/unifi 6 | services: 7 | - docker 8 | before_install: 9 | - sudo rm -rf /var/lib/apt/lists/* 10 | - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 11 | - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) edge" 12 | - sudo apt-get update 13 | - sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce 14 | - mkdir -vp ~/.docker/cli-plugins/ 15 | - curl --silent -L "https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-amd64" > ~/.docker/cli-plugins/docker-buildx 16 | - chmod a+x ~/.docker/cli-plugins/docker-buildx 17 | install: 18 | - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 19 | - docker buildx create --use 20 | script: bash ci.sh 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-bullseye as permset 2 | WORKDIR /src 3 | RUN git clone https://github.com/jacobalberty/permset.git /src && \ 4 | mkdir -p /out && \ 5 | go build -ldflags "-X main.chownDir=/unifi" -o /out/permset 6 | 7 | FROM ubuntu:20.04 8 | 9 | LABEL maintainer="Jacob Alberty " 10 | 11 | ARG DEBIAN_FRONTEND=noninteractive 12 | 13 | ARG PKGURL=https://dl.ui.com/unifi/9.1.120/unifi_sysvinit_all.deb 14 | 15 | ENV BASEDIR=/usr/lib/unifi \ 16 | DATADIR=/unifi/data \ 17 | LOGDIR=/unifi/log \ 18 | CERTDIR=/unifi/cert \ 19 | RUNDIR=/unifi/run \ 20 | ORUNDIR=/var/run/unifi \ 21 | ODATADIR=/var/lib/unifi \ 22 | OLOGDIR=/var/log/unifi \ 23 | CERTNAME=cert.pem \ 24 | CERT_PRIVATE_NAME=privkey.pem \ 25 | CERT_IS_CHAIN=false \ 26 | GOSU_VERSION=1.10 \ 27 | BIND_PRIV=true \ 28 | RUNAS_UID0=true \ 29 | UNIFI_GID=999 \ 30 | UNIFI_UID=999 31 | 32 | # Install gosu 33 | # https://github.com/tianon/gosu/blob/master/INSTALL.md 34 | # This should be integrated with the main run because it duplicates a lot of the steps there 35 | # but for now while shoehorning gosu in it is seperate 36 | RUN set -eux; \ 37 | apt-get update; \ 38 | apt-get install -y gosu; \ 39 | rm -rf /var/lib/apt/lists/* 40 | 41 | RUN mkdir -p /usr/unifi \ 42 | /usr/local/unifi/init.d \ 43 | /usr/unifi/init.d \ 44 | /usr/local/docker 45 | COPY docker-entrypoint.sh /usr/local/bin/ 46 | COPY docker-healthcheck.sh /usr/local/bin/ 47 | COPY docker-build.sh /usr/local/bin/ 48 | COPY functions /usr/unifi/functions 49 | COPY import_cert /usr/unifi/init.d/ 50 | COPY pre_build /usr/local/docker/pre_build 51 | RUN chmod +x /usr/local/bin/docker-entrypoint.sh \ 52 | && chmod +x /usr/unifi/init.d/import_cert \ 53 | && chmod +x /usr/local/bin/docker-healthcheck.sh \ 54 | && chmod +x /usr/local/bin/docker-build.sh \ 55 | && chmod -R +x /usr/local/docker/pre_build 56 | 57 | # Push installing openjdk-8-jre first, so that the unifi package doesn't pull in openjdk-7-jre as a dependency? Else uncomment and just go with openjdk-7. 58 | RUN set -ex \ 59 | && mkdir -p /usr/share/man/man1/ \ 60 | && groupadd -r unifi -g $UNIFI_GID \ 61 | && useradd --no-log-init -r -u $UNIFI_UID -g $UNIFI_GID unifi \ 62 | && /usr/local/bin/docker-build.sh "${PKGURL}" 63 | 64 | COPY --from=permset /out/permset /usr/local/bin/permset 65 | RUN chown 0.0 /usr/local/bin/permset && \ 66 | chmod +s /usr/local/bin/permset 67 | 68 | RUN mkdir -p /unifi && chown unifi:unifi -R /unifi 69 | 70 | # Apply any hotfixes that were included 71 | COPY hotfixes /usr/local/unifi/hotfixes 72 | 73 | RUN chmod +x /usr/local/unifi/hotfixes/* && run-parts /usr/local/unifi/hotfixes 74 | 75 | VOLUME ["/unifi", "${RUNDIR}"] 76 | 77 | EXPOSE 6789/tcp 8080/tcp 8443/tcp 8880/tcp 8843/tcp 3478/udp 10001/udp 78 | 79 | WORKDIR /unifi 80 | 81 | HEALTHCHECK --start-period=5m CMD /usr/local/bin/docker-healthcheck.sh || exit 1 82 | 83 | # execute controller using JSVC like original debian package does 84 | ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] 85 | 86 | CMD ["unifi"] 87 | 88 | # execute the conroller directly without using the service 89 | #ENTRYPOINT ["/usr/bin/java", "-Xmx${JVM_MAX_HEAP_SIZE}", "-jar", "/usr/lib/unifi/lib/ace.jar"] 90 | # See issue #12 on github: probably want to consider how JSVC handled creating multiple processes, issuing the -stop instraction, etc. Not sure if the above ace.jar class gracefully handles TERM signals. 91 | #CMD ["start"] 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unifi-in-Docker (unifi-docker) 2 | 3 | This repo contains a Dockerized version of [Ubiqiti Network's](https://www.ubnt.com/) Unifi Controller. 4 | 5 | **Why bother?** Using Docker, you can stop worrying about version 6 | hassles and update notices for 7 | Unifi Controller, Java, _or_ your OS. 8 | A Docker container wraps everything into one well-tested bundle. 9 | 10 | To install, a couple lines on the command-line starts the container. 11 | To upgrade, just stop the old container, and start up the new. 12 | It's really that simple. 13 | 14 | This container has been tested on Ubuntu, Debian, macOS, Windows, 15 | and even Raspberry Pi hardware. 16 | 17 | **Latest Version:** The latest version is shown in the first line 18 | of the [Current Information](#current-information) table on this page. 19 | 20 | ## Setting up, Running, Stopping, Upgrading 21 | 22 | First, install Docker on the "Docker host" - 23 | the machine that will run the Docker 24 | and Unifi Controller software. 25 | Use any of the guides on the internet to install on your Docker host. 26 | For Windows, see the [Microsoft guide for installing Docker.](https://docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-containers) 27 | 28 | Then use the following steps to set up the directories 29 | and start the Docker container running. 30 | 31 | ### Setting up directories 32 | 33 | _One-time setup:_ create the `unifi` directory on the Docker host. 34 | Within that directory, create two sub-directories: `data` and `log`. 35 | 36 | ```bash 37 | cd # by default, use the home directory 38 | mkdir -p unifi/data 39 | mkdir -p unifi/log 40 | ``` 41 | 42 | _Note:_ By default, this README assumes you will use the home directory 43 | on Linux, Unix, macOS. 44 | If you create the directory elsewhere, read the 45 | [Options section](#options-on-the-command-line) 46 | below to adjust.) 47 | 48 | ### Running Unifi-in-Docker 49 | 50 | Each time you want to start Unifi, use this command. 51 | Each of the options is [described below.](#options-on-the-command-line) 52 | 53 | ```bash 54 | docker run -d --init \ 55 | --restart=unless-stopped \ 56 | -p 8080:8080 -p 8443:8443 -p 3478:3478/udp \ 57 | -e TZ='Africa/Johannesburg' \ 58 | -v ~/unifi:/unifi \ 59 | --user unifi \ 60 | --name unifi \ 61 | jacobalberty/unifi 62 | ``` 63 | 64 | In a minute or two, (after Unifi Controller starts up) you can go to 65 | [https://docker-host-address:8443](https://docker-host-address:8443) 66 | to complete configuration from the web (initial install) or resume using Unifi Controller. 67 | 68 | **Important:** Two points to be aware of when you're setting up your Unifi Controller: 69 | 70 | * When your browser initially connects to the link above, you will 71 | see a warning about an untrusted certificate. 72 | If you are _certain_ that you have typed the address of the 73 | Docker host correctly, agree to the connection. 74 | * See the note below about **Override "Inform Host" IP** so your 75 | Unifi devices can "find" the Unifi Controller. 76 | 77 | ### Stopping Unifi-in-Docker 78 | 79 | To change options, stop the Docker container then re-run the `docker run...` command 80 | above with the new options. 81 | _Note:_ The `docker rm unifi` command simply removes the "name" from the previous Docker image. 82 | No time-consuming rebuild is required. 83 | 84 | ```bash 85 | docker stop unifi 86 | docker rm unifi 87 | ``` 88 | ### Upgrading Unifi Controller 89 | 90 | All the configuration and other files created by Unifi Controller 91 | are stored on the Docker host's local disk (`~/unifi` by default.) 92 | No information is retained within the container. 93 | An upgrade to a new version of Unifi Controller simply retrieves a new Docker container, 94 | which then re-uses the configuration from the local disk. 95 | The upgrade process is: 96 | 97 | 1. **MAKE A BACKUP** on another computer, not the Docker host _(Always, every time...)_ 98 | 2. Stop the current container (see above) 99 | 3. Enter `docker run...` with the newer container tag (see [Current Information](#current-information) section below.) 100 | 101 | ## Options on the Command Line 102 | 103 | The options for the `docker run...` command are: 104 | 105 | - `-d` - Detached mode: Unifi-in-Docker runs in the background 106 | - `--init` - Recommended to ensure processes get reaped when they die 107 | - `--restart=unless-stopped` - If the container should stop for some reason, 108 | restart it unless you issue a `docker stop ...` 109 | - `-p ...` - Set the ports to pass through to the container. 110 | `-p 8080:8080 -p 8443:8443 -p 3478:3478/udp` 111 | is the minimal set for a working Unifi Controller. 112 | - `-e TZ=...` Set an environment variable named `TZ` with the desired time zone. 113 | Find your time zone in this 114 | [list of timezones.](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) 115 | - `-e ...` See the [Environment Variables](#environment-variables) 116 | section for more environment variables. 117 | - `-v ...` - Bind the volume `~/unifi` on the Docker host 118 | to the directory `/unifi`inside the container. 119 | **These instructions assume you placed the "unifi" directory in your home directory.** 120 | If you created the directory elsewhere, modify the `~/unifi` part of this option to match. 121 | See the [Volumes](#volumes) discussion for other volumes used by Unifi Controller. 122 | - `--user unifi` - Run as a non-root user. See the [Run as non-root User](#run-as-non-root-user) discussion below 123 | - `jacobalberty/unifi` - the name of the container to use. 124 | The `jacobalberty...` image is retrieved from [Dockerhub.](https://hub.docker.com/r/jacobalberty/unifi) 125 | The [Current Information](#current-information) section below discusses the versions/tags that are available. 126 | 127 | ## Current Information 128 | 129 | The current tested version of unifi-docker is listed in the table below. 130 | You can choose the version of Unifi Controller in the `docker run ...` command. 131 | In Docker terminology, these versions are specified by "tags". 132 | 133 | For example, in this project the container named `jacobalberty/unifi` 134 | (with no "tag") 135 | provides the most recent stable release. 136 | The table below lists recent versions. 137 | 138 | The `rc` tag (for example, `jacobalberty/unifi:rc`) 139 | uses the most recent Release Candidate from the UniFi APT repository. 140 | 141 | You may also specify a version number (e.g., `jacobalberty/unifi:stable6`) 142 | to get a specific version number, as shown in the table below. 143 | 144 | _Note:_ In Docker, specifying an image with no tag 145 | (e.g., `jacobalberty/unifi`) gets the "latest" tag. 146 | For Unifi-in-Docker, this uses the most recent stable version. 147 | 148 | | Tag | Description | Changelog | 149 | |-------------------------------------------------------------------------------------------|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| 150 | | [`latest` `v9.1.120`](https://github.com/jacobalberty/unifi-docker/blob/master/Dockerfile) | Current Stable: Version 9.1.120 as of 2025-26-04 | [Change Log 9.1.120](https://community.ui.com/releases/UniFi-Network-Application-9-1-120/a5e88ae2-3c44-420a-bebb-5120bf2288b2) | 151 | | [`stable-6`](https://github.com/jacobalberty/unifi-docker/blob/stable-6/Dockerfile) | Final stable version 6 (6.5.55) | [Change Log 6.5.55](https://community.ui.com/releases/UniFi-Network-Application-6-5-55/48c64137-4a4a-41f7-b7e4-3bee505ae16e) | 152 | | [`stable-5`](https://github.com/jacobalberty/unifi-docker/blob/stable-5/Dockerfile) | Final stable version 5 (5.4.23) | [Change Log 5.14.23](https://community.ui.com/releases/UniFi-Network-Controller-5-14-23/daf90732-30ad-48ee-81e7-1dcb374eba2a) | 153 | 154 | ### multiarch 155 | 156 | All available containers now support multiarch with `amd64`, `armhf`, and `arm64` builds included. 157 | `armhf` for now uses mongodb 3.4, I do not see much of a path forward for `armhf` due 158 | to the lack of mongodb support for 32 bit arm, but I will 159 | support it as long as feasibly possible, for now that date seems to be expiration of support for ubuntu 18.04. 160 | 161 | ## Adopting Access Points and Unifi Devices 162 | 163 | #### Override "Inform Host" IP 164 | 165 | For your Unifi devices to "find" the Unifi Controller running in Docker, 166 | you _MUST_ override the Inform Host IP 167 | with the address of the Docker host computer. 168 | (By default, the Docker container usually gets the internal address 172.17.x.x 169 | while Unifi devices connect to the (external) address of the Docker host.) 170 | To do this: 171 | 172 | * Find **Settings -> System -> Other Configuration -> Override Inform Host:** in the Unifi Controller web GUI. 173 | (It's near the bottom of that page.) 174 | * Check the "Enable" box, and enter the IP address of the Docker host machine. 175 | * Save settings in Unifi Controller 176 | * Restart UniFi-in-Docker container with `docker stop ...` and `docker run ...` commands. 177 | 178 | _Hint: Port 10001 should be forwareded to make it work._ 179 | 180 | See [Side Projects](https://github.com/jacobalberty/unifi-docker/blob/master/Side-Projects.md#other-techniques-for-adoption) for 181 | other techniques to get Unifi devices to adopt your 182 | new Unifi Controller. 183 | 184 | ## Volumes 185 | 186 | Unifi looks for the `/unifi` directory (within the container) 187 | for its special purpose subdirectories: 188 | 189 | * `/unifi/data` This contains your UniFi configuration data. (formerly: `/var/lib/unifi`) 190 | 191 | * `/unifi/log` This contains UniFi log files (formerly: `/var/log/unifi`) 192 | 193 | * `/unifi/cert` Place custom SSL certs in this directory. 194 | For more information regarding the naming of the certificates, 195 | see [Certificate Support](#certificate-support). (formerly: `/var/cert/unifi`) 196 | 197 | * `/unifi/init.d` 198 | You can place scripts you want to launch every time the container starts in here 199 | 200 | * `/var/run/unifi` 201 | Run information, in general you will not need to touch this volume. 202 | It is there to ensure UniFi has a place to write its PID files 203 | 204 | ### Legacy Volumes 205 | 206 | These are no longer actually volumes, rather they exist for legacy compatibility. 207 | You are urged to move to the new volumes ASAP. 208 | 209 | * `/var/lib/unifi` New name: `/unifi/data` 210 | * `/var/log/unifi` New name: `/unifi/log` 211 | 212 | ## Environment Variables: 213 | 214 | You can pass in environment variables using the `-e` option when you invoke `docker run...` 215 | See the `TZ` in the example above. 216 | Other environment variables: 217 | 218 | * `UNIFI_HTTP_PORT` 219 | This is the HTTP port used by the Web interface. Browsers will be redirected to the `UNIFI_HTTPS_PORT`. 220 | **Default: 8080** 221 | 222 | * `UNIFI_HTTPS_PORT` 223 | This is the HTTPS port used by the Web interface. 224 | **Default: 8443** 225 | 226 | * `PORTAL_HTTP_PORT` 227 | Port used for HTTP portal redirection. 228 | **Default: 80** 229 | 230 | * `PORTAL_HTTPS_PORT` 231 | Port used for HTTPS portal redirection. 232 | **Default: 8843** 233 | 234 | * `UNIFI_STDOUT` 235 | Controller outputs logs to stdout in addition to server.log 236 | **Default: unset** 237 | 238 | * `TZ` 239 | TimeZone. (i.e America/Chicago) 240 | 241 | * `JVM_MAX_THREAD_STACK_SIZE` 242 | Used to set max thread stack size for the JVM 243 | Example: 244 | 245 | ``` 246 | --env JVM_MAX_THREAD_STACK_SIZE=1280k 247 | ``` 248 | 249 | as a fix for [https://community.ubnt.com/t5/UniFi-Routing-Switching/IMPORTANT-Debian-Ubuntu-users-MUST-READ-Updated-06-21/m-p/1968251#M48264](https://community.ubnt.com/t5/UniFi-Routing-Switching/IMPORTANT-Debian-Ubuntu-users-MUST-READ-Updated-06-21/m-p/1968251#M48264) 250 | 251 | * `LOTSOFDEVICES` 252 | Enable this with `true` if you run a system with a lot of devices 253 | and/or with a low powered system (like a Raspberry Pi). 254 | This makes a few adjustments to try and improve performance: 255 | 256 | * enable unifi.G1GC.enabled 257 | * set unifi.xms to JVM\_INIT\_HEAP\_SIZE 258 | * set unifi.xmx to JVM\_MAX\_HEAP\_SIZE 259 | * enable unifi.db.nojournal 260 | * set unifi.dg.extraargs to --quiet 261 | 262 | See [the Unifi support site](https://help.ui.com/hc/en-us/articles/115005159588-UniFi-How-to-Tune-the-Network-Application-for-High-Number-of-UniFi-Devices) 263 | for an explanation of some of those options. 264 | **Default: unset** 265 | 266 | * `JVM_EXTRA_OPTS` 267 | Used to start the JVM with additional arguments. 268 | **Default: unset** 269 | 270 | * `JVM_INIT_HEAP_SIZE` 271 | Set the starting size of the javascript engine for example: `1024M` 272 | **Default: unset** 273 | 274 | * `JVM_MAX_HEAP_SIZE` 275 | Java Virtual Machine (JVM) allocates available memory. 276 | For larger installations a larger value is recommended. For memory constrained system this value can be lowered. 277 | **Default: 1024M** 278 | 279 | ## Exposed Ports 280 | 281 | The Unifi-in-Docker container exposes the following ports. 282 | A minimal Unifi Controller installation requires you 283 | expose the first three with the `-p ...` option. 284 | 285 | * 8080/tcp - Device command/control 286 | * 8443/tcp - Web interface + API 287 | * 3478/udp - STUN service 288 | * 8843/tcp - HTTPS portal _(optional)_ 289 | * 8880/tcp - HTTP portal _(optional)_ 290 | * 6789/tcp - Speed Test (unifi5 only) _(optional)_ 291 | * 10001/udp - Used for device discovery _(optional)_ 292 | 293 | See [UniFi - Ports Used](https://help.ubnt.com/hc/en-us/articles/218506997-UniFi-Ports-Used) for more information. 294 | 295 | ## Run as non-root User 296 | 297 | The default container runs Unifi Controller as root. 298 | The recommended `docker run...` command above starts 299 | Unifi Controller so the image runs as `unifi` (non-root) 300 | user with the uid/gid 999/999. 301 | You can also set your data and logs directories to be 302 | owned by the proper gid. 303 | 304 | _Note:_ When you run as a non-root user, 305 | you will not be able to bind to lower ports by default. 306 | (This would not necessary if you are using the default ports.) 307 | If you must do this, also pass the 308 | `--sysctl net.ipv4.ip_unprivileged_port_start=0` 309 | option on the `docker run...` to bind to whatever port you wish. 310 | 311 | ## Certificate Support 312 | 313 | To use custom SSL certs, you must map a volume with the certs to `/unifi/cert` 314 | 315 | They should be named: 316 | 317 | ```shell 318 | cert.pem # The Certificate 319 | privkey.pem # Private key for the cert 320 | chain.pem # full cert chain 321 | ``` 322 | 323 | If your certificate or private key have different names, you can set the environment variables `CERTNAME` and `CERT_PRIVATE_NAME` to the name of your certificate/private key, e.g. `CERTNAME=my-cert.pem` and `CERT_PRIVATE_NAME=my-privkey.pem`. 324 | 325 | For letsencrypt certs, we'll autodetect that and add the needed Identrust X3 CA Cert automatically. In case your letsencrypt cert is already the chained certificate, you can set the `CERT_IS_CHAIN` environment variable to `true`, e.g. `CERT_IS_CHAIN=true`. This option also works together with a custom `CERTNAME`. 326 | 327 | ### Certificates Using Elliptic Curve Algorithms 328 | 329 | If your certs use elliptic curve algorithms, which currently seems to be the default with letsencrypt certs, you might additionally have to set the `UNIFI_ECC_CERT` environment variable to `true`, otherwise clients will fail to establish a secure connection. For example an attempt with `curl` will show: 330 | 331 | ```shell 332 | % curl -vvv https://my.server.com:8443 333 | curl: (35) error:1404B410:SSL routines:ST_CONNECT:sslv3 alert handshake failure 334 | ``` 335 | 336 | You can check your certificate for this with the following command: 337 | 338 | ```shell 339 | % openssl x509 -text < cert.pem | grep 'Public Key Algorithm' 340 | Public Key Algorithm: id-ecPublicKey 341 | ``` 342 | 343 | If the output contains `id-ec` as shown in the example, then your certificate might be affected. 344 | 345 | ## Additional Information 346 | 347 | This document describes everything you need to get Unifi-in-Docker running. 348 | The [Side Projects and Background Info](https://github.com/jacobalberty/unifi-docker/blob/master/Side-Projects.md) page 349 | provides more about what we've learned while developing Unifi-in-Docker. 350 | 351 | ## TODO 352 | 353 | This list is empty for now, please [add your suggestions](https://github.com/jacobalberty/unifi-docker/issues). 354 | -------------------------------------------------------------------------------- /Side-Projects.md: -------------------------------------------------------------------------------- 1 | # Side Projects and Background Info 2 | 3 | The [README.md](./README.md) document describes how to get 4 | Unifi-in-Docker running for the most common case - 5 | a single easy-to-use container that runs everything. 6 | 7 | This document describes background, side projects, 8 | or other information we discovered while producing the 9 | Unifi-in-Docker container. 10 | 11 | ## Running with separate Mongo container 12 | 13 | The `docker-compose.yml` file in this repository provides a 14 | single command that orchestrates all the actions required 15 | to bring up Mongo and Unifi Controller in separate containers, 16 | using named volumes for important directories. 17 | 18 | Simply copy the `docker-compose.yml` file 19 | to your host computer's local disk 20 | (or clone this repo) and run: 21 | 22 | ```bash 23 | cd 24 | docker-compose up -d 25 | ``` 26 | 27 | **Setting Options:** 28 | 29 | * The `docker-compose.yml` file contains the options 30 | passed to Unifi Controller when it starts. 31 | Edit the `docker-compose.yml` file setting its values according to the 32 | [Options on the command line.](./README.md#options-on-the-command-line) 33 | * _Optional:_ Add additional `-e ` to the `docker-compose up` line 34 | 35 | To change options to Unifi Controller:: 36 | 37 | ```bash 38 | cd 39 | docker-compose down # this stops Unifi Controller and MongoDB 40 | # ... edit the options in the docker-compose.yml file ... 41 | docker-compose up ... # to resume operation 42 | ``` 43 | 44 | ### External MongoDB environment variables 45 | 46 | These variables are used to implement support for an [external MongoDB server](https://community.ubnt.com/t5/UniFi-Wireless/External-MongoDB-Server/td-p/1305297) and must all be set in order for this feature to work. Once all are set then the configuration file value for `db.mongo.local` will automatically be set to `false`. 47 | 48 | * `DB_URI` 49 | Maps to `db.mongo.uri`. 50 | 51 | * `STATDB_URI` 52 | Maps to `statdb.mongo.uri`. 53 | 54 | * `DB_NAME` 55 | Maps to `unifi.db.name`. 56 | 57 | ## Beta Users 58 | 59 | The `beta` image has been updated to support package installation at run time. 60 | With this change you can now install the beta releases on more systems, 61 | such as Synology. 62 | This should open up access to the beta program for more users of this docker image. 63 | 64 | **NOTE:** This Beta image only works if you run as root. 65 | It also may need updates to handle permissions to handle the 66 | `RUNAS_UID0=false` changes. 67 | If you have questions, look for or create an issue about this. 68 | 69 | If you would like to submit a new feature for the images, 70 | the beta branch is probably a good one to apply it against as well. 71 | I will be cleaing up the Dockerfile under beta and gradually pushing out 72 | the improvements to the other branches. 73 | So any major changes should apply cleanly against the `beta` branch. 74 | 75 | ### Running Beta Builds from the Command Line 76 | 77 | Using the Beta build is pretty easy: 78 | just substitute the correct URL from the Unifi site 79 | for the `PKGURL` parameter, 80 | and use `jacobalberty/unifi:beta` for the image 81 | like this: 82 | 83 | ```bash 84 | docker run -d --init \ 85 | --restart=unless-stopped \ 86 | -p 8080:8080 -p 8443:8443 -p 3478:3478/udp \ 87 | -e TZ='Africa/Johannesburg' \ 88 | -v ~/unifi:/unifi \ 89 | --name unifi \ 90 | -e PKGURL=https://dl.ubnt.com/unifi/5.6.30/unifi_sysvinit_all.deb \ 91 | jacobalberty/unifi:beta 92 | ``` 93 | 94 | ### Running the Beta Using `docker-compose.yml` 95 | 96 | In the containers service definition of the `docker-compose.yml` file, replace `image: jacobalberty/unifi` with the following: 97 | 98 | ```shell 99 | image: jacobalberty/unifi:beta 100 | environment: 101 | PKGURL: https://dl.ubnt.com/unifi/5.6.40/unifi_sysvinit_all.deb 102 | ``` 103 | 104 | Replace the PKGURL: link with a link to the package you want. 105 | 106 | ## Init scripts 107 | 108 | You may now place init scripts to be launched during the unifi startup in /usr/local/unifi/init.d to perform any actions unique to your unifi setup. An example bash script to set up certificates is in `/usr/unifi/init.d/import_cert`. 109 | 110 | ## Other Techniques for Adoption 111 | 112 | The following are not strictly required for Unifi-in-Docker, 113 | but they collect information that may be helpful as you 114 | move to a new controller instance. 115 | 116 | ### Use Unifi export and migrate tool 117 | 118 | Unifi can export and migrate the APs to a new controller 119 | [see this article for example.](https://lazyadmin.nl/home-network/migrate-unifi-controller/) 120 | 121 | #### SSH Adoption 122 | 123 | SSH into the device: 124 | ``` 125 | set-inform http://:8080/inform 126 | ``` 127 | 128 | #### Force Migration 129 | 130 | Force an AP to migrate using [this Unifi community article.](https://community.ui.com/questions/Migrating-UNIFI-APs-to-new-controller/9ca9d8e9-780d-404d-84df-e7762cb810fd) 131 | 132 | #### Older versions of Unifi Controller 133 | 134 | Older Unifi Controllers use a different name for the "Override Inform Host option". 135 | Look for **Settings -> Controller:** 136 | Enter the IP address of the Docker host machine in "Controller Hostname/IP", 137 | and check the "Override inform host with controller hostname/IP". 138 | 139 | #### Other Options 140 | 141 | You can see more options on the [UniFi website](https://help.ubnt.com/hc/en-us/articles/204909754-UniFi-Layer-3-methods-for-UAP-adoption-and-management) 142 | 143 | ### Layer 2 Adoption 144 | 145 | The layer 3 techniques above should be all you need to get new APs to adopt your controller running in Docker. 146 | You can also configure the Docker instance so that its IP address matches its host address so that Layer 2 adoption works 147 | using either of these settings. 148 | 149 | #### Host Networking 150 | 151 | If you launch the container using host networking \(With the `--net=host` parameter on `docker run`\) Layer 2 adoption works as if the controller is installed on the host. 152 | 153 | #### Bridge Networking 154 | 155 | It is possible to configure the `macvlan` driver to bridge your container to the host's networking adapter. 156 | Specific instructions for this container are not yet available but you can read a write-up for docker at 157 | [collabnix.com/docker-17-06-swarm-mode-now-with-macvlan-support](http://collabnix.com/docker-17-06-swarm-mode-now-with-macvlan-support/). 158 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | getTags() { 4 | BRANCH="${TRAVIS_BRANCH:-latest}" 5 | if [ $BRANCH = 'master' ]; then 6 | BRANCH=latest 7 | fi 8 | echo --tag $DOCKER_REPO:$BRANCH 9 | for tag in $DOCKER_TAGS; do 10 | echo --tag $DOCKER_REPO:$tag 11 | done 12 | } 13 | 14 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 15 | docker buildx build \ 16 | --progress plain \ 17 | --platform linux/arm/v7,linux/arm64/v8,linux/amd64 \ 18 | . 19 | 20 | docker build -t unifi:latest . 21 | 22 | docker run -d -p 8443:8443 -p 8080:8080 -e PKGURL --name unifi unifi:latest 23 | docker ps | grep -q unifi 24 | docker logs unifi 25 | sleep 10 && curl --connect-timeout 5 --max-time 10 --retry 5 --retry-delay 0 --retry-max-time 60 -kILs --fail http://127.0.0.1:8080 || exit 1 26 | exit $? 27 | fi 28 | echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin &> /dev/null 29 | docker buildx build \ 30 | --progress plain \ 31 | --platform linux/arm/v7,linux/arm64/v8,linux/amd64 \ 32 | $(getTags) \ 33 | --push \ 34 | . 35 | -------------------------------------------------------------------------------- /docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # fail on error 4 | set -e 5 | 6 | # Retry 5 times with a wait of 10 seconds between each retry 7 | tryfail() { 8 | for i in $(seq 1 5); 9 | do [ $i -gt 1 ] && sleep 10; $* && s=0 && break || s=$?; done; 10 | (exit $s) 11 | } 12 | 13 | # Try multiple keyservers in case of failure 14 | addKey() { 15 | for server in $(shuf -e ha.pool.sks-keyservers.net \ 16 | hkp://p80.pool.sks-keyservers.net:80 \ 17 | keyserver.ubuntu.com \ 18 | hkp://keyserver.ubuntu.com:80 \ 19 | pgp.mit.edu) ; do \ 20 | if apt-key adv --keyserver "$server" --recv "$1"; then 21 | exit 0 22 | fi 23 | done 24 | return 1 25 | } 26 | 27 | if [ "x${1}" == "x" ]; then 28 | echo please pass PKGURL as an environment variable 29 | exit 0 30 | fi 31 | 32 | apt-get update 33 | apt-get install -qy --no-install-recommends \ 34 | apt-transport-https \ 35 | curl \ 36 | dirmngr \ 37 | gpg \ 38 | gpg-agent \ 39 | openjdk-17-jre-headless \ 40 | procps \ 41 | libcap2-bin \ 42 | tzdata 43 | echo 'deb https://www.ui.com/downloads/unifi/debian stable ubiquiti' | tee /etc/apt/sources.list.d/100-ubnt-unifi.list 44 | tryfail apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 06E85760C0A52C50 45 | 46 | if [ -d "/usr/local/docker/pre_build/$(dpkg --print-architecture)" ]; then 47 | find "/usr/local/docker/pre_build/$(dpkg --print-architecture)" -type f -exec '{}' \; 48 | fi 49 | 50 | curl -L -o ./unifi.deb "${1}" 51 | apt -qy install ./unifi.deb 52 | rm -f ./unifi.deb 53 | chown -R unifi:unifi /usr/lib/unifi 54 | rm -rf /var/lib/apt/lists/* 55 | 56 | rm -rf ${ODATADIR} ${OLOGDIR} ${ORUNDIR} ${BASEDIR}/data ${BASEDIR}/run ${BASEDIR}/logs 57 | mkdir -p ${DATADIR} ${LOGDIR} ${RUNDIR} 58 | ln -s ${DATADIR} ${BASEDIR}/data 59 | ln -s ${RUNDIR} ${BASEDIR}/run 60 | ln -s ${LOGDIR} ${BASEDIR}/logs 61 | ln -s ${DATADIR} ${ODATADIR} 62 | ln -s ${LOGDIR} ${OLOGDIR} 63 | ln -s ${RUNDIR} ${ORUNDIR} 64 | mkdir -p /var/cert ${CERTDIR} 65 | ln -s ${CERTDIR} /var/cert/unifi 66 | 67 | rm -rf "${0}" 68 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | services: 3 | mongo: 4 | image: mongo:3.6 5 | container_name: ${COMPOSE_PROJECT_NAME}_mongo 6 | networks: 7 | - unifi 8 | restart: always 9 | volumes: 10 | - db:/data/db 11 | - dbcfg:/data/configdb 12 | controller: 13 | image: "jacobalberty/unifi:${TAG:-latest}" 14 | container_name: ${COMPOSE_PROJECT_NAME}_controller 15 | depends_on: 16 | - mongo 17 | init: true 18 | networks: 19 | - unifi 20 | restart: always 21 | volumes: 22 | - dir:/unifi 23 | - data:/unifi/data 24 | - log:/unifi/log 25 | - cert:/unifi/cert 26 | - init:/unifi/init.d 27 | - run:/var/run/unifi 28 | # Mount local folder for backups and autobackups 29 | - ./backup:/unifi/data/backup 30 | user: unifi 31 | sysctls: 32 | net.ipv4.ip_unprivileged_port_start: 0 33 | environment: 34 | DB_URI: mongodb://mongo/unifi 35 | STATDB_URI: mongodb://mongo/unifi_stat 36 | DB_NAME: unifi 37 | ports: 38 | - "3478:3478/udp" # STUN 39 | - "6789:6789/tcp" # Speed test 40 | - "8080:8080/tcp" # Device/ controller comm. 41 | - "8443:8443/tcp" # Controller GUI/API as seen in a web browser 42 | - "8880:8880/tcp" # HTTP portal redirection 43 | - "8843:8843/tcp" # HTTPS portal redirection 44 | - "10001:10001/udp" # AP discovery 45 | logs: 46 | image: bash 47 | container_name: ${COMPOSE_PROJECT_NAME}_logs 48 | depends_on: 49 | - controller 50 | command: bash -c 'tail -F /unifi/log/*.log' 51 | restart: always 52 | volumes: 53 | - log:/unifi/log 54 | 55 | volumes: 56 | db: 57 | dbcfg: 58 | data: 59 | log: 60 | cert: 61 | init: 62 | dir: 63 | run: 64 | 65 | networks: 66 | unifi: 67 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . /usr/unifi/functions 4 | 5 | # Check that any included hotfixes have been properly applied and exit if not 6 | if ! validate; then 7 | echo "Missing an included hotfix" 8 | exit 1 9 | fi 10 | 11 | if [ -x /usr/local/bin/docker-build.sh ]; then 12 | /usr/local/bin/docker-build.sh "${PKGURL}" 13 | fi 14 | 15 | exit_handler() { 16 | log "Exit signal received, shutting down" 17 | java -jar ${BASEDIR}/lib/ace.jar stop 18 | for i in `seq 1 10` ; do 19 | [ -z "$(pgrep -f ${BASEDIR}/lib/ace.jar)" ] && break 20 | # graceful shutdown 21 | [ $i -gt 1 ] && [ -d ${BASEDIR}/run ] && touch ${BASEDIR}/run/server.stop || true 22 | # savage shutdown 23 | [ $i -gt 7 ] && pkill -f ${BASEDIR}/lib/ace.jar || true 24 | sleep 1 25 | done 26 | # shutdown mongod 27 | if [ -f ${MONGOLOCK} ]; then 28 | mongo localhost:${MONGOPORT} --eval "db.getSiblingDB('admin').shutdownServer()" >/dev/null 2>&1 29 | fi 30 | exit ${?}; 31 | } 32 | 33 | trap 'kill ${!}; exit_handler' SIGHUP SIGINT SIGQUIT SIGTERM 34 | 35 | [ "x${JAVA_HOME}" != "x" ] || set_java_home 36 | 37 | 38 | # vars similar to those found in unifi.init 39 | MONGOPORT=27117 40 | 41 | CODEPATH=${BASEDIR} 42 | DATALINK=${BASEDIR}/data 43 | LOGLINK=${BASEDIR}/logs 44 | RUNLINK=${BASEDIR}/run 45 | 46 | DIRS="${RUNDIR} ${LOGDIR} ${DATADIR} ${BASEDIR}" 47 | 48 | JVM_MAX_HEAP_SIZE=${JVM_MAX_HEAP_SIZE:-1024M} 49 | #JVM_INIT_HEAP_SIZE= 50 | 51 | #JAVA_ENTROPY_GATHER_DEVICE= 52 | #UNIFI_JVM_EXTRA_OPTS= 53 | #ENABLE_UNIFI=yes 54 | 55 | 56 | MONGOLOCK="${DATAPATH}/db/mongod.lock" 57 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} --add-opens=java.base/java.time=ALL-UNNAMED -Dunifi.datadir=${DATADIR} -Dunifi.logdir=${LOGDIR} -Dunifi.rundir=${RUNDIR}" 58 | PIDFILE=/var/run/unifi/unifi.pid 59 | 60 | if [ ! -z "${JVM_MAX_HEAP_SIZE}" ]; then 61 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Xmx${JVM_MAX_HEAP_SIZE}" 62 | fi 63 | 64 | if [ ! -z "${JVM_INIT_HEAP_SIZE}" ]; then 65 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Xms${JVM_INIT_HEAP_SIZE}" 66 | fi 67 | 68 | if [ ! -z "${JVM_MAX_THREAD_STACK_SIZE}" ]; then 69 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Xss${JVM_MAX_THREAD_STACK_SIZE}" 70 | fi 71 | 72 | 73 | JVM_OPTS="${JVM_EXTRA_OPTS} 74 | -Djava.awt.headless=true 75 | -Dfile.encoding=UTF-8" 76 | 77 | # Cleaning /var/run/unifi/* See issue #26, Docker takes care of exlusivity in the container anyway. 78 | rm -f /var/run/unifi/unifi.pid 79 | 80 | run-parts /usr/local/unifi/init.d 81 | run-parts /usr/unifi/init.d 82 | 83 | if [ -d "/unifi/init.d" ]; then 84 | run-parts "/unifi/init.d" 85 | fi 86 | 87 | # Used to generate simple key/value pairs, for example system.properties 88 | confSet () { 89 | file=$1 90 | key=$2 91 | value=$3 92 | if [ "$newfile" != true ] && grep -q "^${key} *=" "$file"; then 93 | ekey=$(echo "$key" | sed -e 's/[]\/$*.^|[]/\\&/g') 94 | evalue=$(echo "$value" | sed -e 's/[\/&]/\\&/g') 95 | sed -i "s/^\(${ekey}\s*=\s*\).*$/\1${evalue}/" "$file" 96 | else 97 | echo "${key}=${value}" >> "$file" 98 | fi 99 | } 100 | 101 | confFile="${DATADIR}/system.properties" 102 | if [ -e "$confFile" ]; then 103 | newfile=false 104 | else 105 | newfile=true 106 | fi 107 | 108 | declare -A settings 109 | 110 | h2mb() { 111 | awkcmd=' 112 | /[0-9]$/{print $1/1024/1024;next}; 113 | /[mM]$/{printf "%u\n", $1;next}; 114 | /[kK]$/{printf "%u\n", $1/1024;next} 115 | /[gG]$/{printf "%u\n", $1*1024;next} 116 | ' 117 | echo $1 | awk "${awkcmd}" 118 | } 119 | 120 | if ! [[ -z "$LOTSOFDEVICES" ]]; then 121 | settings["unifi.G1GC.enabled"]="true" 122 | settings["unifi.xms"]="$(h2mb $JVM_INIT_HEAP_SIZE)" 123 | settings["unifi.xmx"]="$(h2mb ${JVM_MAX_HEAP_SIZE:-1024M})" 124 | # Reduce MongoDB I/O (issue #300) 125 | settings["unifi.db.nojournal"]="true" 126 | settings["unifi.db.extraargs"]="--quiet" 127 | fi 128 | 129 | # Implements issue #30 130 | if ! [[ -z "$DB_URI" || -z "$STATDB_URI" || -z "$DB_NAME" ]]; then 131 | settings["db.mongo.local"]="false" 132 | settings["db.mongo.uri"]="$DB_URI" 133 | settings["statdb.mongo.uri"]="$STATDB_URI" 134 | settings["unifi.db.name"]="$DB_NAME" 135 | fi 136 | 137 | if ! [[ -z "$PORTAL_HTTP_PORT" ]]; then 138 | settings["portal.http.port"]="$PORTAL_HTTP_PORT" 139 | fi 140 | 141 | if ! [[ -z "$PORTAL_HTTPS_PORT" ]]; then 142 | settings["portal.https.port"]="$PORTAL_HTTPS_PORT" 143 | fi 144 | 145 | if ! [[ -z "$UNIFI_HTTP_PORT" ]]; then 146 | settings["unifi.http.port"]="$UNIFI_HTTP_PORT" 147 | fi 148 | 149 | if ! [[ -z "$UNIFI_HTTPS_PORT" ]]; then 150 | settings["unifi.https.port"]="$UNIFI_HTTPS_PORT" 151 | fi 152 | 153 | if [[ "$UNIFI_ECC_CERT" == "true" ]]; then 154 | settings["unifi.https.sslEnabledProtocols"]="TLSv1.2" 155 | settings["unifi.https.ciphers"]="TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" 156 | fi 157 | 158 | if [[ "$UNIFI_STDOUT" == "true" ]]; then 159 | settings["unifi.logStdout"]="true" 160 | fi 161 | 162 | UNIFI_CMD="java ${JVM_OPTS} -jar ${BASEDIR}/lib/ace.jar start" 163 | 164 | if [ "$EUID" -ne 0 ] && command -v permset &> /dev/null 165 | then 166 | permset 167 | fi 168 | 169 | # controller writes to relative path logs/server.log 170 | cd ${BASEDIR} 171 | 172 | CUID=$(id -u) 173 | 174 | if [[ "${@}" == "unifi" ]]; then 175 | # keep attached to shell so we can wait on it 176 | log 'Starting unifi controller service.' 177 | for dir in "${DATADIR}" "${LOGDIR}"; do 178 | if [ ! -d "${dir}" ]; then 179 | if [ "${UNSAFE_IO}" == "true" ]; then 180 | rm -rf "${dir}" 181 | fi 182 | mkdir -p "${dir}" 183 | fi 184 | done 185 | for key in "${!settings[@]}"; do 186 | confSet "$confFile" "$key" "${settings[$key]}" 187 | done 188 | if [ "${RUNAS_UID0}" == "true" ] || [ "${CUID}" != "0" ]; then 189 | if [ "${CUID}" == 0 ]; then 190 | log 'WARNING: Running UniFi in insecure (root) mode' 191 | fi 192 | ${UNIFI_CMD} & 193 | elif [ "${RUNAS_UID0}" == "false" ]; then 194 | if [ "${BIND_PRIV}" == "true" ]; then 195 | if setcap 'cap_net_bind_service=+ep' "${JAVA_HOME}/bin/java"; then 196 | sleep 1 197 | else 198 | log "ERROR: setcap failed, can not continue" 199 | log "ERROR: You may either launch with -e BIND_PRIV=false and only use ports >1024" 200 | log "ERROR: or run this container as root with -e RUNAS_UID0=true" 201 | exit 1 202 | fi 203 | fi 204 | if [ "$(id unifi -u)" != "${UNIFI_UID}" ] || [ "$(id unifi -g)" != "${UNIFI_GID}" ]; then 205 | log "INFO: Changing 'unifi' UID to '${UNIFI_UID}' and GID to '${UNIFI_GID}'" 206 | usermod -o -u ${UNIFI_UID} unifi && groupmod -o -g ${UNIFI_GID} unifi 207 | fi 208 | # Using a loop here so I can check more directories easily later 209 | for dir in ${DIRS}; do 210 | if [ "$(stat -c '%u' "${dir}")" != "${UNIFI_UID}" ]; then 211 | chown -R "${UNIFI_UID}:${UNIFI_GID}" "${dir}" 212 | fi 213 | done 214 | gosu unifi:unifi ${UNIFI_CMD} & 215 | fi 216 | wait 217 | log "WARN: unifi service process ended without being signaled? Check for errors in ${LOGDIR}." >&2 218 | else 219 | log "Executing: ${@}" 220 | exec ${@} 221 | fi 222 | exit 1 223 | -------------------------------------------------------------------------------- /docker-healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SYSPROPS_FILE=${DATADIR}/system.properties 4 | if [ -f "${SYSPROPS_FILE}" ]; then 5 | SYSPROPS_PORT=`grep "^unifi.https.port=" ${SYSPROPS_FILE} | cut -d'=' -f2` 6 | fi 7 | PORT=${SYSPROPS_PORT:-8443} 8 | 9 | curl --max-time 5 -kILs --fail https://localhost:${PORT} 10 | -------------------------------------------------------------------------------- /examples/fixing_backup_errors/README.md: -------------------------------------------------------------------------------- 1 | # Failing to create backups from web UI 2 | 3 | This text is about problems relating to backups and downloading of backup files. 4 | 5 | ## Background 6 | 7 | Doing backups of your Unifi configuration is important. 8 | Without backups you run the risk of having to re-configure everything if your controller for some reason dies. This can be a massive task for large networks. 9 | 10 | This text is an attempt at providing some more context and concrete commands to solve the problems discussed in [this issue](https://github.com/jacobalberty/unifi-docker/issues/512). 11 | 12 | ## Problem 13 | 14 | After doing a fresh install of the Unifi controller using this Docker image you *may* run into errors when trying to do backups. 15 | Depending on your local setup things may work fine out of the box, but in other cases you can run into this backup problem. 16 | 17 | Clicking the "Download Backup" link simply does... nothing. 18 | 19 | ![Backup options in Unifi controller](unifi-backup-1.png "Backup options in Unifi controller") 20 | 21 | A closer look into the logs is easy though (adapt container name as needed): 22 | 23 | ```bash 24 | docker logs unifi_logs -f 25 | ``` 26 | 27 | If you see something like the below you probably suffer from the download-backups-problem: 28 | 29 | ``` 30 | [2022-01-20T14:00:01,290] ERROR system - zipDir error 31 | java.io.FileNotFoundException: /usr/lib/unifi/data/backup/autobackup/autobackup_6.5.55_20220120_1300_1642683600011.unf (No such file or directory) 32 | ``` 33 | 34 | ## Configuration 35 | 36 | The Docker container was started with this `docker-compose.yaml` file, which is pretty much standard except for the hard-coded container names: 37 | 38 | ```yaml 39 | version: '2.3' 40 | services: 41 | mongo: 42 | image: mongo:3.6 43 | # container_name: ${COMPOSE_PROJECT_NAME}_mongo 44 | container_name: unifi_mongo 45 | networks: 46 | - unifi 47 | restart: always 48 | volumes: 49 | - db:/data/db 50 | - dbcfg:/data/configdb 51 | controller: 52 | # image: "jacobalberty/unifi:${TAG:-latest}" 53 | image: jacobalberty/unifi:latest 54 | # container_name: ${COMPOSE_PROJECT_NAME}_controller 55 | container_name: unifi_controller 56 | depends_on: 57 | - mongo 58 | init: true 59 | networks: 60 | - unifi 61 | restart: always 62 | volumes: 63 | - dir:/unifi 64 | - data:/unifi/data 65 | - log:/unifi/log 66 | - cert:/unifi/cert 67 | - init:/unifi/init.d 68 | - run:/var/run/unifi 69 | # Mount local folder for backups and autobackups 70 | - ./backup:/unifi/data/backup 71 | user: unifi 72 | sysctls: 73 | net.ipv4.ip_unprivileged_port_start: 0 74 | environment: 75 | DB_URI: mongodb://mongo/unifi 76 | STATDB_URI: mongodb://mongo/unifi_stat 77 | DB_NAME: unifi 78 | TZ: Europe/Stockholm 79 | ports: 80 | - "3478:3478/udp" # STUN 81 | - "6789:6789/tcp" # Speed test 82 | - "8080:8080/tcp" # Device/ controller comm. 83 | - "8443:8443/tcp" # Controller GUI/API as seen in a web browser 84 | - "8880:8880/tcp" # HTTP portal redirection 85 | - "8843:8843/tcp" # HTTPS portal redirection 86 | - "10001:10001/udp" # AP discovery 87 | 88 | logs: 89 | image: bash 90 | # container_name: ${COMPOSE_PROJECT_NAME}_logs 91 | container_name: unifi_logs 92 | depends_on: 93 | - controller 94 | command: bash -c 'tail -F /unifi/log/*.log' 95 | restart: always 96 | volumes: 97 | - log:/unifi/log 98 | 99 | volumes: 100 | db: 101 | dbcfg: 102 | data: 103 | log: 104 | cert: 105 | init: 106 | dir: 107 | run: 108 | 109 | networks: 110 | unifi: 111 | ``` 112 | 113 | ## Investigation 114 | 115 | The Docker container will use user `unifi` to run the controller. 116 | Looking at the /etc/passwed file in the running container we get: 117 | 118 | ```bash 119 | root@abc1:/opt/docker/container# docker exec -it unifi_controller bash 120 | unifi@2ddc781f5259:/unifi$ cat /etc/passwd 121 | root:x:0:0:root:/root:/bin/bash 122 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 123 | bin:x:2:2:bin:/bin:/usr/sbin/nologin 124 | sys:x:3:3:sys:/dev:/usr/sbin/nologin 125 | sync:x:4:65534:sync:/bin:/bin/sync 126 | games:x:5:60:games:/usr/games:/usr/sbin/nologin 127 | man:x:6:12:man:/var/cache/man:/usr/sbin/nologin 128 | lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin 129 | mail:x:8:8:mail:/var/mail:/usr/sbin/nologin 130 | news:x:9:9:news:/var/spool/news:/usr/sbin/nologin 131 | uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin 132 | proxy:x:13:13:proxy:/bin:/usr/sbin/nologin 133 | www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin 134 | backup:x:34:34:backup:/var/backups:/usr/sbin/nologin 135 | list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin 136 | irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin 137 | gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin 138 | nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin 139 | _apt:x:100:65534::/nonexistent:/usr/sbin/nologin 140 | unifi:x:999:999::/home/unifi:/bin/sh 141 | mongodb:x:101:102::/var/lib/mongodb:/usr/sbin/nologin 142 | unifi@2ddc781f5259:/unifi$ 143 | ``` 144 | 145 | Looks like the `unifi` user in the running container has userid=999 and groupid=999. 146 | 147 | Now, let's take a look at that path we get an error for in the logs: 148 | 149 | ```bash 150 | unifi@64602e3bbf81:/unifi$ ls -la /usr/lib/unifi/data/backup/ 151 | total 8 152 | drwxr-xr-x 2 root root 4096 Jan 20 13:14 . 153 | drwxr-xr-x 4 unifi unifi 4096 Jan 20 13:42 .. 154 | unifi@64602e3bbf81:/unifi$ 155 | ``` 156 | 157 | See how the current directory (`.`) has an owner of `root:root`, whereas the directory above it has `unifi:unfi` as owner? 158 | That's a clue that there is a permissions problem. 159 | 160 | Going back to the docker-compose file, we can see that `/unifi/data/backup` in the container is mapped to `./backup` on the host computer. 161 | 162 | Next step is to check the permissions on the host computer for that `./backup` directory. It must be readable/writable by the container's `unifi:unifi` acount - otherwise the code running in the container won't be able to access anything in `/unifi/data/backup` and below. 163 | 164 | ```bash 165 | root@abc1:/opt/docker/container/unifi-controller# ls -la 166 | total 16 167 | drwxr-xr-x 3 root root 4096 Jan 20 13:14 . 168 | drwxr-xr-x 47 root root 4096 Nov 21 20:04 .. 169 | drwxr-xr-x 2 root root 4096 Jan 20 13:14 backup 170 | -rw-r--r-- 1 root root 2590 Jan 20 13:10 docker-compose.yaml 171 | root@abc1:/opt/docker/container/unifi-controller# 172 | ``` 173 | 174 | Ah! The second last line (for the backup subdirectory) has an owner of `root:root` (that's the host's root user, btw). 175 | No good. It must be a user on the host with userid:groupid of 999:999. 176 | 177 | The solution would then be to create a suitable user on the host computer and then change the owner of the `./backup` directory. 178 | 179 | ## Solution 180 | 181 | It should be noted that the problem described in this text can be solved in several ways. The container could be made to use another user (that already exists on the host computer), the container could be forced to run as root etc. 182 | What's described below worked in one case, but your milage may vary. 183 | 184 | ### Create unifi user on host computer 185 | 186 | The command below is executed as root. Doing a sudo should achieve the same result. 187 | 188 | ```bash 189 | root@abc1: adduser --uid 999 --gid 999 unifi 190 | ``` 191 | 192 | ### Changing owner of backup directory on host 193 | 194 | ```bash 195 | root@abc1:/opt/docker/container/unifi-controller# chown -R 999:999 backup 196 | root@abc1:/opt/docker/container/unifi-controller# ls -la 197 | total 16 198 | drwxr-xr-x 3 root root 4096 Jan 20 13:14 . 199 | drwxr-xr-x 47 root root 4096 Nov 21 20:04 .. 200 | drwxr-xr-x 3 unifi unifi 4096 Jan 20 18:31 backup 201 | -rw-r--r-- 1 root root 2590 Jan 20 13:10 docker-compose.yaml 202 | root@abc1:/opt/docker/container/unifi-controller# 203 | ``` 204 | 205 | Great, the `./backup` directory now has the permissions needed in order for the container's unifi user to access it. 206 | 207 | ### Testing the solution 208 | 209 | Restart the container, then try downloading the backup file again. 210 | Should work - at least if your original issues was the same that lead up to this walk-through... 211 | 212 | ## Automatic backups 213 | 214 | Good news: The work above also fixes automatic backups. 215 | 216 | The automatic backups rely on files in the same backup directory structure within the container as the manual backups. 217 | 218 | Give it a try: Set a time for automatic backups and check back once that time has passed. There should now be a downloadable backup file in the web UI. 219 | Examples of such files are shown in the screen shot above. 220 | -------------------------------------------------------------------------------- /examples/fixing_backup_errors/unifi-backup-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobalberty/unifi-docker/baacb5917a66525341195b793e6c2f13f38fbbb5/examples/fixing_backup_errors/unifi-backup-1.png -------------------------------------------------------------------------------- /examples/traefikv2/.env.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobalberty/unifi-docker/baacb5917a66525341195b793e6c2f13f38fbbb5/examples/traefikv2/.env.example -------------------------------------------------------------------------------- /examples/traefikv2/README.md: -------------------------------------------------------------------------------- 1 | # Unifi controller with traefik version 2 2 | 3 | To run this, create a .env file, look at the .env.example for example values. 4 | 5 | Then start the service with 6 | `docker-compose up -d` 7 | 8 | ## Traefik & HTTPS 9 | 10 | Since the unifi controller runs on HTTPS, traefik will generate an error if not the following value is provided with the traefik container. 11 | `serverstransport.insecureskipverify=true` 12 | 13 | This ignores the unsecure HTTPS protocol from the container 14 | 15 | Read more about this here: https://doc.traefik.io/traefik/reference/static-configuration/cli/ 16 | -------------------------------------------------------------------------------- /examples/traefikv2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | controller: 3 | image: jacobalberty/unifi:$TAG_NAME 4 | container_name: unifi 5 | networks: 6 | - unifi 7 | - proxy # This doesn't have to be proxy, it's the name of the network traefik is connected to 8 | restart: always 9 | volumes: 10 | - $UNIFI_DATA:/unifi/data 11 | - $UNIFI_LOG:/unifi/log 12 | - $UNIFI_CERT:/unifi/cert 13 | - $UNIFI_INIT:/unifi/init.d 14 | environment: 15 | TZ: $UNIFI_TZ 16 | ports: 17 | - 3478:3478/udp # STUN 18 | - 6789:6789/tcp # Speed test 19 | - 8080:8080/tcp # Device/ controller comm. 20 | - 8443:8443/tcp # Controller GUI/API as seen in a web browser 21 | - 8880:8880/tcp # HTTP portal redirection 22 | - 8843:8843/tcp # HTTPS portal redirection 23 | - 10001:10001/udp # AP discovery 24 | labels: 25 | - "traefik.enable=true" 26 | - "traefik.http.routers.unifi.entrypoints=http" # Change to your entrypoint name 27 | - "traefik.http.routers.unifi.rule=Host(`${UNIFI_URL}`)" 28 | - "traefik.http.middlewares.unifi-https-redirect.redirectscheme.scheme=https" 29 | - "traefik.http.routers.unifi.middlewares=unifi-https-redirect" 30 | - "traefik.http.routers.unifi-secure.entrypoints=https" 31 | - "traefik.http.routers.unifi-secure.rule=Host(`${UNIFI_URL}`)" 32 | - "traefik.http.routers.unifi-secure.tls=true" 33 | - "traefik.http.routers.unifi-secure.tls.certresolver=http" # Change to your certresolver name 34 | - "traefik.http.routers.unifi-secure.service=unifi" 35 | - "traefik.http.services.unifi.loadbalancer.server.port=8443" 36 | - "traefik.docker.network=proxy" 37 | - "traefik.http.services.unifi.loadbalancer.server.scheme=https" 38 | 39 | networks: 40 | unifi: 41 | proxy: 42 | external: true -------------------------------------------------------------------------------- /functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | log() { 4 | echo "$(date +"[%Y-%m-%d %T,%3N]") $*" 5 | } 6 | 7 | set_java_home() { 8 | JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:/jre/bin/java::") 9 | if [ ! -d "${JAVA_HOME}" ]; then 10 | # For some reason readlink failed so lets just make some assumptions instead 11 | # We're assuming openjdk 8 since thats what we install in Dockerfile 12 | arch=`dpkg --print-architecture 2>/dev/null` 13 | JAVA_HOME=/usr/lib/jvm/java-17-openjdk-${arch} 14 | fi 15 | } 16 | 17 | instPkg() { 18 | for pkg in $*; do 19 | if [ $(dpkg-query -W -f='${Status}' "${pkg}" 2>/dev/null | grep -c "ok installed") -eq 0 ]; 20 | then 21 | apt-get -qy install "${pkg}"; 22 | fi 23 | done 24 | } 25 | 26 | # Validate that any included hotfixes have been applied 27 | validate() ( 28 | shopt -s nullglob 29 | for i in /usr/local/unifi/hotfixes/*-validate.md5sum; do 30 | md5sum -c "$i" > /dev/null 2>&1 || return 1 31 | echo "Hotfix validated: $(basename ${i::-16})" 32 | done 33 | ) 34 | -------------------------------------------------------------------------------- /hotfixes/README.md: -------------------------------------------------------------------------------- 1 | This folder should normally remain empty, but just in case there's a hot fix for a major vulnerability the hotfix can be applied in the folder 2 | 3 | run-parts will be executed to apply any hotfixes in the folder so hotfixes should be in the form of a shellscript named after the relevant cve with no extension. 4 | IE the recent log4j would have a shell script named `cve-2021-44228` containing the fix. Then to verify the hotfix applied before launching you could also add a md5sum 5 | file with the name `cve-2021-44228-validate.md5sum`. The docker-entrypoint.sh will not let execution proceed without those md5sums passing. 6 | -------------------------------------------------------------------------------- /import_cert: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 4 | 5 | . /usr/unifi/functions 6 | 7 | if [[ ! -d "${CERTDIR}" || ! -f "${CERTDIR}/${CERTNAME}" ]]; then 8 | exit 0 9 | fi 10 | 11 | log 'Cert directory found. Checking Certs' 12 | 13 | if `md5sum -c "${CERTDIR}/${CERTNAME}.md5" &>/dev/null`; then 14 | log "Cert has not changed, not updating controller." 15 | exit 0 16 | else 17 | if [ ! -e "${DATADIR}/keystore" ]; then 18 | log "WARN: Missing keystore, creating a new one" 19 | 20 | if [ ! -d "${DATADIR}" ]; then 21 | log "Missing data directory, creating..." 22 | mkdir "${DATADIR}" 23 | fi 24 | 25 | keytool -genkey -keyalg RSA -alias unifi -keystore "${DATADIR}/keystore" \ 26 | -storepass aircontrolenterprise -keypass aircontrolenterprise -validity 1825 \ 27 | -keysize 4096 -dname "cn=UniFi" 28 | fi 29 | 30 | TEMPFILE=$(mktemp) 31 | TMPLIST="${TEMPFILE}" 32 | CERTTEMPFILE=$(mktemp) 33 | TMPLIST+=" ${CERTTEMPFILE}" 34 | CERTURI=$(openssl x509 -noout -ocsp_uri -in "${CERTDIR}/${CERTNAME}") 35 | # Identrust cross-signed CA cert needed by the java keystore for import. 36 | # Can get original here: https://www.identrust.com/certificates/trustid/root-download-x3.html 37 | cat > "${CERTTEMPFILE}" <<'_EOF' 38 | -----BEGIN CERTIFICATE----- 39 | MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ 40 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT 41 | DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow 42 | PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD 43 | Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 44 | AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O 45 | rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq 46 | OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b 47 | xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw 48 | 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD 49 | aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV 50 | HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG 51 | SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 52 | ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr 53 | AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz 54 | R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 55 | JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo 56 | Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ 57 | -----END CERTIFICATE----- 58 | _EOF 59 | 60 | log "Cert has changed, updating controller..." 61 | md5sum "${CERTDIR}/${CERTNAME}" > "${CERTDIR}/${CERTNAME}.md5" 62 | log "Using openssl to prepare certificate..." 63 | CHAIN=$(mktemp) 64 | TMPLIST+=" ${CHAIN}" 65 | 66 | if [[ "${CERTURI}" == *"letsencrypt"* && "$CERT_IS_CHAIN" == "true" ]]; then 67 | awk 1 "${CERTTEMPFILE}" "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 68 | elif [[ "${CERTURI}" == *"letsencrypt"* ]]; then 69 | awk 1 "${CERTTEMPFILE}" "${CERTDIR}/chain.pem" "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 70 | elif [[ "${CERTURI}" == *"lencr.org"* && "$CERT_IS_CHAIN" == "true" ]]; then 71 | awk 1 "${CERTTEMPFILE}" "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 72 | elif [[ "${CERTURI}" == *"lencr.org"* ]]; then 73 | awk 1 "${CERTTEMPFILE}" "${CERTDIR}/chain.pem" "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 74 | elif [[ "$CERT_IS_CHAIN" == "true" ]]; then 75 | cat "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 76 | elif [[ -f "${CERTDIR}/ca.pem" ]]; then 77 | awk 1 "${CERTDIR}/ca.pem" "${CERTDIR}/chain.pem" "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 78 | elif [[ -f "${CERTDIR}/chain.pem" ]]; then 79 | awk 1 "${CERTDIR}/chain.pem" "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 80 | else 81 | cat "${CERTDIR}/${CERTNAME}" >> "${CHAIN}" 82 | fi 83 | openssl pkcs12 -export -passout pass:aircontrolenterprise \ 84 | -in "${CHAIN}" \ 85 | -inkey "${CERTDIR}/${CERT_PRIVATE_NAME}" \ 86 | -out "${TEMPFILE}" -name unifi 87 | log "Removing existing certificate from Unifi protected keystore..." 88 | keytool -delete -alias unifi -keystore "${DATADIR}/keystore" \ 89 | -deststorepass aircontrolenterprise 90 | log "Inserting certificate into Unifi keystore..." 91 | keytool -trustcacerts -importkeystore \ 92 | -deststorepass aircontrolenterprise \ 93 | -destkeypass aircontrolenterprise \ 94 | -destkeystore "${DATADIR}/keystore" \ 95 | -srckeystore "${TEMPFILE}" -srcstoretype PKCS12 \ 96 | -srcstorepass aircontrolenterprise \ 97 | -alias unifi 98 | log "Cleaning up temp files" 99 | for file in ${TMPLIST}; do 100 | rm -f "${file}" 101 | done 102 | log "Done!" 103 | fi 104 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Jacob Alberty 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /pre_build/README.md: -------------------------------------------------------------------------------- 1 | This folder is for adding build scripts for various architectures to apply quick fixes for building on other architectures. 2 | 3 | To use you would create a subfolder (ie `armhf`, `amd64`, `arm64`) then place an appropriate shell script in that folder to build. 4 | 5 | This directory was added to enable satisfying dependencies that aren't automatically satisfied by the distro (for ex: mongodb on armhf) 6 | -------------------------------------------------------------------------------- /pre_build/armhf/mongodb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | VER=3.2.22-2 3 | 4 | TMP=$(mktemp -d) 5 | 6 | curl -sL "https://github.com/ddcc/mongodb/releases/download/v${VER}/mongodb_${VER}_armhf.deb" -o "${TMP}/mongodb_${VER}_armhf.deb" 7 | curl -sL "https://github.com/ddcc/mongodb/releases/download/v${VER}/mongodb-server_${VER}_all.deb" -o "${TMP}/mongodb-server_${VER}_all.deb" 8 | for f in clients server-core; do 9 | pn="mongodb-${f}_${VER}_armhf.deb" 10 | curl -sL "https://github.com/ddcc/mongodb/releases/download/v${VER}/${pn}" -o "${TMP}/${pn}" 11 | done 12 | 13 | apt -qy install "$TMP/"* 14 | rm -rf "$TMP" 15 | --------------------------------------------------------------------------------