| Commit message (Collapse) | Author | Age | Files | Lines |
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2 to 4.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v2...v4)
---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
dependency-version: '4'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [github.com/zerolabsco/devianter](https://github.com/zerolabsco/devianter) from 0.3.1 to 0.3.3.
- [Release notes](https://github.com/zerolabsco/devianter/releases)
- [Commits](https://github.com/zerolabsco/devianter/compare/v0.3.1...v0.3.3)
---
updated-dependencies:
- dependency-name: github.com/zerolabsco/devianter
dependency-version: 0.3.3
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* fix: make memcache concurrency-safe and stamp the version at link time
memcache was a crash waiting for traffic. Readers touched tempFS without
holding mx, while a per-entry goroutine deleted from it under the lock: a
concurrent map read and map write, which the runtime treats as a fatal error
that recover cannot catch. The option ships in config.example.json and was the
one cache key SETUP.md never documented, so it read like a free win to enable.
Put every map and field access behind the mutex, and age the whole map from one
janitor instead of a goroutine per cached file, each of which looped forever
holding its entry alive. mx is now a plain Mutex: every operation here mutates
something, and the old code took an RLock to write. Document the option, and
cover it with tests that run the readers, writers and janitor concurrently.
Split the disk/origin fetch out of DownloadAndSendMedia while there, so the
error path returns instead of falling through to write an empty body after the
error page.
Release.Version was hardcoded to 1.3.2, so images tagged v1.3.6 reported 1.3.2
from --help and /api/instance, and --help linked to the wrong release. Take it
from a main.version string the release workflow links in from the git tag.
* fix: reject forged subdomains in the media proxy
DownloadAndSendMedia built its upstream URL by concatenation, pasting the
subdomain segment of the request path straight into the host position. That
segment reaches the handler already percent-decoded, so it can carry "@", "#",
"?" and "/" — the characters that end a host. A request for
/media/file/[email protected]:8080%2F/f/x.jpg
built a URL whose host parsed as 127.0.0.1:8080, with images-wixmp-x demoted to
userinfo, letting any caller aim the instance's fetcher at any address it could
reach, including services behind the firewall.
Validate the label against ^[a-zA-Z0-9-]+$ and refuse anything else with a 400.
Rejecting rather than escaping is what closes this: the label is the host, and
url.URL passes a host through verbatim, so building the URL structurally is not
sufficient on its own. DeviantArt's own media URLs use a hex-and-dash label, and
ParseMedia already splits on the first dot, so a legitimate label cannot contain
one.
Build the URL from url.URL fields as well, which escapes the path, and encode
the token argument, which reached the request unescaped.
Reported by CodeQL as go/request-forgery (CWE-918).
|
| |
|
|
|
|
|
|
|
|
|
| |
An unwritable cache directory degraded silently: media still served, because
the download succeeds before the cache write is attempted, so the only symptom
was one "permission denied" line per request and a cache that never filled.
Every request re-fetched from the CDN.
Probe the directory at startup and exit with the uid and the chown that fixes
it. The container image runs as uid 10000, which is the usual cause with a
bind-mounted cache, so say so in the message and in both compose examples.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)
---
updated-dependencies:
- dependency-name: docker/metadata-action
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)
---
updated-dependencies:
- dependency-name: docker/login-action
dependency-version: '4'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)
---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
dependency-version: '4'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)
---
updated-dependencies:
- dependency-name: docker/build-push-action
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| |
|
|
|
|
| |
The viewport directives were separated with a semicolon, which browsers reject:
"';' is not a valid key-value pair separator". Drop the duplicated trailing
user-scalable directive that the semicolon introduced.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two independent faults made every image fail while pages still rendered.
config.example.json shipped download-proxy=http://127.0.0.1:8080. Only media
fetches go through that proxy — pages reach DeviantArt via devianter on the
default transport — so when nothing listens there, images 502 and the rest of
the page looks fine. In a scratch container 127.0.0.1 is the container itself,
so the default could never work under Docker. Unset it and document that it must
stay empty unless an operator really runs a proxy.
Host was a package global reassigned by every request, so a concurrent request
could overwrite it mid-render and emit URLs on another origin's host and port.
The instance's own default-src 'self' CSP then blocked those images. Thread the
request's host through skunkyart instead, and take it as an explicit argument in
URLBuilder, ParseMedia, ParseDescription, BuildUserPlate and
ConvertDeviantArtURLToSkunkyArt. Feeds keep their absolute URLs.
Also start RefreshInstances after ExecuteConfig rather than before it: the
goroutine read CFG while json.Unmarshal was writing it (a race the detector
flags), and its fetch escaped both the throttle and the configured User-Agent.
Verified: 300 concurrent requests with distinct Host headers now round-trip
their own host (was 1 leak per 300), go test -race is clean, and
cache+proxy both enabled serves 200 image/jpeg cold and from cache.
|
| |
|
|
|
|
|
|
|
|
|
| |
Build and push linux/amd64 + linux/arm64 images to
ghcr.io/zerolabsco/skunky-art on every v* tag, with a signed provenance
attestation. The Dockerfile cross-compiles from $BUILDPLATFORM, so the
arm64 image builds without QEMU emulation.
Default both compose examples to the published image and keep `build: .`
commented out for building from a checkout, and extend dependabot to the
github-actions and docker ecosystems.
|
| |
|
|
|
|
|
|
|
|
| |
The published entry for zerolabs.sh claimed proxifying was off while the
instance runs with proxy enabled, so /about advertised the opposite of
what the instance does. The instance list is fetched from main at runtime,
so this corrects the live about page without a rebuild.
Also point the instances.json link at skunky-art/main, which is what the
app actually fetches, rather than the old SkunkyArt/dev path.
|
| |
|
|
|
|
|
|
|
| |
go.mod requires go 1.25.0, but the Dockerfile still pinned the builder to
1.18, which cannot parse a three-part version string and failed with
"invalid go version '1.25.0': must match format 1.23".
Track the 1.25 tag rather than a patch release so the builder does not
drift out of date again.
|
| |
|
|
|
|
|
|
|
| |
The copyright on /about and in the CLI help credited only lost+skunk and
linked to their matrix account. Credit this fork alongside the original,
as the X11 license requires retaining the upstream notice, and point the
attribution at the upstream repo rather than the stale matrix link.
Replace the upstream matrix room link with this fork's issue tracker.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The remaining golangci-lint findings. These land together because the
Url -> URL rename spans util.go, parsers.go and wrapper.go, and splitting
it would leave an intermediate commit that does not compile.
Download() carried the most serious bug here: try() only prints an error,
it does not return, so a failed request fell through to
resp.Body.Close() on a nil resp and panicked. Every failure path now
returns the zero Downloaded, and callers check Status. ReturnHTTPError
guards against the resulting status 0, which would otherwise panic
WriteHeader. Requests carry a context with a timeout (noctx), and a
download-proxy now routes through ProxiedTransport so it keeps the DA
throttle and timeouts.
Also:
- doc comments on all 48 exported symbols (revive's exported rule, with
checkPrivateReceivers, since most of app is exported methods on the
unexported skunkyart type), plus package docs in new doc.go files so
both the embed and non-embed builds are covered.
- ST1003 naming: UrlBuilder -> URLBuilder, id_search -> idSearch,
cache_config -> cacheConfig, TXT_RAW -> TxtRaw, mediaUrl -> mediaURL.
- explicit json tags on structs that are unmarshaled (musttag); the
hyphenated keys already had tags, the rest relied on case-insensitive
fallback. Behaviour is unchanged.
- modernization: range-over-int, WaitGroup.Go, stale +build lines,
interface{} -> any, strings.Builder over string concatenation in a loop.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Correctness and security findings surfaced by golangci-lint, plus two
latent panics found alongside them.
- router: http.ListenAndServe has no timeouts at all (gosec G114), so a
slow client could hold a connection and its handler open indefinitely.
Replace it with an explicit http.Server carrying read/write/idle
timeouts.
- httpclient: InstallDAThrottle asserted http.DefaultTransport was a
*http.Transport and would panic outright if anything had already
wrapped it -- which is precisely what that function does. Check the
assertion and fall back to a fresh transport. Expose ProxiedTransport
so a configured download-proxy can inherit the same throttle and
timeouts instead of silently bypassing them.
- cache: the Sys() assertion to *syscall.Stat_t is only valid on unix and
would panic elsewhere; skip rotation instead. Indexing
Headers["Content-Type"][0] panics when the header is absent; use
Headers.Get. Cache files are written 0600 rather than 0700, as they are
never executed.
- cli, api: check error returns, and exit rather than nil-dereference a
file handle that failed to open.
SHA-1 and math/rand keep //nolint:gosec with reasons: they are cache-key
hashes and random-artwork picks, not security primitives.
|
| |
|
|
|
|
|
|
|
|
| |
max-size is documented in SETUP.md as megabytes, but the conversion was
`*= 1024 ^ 2`. In Go `^` is XOR, not exponentiation, so this multiplied
by 1026 rather than 1048576 -- a configured max-size of 200 produced a
~205 KB cap instead of 200 MB, wiping the cache almost immediately.
This changes runtime behaviour: the cache will now grow to the size the
config actually asks for.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Enables the standard linters plus revive's exported rule, a set of
correctness/security linters (bodyclose, noctx, gosec, forcetypeassert,
canonicalheader, musttag, predeclared, exhaustive), modernization
linters, and staticcheck's full check set including the doc-comment and
naming checks that are off by default.
The idiom-fighting linters stay off: varnamelen objects to short names
that are idiomatic Go, exhaustruct demands every field be initialized,
gochecknoglobals flags CFG/Host which are deliberate, and tagliatelle
would rename JSON config keys and break existing instances' configs.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.27.0 to 0.57.0.
- [Commits](https://github.com/golang/net/compare/v0.27.0...v0.57.0)
---
updated-dependencies:
- dependency-name: golang.org/x/net
dependency-version: 0.57.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Upstream references still pointed at git.macaw.me, which is unmaintained
and whose instances.json is a dead link. Repoint them at this repo:
- RefreshInstances now fetches instances.json from raw.githubusercontent
- source/version links in index.htm, about.htm, and CLI help use the
GitHub releases tag URL
- SETUP.md/SETUP-RU.md language switch links were Gitea-absolute paths
that 404 on GitHub; make them relative
- --add-instance message says 'main' branch, matching this repo
The fork attribution in README.md is left as-is: it credits upstream
rather than pointing at the source.
|
| |\
| |
| |
| |
| |
| | |
Migrate to github.com/zerolabsco/devianter v0.3.1 and adapt to its
reordered return values, throttle outbound DeviantArt requests, harden
the random-art retry loop, and fix the darwin build.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
CloudFront/WAF blocks some egress IPs on the /_puppy path, making every
DA-backed page fail while Go tries to unmarshal an HTML 403 page.
Routing outbound through a non-blocked exit fixes it with no code change,
since devianter's client honors HTTPS_PROXY.
Adds a compose stack with an optional gluetun sidecar behind the "vpn"
profile (off by default, so the stock direct setup is unchanged) and a
matching .env.example. Ignore .env so real credentials stay out of git.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
DeviantArt fronts its API with CloudFront + WAF, which bans egress IPs
that hit it too hard. devianter issues requests with a bare http.Client,
so unbounded concurrent handlers each pulled ~150-200 KB of JSON, which
both risked a ban and could exhaust the process under a bot flood.
Wrap the default transport to bound rate and concurrency for
deviantart.com and add timeouts. Other hosts (wixmp image CDN) pass
straight through, so media stays fast, and ProxyFromEnvironment is
preserved so HTTPS_PROXY egress still works.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Three bugs in Random():
- The retry loop was unbounded. Only the NSFW path incremented attempt,
so a run of DeviantArt errors span forever, hammering the API and
risking an egress-IP ban.
- string(rand.Intn(999)) converts a rune, not a number: string(65) is
"A", not "65". Searches were querying garbage. Use strconv.Itoa.
- rand.Intn panics on 0, so an empty result set crashed the handler.
Skip empty results.
The exhausted-retries error now fires when the loop ends rather than
falling through to index an empty slice.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Repoint the import path from git.macaw.me/skunky/devianter, replacing a
placeholder v0.0.0 require that had no go.sum entry and did not resolve.
v0.3.1 reorders the last two return values of PerformSearch, Group.Get
and Group.Gallery from (..., error, Error) to (..., Error, error);
adapt the call sites. The two types differ, so the compiler enforces
this.
|
| | |
| |
| |
| |
| |
| | |
macOS names the stat ctime field Ctimespec rather than Ctim, so stat.go
failed to compile on a Mac. Split the darwin case into its own file and
exclude darwin from the generic variant. Deploys are unaffected.
|
| |/ |
|
| |
|
|
|
|
|
| |
* hard wrap LICENSE for readability
* translate TODO.md from Russian to English
* clean up TODO.md into a proper GFM todo list
* fix: remove dead service links and add working links
* update README to reflect new repository and structure
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
| |
улучшения ксс, фикс нсфв, фикс максимального размера кеша
|
| | |
|
| | |
|
| | |
|
| |\
| |
| |
| | |
Reviewed-on: https://git.macaw.me/skunky/SkunkyArt/pulls/6
|
| |/ |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|