Added the starts of a new Nixos Package

This commit is contained in:
Chris Mann
2026-04-19 11:33:53 +02:00
parent 265d5abaf8
commit fd4153b332
6 changed files with 583 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
## Nixos Implementation of OxiCloud
This folder sets up a Nixos module of OxiCloud.
+59
View File
@@ -0,0 +1,59 @@
{
system ? builtins.currentSystem,
pkgs ? import <nixpkgs> {},
withManuals ? false, # building the manuals is expensive
}: let
lib = pkgs.lib;
oxicloud = pkgs.callPackage ./package.nix {};
in {
shell = pkgs.mkShell {
packages = [
oxicloud
# Alpine Rust
pkgs.postgresql
pkgs.cargo
pkgs.rustup
pkgs.openssl
pkgs.binutils
pkgs.zlib-ng
pkgs.zstd
pkgs.mpfr
pkgs.cryptopp
pkgs.cacert
pkgs.mkcert
pkgs.certstrap
pkgs.musl
pkgs.isl
pkgs.gcc
pkgs.libgcc
pkgs.jansson
pkgs.libatomic_ops
pkgs.pax-utils
pkgs.gomp
pkgs.libressl
pkgs.mpc
pkgs.pkg-config
pkgs.libpq
pkgs.libpqxx
pkgs.perl
pkgs.gnumake
pkgs.su-exec
# Postgres
pkgs.tzdata
pkgs.keyutils
pkgs.gsasl
pkgs.xz
pkgs.libedit
pkgs.libuuid
pkgs.libxslt
# WOPI
# collabora/code:latest
# Extra (I think)
pkgs.ffmpeg
pkgs.libavif
pkgs.librtprocess
pkgs.pdftk
pkgs.imagemagick
];
};
}
+28
View File
@@ -0,0 +1,28 @@
{
description = "OxiCloud Nix flake with module";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
};
outputs = {
self,
nixpkgs,
}: let
systems = ["x86_64-linux"];
forAllSystems = f:
nixpkgs.lib.genAttrs systems (
system:
f (import nixpkgs {inherit system;})
);
in {
packages = forAllSystems (pkgs: {
oxicloud = pkgs.callPackage ./package.nix {};
});
nixosModules.oxicloud = import ./module.nix;
# optional default
defaultPackage.x86_64-linux = self.packages.x86_64-linux.oxicloud;
};
}
+406
View File
@@ -0,0 +1,406 @@
{
config,
lib,
pkgs,
oxicloud ? callPackage ./package.nix {},
...
}: let
cfg = config.services.oxicloud;
generatedEnv = pkgs.writeText "oxicloud-generated.env" ''
OXICLOUD_STORAGE_PATH=${cfg.storagePath}
OXICLOUD_STATIC_PATH=${cfg.staticPath}
OXICLOUD_SERVER_PORT=${builtins.toString cfg.port}
OXICLOUD_SERVER_HOST=${cfg.host}
OXICLOUD_BASE_URL=${cfg.baseUrl}
OXICLOUD_DB_CONNECTION_STRING=postgres://${cfg.database.user}:${builtins.readFile cfg.database.password.file}@${cfg.database.host}:${builtins.toString cfg.database.port}/${cfg.database.name}
OXICLOUD_DB_MAX_CONNECTIONS=${builtins.toString cfg.database.maxConnections}
OXICLOUD_DB_MIN_CONNECTIONS=${builtins.toString cfg.database.minConnections}
OXICLOUD_ENABLE_AUTH=${lib.boolToString cfg.auth.enable}
OXICLOUD_JWT_SECRET=${builtins.readFile cfg.auth.jwtSecret.file}
OXICLOUD_ACCESS_TOKEN_EXPIRY_SECS=${builtins.toString cfg.auth.accessTokenExpirySecs}
OXICLOUD_REFRESH_TOKEN_EXPIRY_SECS=${builtins.toString cfg.auth.refreshTokenExpirySecs}
OXICLOUD_ENABLE_USER_STORAGE_QUOTAS=${lib.boolToString cfg.features.userStorageQuotas}
OXICLOUD_ENABLE_FILE_SHARING=${lib.boolToString cfg.features.fileSharing}
OXICLOUD_ENABLE_TRASH=${lib.boolToString cfg.features.trash}
OXICLOUD_ENABLE_SEARCH=${lib.boolToString cfg.features.search}
OXICLOUD_OIDC_ENABLED=${lib.boolToString cfg.sso.enable}
OXICLOUD_OIDC_ISSUER_URL=${cfg.sso.issuerUrl}
OXICLOUD_OIDC_CLIENT_ID=${cfg.sso.clientId}
OXICLOUD_OIDC_CLIENT_SECRET=${builtins.readFile cfg.sso.clientSecret.file}
OXICLOUD_OIDC_REDIRECT_URI=${cfg.sso.redirectUri}
OXICLOUD_OIDC_SCOPES=${cfg.sso.scopes}
OXICLOUD_OIDC_FRONTEND_URL=${cfg.sso.frontendUrl}
OXICLOUD_WOPI_ENABLED=${lib.boolToString cfg.wopi.enable}
OXICLOUD_WOPI_DISCOVERY_URL=${cfg.wopi.discoveryUrl}
OXICLOUD_WOPI_SECRET=${builtins.readFile cfg.wopi.secret.file}
'';
in {
options.services.oxicloud = {
enable = lib.mkEnableOption "OxiCloud";
envFile = lib.mkOption {
type = lib.types.path;
default = "/etc/oxicloud/oxicloud.env";
description = "Environment file containing secrets (DATABASE_URL, etc.)";
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/oxicloud";
description = "Directory for OxiCloud data";
};
storagePath = lib.mkOption {
type = lib.types.path;
default = "${cfg.dataDir}/storage";
description = "Path for storage file storage";
};
staticPath = lib.mkOption {
type = lib.types.path;
default = "${cfg.dataDir}/static";
description = "Path for static file storage";
};
port = lib.mkOption {
type = lib.types.int;
default = 8086;
description = "Server port";
};
host = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Server bind address";
};
baseUrl = lib.mkOption {
type = lib.types.str;
default = "http://${cfg.host}:${builtins.toString cfg.port}";
description = "Public base URL for share links";
};
database = {
url = lib.mkOption {
type = lib.types.str;
default = "";
description = "PostgreSQL connection string: postgres://postgres:postgres@localhost:5432/oxicloud";
};
user = lib.mkOption {
type = lib.types.str;
default = "oxicloud";
description = "Name of the Postgresql database (if not database url)";
};
password.file = lib.mkOption {
type = lib.types.str;
default = "oxicloud";
description = "Path to the database password (if not database url)";
};
host = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = "Host to the postgresql database (if not database url)";
};
port = lib.mkOption {
type = lib.types.int;
default = 5432;
description = "Port to the postgresql database (if not database url)";
};
maxConnections = lib.mkOption {
type = lib.types.int;
default = 20;
description = "Max pool connections";
};
minConnections = lib.mkOption {
type = lib.types.int;
default = 5;
description = "Min pool connections";
};
};
auth = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Enable authentication";
};
jwtSecret.file = lib.mkOption {
type = lib.types.str;
default = "/etc/oxicloud/jwt.secret";
description = "JWT signing secret";
};
accessTokenExpirySecs = lib.mkOption {
type = lib.types.int;
default = 3600;
description = "Access token lifetime (seconds)";
};
refreshTokenExpirySecs = lib.mkOption {
type = lib.types.int;
default = 2592000;
description = "Refresh token lifetime (seconds)";
};
};
features = {
userStorageQuotas = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Per-user storage quotas";
};
fileSharing = lib.mkOption {
type = lib.types.bool;
default = true;
description = "File/folder sharing";
};
trash = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Trash / recycle bin";
};
search = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Search";
};
};
oauth2 = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable OIDC";
};
issuerUrl = lib.mkOption {
type = lib.types.str;
default = "";
description = "OIDC issuer URL";
};
clientId = lib.mkOption {
type = lib.types.str;
default = "";
description = "Client ID";
};
clientSecret.file = lib.mkOption {
type = lib.types.str;
default = "/etc/oxicloud/oidc.secret";
description = "Client secret";
};
redirectUri = lib.mkOption {
type = lib.types.str;
default = "http://localhost:8086/api/auth/oidc/callback";
description = "Callback URL";
};
scopes = lib.mkOption {
type = lib.types.str;
default = "openid";
description = "profile email Requested scopes";
};
frontendUrl = lib.mkOption {
type = lib.types.str;
default = "http://${cfg.host}:${builtins.toString cfg.port}";
description = "Frontend URL";
};
autoProvision = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Auto-create users on first SSO login";
};
adminGroups = lib.mkOption {
type = lib.types.str;
default = "";
description = "Groups that grant admin role";
};
disablePasswordLogin = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Hide password form when OIDC enabled";
};
providerName = lib.mkOption {
type = lib.types.str;
default = "SSO";
description = "Display name for the provider";
};
};
wopi = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable WOPI";
};
discoveryUrl = lib.mkOption {
type = lib.types.str;
default = "";
description = "Collabora/OnlyOffice discovery URL";
};
secret.file = lib.mkOption {
type = lib.types.str;
default = "/etc/oxicloud/wopi.secret";
description = "(JWT secret) WOPI token signing key";
};
tokenTtlSecs = lib.mkOption {
type = lib.types.int;
default = 86400;
description = "Token lifetime";
};
lockTtlSecs = lib.mkOption {
type = lib.types.int;
default = 1800;
description = "Lock expiration";
};
};
package = lib.mkOption {
type = lib.types.package;
default = oxicloud;
description = "oxicloud nix package";
};
user = lib.mkOption {
type = lib.types.str;
default = "oxicloud";
description = "User to run OxiCloud service";
};
group = lib.mkOption {
type = lib.types.str;
default = "services";
description = "Group to run OxiCloud service";
};
};
config = lib.mkIf cfg.enable {
systemd.services.oxicloud-env = {
description = "Prepare OxiCloud environment file";
wantedBy = ["multi-user.target"];
before = ["oxicloud.service"];
serviceConfig.Type = "oneshot";
script = ''
install -d -m 0755 ${cfg.dataDir}
cp ${generatedEnv} ${cfg.dataDir}/.env
if [ -f ${cfg.envFile} ]; then
# append overrides (last wins in dotenv parsing)
cat ${cfg.envFile} >> ${cfg.dataDir}/.env
fi
chown ${cfg.user}:${cfg.group} ${cfg.dataDir}/.env
chmod 640 ${cfg.dataDir}/.env
'';
};
systemd.tmpfiles.rules = [
"d ${cfg.dataDir} 0700 ${cfg.user} ${cfg.group} -"
"d ${cfg.storagePath} 0700 ${cfg.user} ${cfg.group} -"
"d ${cfg.staticPath} 0750 ${cfg.user} ${cfg.group} -"
"d /etc/oxicloud 0750 root root -"
"f ${cfg.auth.jwtSecret.file} 0600 oxicloud services -"
"f ${cfg.envFile} 0600 oxicloud services -"
"f ${cfg.oauth2.clientSecret.file} 0600 oxicloud services -"
"f ${cfg.wopi.secret.file} 0600 oxicloud services -"
];
# Create user/group only if using defaults
users.users = lib.mkIf (cfg.user == "oxicloud") {
oxicloud = {
isSystemUser = true;
group = cfg.group;
description = "OxiCloud service user";
};
};
users.groups = lib.mkIf (cfg.group == "oxicloud") {
oxicloud = {};
};
systemd.services.oxicloud = {
description = "OxiCloud server";
after = ["network.target" "postgresql.service" "oxicloud-env.service"];
wantedBy = ["multi-user.target"];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
EnvironmentFile = [
"-${cfg.dataDir}/.env"
"-${generatedEnv}"
"-${cfg.envFile}"
];
ExecStart = "${cfg.package}/bin/oxicloud";
Restart = "always";
WorkingDirectory = cfg.dataDir;
ReadWritePaths = [cfg.dataDir];
# 🔒 hardening
# ProtectSystem = "strict";
# ProtectHome = true;
AmbientCapabilities = lib.mkIf (cfg.port < 1024) ["CAP_NET_BIND_SERVICE"];
CapabilityBoundingSet =
if (cfg.port < 1024)
then ["CAP_NET_BIND_SERVICE"]
else [""];
DeviceAllow = [""];
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "full";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
};
};
};
}
+2
View File
@@ -0,0 +1,2 @@
DATABASE_URL=postgres://oxicloud:password@localhost/oxicloud
RUST_LOG=info
+85
View File
@@ -0,0 +1,85 @@
{pkgs ? import <nixpkgs> {}}:
pkgs.rustPlatform.buildRustPackage rec {
pname = "oxicloud";
version = "v.0.5.5";
src = pkgs.fetchFromGitHub {
owner = "DioCrafts";
repo = "OxiCloud";
rev = "v0.5.5";
hash = "sha256-Nn8qgLdiw7w4PZIMCiI+UHZGNW64fjWZ5mErTJifRZU=";
};
cargoHash = "sha256-4KfrKL2AKkTt3cOXdl9Xr2qed+qy8WSWuqYfN8WJ0bQ=";
buildInputs = [
# Alpine Rust
pkgs.postgresql
pkgs.cargo
pkgs.rustup
pkgs.openssl
pkgs.binutils
pkgs.zlib-ng
pkgs.zstd
pkgs.mpfr
pkgs.cryptopp
pkgs.cacert
pkgs.mkcert
pkgs.certstrap
pkgs.musl
pkgs.isl
pkgs.gcc
pkgs.libgcc
pkgs.jansson
pkgs.libatomic_ops
pkgs.pax-utils
pkgs.gomp
pkgs.libressl
pkgs.mpc
pkgs.pkg-config
pkgs.libpq
pkgs.libpqxx
pkgs.perl
pkgs.gnumake
pkgs.su-exec
# Postgres
pkgs.tzdata
pkgs.keyutils
pkgs.gsasl
pkgs.xz
pkgs.libedit
pkgs.libuuid
pkgs.libxslt
# WOPI
# collabora/code:latest
# Extra (I think)
pkgs.ffmpeg
pkgs.libavif
pkgs.librtprocess
pkgs.pdftk
pkgs.imagemagick
];
# If tests fail due to DB, disable:
# doCheck = false;
postInstall = ''
mkdir -p $out/bin
# main server
if [ -f target/release/ ]; then
cp -a target/release/oxicloud $out/bin/oxicloud
cp -a target/release/generate-openapi $out/bin/generate-openapi
fi
cp -a static $out/static
cp -a static-dist $out/static-dist
'';
meta = with pkgs.lib; {
description = "Lightweight Rust-powered self-hosted cloud (Nextcloud alternative)";
homepage = "https://github.com/DioCrafts/OxiCloud";
license = licenses.mit;
platforms = platforms.linux;
};
}