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
|
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 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 \"$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 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 \\",
@sh " -f \("P /*", ($rsync_sources[] | "R /\(.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 -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 \($target.user)@\($target.host) -p \($target.port) -T";
[
(if $use_force then null else checktarget_script end),
git_script
]
| (if $is_local then . else [ssh_target] end),
[
rsync_script
]
| compile
|