Recently Written · git

sbm-webpublish

Publish an sbm bookmark file as a list of links on a web site

git clone https://github.com/equwal/sbm-webpublish

Log | Files | Refs


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)
+		}
+	}
+}