I was playing around with unifi nixos module and then I got the message that the unifi-controller software will not be updated anymore and only the new unifi os server / uosserver will be supported. Since i found the blog post Running UniFi OS Server in Docker | UniHosted , i knew that it was basically a podman oci image. Conclusion, here is a for me working nixos-module:
module.nix:
{
config,
lib,
pkgs,
...
}: let
inherit
(lib)
mkEnableOption
mkIf
mkOption
types
;
cfg = config.services.unifi-os-server;
stateDir = "/var/lib/unifi-os";
# Capture unifi-core stdout/stderr to readable files
# (the container's journal is only accessible as root)
ucoreDebug = pkgs.writeText "unifi-core-debug.conf" ''
[Service]
StandardOutput=append:/data/unifi-core/logs/stdout.log
StandardError=append:/data/unifi-core/logs/stderr.log
'';
# Fix missing directories that services expect but don't create on first run.
ucorePreStartFix = pkgs.writeText "unifi-core-prestart-fix.conf" ''
[Service]
ExecStartPre=-/bin/mkdir -p /data/unifi-core/config/http
ExecStartPre=-/bin/mkdir -p /var/log/nginx
'';
# MongoDB needs writable log and data dirs; + runs as root regardless of User=
mongoPreStartFix = pkgs.writeText "mongodb-prestart-fix.conf" ''
[Service]
ExecStartPre=+/bin/bash -c "mkdir -p /var/log/mongodb && chown mongodb:mongodb /var/log/mongodb /var/lib/mongodb"
'';
in {
# reverse engineered via
# https://www.unihosted.com/blog/running-unifi-os-server-in-docker
options.services.unifi-os-server = {
enable = mkEnableOption "UniFi OS Server container (podman)";
package = mkOption {
type = types.package;
description = ''
Package containing the extracted UniFi OS Server OCI archive at
`image.tar`. Build with:
`pkgs.callPackage ./pkgs/unifi-os-server-image { sha256 = "…"; }`
'';
};
imageTag = mkOption {
type = types.str;
description = ''
Exact image name:tag embedded in `image.tar`.
Must match the repository:tag inside the archive.
'';
example = "uosserver:0.0.54";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Whether or not to open the minimum required ports on the firewall.
This is necessary to allow firmware upgrades and device discovery to
work. For remote login, you should additionally open (or forward) port
8443.
'';
};
environment = mkOption {
type = types.attrsOf types.str;
default = {};
description = "Additional environment variables for the container.";
};
extraVolumes = mkOption {
type = types.listOf types.str;
default = [];
example = ["/etc/ssl/certs:/etc/rabbitmq/ssl:ro"];
description = "Additional bind mounts beyond the defaults.";
};
extraOptions = mkOption {
type = types.listOf types.str;
default = [];
description = "Extra arguments passed to podman.";
};
};
config = mkIf cfg.enable {
virtualisation.podman.enable = true;
virtualisation.oci-containers.backend = "podman";
# https://www.crosstalksolutions.com/complete-unifi-os-server-installation-on-linux-best-practices/
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [
443 # HTTPS portal
8080 # UAP device inform
8443 # Controller HTTPS
8843 # HTTPS portal redirect
8880 # HTTP portal redirect
6789 # Mobile speed test
];
allowedUDPPorts = [
3478 # STUN
10001 # Device discovery
];
};
systemd.services.podman-unifi-os-server = {
# Make sure package upgrades trigger a service restart
restartTriggers = [cfg.package];
serviceConfig = {
StateDirectory = [
"unifi-os"
"unifi-os/persistent"
"unifi-os/data"
"unifi-os/srv"
"unifi-os/unifi"
"unifi-os/mongodb"
];
LogsDirectory = "unifi-os";
};
preStart = lib.mkAfter ''
uuid_file="${stateDir}/data/uos_uuid"
# The Java UniFi controller requires exactly UUID v5 (SHA-1 name-based).
# Generate a stable v5 UUID derived from the machine-id.
if ! grep -qP '^[0-9a-f]{8}-[0-9a-f]{4}-5' "$uuid_file" 2>/dev/null; then
${pkgs.util-linux}/bin/uuidgen -s -n @dns -N "$(cat /etc/machine-id)" > "$uuid_file"
fi
'';
};
virtualisation.oci-containers.containers.unifi-os-server = {
image = cfg.imageTag;
imageFile = pkgs.runCommand "unifi-os-image.tar" {} ''
ln -s ${cfg.package}/image.tar $out
'';
autoStart = true;
privileged = true;
ports = [
"443:443"
"8080:8080"
"8443:8443"
"8843:8843"
"8880:8880"
"6789:6789"
"3478:3478/udp"
"10001:10001/udp"
];
environment =
{
UOS_SYSTEM_IP = "127.0.0.1";
UOS_SERVER_VERSION = cfg.package.version;
FIRMWARE_PLATFORM =
if pkgs.stdenv.hostPlatform.isAarch64
then "linux-arm64"
else "linux-x64";
}
// cfg.environment;
volumes =
[
"${stateDir}/persistent:/persistent"
"/var/log/unifi-os:/var/log"
"${stateDir}/data:/data"
"${stateDir}/srv:/srv"
"${stateDir}/unifi:/var/lib/unifi"
"${stateDir}/mongodb:/var/lib/mongodb"
"${ucoreDebug}:/etc/systemd/system/unifi-core.service.d/debug.conf:ro"
"${ucorePreStartFix}:/etc/systemd/system/unifi-core.service.d/prestart-fix.conf:ro"
"${mongoPreStartFix}:/etc/systemd/system/mongodb.service.d/prestart-fix.conf:ro"
]
++ cfg.extraVolumes;
extraOptions =
[
"--systemd=always"
"--add-host=host.docker.internal:host-gateway"
]
++ cfg.extraOptions;
};
};
}
and since the image needs to fetched from somewhere here is a derivation for it:
{
lib,
stdenvNoCC,
fetchurl,
binwalk,
coreutils,
findutils,
gnugrep,
version ? "5.0.6",
# url ? "https://fw-download.ubnt.com/data/unifi-os-server/df5b-linux-arm64-5.0.6-f35e944c-f4b6-4190-93a8-be61b96c58f4.6-arm64",
url ? "https://fw-download.ubnt.com/data/unifi-os-server/1856-linux-x64-5.0.6-33f4990f-6c68-4e72-9d9c-477496c22450.6-x64",
sha256,
}:
stdenvNoCC.mkDerivation rec {
# reverse engineered via
# https://www.unihosted.com/blog/running-unifi-os-server-in-docker
pname = "unifi-os-server-image";
inherit version;
src = fetchurl {
inherit url sha256;
};
nativeBuildInputs = [
binwalk
coreutils
findutils
gnugrep
];
dontUnpack = true;
installPhase = ''
set -euo pipefail
work="$PWD/work"
mkdir -p "$work"
cp "$src" "$work/unifi-os-installer"
chmod u+w "$work/unifi-os-installer"
cd "$work"
binwalk -e ./unifi-os-installer >/dev/null
image_tar="$(find . -type f -name image.tar | head -n1)"
if [ -z "$image_tar" ]; then
echo "Could not find embedded image.tar in UniFi OS installer" >&2
exit 1
fi
mkdir -p "$out"
cp "$image_tar" "$out/image.tar"
'';
meta = with lib; {
description = "Extracted OCI image archive from the UniFi OS Server installer";
homepage = "https://help.ui.com/hc/en-us/articles/34210126298775-Self-Hosting-UniFi";
license = licenses.unfreeRedistributableFirmware;
platforms = platforms.linux;
sourceProvenance = with sourceTypes; [binaryNativeCode];
};
}
services.unifi-os-server = {
enable = true;
package = pkgs.callPackage ../../pkgs/unifi-os-server-image {
sha256 = "sha256-IPoWR5GTiy7J1WgMEYdTxGo26qM2nO+U1c742pRo354=";
};
imageTag = "uosserver:0.0.54";
};
am happy to get some feedback and whom ever this will help, i wish you a happy day.
EDITS: Since images are not always built in a sandbox a few tweaks were missing i added them and an service enable example
EDIT 5: fixed crashing mongodb