feat: added sftpgo to puffer
This commit is contained in:
parent
519ab56711
commit
0a343d7825
7 changed files with 422 additions and 44 deletions
|
@ -1,16 +1,23 @@
|
|||
{ abs, lib, pkgs, ... }@inputs:
|
||||
{ abs, lib, config, pkgs, ... }@inputs:
|
||||
|
||||
let
|
||||
containers = (import (abs "lib/containers.nix") inputs);
|
||||
avahi = (import (abs "lib/avahi.nix") inputs);
|
||||
systemd = (import (abs "lib/systemd.nix") inputs);
|
||||
in
|
||||
containers.mkNixosContainer {
|
||||
name = "puffer";
|
||||
ip = "10.42.0.5";
|
||||
private = false;
|
||||
containers = import (abs "lib/containers.nix") inputs;
|
||||
avahi = import (abs "lib/avahi.nix") inputs;
|
||||
systemd = import (abs "lib/systemd.nix") inputs;
|
||||
sftpgo = import (abs "services/sftpgo.nix") inputs;
|
||||
secrets = import (abs "lib/secrets.nix");
|
||||
|
||||
sftpKey = secrets.mount config "sftpgo-ed25519";
|
||||
|
||||
sambaConfig = {
|
||||
imports = [
|
||||
(systemd.mkOneshot {
|
||||
name = "smb-guest-setup";
|
||||
# for whatever reason smbd refuses to write unless we set the password
|
||||
script = "${pkgs.samba}/bin/smbpasswd -a smb-guest -n";
|
||||
})
|
||||
];
|
||||
|
||||
config = { ... }: {
|
||||
services.samba = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
|
@ -81,49 +88,90 @@ containers.mkNixosContainer {
|
|||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
(systemd.mkOneshot {
|
||||
name = "smb-guest-setup";
|
||||
# for whatever reason smbd refuses to write unless we set the password
|
||||
script = "${pkgs.samba}/bin/smbpasswd -a smb-guest -n";
|
||||
})
|
||||
(avahi.setup {
|
||||
name = "puffer";
|
||||
services = [
|
||||
{ type = "_smb._tcp"; port = 445; }
|
||||
# cancer stuff for macs to see this disk as a time machine-compatible disk
|
||||
[
|
||||
{ type = "_adisk._tcp"; port = 9; }
|
||||
{ txt-record = "sys=waMa=0,adVF=0x100"; }
|
||||
{ txt-record = "dk0=adVN=Puffer TimeMachine,adVF=0x82"; }
|
||||
]
|
||||
{ type = "_device-info._tcp"; port = 0; txt-record = "model=TimeCapsule8,119"; }
|
||||
avahiConfig = avahi.setup {
|
||||
name = "puffer";
|
||||
services = [
|
||||
{ type = "_smb._tcp"; port = 445; }
|
||||
# cancer stuff for macs to see this disk as a time machine-compatible disk
|
||||
[
|
||||
{ type = "_adisk._tcp"; port = 9; }
|
||||
{ txt-record = "sys=waMa=0,adVF=0x100"; }
|
||||
{ txt-record = "dk0=adVN=Puffer TimeMachine,adVF=0x82"; }
|
||||
]
|
||||
{ type = "_device-info._tcp"; port = 0; txt-record = "model=TimeCapsule8,119"; }
|
||||
];
|
||||
};
|
||||
|
||||
sftpgoConfig = sftpgo.setup {
|
||||
package = pkgs.callPackage (abs "packages/sftpgo.nix") {
|
||||
tags = [ "nogcs" "nos3" "noazblob" "nobolt" "nomysql" "nopgsql" "nometrics" "bundle" ];
|
||||
};
|
||||
|
||||
config = {
|
||||
sftpd = {
|
||||
bindings = [
|
||||
{ port = 22; }
|
||||
];
|
||||
})
|
||||
];
|
||||
host_keys = [ "id_ed25519" ];
|
||||
};
|
||||
};
|
||||
keys.ed25519 = sftpKey.path;
|
||||
|
||||
users.groups.puffer = { };
|
||||
users.users.smb-guest = {
|
||||
isNormalUser = true;
|
||||
description = "Guest account for Samba";
|
||||
extraGroups = [ "puffer" ];
|
||||
createHome = false;
|
||||
shell = pkgs.shadow;
|
||||
users.guest = {
|
||||
# argon-hashed 0
|
||||
password = "$2a$10$IcGdNtx10ycmPRD6lA4c0uNfRXTEchFRzCZEDkngTjzForn6pd0Wa";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /mnt/puffer/Public 0755 smb-guest puffer - -"
|
||||
"d /mnt/puffer/Backups 0755 smb-guest puffer - -"
|
||||
folders.Public.path = "/mnt/puffer/Public";
|
||||
|
||||
usersFolders = [
|
||||
{ username = "guest"; folder = "Public"; }
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
mounts = {
|
||||
"/mnt/puffer" = {
|
||||
hostPath = "/mnt/puffer";
|
||||
isReadOnly = false;
|
||||
container = containers.mkNixosContainer {
|
||||
name = "puffer";
|
||||
ip = "10.42.0.5";
|
||||
private = false;
|
||||
|
||||
config = { ... }: {
|
||||
imports = [
|
||||
sambaConfig
|
||||
avahiConfig
|
||||
sftpgoConfig
|
||||
];
|
||||
|
||||
users.groups.puffer = { };
|
||||
users.users.smb-guest = {
|
||||
isNormalUser = true;
|
||||
description = "Guest account for Samba";
|
||||
extraGroups = [ "puffer" ];
|
||||
createHome = false;
|
||||
shell = pkgs.shadow;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /mnt/puffer/Public 0755 smb-guest puffer - -"
|
||||
"d /mnt/puffer/Backups 0755 smb-guest puffer - -"
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||
};
|
||||
|
||||
mounts = {
|
||||
"/mnt/puffer" = {
|
||||
hostPath = "/mnt/puffer";
|
||||
isReadOnly = false;
|
||||
};
|
||||
} // (sftpKey.mounts);
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(secrets.declare [ "sftpgo-ed25519" ])
|
||||
container
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -15,4 +15,20 @@
|
|||
defs
|
||||
);
|
||||
};
|
||||
|
||||
file = config: name: config.age.secrets.${name}.path;
|
||||
|
||||
mount = config: name:
|
||||
let
|
||||
path = config.age.secrets.${name}.path;
|
||||
in
|
||||
{
|
||||
path = path;
|
||||
mounts = {
|
||||
${path} = {
|
||||
hostPath = path;
|
||||
isReadOnly = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
54
lib/sqlite.nix
Normal file
54
lib/sqlite.nix
Normal file
|
@ -0,0 +1,54 @@
|
|||
{ lib, ... }@inputs:
|
||||
|
||||
rec {
|
||||
mkValue = value:
|
||||
if builtins.isInt value then builtins.toString value
|
||||
else if builtins.isString value then "'" + (builtins.replaceStrings [ "'" ] [ "''" ] value) + "'"
|
||||
else if builtins.isAttrs value then
|
||||
if builtins.hasAttr "_raw" value then "(" + value._raw + ")"
|
||||
else mkValue (builtins.toJSON value)
|
||||
else if value == null then "null"
|
||||
else throw "invalid type: ${builtins.typeOf value} (value ${builtins.toString value})";
|
||||
mkIdent = value: "\"" + (builtins.replaceStrings [ "\"" ] [ "\"\"" ] value) + "\"";
|
||||
|
||||
mkTableName = table: if builtins.isList table then builtins.concatStringsSep "." table else table;
|
||||
|
||||
sql = sql: { _raw = sql; };
|
||||
|
||||
insert = { into, value, orReplace ? false }:
|
||||
let
|
||||
columns = builtins.attrNames value;
|
||||
values = builtins.map (name: value.${name}) columns;
|
||||
cmd = if orReplace then "insert or replace" else "insert";
|
||||
in
|
||||
"${cmd} into ${mkTableName into} " +
|
||||
"(${builtins.concatStringsSep ", " (builtins.map mkIdent columns)}) " +
|
||||
"values (${builtins.concatStringsSep ", " (builtins.map mkValue values)})";
|
||||
|
||||
mkWhere = where:
|
||||
if builtins.isAttrs where then
|
||||
builtins.concatStringsSep " and " (builtins.map (name: "${name} = ${mkValue where.${name}}") (builtins.attrNames where))
|
||||
else where;
|
||||
|
||||
select =
|
||||
{ from
|
||||
, where
|
||||
, orderBy ? null
|
||||
, limit ? null
|
||||
, offset ? null
|
||||
, columns ? null
|
||||
}:
|
||||
let
|
||||
whereClause = if where != null then " where ${mkWhere where}" else "";
|
||||
orderByClause = if orderBy != null then " order by ${orderBy}" else "";
|
||||
limitClause = if limit != null then " limit ${limit}" else "";
|
||||
offsetClause = if offset != null then " offset ${offset}" else "";
|
||||
columnsClause = if columns != null then builtins.concatStringsSep ", " (builtins.map mkIdent columns) else "*";
|
||||
in
|
||||
"select ${columnsClause} from ${mkTableName from}${whereClause}${orderByClause}${limitClause}${offsetClause}";
|
||||
|
||||
script = statements:
|
||||
builtins.concatStringsSep "\n" (
|
||||
builtins.map (statement: statement + ";") (lib.lists.flatten statements)
|
||||
);
|
||||
}
|
60
packages/sftpgo.nix
Normal file
60
packages/sftpgo.nix
Normal file
|
@ -0,0 +1,60 @@
|
|||
# based on https://github.com/NixOS/nixpkgs/blob/63c4175cb0fb03cab301d7ba058e4937bec464e2/pkgs/servers/sftpgo/default.nix
|
||||
# modified to support bundled usage of static files
|
||||
|
||||
{ lib
|
||||
, buildGoModule
|
||||
, fetchFromGitHub
|
||||
, installShellFiles
|
||||
, nixosTests
|
||||
, tags ? []
|
||||
}:
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "sftpgo";
|
||||
version = "2.5.6";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "drakkan";
|
||||
repo = "sftpgo";
|
||||
rev = "refs/tags/v${version}";
|
||||
hash = "sha256-ea4DbPwi2tcRgmbNsZKKUOVkp6vjRbr679yAP7znNUc=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-8TBDaDBLy+82BwsaLncDknVIrauF0eop9e2ZhwcLmIs=";
|
||||
|
||||
inherit tags;
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X github.com/drakkan/sftpgo/v2/internal/version.commit=${src.rev}"
|
||||
"-X github.com/drakkan/sftpgo/v2/internal/version.date=1970-01-01T00:00:00Z"
|
||||
];
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
|
||||
doCheck = false;
|
||||
|
||||
subPackages = [ "." ];
|
||||
|
||||
preBuild = if builtins.any (x: x == "bundle") tags then ''
|
||||
cp -rf openapi internal/bundle/openapi
|
||||
cp -rf static internal/bundle/static
|
||||
cp -rf templates internal/bundle/templates
|
||||
'' else null;
|
||||
|
||||
postInstall = ''
|
||||
$out/bin/sftpgo gen man
|
||||
installManPage man/*.1
|
||||
|
||||
installShellCompletion --cmd sftpgo \
|
||||
--bash <($out/bin/sftpgo gen completion bash) \
|
||||
--zsh <($out/bin/sftpgo gen completion zsh) \
|
||||
--fish <($out/bin/sftpgo gen completion fish)
|
||||
|
||||
shareDirectory="$out/share/sftpgo"
|
||||
mkdir -p "$shareDirectory"
|
||||
cp -r ./{openapi,static,templates} "$shareDirectory"
|
||||
'';
|
||||
|
||||
passthru.tests = nixosTests.sftpgo;
|
||||
}
|
5
secrets/sftpgo-ed25519.age
Normal file
5
secrets/sftpgo-ed25519.age
Normal file
|
@ -0,0 +1,5 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 sj88Xw 2YLa2A0IPI5OYU1pACLEZmIXse0w3rwthY/0IhUfITE
|
||||
W3fHzNoxCouKZXeEZpfZTAfIAKIF07rCDvvnNHgF23Y
|
||||
--- dIuhxlxaabb+kOpULhF3wcj6CaDpjnLCdog9dTnsWyo
|
||||
%Å0ÕÛE~G öZ^UFR‘”ji©w/Ö‘ÄCM~þv©«AŠdá>[h´ëAÁÊÆãÊ¿œV>D>[m?€y–£CZ©Ù^yĹ˜TP§“Ó<ÖDL¡Î£âFÞ©³Ld<4C>éÏ
j1(¸A¢(êõ7ë-î§
<0A>}“°JQ4¦EÝÍ›î%'ìªí/‹
|
194
services/sftpgo.nix
Normal file
194
services/sftpgo.nix
Normal file
|
@ -0,0 +1,194 @@
|
|||
{ lib, abs, pkgs, ... }@inputs:
|
||||
|
||||
let
|
||||
sqlite = import (abs "lib/sqlite.nix") inputs;
|
||||
|
||||
defaultFilesystem = {
|
||||
provider = 0;
|
||||
osconfig = { };
|
||||
s3config = { };
|
||||
gcsconfig = { };
|
||||
azblobconfig = { };
|
||||
cryptconfig = { };
|
||||
sftpconfig = { };
|
||||
httpconfig = { };
|
||||
};
|
||||
|
||||
insertFolder = name: folder: sqlite.insert {
|
||||
into = "folders";
|
||||
value = {
|
||||
name = name;
|
||||
description = folder.description or "";
|
||||
path = folder.path;
|
||||
used_quota_size = "0";
|
||||
used_quota_files = "0";
|
||||
last_quota_update = "0";
|
||||
filesystem = defaultFilesystem;
|
||||
};
|
||||
};
|
||||
|
||||
insertUser = username: user: sqlite.insert {
|
||||
into = "users";
|
||||
orReplace = true;
|
||||
value = {
|
||||
username = username;
|
||||
status = "1";
|
||||
expiration_date = "0";
|
||||
description = user.description or "";
|
||||
password = user.password or "";
|
||||
public_keys = builtins.toJSON (user.publicKeys or [ ]);
|
||||
home_dir = user.home or "/tmp/${username}";
|
||||
uid = "0";
|
||||
gid = "0";
|
||||
max_sessions = "0";
|
||||
quota_size = "0";
|
||||
quota_files = "0";
|
||||
last_login = "0";
|
||||
permissions."/" = [ "*" ];
|
||||
used_quota_size = "0";
|
||||
used_quota_files = "0";
|
||||
last_quota_update = "0";
|
||||
upload_bandwidth = "0";
|
||||
download_bandwidth = "0";
|
||||
filters = {
|
||||
hooks = {
|
||||
external_auth_disabled = false;
|
||||
pre_login_disabled = false;
|
||||
check_password_disabled = false;
|
||||
};
|
||||
totp_config = {
|
||||
secret = { };
|
||||
};
|
||||
};
|
||||
filesystem = defaultFilesystem;
|
||||
additional_info = "";
|
||||
created_at = "0";
|
||||
updated_at = "0";
|
||||
email = "";
|
||||
upload_data_transfer = "0";
|
||||
download_data_transfer = "0";
|
||||
total_data_transfer = "0";
|
||||
used_upload_data_transfer = "0";
|
||||
used_download_data_transfer = "0";
|
||||
deleted_at = "0";
|
||||
first_download = "0";
|
||||
first_upload = "0";
|
||||
role_id = null;
|
||||
last_password_change = "0";
|
||||
};
|
||||
};
|
||||
|
||||
insertUserFolder = userFolder: sqlite.insert {
|
||||
into = "users_folders_mapping";
|
||||
orReplace = true;
|
||||
value = {
|
||||
user_id = sqlite.sql (
|
||||
sqlite.select {
|
||||
from = "users";
|
||||
columns = [ "id" ];
|
||||
where.username = userFolder.username;
|
||||
}
|
||||
);
|
||||
folder_id = sqlite.sql (
|
||||
sqlite.select {
|
||||
from = "folders";
|
||||
columns = [ "id" ];
|
||||
where.name = userFolder.folder;
|
||||
}
|
||||
);
|
||||
virtual_path = userFolder.path or "/${userFolder.folder}";
|
||||
quota_size = -1;
|
||||
quota_files = -1;
|
||||
};
|
||||
};
|
||||
|
||||
insertAdmin = username: admin: sqlite.insert {
|
||||
into = "admins";
|
||||
value = {
|
||||
username = username;
|
||||
description = admin.description or "";
|
||||
password = admin.password;
|
||||
email = admin.email or "";
|
||||
status = "1";
|
||||
permissions = "[\"*\"]";
|
||||
filters = {
|
||||
totp_config = { secret = { }; };
|
||||
preferences = { };
|
||||
};
|
||||
additional_info = "";
|
||||
last_login = "0";
|
||||
created_at = "0";
|
||||
updated_at = "0";
|
||||
role_id = null;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
setup =
|
||||
{
|
||||
# paths to private keys for sftp server
|
||||
# all required with default config, but can be omitted if you change the config to not require them
|
||||
# { ecdsa?: string, ed25519?: string, rsa?: string }
|
||||
keys ? { }
|
||||
# https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md
|
||||
, config ? null
|
||||
# username => {
|
||||
# home?: string, # defaults to /tmp/username
|
||||
# description?: string,
|
||||
# password?: string, # argon2 hashed
|
||||
# publicKeys?: string[]
|
||||
# permisisons?: Record<string, string>, e.g,
|
||||
# }
|
||||
, users ? { }
|
||||
# name => { description?: string, path: string }
|
||||
, folders ? { }
|
||||
# username => {
|
||||
# password: string, # argon2 hashed
|
||||
# description?: string,
|
||||
# email?: string,
|
||||
# }
|
||||
, admins ? {
|
||||
# argon2 hash of password "admin"
|
||||
admin.password = "$2a$10$7QZqmQNWwfbgIwc5Jskkgea7s8dffkbwPUW30MEShpDpZWxMVrFaa";
|
||||
}
|
||||
# array of {
|
||||
# username: string,
|
||||
# folder: string, # name of folder
|
||||
# path?: string # virtual path, e.g. /Folder (defaults to /${folder})
|
||||
# }
|
||||
, usersFolders ? null
|
||||
, package ? pkgs.sftpgo
|
||||
, sqlitePackage ? pkgs.sqlite
|
||||
, serviceName ? "sftpgo"
|
||||
}:
|
||||
let
|
||||
configFile = if config != null then pkgs.writeText "sftpgo.json" (builtins.toJSON config) else null;
|
||||
|
||||
sqliteStatements = [ ] ++
|
||||
lib.optionals (users != null) (lib.attrsets.mapAttrsToList insertUser users) ++
|
||||
lib.optionals (folders != null) (lib.attrsets.mapAttrsToList insertFolder folders) ++
|
||||
lib.optionals (admins != null) (lib.attrsets.mapAttrsToList insertAdmin admins) ++
|
||||
lib.optionals (usersFolders != null) (builtins.map insertUserFolder usersFolders);
|
||||
sqliteScript = pkgs.writeText "sftpgo-init.sql" (sqlite.script sqliteStatements);
|
||||
in
|
||||
{
|
||||
systemd.services.${serviceName} = {
|
||||
description = "${serviceName} Daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ package sqlitePackage ];
|
||||
serviceConfig.PrivateTmp = true;
|
||||
script = ''
|
||||
sftpgo initprovider
|
||||
sqlite3 sftpgo.db < ${sqliteScript}
|
||||
|
||||
${lib.optionalString (keys ? ecdsa) "cp ${keys.ecdsa} id_ecdsa"}
|
||||
${lib.optionalString (keys ? ed25519) "cp ${keys.ed25519} id_ed25519"}
|
||||
${lib.optionalString (keys ? rsa) "cp ${keys.rsa} id_rsa"}
|
||||
${lib.optionalString (configFile != null) "cp ${configFile} sftpgo.json"}
|
||||
|
||||
sftpgo serve
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
tree
|
||||
nixpkgs-fmt
|
||||
htop
|
||||
jq
|
||||
inputs.nil.packages.${system}.default
|
||||
inputs.agenix.packages.${system}.default
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue