bin/flake.nix
2025-11-30 18:20:47 -05:00

300 lines
10 KiB
Nix

{
description = "A minimal pastebin which also accepts binary files like Images, PDFs and ships multiple clients";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" ];
targets = [ "x86_64-unknown-linux-musl" "aarch64-unknown-linux-musl" ];
};
in
{
packages = {
default = self.packages.${system}.bin;
bin = pkgs.rustPlatform.buildRustPackage rec {
pname = "bin";
version = "2.4.0";
src = pkgs.fetchFromGitHub {
owner = "wantguns";
repo = "bin";
rev = "v${version}";
hash = "sha256-gUTDe9LPJ0Y2JLnbLv2FQaUjeNj6a1LQ3V5Z9SVnxkc=";
};
cargoHash = "sha256-2SiHrc0L9YiL2vLzKPnJ4BREPyeODJlLNReJPPpKlAE=";
nativeBuildInputs = with pkgs; [
pkg-config
];
buildInputs = with pkgs; [
openssl
] ++ lib.optionals stdenv.isDarwin [
darwin.apple_sdk.frameworks.Security
];
# Enable optimizations
CARGO_BUILD_INCREMENTAL = "false";
RUST_BACKTRACE = "full";
meta = with pkgs.lib; {
description = "A minimal pastebin which also accepts binary files";
homepage = "https://github.com/wantguns/bin";
license = licenses.lgpl3Only;
maintainers = [];
platforms = platforms.all;
};
};
docker = pkgs.dockerTools.buildImage {
name = "bin";
tag = "latest";
created = "now";
contents = [ self.packages.${system}.bin pkgs.cacert ];
config = {
Cmd = [ "${self.packages.${system}.bin}/bin/bin" ];
ExposedPorts = {
"6162/tcp" = {};
};
Env = [
"BIN_ADDRESS=0.0.0.0"
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
];
WorkingDir = "/";
Volumes = {
"/upload" = {};
};
};
};
};
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
rustToolchain
pkg-config
openssl
cargo-watch
rust-analyzer
clippy
rustfmt
];
RUST_BACKTRACE = 1;
};
}) // {
nixosModules = {
default = self.nixosModules.bin;
bin = { config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.bin;
in
{
options.services.bin = {
enable = mkEnableOption "bin pastebin service";
package = mkOption {
type = types.package;
default = self.packages.${pkgs.system}.bin;
defaultText = literalExpression "pkgs.bin";
description = "The bin package to use";
};
user = mkOption {
type = types.str;
default = "bin";
description = "User under which bin runs";
};
group = mkOption {
type = types.str;
default = "bin";
description = "Group under which bin runs";
};
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address on which the webserver runs";
};
port = mkOption {
type = types.port;
default = 6162;
description = "Port on which the webserver runs";
};
uploadDir = mkOption {
type = types.path;
default = "/var/lib/bin/upload";
description = "Path to the uploads folder";
};
binaryUploadLimit = mkOption {
type = types.int;
default = 100;
description = "Binary uploads file size limit (in MiB)";
};
clientDescription = mkOption {
type = types.bool;
default = true;
description = "Include client description on landing page";
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [];
description = "Extra command-line arguments to pass to bin";
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
File containing environment variables for bin.
Useful for setting Rocket-specific configurations.
'';
example = literalExpression ''
pkgs.writeText "bin-env" '''
BIN_LIMITS={form="16 MiB"}
BIN_WORKERS=8
BIN_IDENT=false
'''
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Whether to open the firewall for the bin port";
};
nginx = {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable nginx reverse proxy for bin";
};
domain = mkOption {
type = types.str;
default = "paste.example.com";
description = "Domain name for the bin service";
};
enableSSL = mkOption {
type = types.bool;
default = true;
description = "Whether to enable SSL (uses ACME/Let's Encrypt)";
};
};
};
config = mkIf cfg.enable {
systemd.services.bin = {
description = "bin pastebin service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = cfg.user;
Group = cfg.group;
ExecStart = ''
${cfg.package}/bin/bin \
--address ${cfg.address} \
--port ${toString cfg.port} \
--upload ${cfg.uploadDir} \
--binary-upload-limit ${toString cfg.binaryUploadLimit} \
${optionalString cfg.clientDescription "--client-desc"} \
${concatStringsSep " " cfg.extraArgs}
'';
Restart = "on-failure";
RestartSec = 5;
StateDirectory = "bin";
WorkingDirectory = "/var/lib/bin";
EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
# Security hardening
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ReadWritePaths = [ cfg.uploadDir ];
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
LockPersonality = true;
RestrictRealtime = true;
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
PrivateDevices = true;
RestrictNamespaces = true;
RestrictSUIDSGID = true;
RemoveIPC = true;
};
};
users.users = mkIf (cfg.user == "bin") {
bin = {
isSystemUser = true;
group = cfg.group;
home = "/var/lib/bin";
createHome = true;
};
};
users.groups = mkIf (cfg.group == "bin") {
bin = {};
};
systemd.tmpfiles.rules = [
"d ${cfg.uploadDir} 0755 ${cfg.user} ${cfg.group} -"
];
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
services.nginx = mkIf cfg.nginx.enable {
enable = true;
virtualHosts.${cfg.nginx.domain} = {
forceSSL = cfg.nginx.enableSSL;
enableACME = cfg.nginx.enableSSL;
locations."/" = {
proxyPass = "http://${cfg.address}:${toString cfg.port}";
proxyWebsockets = true;
extraConfig = ''
client_max_body_size ${toString cfg.binaryUploadLimit}M;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
'';
};
};
};
};
};
};
};
}