Commit 9db3cae4a5

9db3cae4a590f7e4c38b958d5ceb24331976e715

parent: 7bdc11dec1

Verified · cmc ci/build: success ci/lint: success ci/test: success

cmc <hello@cleberg.net> · 2026-09-11 14:35 UTC

Write a working systemd unit and expand the README

The unit uses WorkingDirectory, DynamicUser with a state directory for
the cache, restart on failure, and the usual hardening. The README
lists the pages, how to run without Docker, what REDIRECTS.md is for,
the upstream-traffic measures, and the develop loop.

Closes #25
Closes #27

Layout: unified · split

README.org +43 −2
@@ -7,9 +7,29 @@ this fork keeps it maintained.
7 7
8* what 8* what
9skunkyart. alternative frontend for deviantart. works with no javascript. 9skunkyart. alternative frontend for deviantart. works with no javascript.
10one instance, one config file, no database.
10 11
11instances: [[file:INSTANCES.md][INSTANCES.md]] 12instances: [[file:INSTANCES.md][INSTANCES.md]]
12 13
14pages:
15
16- ~/~ search box, links to daily deviations and about
17- ~/dd~ daily deviations; ~?atom=true~ for the feed
18- ~/search?q=<q>&type=all|tag|r~ search art, tags, or groups
19- ~/post/<author>/<name-id>~ one deviation, with a link to its comments
20- ~/group_user?q=<name>&type=about|gallery|favourites~ a user or group;
21 ~&atom=true~ on a gallery for its feed
22- ~/api/...~ json, documented in [[file:API.md][API.md]]
23
24[[file:REDIRECTS.md][REDIRECTS.md]] maps deviantart.com urls onto these, for browser redirector
25extensions such as libredirect.
26
27deviantart blocks egress ips that ask too often, so the instance keeps its
28upstream traffic down: api responses are cached in memory and coalesced, media
29and avatars are cached on disk, comments load on request, pages carry
30cache-control headers, crawlers get a robots.txt, and each client has a
31request budget. all of it is on by default and tunable in [[file:SETUP.md][SETUP.md]].
32
13* build 33* build
14build with the embed tag to embed presets in the binary. skip the tag if you'll 34build with the embed tag to embed presets in the binary. skip the tag if you'll
15modify templates. add ~-ldflags "-w -s"~ (~gccgo: gccgoflags~) to shrink the output: 35modify templates. add ~-ldflags "-w -s"~ (~gccgo: gccgoflags~) to shrink the output:
@@ -21,6 +41,19 @@ go build -tags embed -ldflags "-w -s"
21that build reports its version as dev. stamp one in with ~-X 41that build reports its version as dev. stamp one in with ~-X
22main.version=<version>~, as the release workflow does from the git tag. 42main.version=<version>~, as the release workflow does from the git tag.
23 43
44* run
45without docker: put the binary (and ~static/~, unless built with the embed
46tag) in a directory with a ~config.json~, then
47
48#+begin_src sh
49./skunkyart -c config.json
50#+end_src
51
52~config.json~ is optional; without it the built-in defaults listen on
53127.0.0.1:3003 with the caches on. service files for systemd and openrc are in
54[[file:services/][services/]]. put a reverse proxy with tls in front; SETUP.md has the nginx
55stanza.
56
24* docker 57* docker
25multi-arch images (~linux/amd64~, ~linux/arm64~) publish to ghcr on every release 58multi-arch images (~linux/amd64~, ~linux/arm64~) publish to ghcr on every release
26tag: 59tag:
@@ -37,6 +70,14 @@ reproducible upgrades. [[file:compose.example.yaml][compose.example.yaml]] uses
37sample config in [[file:config.example.json][config.example.json]]. custom config with ~--config~. directive 70sample config in [[file:config.example.json][config.example.json]]. custom config with ~--config~. directive
38details in [[file:SETUP.md][SETUP.md]]. 71details in [[file:SETUP.md][SETUP.md]].
39 72
73* develop
74#+begin_src sh
75go test ./... -race
76go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 run ./...
77#+end_src
78
79ci runs both on every push. the roadmap is [[file:ROADMAP.md][ROADMAP.md]].
80
40* instances 81* instances
41add yours by pr to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use ~--add-instance~ to 82add yours by merge request to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use
42write both), or open an issue. 83~--add-instance~ to write both), or open an issue.
services/skunkyart.example.service +31 −4
@@ -1,11 +1,38 @@
1# Note: i didn't use systemd, so it can be not works :) 1# systemd unit for SkunkyArt. Install the binary and its static/ directory
2# (or a binary built with -tags embed) under /opt/skunkyart, put config.json
3# beside it, then:
4#
5# cp services/skunkyart.example.service /etc/systemd/system/skunkyart.service
6# systemctl daemon-reload
7# systemctl enable --now skunkyart
8#
9# DynamicUser gives the service a throwaway account with no home and no
10# shell; StateDirectory is the one writable place it gets, mounted at
11# /var/lib/skunkyart, which is where the media cache goes.
2 12
3[Unit] 13[Unit]
4Description=Privacy-oriented frontend for DeviantArt 14Description=SkunkyArt, an alternative frontend for DeviantArt
15After=network-online.target
16Wants=network-online.target
5 17
6[Service] 18[Service]
7Directory=<path-to-dir-with-skunkyart> 19WorkingDirectory=/opt/skunkyart
8ExecStart=<path-to-dir-skunkyart> 20ExecStart=/opt/skunkyart/skunkyart -c /opt/skunkyart/config.json
21Restart=on-failure
22RestartSec=5s
23
24DynamicUser=yes
25StateDirectory=skunkyart
26# Point "cache": {"path": "/var/lib/skunkyart"} at the state directory.
27ProtectSystem=strict
28ProtectHome=yes
29PrivateTmp=yes
30NoNewPrivileges=yes
31PrivateDevices=yes
32ProtectKernelTunables=yes
33ProtectControlGroups=yes
34RestrictAddressFamilies=AF_INET AF_INET6
35LockPersonality=yes
9 36
10[Install] 37[Install]
11WantedBy=multi-user.target 38WantedBy=multi-user.target