Merge remote-tracking branch 'cd/master'
This commit is contained in:
commit
72d821d96c
286
krebs/3modules/backup.nix
Normal file
286
krebs/3modules/backup.nix
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
out = {
|
||||||
|
options.krebs.backup = api;
|
||||||
|
config = mkIf cfg.enable imp;
|
||||||
|
};
|
||||||
|
|
||||||
|
cfg = config.krebs.backup;
|
||||||
|
|
||||||
|
api = {
|
||||||
|
enable = mkEnableOption "krebs.backup" // { default = true; };
|
||||||
|
plans = mkOption {
|
||||||
|
default = {};
|
||||||
|
type = types.attrsOf (types.submodule ({
|
||||||
|
# TODO enable = mkEnableOption "TODO" // { default = true; };
|
||||||
|
options = {
|
||||||
|
method = mkOption {
|
||||||
|
type = types.enum ["pull" "push"];
|
||||||
|
};
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
src = mkOption {
|
||||||
|
type = types.krebs.file-location;
|
||||||
|
};
|
||||||
|
dst = mkOption {
|
||||||
|
type = types.krebs.file-location;
|
||||||
|
};
|
||||||
|
startAt = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
snapshots = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
format = mkOption {
|
||||||
|
type = types.str; # TODO date's +FORMAT
|
||||||
|
};
|
||||||
|
retain = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null; # null = retain all snapshots
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
imp = {
|
||||||
|
users.groups.backup.gid = genid "backup";
|
||||||
|
users.users = {}
|
||||||
|
// {
|
||||||
|
root.openssh.authorizedKeys.keys =
|
||||||
|
map (plan: plan.dst.host.ssh.pubkey)
|
||||||
|
(filter isPullSrc (attrValues cfg.plans))
|
||||||
|
++
|
||||||
|
map (plan: plan.src.host.ssh.pubkey)
|
||||||
|
(filter isPushDst (attrValues cfg.plans))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
systemd.services =
|
||||||
|
flip mapAttrs' (filterAttrs (_:isPullDst) cfg.plans) (name: plan: {
|
||||||
|
name = "backup.${name}.pull";
|
||||||
|
value = makePullService plan;
|
||||||
|
})
|
||||||
|
//
|
||||||
|
flip mapAttrs' (filterAttrs (_:isPushSrc) cfg.plans) (name: plan: {
|
||||||
|
name = "backup.${name}.push";
|
||||||
|
value = makePushService plan;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
isPushSrc = plan:
|
||||||
|
plan.method == "push" &&
|
||||||
|
plan.src.host.name == config.krebs.build.host.name;
|
||||||
|
|
||||||
|
isPullSrc = plan:
|
||||||
|
plan.method == "pull" &&
|
||||||
|
plan.src.host.name == config.krebs.build.host.name;
|
||||||
|
|
||||||
|
isPushDst = plan:
|
||||||
|
plan.method == "push" &&
|
||||||
|
plan.dst.host.name == config.krebs.build.host.name;
|
||||||
|
|
||||||
|
isPullDst = plan:
|
||||||
|
plan.method == "pull" &&
|
||||||
|
plan.dst.host.name == config.krebs.build.host.name;
|
||||||
|
|
||||||
|
# TODO push destination needs this in the dst.user's PATH
|
||||||
|
service-path = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.gnused
|
||||||
|
pkgs.openssh
|
||||||
|
pkgs.rsync
|
||||||
|
pkgs.utillinux
|
||||||
|
];
|
||||||
|
|
||||||
|
# TODO if there is plan.user, then use its privkey
|
||||||
|
makePushService = plan: assert isPushSrc plan; {
|
||||||
|
path = service-path;
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = push plan;
|
||||||
|
Type = "oneshot";
|
||||||
|
};
|
||||||
|
startAt = plan.startAt;
|
||||||
|
};
|
||||||
|
|
||||||
|
makePullService = plan: assert isPullDst plan; {
|
||||||
|
path = service-path;
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = pull plan;
|
||||||
|
Type = "oneshot";
|
||||||
|
};
|
||||||
|
startAt = plan.startAt;
|
||||||
|
};
|
||||||
|
|
||||||
|
push = plan: let
|
||||||
|
# We use writeDashBin and return the absolute path so systemd will produce
|
||||||
|
# nice names in the log, i.e. without the Nix store hash.
|
||||||
|
out = "${main}/bin/${main.name}";
|
||||||
|
|
||||||
|
main = writeDashBin "backup.${plan.name}.push" ''
|
||||||
|
set -efu
|
||||||
|
dst=${shell.escape plan.dst.path}
|
||||||
|
|
||||||
|
mkdir -m 0700 -p "$dst"
|
||||||
|
exec flock -n "$dst" ${critical-section}
|
||||||
|
'';
|
||||||
|
|
||||||
|
critical-section = writeDash "backup.${plan.name}.push.critical-section" ''
|
||||||
|
# TODO check if there is a previous
|
||||||
|
set -efu
|
||||||
|
identity=${shell.escape plan.src.host.ssh.privkey.path}
|
||||||
|
src=${shell.escape plan.src.path}
|
||||||
|
dst_target=${shell.escape "root@${getFQDN plan.dst.host}"}
|
||||||
|
dst_path=${shell.escape plan.dst.path}
|
||||||
|
dst=$dst_target:$dst_path
|
||||||
|
|
||||||
|
# Export NOW so runtime of rsync doesn't influence snapshot naming.
|
||||||
|
export NOW
|
||||||
|
NOW=$(date +%s)
|
||||||
|
|
||||||
|
echo >&2 "update snapshot: current; $src -> $dst"
|
||||||
|
rsync >&2 \
|
||||||
|
-aAXF --delete \
|
||||||
|
-e "ssh -F /dev/null -i $identity" \
|
||||||
|
--rsync-path ${shell.escape
|
||||||
|
"mkdir -m 0700 -p ${shell.escape plan.dst.path} && rsync"} \
|
||||||
|
--link-dest="$dst_path/current" \
|
||||||
|
"$src/" \
|
||||||
|
"$dst/.partial"
|
||||||
|
|
||||||
|
exec ssh -F /dev/null \
|
||||||
|
-i "$identity" \
|
||||||
|
"$dst_target" \
|
||||||
|
-T \
|
||||||
|
env NOW="$NOW" /bin/sh < ${remote-snapshot}
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
|
||||||
|
remote-snapshot = writeDash "backup.${plan.name}.push.remote-snapshot" ''
|
||||||
|
set -efu
|
||||||
|
dst=${shell.escape plan.dst.path}
|
||||||
|
|
||||||
|
if test -e "$dst/current"; then
|
||||||
|
mv "$dst/current" "$dst/.previous"
|
||||||
|
fi
|
||||||
|
mv "$dst/.partial" "$dst/current"
|
||||||
|
rm -fR "$dst/.previous"
|
||||||
|
echo >&2
|
||||||
|
|
||||||
|
(${(take-snapshots plan).text})
|
||||||
|
'';
|
||||||
|
|
||||||
|
in out;
|
||||||
|
|
||||||
|
# TODO admit plan.dst.user and its ssh identity
|
||||||
|
pull = plan: let
|
||||||
|
# We use writeDashBin and return the absolute path so systemd will produce
|
||||||
|
# nice names in the log, i.e. without the Nix store hash.
|
||||||
|
out = "${main}/bin/${main.name}";
|
||||||
|
|
||||||
|
main = writeDashBin "backup.${plan.name}.pull" ''
|
||||||
|
set -efu
|
||||||
|
dst=${shell.escape plan.dst.path}
|
||||||
|
|
||||||
|
mkdir -m 0700 -p "$dst"
|
||||||
|
exec flock -n "$dst" ${critical-section}
|
||||||
|
'';
|
||||||
|
|
||||||
|
critical-section = writeDash "backup.${plan.name}.pull.critical-section" ''
|
||||||
|
# TODO check if there is a previous
|
||||||
|
set -efu
|
||||||
|
identity=${shell.escape plan.dst.host.ssh.privkey.path}
|
||||||
|
src=${shell.escape "root@${getFQDN plan.src.host}:${plan.src.path}"}
|
||||||
|
dst=${shell.escape plan.dst.path}
|
||||||
|
|
||||||
|
# Export NOW so runtime of rsync doesn't influence snapshot naming.
|
||||||
|
export NOW
|
||||||
|
NOW=$(date +%s)
|
||||||
|
|
||||||
|
echo >&2 "update snapshot: current; $dst <- $src"
|
||||||
|
mkdir -m 0700 -p ${shell.escape plan.dst.path}
|
||||||
|
rsync >&2 \
|
||||||
|
-aAXF --delete \
|
||||||
|
-e "ssh -F /dev/null -i $identity" \
|
||||||
|
--link-dest="$dst/current" \
|
||||||
|
"$src/" \
|
||||||
|
"$dst/.partial"
|
||||||
|
mv "$dst/current" "$dst/.previous"
|
||||||
|
mv "$dst/.partial" "$dst/current"
|
||||||
|
rm -fR "$dst/.previous"
|
||||||
|
echo >&2
|
||||||
|
|
||||||
|
exec ${take-snapshots plan}
|
||||||
|
'';
|
||||||
|
in out;
|
||||||
|
|
||||||
|
take-snapshots = plan: writeDash "backup.${plan.name}.take-snapshots" ''
|
||||||
|
set -efu
|
||||||
|
NOW=''${NOW-$(date +%s)}
|
||||||
|
dst=${shell.escape plan.dst.path}
|
||||||
|
|
||||||
|
snapshot() {(
|
||||||
|
: $ns $format $retain
|
||||||
|
name=$(date --date="@$NOW" +"$format")
|
||||||
|
if ! test -e "$dst/$ns/$name"; then
|
||||||
|
echo >&2 "create snapshot: $ns/$name"
|
||||||
|
mkdir -m 0700 -p "$dst/$ns"
|
||||||
|
rsync >&2 \
|
||||||
|
-aAXF --delete \
|
||||||
|
--link-dest="$dst/current" \
|
||||||
|
"$dst/current/" \
|
||||||
|
"$dst/$ns/.partial.$name"
|
||||||
|
mv "$dst/$ns/.partial.$name" "$dst/$ns/$name"
|
||||||
|
echo >&2
|
||||||
|
fi
|
||||||
|
case $retain in
|
||||||
|
([0-9]*)
|
||||||
|
delete_from=$(($retain + 1))
|
||||||
|
ls -r "$dst/$ns" \
|
||||||
|
| sed -n "$delete_from,\$p" \
|
||||||
|
| while read old_name; do
|
||||||
|
echo >&2 "delete snapshot: $ns/$old_name"
|
||||||
|
rm -fR "$dst/$ns/$old_name"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
(ALL)
|
||||||
|
:
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
)}
|
||||||
|
|
||||||
|
${concatStringsSep "\n" (mapAttrsToList (ns: { format, retain ? null, ... }:
|
||||||
|
toString (map shell.escape [
|
||||||
|
"ns=${ns}"
|
||||||
|
"format=${format}"
|
||||||
|
"retain=${if retain == null then "ALL" else toString retain}"
|
||||||
|
"snapshot"
|
||||||
|
]))
|
||||||
|
plan.snapshots)}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# TODO getFQDN: admit hosts in other domains
|
||||||
|
getFQDN = host: "${host.name}.${config.krebs.search-domain}";
|
||||||
|
|
||||||
|
writeDash = name: text: pkgs.writeScript name ''
|
||||||
|
#! ${pkgs.dash}/bin/dash
|
||||||
|
${text}
|
||||||
|
'';
|
||||||
|
|
||||||
|
writeDashBin = name: text: pkgs.writeTextFile {
|
||||||
|
executable = true;
|
||||||
|
destination = "/bin/${name}";
|
||||||
|
name = name;
|
||||||
|
text = ''
|
||||||
|
#! ${pkgs.dash}/bin/dash
|
||||||
|
${text}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
in out
|
@ -7,6 +7,7 @@ let
|
|||||||
out = {
|
out = {
|
||||||
imports = [
|
imports = [
|
||||||
./apt-cacher-ng.nix
|
./apt-cacher-ng.nix
|
||||||
|
./backup.nix
|
||||||
./bepasty-server.nix
|
./bepasty-server.nix
|
||||||
./build.nix
|
./build.nix
|
||||||
./buildbot/master.nix
|
./buildbot/master.nix
|
||||||
|
@ -177,4 +177,21 @@ types // rec {
|
|||||||
addr6 = str;
|
addr6 = str;
|
||||||
hostname = str;
|
hostname = str;
|
||||||
label = str;
|
label = str;
|
||||||
|
|
||||||
|
krebs.file-location = types.submodule {
|
||||||
|
options = {
|
||||||
|
# TODO user
|
||||||
|
host = mkOption {
|
||||||
|
type = host;
|
||||||
|
};
|
||||||
|
# TODO merge with ssl.privkey.path
|
||||||
|
path = mkOption {
|
||||||
|
type = types.either types.path types.str;
|
||||||
|
apply = x: {
|
||||||
|
path = toString x;
|
||||||
|
string = x;
|
||||||
|
}.${typeOf x};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
42
tv/2configs/backup.nix
Normal file
42
tv/2configs/backup.nix
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
with lib;
|
||||||
|
{
|
||||||
|
krebs.backup.plans = addNames {
|
||||||
|
xu-test-cd = {
|
||||||
|
method = "push";
|
||||||
|
|
||||||
|
src = { host = config.krebs.hosts.xu; path = "/tmp/xu-test"; };
|
||||||
|
dst = { host = config.krebs.hosts.cd; path = "/tmp/backups/xu-test"; };
|
||||||
|
|
||||||
|
#startAt = "0,6,12,18:00";
|
||||||
|
startAt = "minutely";
|
||||||
|
snapshots = {
|
||||||
|
minutely = { format = "%Y-%m-%dT%H:%M"; retain = 5; };
|
||||||
|
hourly = { format = "%Y-%m-%dT%H"; retain = 4; };
|
||||||
|
daily = { format = "%Y-%m-%d"; retain = 7; };
|
||||||
|
weekly = { format = "%YW%W"; retain = 4; };
|
||||||
|
monthly = { format = "%Y-%m"; retain = 12; };
|
||||||
|
yearly = { format = "%Y"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
#xu-test-wu = {
|
||||||
|
# method = "push";
|
||||||
|
# dst = { user = tv; host = wu; path = "/krebs/backup/xu-test"; };
|
||||||
|
#};
|
||||||
|
cd-test-xu = {
|
||||||
|
method = "pull";
|
||||||
|
src = { host = config.krebs.hosts.cd; path = "/tmp/cd-test"; };
|
||||||
|
dst = { host = config.krebs.hosts.xu; path = "/tmp/backups/cd-test"; };
|
||||||
|
startAt = "minutely";
|
||||||
|
snapshots = {
|
||||||
|
minutely = { format = "%Y-%m-%dT%H:%M"; retain = 5; };
|
||||||
|
hourly = { format = "%Y-%m-%dT%H"; retain = 4; };
|
||||||
|
daily = { format = "%Y-%m-%d"; retain = 7; };
|
||||||
|
weekly = { format = "%YW%W"; retain = 4; };
|
||||||
|
monthly = { format = "%Y-%m"; retain = 12; };
|
||||||
|
yearly = { format = "%Y"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
@ -28,6 +28,7 @@ with lib;
|
|||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
<secrets>
|
<secrets>
|
||||||
|
./backup.nix
|
||||||
./vim.nix
|
./vim.nix
|
||||||
{
|
{
|
||||||
# stockholm dependencies
|
# stockholm dependencies
|
||||||
|
@ -4,7 +4,7 @@ with lib;
|
|||||||
let
|
let
|
||||||
out = {
|
out = {
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
pkgs.vim
|
vim
|
||||||
];
|
];
|
||||||
|
|
||||||
# Nano really is just a stupid name for Vim.
|
# Nano really is just a stupid name for Vim.
|
||||||
@ -22,14 +22,38 @@ let
|
|||||||
"${pkgs.vimPlugins.undotree}/share/vim-plugins/undotree"
|
"${pkgs.vimPlugins.undotree}/share/vim-plugins/undotree"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
dirs = {
|
||||||
|
backupdir = "$HOME/.cache/vim/backup";
|
||||||
|
swapdir = "$HOME/.cache/vim/swap";
|
||||||
|
undodir = "$HOME/.cache/vim/undo";
|
||||||
|
};
|
||||||
|
files = {
|
||||||
|
viminfo = "$HOME/.cache/vim/info";
|
||||||
|
};
|
||||||
|
|
||||||
|
mkdirs = let
|
||||||
|
dirOf = s: let out = concatStringsSep "/" (init (splitString "/" s));
|
||||||
|
in assert out != ""; out;
|
||||||
|
alldirs = attrValues dirs ++ map dirOf (attrValues files);
|
||||||
|
in unique (sort lessThan alldirs);
|
||||||
|
|
||||||
|
vim = pkgs.writeScriptBin "vim" ''
|
||||||
|
#! ${pkgs.dash}/bin/dash
|
||||||
|
set -f
|
||||||
|
umask 0077
|
||||||
|
${concatStringsSep "\n" (map (x: "mkdir -p ${x}") mkdirs)}
|
||||||
|
umask 0022
|
||||||
|
exec ${pkgs.vim}/bin/vim "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
vimrc = pkgs.writeText "vimrc" ''
|
vimrc = pkgs.writeText "vimrc" ''
|
||||||
set nocompatible
|
set nocompatible
|
||||||
|
|
||||||
set autoindent
|
set autoindent
|
||||||
set backspace=indent,eol,start
|
set backspace=indent,eol,start
|
||||||
set backup
|
set backup
|
||||||
set backupdir=$HOME/.vim/backup/
|
set backupdir=${dirs.backupdir}/
|
||||||
set directory=$HOME/.vim/cache//
|
set directory=${dirs.swapdir}//
|
||||||
set hlsearch
|
set hlsearch
|
||||||
set incsearch
|
set incsearch
|
||||||
set mouse=a
|
set mouse=a
|
||||||
@ -40,11 +64,11 @@ let
|
|||||||
set showcmd
|
set showcmd
|
||||||
set showmatch
|
set showmatch
|
||||||
set ttimeoutlen=0
|
set ttimeoutlen=0
|
||||||
set undodir=$HOME/.vim/undo
|
set undodir=${dirs.undodir}
|
||||||
set undofile
|
set undofile
|
||||||
set undolevels=1000000
|
set undolevels=1000000
|
||||||
set undoreload=1000000
|
set undoreload=1000000
|
||||||
set viminfo='20,<1000,s100,h,n$HOME/.vim/cache/info
|
set viminfo='20,<1000,s100,h,n${files.viminfo}
|
||||||
set visualbell
|
set visualbell
|
||||||
set wildignore+=*.o,*.class,*.hi,*.dyn_hi,*.dyn_o
|
set wildignore+=*.o,*.class,*.hi,*.dyn_hi,*.dyn_o
|
||||||
set wildmenu
|
set wildmenu
|
||||||
|
@ -48,7 +48,7 @@ let
|
|||||||
"slock"
|
"slock"
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.services.display-manager = mkForce {};
|
systemd.services.display-manager.enable = false;
|
||||||
|
|
||||||
services.xserver.enable = true;
|
services.xserver.enable = true;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user