Commit 2a420dbcd2
2a420dbcd2ecc545737f312e1ca5f53f18c8eac6
parent: 9af155d40a
Unsigned
cmc <hello@cleberg.net> · 2026-08-08 20:32 UTC
Fix a11y labels, Docker quoting, pin Actions to SHA, correct input type
Layout: unified · split
.github/workflows/release.yml
+6 −6
| @@ -20,15 +20,15 @@ jobs: |
| 20 | attestations: write |
20 | attestations: write |
| 21 | steps: |
21 | steps: |
| 22 | - name: Checkout |
22 | - name: Checkout |
| 23 | uses: actions/checkout@v7 |
23 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 |
| 24 | |
24 | |
| 25 | # The Dockerfile cross-compiles with Go's GOOS/GOARCH from $BUILDPLATFORM, |
25 | # The Dockerfile cross-compiles with Go's GOOS/GOARCH from $BUILDPLATFORM, |
| 26 | # so no QEMU emulation is needed for the arm64 image. |
26 | # so no QEMU emulation is needed for the arm64 image. |
| 27 | - name: Set up Buildx |
27 | - name: Set up Buildx |
| 28 | uses: docker/setup-buildx-action@v4 |
28 | uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 |
| 29 | |
29 | |
| 30 | - name: Log in to ${{ env.REGISTRY }} |
30 | - name: Log in to ${{ env.REGISTRY }} |
| 31 | uses: docker/login-action@v4 |
31 | uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 |
| 32 | with: |
32 | with: |
| 33 | registry: ${{ env.REGISTRY }} |
33 | registry: ${{ env.REGISTRY }} |
| 34 | username: ${{ github.actor }} |
34 | username: ${{ github.actor }} |
| @@ -36,7 +36,7 @@ jobs: |
| 36 | |
36 | |
| 37 | - name: Derive tags and labels |
37 | - name: Derive tags and labels |
| 38 | id: meta |
38 | id: meta |
| 39 | uses: docker/metadata-action@v6 |
39 | uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 |
| 40 | with: |
40 | with: |
| 41 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} |
41 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} |
| 42 | # A semver tag v1.3.4 publishes 1.3.4, 1.3, 1 and latest. |
42 | # A semver tag v1.3.4 publishes 1.3.4, 1.3, 1 and latest. |
| @@ -53,7 +53,7 @@ jobs: |
| 53 | |
53 | |
| 54 | - name: Build and push |
54 | - name: Build and push |
| 55 | id: build |
55 | id: build |
| 56 | uses: docker/build-push-action@v7 |
56 | uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 |
| 57 | with: |
57 | with: |
| 58 | context: . |
58 | context: . |
| 59 | platforms: linux/amd64,linux/arm64 |
59 | platforms: linux/amd64,linux/arm64 |
| @@ -68,7 +68,7 @@ jobs: |
| 68 | cache-to: type=gha,mode=max |
68 | cache-to: type=gha,mode=max |
| 69 | |
69 | |
| 70 | - name: Attest build provenance |
70 | - name: Attest build provenance |
| 71 | uses: actions/attest-build-provenance@v4 |
71 | uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4 |
| 72 | with: |
72 | with: |
| 73 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} |
73 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} |
| 74 | subject-digest: ${{ steps.build.outputs.digest }} |
74 | subject-digest: ${{ steps.build.outputs.digest }} |
Dockerfile
+2 −1
| @@ -8,7 +8,8 @@ ARG VERSION=dev |
| 8 | |
8 | |
| 9 | WORKDIR /build |
9 | WORKDIR /build |
| 10 | COPY . . |
10 | COPY . . |
| 11 | RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} GOOS=${TARGETOS} go build -ldflags "-s -w -extldflags '-static' -X main.version=${VERSION}" && \ |
11 | RUN CGO_ENABLED=0 GOARCH="${TARGETARCH}" GOOS="${TARGETOS}" \ |
| |
12 | go build -ldflags "-s -w -extldflags '-static' -X main.version=${VERSION}" && \ |
| 12 | echo "skunkyart:x:10000:10000:SkunkyArt user:/:/sbin/nologin" > /etc/minimal-passwd && \ |
13 | echo "skunkyart:x:10000:10000:SkunkyArt user:/:/sbin/nologin" > /etc/minimal-passwd && \ |
| 13 | echo "skunkyart:x:10000:" > /etc/minimal-group |
14 | echo "skunkyart:x:10000:" > /etc/minimal-group |
| 14 | |
15 | |
app/cache_test.go
+1 −1
| @@ -278,7 +278,7 @@ func TestAgeMemCacheEvicts(t *testing.T) { |
| 278 | if got := memGet(cold); got != nil { |
278 | if got := memGet(cold); got != nil { |
| 279 | t.Errorf("cold entry survived aging: got %q, want nil", got) |
279 | t.Errorf("cold entry survived aging: got %q, want nil", got) |
| 280 | } |
280 | } |
| 281 | if got := memGet(hot); got == nil { |
281 | if memGet(hot) == nil { |
| 282 | t.Error("hot entry was evicted after a hit, want it kept") |
282 | t.Error("hot entry was evicted after a hit, want it kept") |
| 283 | } |
283 | } |
| 284 | } |
284 | } |
app/wrapper.go
+3 −3
| @@ -89,7 +89,7 @@ func (s skunkyart) GRUser() { |
| 89 | |
89 | |
| 90 | folderid, _ := strconv.Atoi(s.Args.Get("folder")) |
90 | folderid, _ := strconv.Atoi(s.Args.Get("folder")) |
| 91 | |
91 | |
| 92 | if a := s.Args.Get("all"); a == "true" { |
92 | if s.Args.Get("all") == "true" { |
| 93 | all = true |
93 | all = true |
| 94 | } |
94 | } |
| 95 | |
95 | |
| @@ -115,7 +115,7 @@ func (s skunkyart) GRUser() { |
| 115 | }) |
115 | }) |
| 116 | } else { |
116 | } else { |
| 117 | for _, x := range content.Content.Gruser.Page.Modules { |
117 | for _, x := range content.Content.Gruser.Page.Modules { |
| 118 | if l := len(x.ModuleData.Folders.Results); l != 0 { |
118 | if len(x.ModuleData.Folders.Results) != 0 { |
| 119 | var folders strings.Builder |
119 | var folders strings.Builder |
| 120 | folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`) |
120 | folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`) |
| 121 | for _, x := range x.ModuleData.Folders.Results { |
121 | for _, x := range x.ModuleData.Folders.Results { |
| @@ -309,7 +309,7 @@ func (s skunkyart) Search() { |
| 309 | } |
309 | } |
| 310 | } |
310 | } |
| 311 | |
311 | |
| 312 | if l := len(usernames); l != 0 { |
312 | if len(usernames) != 0 { |
| 313 | ss.List += `<div class="content plates">` |
313 | ss.List += `<div class="content plates">` |
| 314 | for x := range len(usernames) { |
314 | for x := range len(usernames) { |
| 315 | ss.List += BuildUserPlate(s.Host, usernames[x]) |
315 | ss.List += BuildUserPlate(s.Host, usernames[x]) |
static/html/about.htm
+1 −1
| @@ -1,5 +1,5 @@ |
| 1 | <!DOCTYPE html> |
1 | <!DOCTYPE html> |
| 2 | <html> |
2 | <html lang="en"> |
| 3 | {{template "head" .}} |
3 | {{template "head" .}} |
| 4 | <main> |
4 | <main> |
| 5 | {{template "header" .}} |
5 | {{template "header" .}} |
static/html/daily.htm
+1 −1
| @@ -1,5 +1,5 @@ |
| 1 | <!DOCTYPE html> |
1 | <!DOCTYPE html> |
| 2 | <html> |
2 | <html lang="en"> |
| 3 | {{template "head" . }} |
3 | {{template "head" . }} |
| 4 | <main> |
4 | <main> |
| 5 | {{template "header" . }} |
5 | {{template "header" . }} |
static/html/deviantion.htm
+3 −3
| @@ -1,10 +1,10 @@ |
| 1 | <!DOCTYPE html> |
1 | <!DOCTYPE html> |
| 2 | <html> |
2 | <html lang="en"> |
| 3 | {{template "head" . }} |
3 | {{template "head" . }} |
| 4 | <main> |
4 | <main> |
| 5 | {{template "header" . }} |
5 | {{template "header" . }} |
| 6 | <figure> |
6 | <figure> |
| 7 | <img src="{{.BasePath}}media/emojitar/{{.Templates.Deviation.Post.Deviation.Author.Username}}?type=a" width="30px"> |
7 | <img src="{{.BasePath}}media/emojitar/{{.Templates.Deviation.Post.Deviation.Author.Username}}?type=a" width="30px" alt="{{.Templates.Deviation.Post.Deviation.Author.Username}} avatar"> |
| 8 | <span><strong><a href="{{.BasePath}}group_user?type=about&q={{.Templates.Deviation.Post.Deviation.Author.Username}}">{{.Templates.Deviation.Post.Deviation.Author.Username}}</a></strong> — {{if (.Templates.Deviation.Post.Deviation.DD)}} |
8 | <span><strong><a href="{{.BasePath}}group_user?type=about&q={{.Templates.Deviation.Post.Deviation.Author.Username}}">{{.Templates.Deviation.Post.Deviation.Author.Username}}</a></strong> — {{if (.Templates.Deviation.Post.Deviation.DD)}} |
| 9 | <span class="dd" title="Daily Deviation!"><b>{{.Templates.Deviation.Post.Deviation.Title}}</b></span> |
9 | <span class="dd" title="Daily Deviation!"><b>{{.Templates.Deviation.Post.Deviation.Title}}</b></span> |
| 10 | {{else}}{{.Templates.Deviation.Post.Deviation.Title}}{{end}} |
10 | {{else}}{{.Templates.Deviation.Post.Deviation.Title}}{{end}} |
| @@ -13,7 +13,7 @@ |
| 13 | </span> |
13 | </span> |
| 14 | <br> |
14 | <br> |
| 15 | {{if (ne .Templates.Deviation.Post.IMG "")}} |
15 | {{if (ne .Templates.Deviation.Post.IMG "")}} |
| 16 | <a href="{{.Templates.Deviation.Post.IMG}}" title="open/download image"><img src="{{.Templates.Deviation.Post.IMG}}" width="50%"></a> |
16 | <a href="{{.Templates.Deviation.Post.IMG}}" title="open/download image"><img src="{{.Templates.Deviation.Post.IMG}}" width="50%" alt="{{.Templates.Deviation.Post.Deviation.Title}}"></a> |
| 17 | <br> |
17 | <br> |
| 18 | {{end}} |
18 | {{end}} |
| 19 | {{if (ne .Templates.Deviation.Tags "")}} |
19 | {{if (ne .Templates.Deviation.Tags "")}} |
static/html/gruser.htm
+5 −5
| @@ -1,5 +1,5 @@ |
| 1 | <!DOCTYPE html> |
1 | <!DOCTYPE html> |
| 2 | <html> |
2 | <html lang="en"> |
| 3 | {{template "head" . }} |
3 | {{template "head" . }} |
| 4 | <main> |
4 | <main> |
| 5 | <header> |
5 | <header> |
| @@ -17,9 +17,9 @@ |
| 17 | | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a> |
17 | | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a> |
| 18 | </h1> |
18 | </h1> |
| 19 | <form method="get" action="{{.BasePath}}search"> |
19 | <form method="get" action="{{.BasePath}}search"> |
| 20 | <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
20 | <input type="text" name="q" aria-label="Search query" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 21 | <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> |
21 | <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> |
| 22 | <select name="type"> |
22 | <select name="type" aria-label="Search type"> |
| 23 | <option value="gallery">Gallery</option> |
23 | <option value="gallery">Gallery</option> |
| 24 | <option value="folders">Folders</option> |
24 | <option value="folders">Folders</option> |
| 25 | <option value="all">All</option> |
25 | <option value="all">All</option> |
| @@ -31,11 +31,11 @@ |
| 31 | </header> |
31 | </header> |
| 32 | {{if eq .Type 'a'}} |
32 | {{if eq .Type 'a'}} |
| 33 | {{if and (and (ne .Templates.About.Nsfw true) (ne .Templates.GroupUser.About.BGMeta.NSFW true)) (ne .Templates.GroupUser.About.BG "")}} |
33 | {{if and (and (ne .Templates.About.Nsfw true) (ne .Templates.GroupUser.About.BGMeta.NSFW true)) (ne .Templates.GroupUser.About.BG "")}} |
| 34 | <a href="{{.Templates.GroupUser.About.BGMeta.Url}}" class="ubg"><img title="{{if ne .Templates.GroupUser.GR.Owner.Username .Templates.GroupUser.About.BGMeta.Author.Username}} |
34 | <a href="{{.Templates.GroupUser.About.BGMeta.Url}}" class="ubg"><img alt="Profile background" title="{{if ne .Templates.GroupUser.GR.Owner.Username .Templates.GroupUser.About.BGMeta.Author.Username}} |
| 35 | {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a> |
35 | {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a> |
| 36 | {{end}} |
36 | {{end}} |
| 37 | |
37 | |
| 38 | <img src="{{.BasePath}}media/emojitar/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b> |
38 | <img src="{{.BasePath}}media/emojitar/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px" alt="{{.Templates.GroupUser.GR.Owner.Username}} avatar"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b> |
| 39 | {{if (eq .Templates.GroupUser.About.A.Gender "male")}} |
39 | {{if (eq .Templates.GroupUser.About.A.Gender "male")}} |
| 40 | ♂️ |
40 | ♂️ |
| 41 | {{end}} |
41 | {{end}} |
static/html/head.htm
+1 −1
| @@ -21,6 +21,6 @@ |
| 21 | <meta name="referrer" content="no-referrer" /> |
21 | <meta name="referrer" content="no-referrer" /> |
| 22 | <link rel="stylesheet" href="{{.BasePath}}stylesheet"> |
22 | <link rel="stylesheet" href="{{.BasePath}}stylesheet"> |
| 23 | <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> |
23 | <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> |
| 24 | <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4, user-scalable=no"/> |
24 | <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4"/> |
| 25 | </head> |
25 | </head> |
| 26 | {{end}}\ No newline at end of file |
26 | {{end}}\ No newline at end of file |
static/html/header.htm
+2 −2
| @@ -2,8 +2,8 @@ |
| 2 | <header> |
2 | <header> |
| 3 | <h1><a href="">HOME</a> | <a href="dd">DD</a> {{if eq .Endpoint "dd"}}| <a href="{{.Endpoint}}?atom=true">RSS</a>{{end}}</h1> |
3 | <h1><a href="">HOME</a> | <a href="dd">DD</a> {{if eq .Endpoint "dd"}}| <a href="{{.Endpoint}}?atom=true">RSS</a>{{end}}</h1> |
| 4 | <form method="get" action="search"> |
4 | <form method="get" action="search"> |
| 5 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false" value="{{.QueryRaw}}"> |
5 | <input type="text" name="q" aria-label="Search query" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false" value="{{.QueryRaw}}"> |
| 6 | <select name="type"> |
6 | <select name="type" aria-label="Search type"> |
| 7 | <option value="all">All</option> |
7 | <option value="all">All</option> |
| 8 | <option value="tag">Tag</option> |
8 | <option value="tag">Tag</option> |
| 9 | <option value="r">Groups</option> |
9 | <option value="r">Groups</option> |
static/html/index.htm
+5 −5
| @@ -1,10 +1,10 @@ |
| 1 | <!DOCTYPE html> |
1 | <!DOCTYPE html> |
| 2 | <html> |
2 | <html lang="en"> |
| 3 | <head> |
3 | <head> |
| 4 | <title>SkunkyArt</title> |
4 | <title>SkunkyArt</title> |
| 5 | <link rel="stylesheet" href="{{.}}stylesheet"/> |
5 | <link rel="stylesheet" href="{{.}}stylesheet"/> |
| 6 | <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico"> |
6 | <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico"> |
| 7 | <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4, user-scalable=no; user-scalable=0"/> |
7 | <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4"/> |
| 8 | |
8 | |
| 9 | <style> |
9 | <style> |
| 10 | main { |
10 | main { |
| @@ -70,12 +70,12 @@ |
| 70 | </style> |
70 | </style> |
| 71 | </head> |
71 | </head> |
| 72 | <main> |
72 | <main> |
| 73 | <img src="{{.}}favicon.ico" title="SkunkyArt logo" draggable="false"> |
73 | <img src="{{.}}favicon.ico" title="SkunkyArt logo" alt="SkunkyArt logo" draggable="false"> |
| 74 | <div> |
74 | <div> |
| 75 | <h1><a href="{{.}}dd">Daily Deviations</a> | <a href="{{.}}about">About</a></h1> |
75 | <h1><a href="{{.}}dd">Daily Deviations</a> | <a href="{{.}}about">About</a></h1> |
| 76 | <form method="get" action="{{.}}search"> |
76 | <form method="get" action="{{.}}search"> |
| 77 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
77 | <input type="text" name="q" aria-label="Search query" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 78 | <select name="type"> |
78 | <select name="type" aria-label="Search type"> |
| 79 | <option value="all">All</option> |
79 | <option value="all">All</option> |
| 80 | <option value="tag">Tag</option> |
80 | <option value="tag">Tag</option> |
| 81 | <option value="r">Groups</option> |
81 | <option value="r">Groups</option> |
static/html/search.htm
+1 −1
| @@ -1,5 +1,5 @@ |
| 1 | <!DOCTYPE html> |
1 | <!DOCTYPE html> |
| 2 | <html> |
2 | <html lang="en"> |
| 3 | {{template "head" . }} |
3 | {{template "head" . }} |
| 4 | <main> |
4 | <main> |
| 5 | {{template "header" . }} |
5 | {{template "header" . }} |