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.
77
88* what
99skunkyart. alternative frontend for deviantart. works with no javascript.
10one instance, one config file, no database.
1011
1112instances: [[file:INSTANCES.md][INSTANCES.md]]
1213
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
1333* build
1434build with the embed tag to embed presets in the binary. skip the tag if you'll
1535modify templates. add ~-ldflags "-w -s"~ (~gccgo: gccgoflags~) to shrink the output:
@@ -21,6 +41,19 @@ go build -tags embed -ldflags "-w -s"
2141that build reports its version as dev. stamp one in with ~-X
2242main.version=<version>~, as the release workflow does from the git tag.
2343
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
2457* docker
2558multi-arch images (~linux/amd64~, ~linux/arm64~) publish to ghcr on every release
2659tag:
@@ -37,6 +70,14 @@ reproducible upgrades. [[file:compose.example.yaml][compose.example.yaml]] uses
3770sample config in [[file:config.example.json][config.example.json]]. custom config with ~--config~. directive
3871details in [[file:SETUP.md][SETUP.md]].
3972
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
4081* instances
41add yours by pr to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use ~--add-instance~ to
42write both), or open an issue.
82add yours by merge request to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use
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.
212
313[Unit]
4Description=Privacy-oriented frontend for DeviantArt
14Description=SkunkyArt, an alternative frontend for DeviantArt
15After=network-online.target
16Wants=network-online.target
517
618[Service]
7Directory=<path-to-dir-with-skunkyart>
8ExecStart=<path-to-dir-skunkyart>
19WorkingDirectory=/opt/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
936
1037[Install]
1138WantedBy=multi-user.target