Added the starts of a new Nixos Package
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
## Nixos Implementation of OxiCloud
|
||||
|
||||
This folder sets up a Nixos module of OxiCloud.
|
||||
@@ -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
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
DATABASE_URL=postgres://oxicloud:password@localhost/oxicloud
|
||||
RUST_LOG=info
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user