commit a4044996cf56999c1cb48afd77eedb18ac888a7a equwal <13551856+equwal@users.noreply.github.com> 2026-09-25 01:34:55 -0700 deploy.sh: build under Cygwin, add the nginx include line Build with -buildvcs=false. Under Cygwin, git refuses a checkout that Git for Windows made (dubious ownership), and go build stopped with: error obtaining VCS status: exit status 128. Take the name of the nginx site file as a second argument. When the file does not include the snippet, add the include line to each HTTPS server block with include.awk. Keep a copy of the file, and put it back if nginx -t fails. So nobody must edit the file on the server by hand. Use location ^~ /links/, so that a regular expression location of the site does not take /links/atom.xml.
deploy/deploy.sh | 44 ++++++++++++++++++++++++++++++----- deploy/include.awk | 12 ++++++++++ deploy/nginx-location.conf | 8 ++++--- deploy_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 9 deletions(-)
diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 93af47b..3a9c0b3 100644 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -2,17 +2,26 @@ # deploy.sh: build sbm-webpublish for Linux and install it on a server with # systemd and nginx. # -# sh deploy/deploy.sh root@example.com +# sh deploy/deploy.sh root@example.com [site] +# +# site is the name of the nginx site file in /etc/nginx/sites-enabled. When +# that file does not include the nginx snippet, the script adds the include +# line to each HTTPS server block of the file (see include.awk). It keeps a +# copy of the file, and it puts the copy back if nginx -t fails. # # When the server has no admin password, the script makes one. Read it on # the server with: cat /etc/sbm-webpublish.env set -e -host=${1:?usage: sh deploy/deploy.sh user@host} +host=${1:?usage: sh deploy/deploy.sh user@host [site]} +site=$2 cd "$(dirname "$0")/.." -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -o sbm-webpublish . -scp sbm-webpublish deploy/sbm-webpublish.service deploy/nginx-location.conf "$host:/tmp/" +# -buildvcs=false: the build does not need the git status. Under Cygwin, git +# refuses a checkout that Git for Windows made ("dubious ownership"), and +# the build stops. +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -buildvcs=false -trimpath -o sbm-webpublish . +scp sbm-webpublish deploy/sbm-webpublish.service deploy/nginx-location.conf deploy/include.awk "$host:/tmp/" rm sbm-webpublish -ssh "$host" sh -s << 'EOF' +ssh "$host" "SITE='$site' sh -s" << 'EOF' set -e install -m 755 /tmp/sbm-webpublish /usr/local/bin/sbm-webpublish install -m 644 /tmp/sbm-webpublish.service /etc/systemd/system/sbm-webpublish.service @@ -31,12 +40,35 @@ if ! systemctl is-active --quiet sbm-webpublish; then exit 1 fi echo "sbm-webpublish is running" + +conf=/etc/nginx/sites-enabled/$SITE +copy=/etc/nginx/$SITE.before-sbm-webpublish +if [ -n "$SITE" ] && [ ! -f "$conf" ]; then + echo "no nginx site file $conf" + exit 1 +fi +if [ -n "$SITE" ] && ! grep -q 'snippets/sbm-webpublish.conf' "$conf"; then + # The copy and the new file are outside sites-enabled, so that nginx + # does not read them. cat writes through a symbolic link. + cp "$conf" "$copy" + awk -f /tmp/include.awk "$copy" > /tmp/sbm-webpublish.site + cat /tmp/sbm-webpublish.site > "$conf" + rm /tmp/sbm-webpublish.site + if ! nginx -t; then + cat "$copy" > "$conf" + echo "nginx -t failed. $conf is as before." + exit 1 + fi + echo "added the include line to $conf (the old file is $copy)" +fi +rm /tmp/include.awk if grep -qs 'snippets/sbm-webpublish.conf' /etc/nginx/sites-enabled/*; then nginx -t systemctl reload nginx echo "nginx reloaded" else - echo "Add this line to the server block of the site:" + echo "No HTTPS server block got the include line. Add this line to the" + echo "HTTPS server block of the site:" echo " include snippets/sbm-webpublish.conf;" echo "Then run: nginx -t && systemctl reload nginx" fi diff --git a/deploy/include.awk b/deploy/include.awk new file mode 100644 index 0000000..7c3c1c3 --- /dev/null +++ b/deploy/include.awk @@ -0,0 +1,12 @@ +# include.awk: add "include snippets/sbm-webpublish.conf;" to each HTTPS +# server block of an nginx site file. The line goes above the first +# "listen ... 443" line of each server block, because only an HTTPS server +# block has that line. deploy.sh runs this only when the file does not +# include the snippet yet. +/^[ \t]*server[ \t]*(\{|$)/ { done = 0 } +!done && /^[ \t]*listen[ \t]+([^;]*:)?443([ \t;]|$)/ { + match($0, /^[ \t]*/) + print substr($0, 1, RLENGTH) "include snippets/sbm-webpublish.conf;" + done = 1 +} +{ print } diff --git a/deploy/nginx-location.conf b/deploy/nginx-location.conf index c863f4a..886db49 100644 --- a/deploy/nginx-location.conf +++ b/deploy/nginx-location.conf @@ -1,12 +1,14 @@ # nginx snippet for sbm-webpublish. deploy.sh installs it as -# /etc/nginx/snippets/sbm-webpublish.conf. Include it in the server block of -# the site: +# /etc/nginx/snippets/sbm-webpublish.conf. Include it in the HTTPS server +# block of the site: # # include snippets/sbm-webpublish.conf; # # nginx continues to serve the static site. It sends only /links/ to # sbm-webpublish. Serve the site over HTTPS: the admin page uses basic auth. -location /links/ { +# "^~" stops the search for a regular expression location, so that a +# location such as "~ \.xml$" of the site does not take /links/atom.xml. +location ^~ /links/ { proxy_pass http://127.0.0.1:8082; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; diff --git a/deploy_test.go b/deploy_test.go new file mode 100644 index 0000000..fdfffae --- /dev/null +++ b/deploy_test.go @@ -0,0 +1,57 @@ +package main + +import ( + "os/exec" + "strings" + "testing" +) + +// TestIncludeAWK checks deploy/include.awk. deploy.sh runs it on the nginx +// site file of the server, so a mistake in it breaks the site. The test +// needs awk. +func TestIncludeAWK(t *testing.T) { + awk, err := exec.LookPath("awk") + if err != nil { + t.Skip("no awk:", err) + } + const inc = "include snippets/sbm-webpublish.conf;" + certbot := `server { + server_name a.com; + root /var/www/a; + location / { + try_files $uri $uri/ =404; + } + listen [::]:443 ssl ipv6only=on; # managed by Certbot + listen 443 ssl; # managed by Certbot +} +server { + if ($host = a.com) { + return 301 https://$host$request_uri; + } # managed by Certbot + listen 80; + server_name a.com; + return 404; # managed by Certbot +} +` + plain := "server {\n listen 80;\n listen 8443 ssl;\n # listen 443 ssl;\n server_name a.com;\n}\n" + for _, c := range []struct{ name, in, want string }{ + // One line in the HTTPS block, above its first listen line. The + // block for port 80 stays the same. + {"certbot", certbot, strings.Replace(certbot, " listen [::]:443", " "+inc+"\n listen [::]:443", 1)}, + // Each HTTPS block gets the line, with the indent of its listen line. + {"two blocks", "server\n{\n\tlisten 127.0.0.1:443 ssl;\n}\nserver {\n listen 443;\n}\n", + "server\n{\n\t" + inc + "\n\tlisten 127.0.0.1:443 ssl;\n}\nserver {\n " + inc + "\n listen 443;\n}\n"}, + // Port 8443 and a comment are not HTTPS on port 443. + {"no https", plain, plain}, + } { + cmd := exec.Command(awk, "-f", "deploy/include.awk") + cmd.Stdin = strings.NewReader(c.in) + out, err := cmd.Output() + if err != nil { + t.Fatalf("%s: %v", c.name, err) + } + if string(out) != c.want { + t.Errorf("%s: got\n%s\nwant\n%s", c.name, out, c.want) + } + } +}