1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
def default(value; f): if . == null then value else f end;
def default(value): default(value; .);
($target_spec
| match("^(?:([^@]+)@)?([^:/]+)(?::([^/]*))?(/.*)?")
| {
user: .captures[0].string | default("root"),
host: .captures[1].string,
port: .captures[2].string | default(22;
if test("^[0-9]+$") then fromjson else
error(@json "bad target port: \(.)")
end),
path: .captures[3].string | default("/var/src"),
}
)
as $target |
(($target.host == $origin_host) and ($target.user == $origin_user))
as $is_local |
to_entries |
map(select(.value.type == "file")) as $file_sources |
map(select(.value.type == "git")) as $git_sources |
map(select(.value.type == "symlink")) as $symlink_sources |
($file_sources + $symlink_sources) as $rsync_sources |
# Safeguard to prevent clobbering of misspelled targets.
# This script must run at target first.
def checktarget_script:
@sh "if ! test -f \($target.path)/.populate; then",
@sh " echo error: missing sentinel file: \($target.host):\($target.path)/.populate >&2",
@sh " exit 1",
@sh "fi";
def forcetarget_script:
@sh "mkdir -vp \($target.path)",
@sh "touch \($target.path)/.populate";
def git_script:
@sh "fetch_git(){(",
#@sh " export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt",
@sh " dst_dir=\($target.path)/$1",
@sh " src_url=$2",
@sh " src_ref=$3",
@sh " if ! test -e \"$dst_dir\"; then",
@sh " git clone -v \"$src_url\" \"$dst_dir\"",
@sh " fi",
@sh " cd \"$dst_dir\"",
@sh " if ! url=$(git config remote.origin.url); then",
@sh " git remote add origin \"$src_url\"",
@sh " elif test \"$url\" != \"$src_url\"; then",
@sh " git remote set-url origin \"$src_url\"",
@sh " fi",
# TODO resolve src_ref to commit hash",
@sh " hash=$src_ref",
@sh " if ! test \"$(git log --format=%H -1)\" = \"$hash\"; then",
@sh " git fetch -v origin",
@sh " git checkout \"$hash\" -- \"$dst_dir\"",
@sh " git checkout -f \"$hash\"",
@sh " fi",
@sh " git clean -dxf",
@sh ")}",
($git_sources[] |
@sh "fetch_git \(.key) \(.value.git.url) \(.value.git.ref)");
def rsync_script:
@sh "srcdir=$(mktemp -dt populate.XXXXXXXX)",
@sh "chmod 0755 \"$srcdir\"",
@sh "trap cleanup EXIT",
@sh "cleanup() {",
@sh " (set +f; rm -f \"$srcdir\"/*)",
@sh " rmdir \"$srcdir\"",
@sh "}",
($symlink_sources[] |
@sh "ln -s \(.value.symlink.target) \"$srcdir\"/\(.key)"),
@sh "proot \\",
($file_sources[] |
@sh " -b \(.value.file.path):\"$srcdir\"/\(.key) \\"),
@sh " rsync \\",
@sh " -vFrlptD \\",
@sh " --delete-excluded \\",
@sh " -f \("P /*", ($rsync_sources[] | "R /\(.key)")) \\",
($file_sources[] | .key as $key | .value.file.exclude | select(. != null)[] |
@sh " -f \("- /\($key)/\(.)") \\"),
@sh " \"$srcdir\"/ \\",
(if $is_local then
@sh " \($target.path)"
else
# ControlPersist=no so we reuse existing control sockets but if we
# create a new one, then remove it immediately after we are done so
# this script does not hang.
@sh " -e \("\($ssh_cmd) -o ControlPersist=no -p \($target.port)") \\",
@sh " \($target.user)@\($target.host):\($target.path)"
end);
def compile:
[
@sh "set -euf",
.[]
]
| map(select(. != null)) | join("\n");
def ssh_target:
@sh "echo \(compile) \\",
@sh " | \($ssh_cmd) \($target.user)@\($target.host) -p \($target.port) \\",
@sh " -T \("nix-shell -p git --run /bin/sh")";
[
(if $use_force then forcetarget_script else checktarget_script end),
git_script
]
| (if $is_local then . else [ssh_target] end),
[
rsync_script
]
| compile
|