summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xSETUP.md5
-rwxr-xr-xapp/config.go29
-rwxr-xr-xcompose.example.yaml5
-rw-r--r--compose.vpn_example.yml4
4 files changed, 39 insertions, 4 deletions
diff --git a/SETUP.md b/SETUP.md
index 4cb8aa8..4e7dd93 100755
--- a/SETUP.md
+++ b/SETUP.md
@@ -12,7 +12,10 @@ Time units:
* `uri` — Instance URI. Example: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/
* `cache` — Caching system; default is off.
* `enabled` — Caching system state, requires boolean value
- * `path` — Path to cache directory, requires absolute filesystem path
+ * `path` — Path to cache directory. It must be writable by the user SkunkyArt
+ runs as, and SkunkyArt refuses to start if it is not. The container image
+ runs as uid 10000, so a bind-mounted cache needs
+ `sudo chown -R 10000:10000 <dir>` on the host.
* `lifetime` — Cached file life time, requires numeric value, followed by multiplicative suffix (see Time Units for details)
* `max-size` — Maximum file size in megabytes
* `update-interval` — Automatic rotation interval
diff --git a/app/config.go b/app/config.go
index 3b85527..ad07aa3 100755
--- a/app/config.go
+++ b/app/config.go
@@ -58,9 +58,28 @@ var CFG = config{
var lifetimeParsed int64
+// checkCacheWritable creates the cache directory if it is missing and confirms
+// this process can actually write into it, returning the error that a real cache
+// write would hit.
+//
+// An unwritable cache directory is otherwise a silent cliff: every media request
+// still succeeds by re-downloading from the CDN, so the only symptom is one
+// "permission denied" line per request and a cache that never fills.
+func checkCacheWritable(path string) error {
+ if err := os.MkdirAll(path, 0700); err != nil {
+ return err
+ }
+ probe := path + "/.skunkyart-write-probe"
+ if err := os.WriteFile(probe, nil, 0600); err != nil {
+ return err
+ }
+ return os.Remove(probe)
+}
+
// ExecuteConfig loads the config file into CFG, validates it, and starts the
// cache rotation loop if caching is on. It exits the process on a config that
-// cannot be read or that asks for caching without proxying.
+// cannot be read, that asks for caching without proxying, or that points caching
+// at a directory this process cannot write.
func ExecuteConfig() {
if CFG.cfg != "" {
f, err := os.ReadFile(CFG.cfg)
@@ -71,6 +90,14 @@ func ExecuteConfig() {
}
if CFG.Cache.Enabled {
+ if err := checkCacheWritable(CFG.Cache.Path); err != nil {
+ exit("Cache directory is not writable by this process (uid "+
+ strconv.Itoa(os.Getuid())+"): "+err.Error()+
+ "\nGrant that uid write access to the directory, or set cache.enabled to false."+
+ "\nThe official container image runs as uid 10000, so a bind-mounted cache needs:"+
+ "\n chown -R 10000:10000 <cache dir on the host>", 1)
+ }
+
if CFG.Cache.Lifetime != "" {
var duration int64
day := 24 * time.Hour.Milliseconds()
diff --git a/compose.example.yaml b/compose.example.yaml
index 2e2f088..d7c20ea 100755
--- a/compose.example.yaml
+++ b/compose.example.yaml
@@ -14,4 +14,7 @@ services:
- no-new-privileges:true
volumes:
- ./config.json:/config.json:ro
- - ./cache:/cache # Ensure cache folder has a 10000:10000 ownership.
+ # The image runs as uid 10000, so the host cache dir must be writable by it:
+ # mkdir -p cache && sudo chown -R 10000:10000 cache
+ # Without this the container exits at startup telling you the same thing.
+ - ./cache:/cache
diff --git a/compose.vpn_example.yml b/compose.vpn_example.yml
index 27cd488..a89262c 100644
--- a/compose.vpn_example.yml
+++ b/compose.vpn_example.yml
@@ -59,7 +59,9 @@ services:
- no-new-privileges:true
volumes:
- ./config.json:/config.json:ro
- - ./cache:/cache # ensure this dir is owned 10000:10000
+ # The image runs as uid 10000, so the host cache dir must be writable by it:
+ # mkdir -p cache && sudo chown -R 10000:10000 cache
+ - ./cache:/cache
environment:
# Empty by default = direct. Set SKUNKY_PROXY in .env to route via the VPN.
- HTTPS_PROXY=${SKUNKY_PROXY:-}