Commit Graph

352 Commits (ec6827bae2f49f1ddaefaf7a4abb84480e3577f9)

Author SHA1 Message Date
Your Name 327348be02 Send Pixelfed User-Agent on federated account deletion deliveries 2 weeks ago
Your Name a1724a4b1c Scope reclaim-username profile deletion and fail on surviving orphan 2 weeks ago
Daniel Supernault f37c5fc95c
Add notification gc 2 weeks ago
Daniel Supernault 896342a57f
Update MediaMoveStorageLocalToCloud.php 2 weeks ago
Daniel Supernault 764a98437d
Fix media gc 2 weeks ago
Daniel Supernault 85fec3ac82
Create PruneOldNotifications.php 2 weeks ago
Your Name 042ab0a6e4 Convert optional() to nullsafe operator
Applies patch 2/21 from pixelfed-staging PR #9: replaces optional($x)->y
with $x?->y across 16 files. Pint-clean.
3 weeks ago
Your Name 9db2218ca6 chore: move resources/lang to top-level lang/ per Laravel 9+ convention
- Relocate translation files from resources/lang to lang/ via git mv
- Update PHP references to use the lang_path() helper
- Update crowdin.yml source/translation paths
- Update phpstan.neon translationDirectories
3 weeks ago
Your Name b8ca4da3a6 Add per-file transfer output and --debug detail to MediaMoveStorageLocalToCloud 3 weeks ago
Your Name 6b14b229d1 Fix media storage migration crash when no .env file exists
The media storage migration commands read/parsed the .env file directly to
check and flip PF_ENABLE_CLOUD. In containerized deploys there is no .env on
disk (config is injected via env vars), so updateEnvFile() threw
'file_get_contents(.env): Failed to open stream' and the scheduled command
exited 1.

- Check the live setting via config_cache('pixelfed.cloud_storage') like the
  rest of the app, instead of parsing .env.
- Make the .env write best-effort in ManagesMediaStorageEnv: skip gracefully
  when the file is missing or read-only, and still apply the runtime + DB
  config-cache updates (the load-bearing changes on a hot server).
- Apply the same fix to the sibling unstable:MediaMoveStorageCloudToLocal.
- Add a regression test covering the no-.env container scenario.
3 weeks ago
Shlee 8dafe75567
Merge pull request #6992 from pixelfed/feat/status-inspector-commands
Feat/status inspector commands
3 weeks ago
Your Name 9888923a84 Add status:instance, status:avatar, status:emoji inspector commands
Diagnostic commands mirroring status:statuses/status:media:
- status:instance {id|domain|url|@user@domain}: instance row, moderation
  state (banned/unlisted/auto_cw), sync timestamps, local profile count.
- status:avatar {avatar_id|profile_id} [--check]: avatar row, storage state
  (local/cloud existence), owning profile, optional live HEAD on remote_url.
- status:emoji {id|:shortcode:|filename} [--check]: emoji row, origin
  (local/remote), local file existence, optional live HEAD on image_remote_url.

Also includes the status:post -> status:statuses rename.
3 weeks ago
Your Name 237ed61b5d Rename status:post to status:statuses
Rename command signature (status:post -> status:statuses), class
(StatusPost -> StatusStatuses), and file to match.
3 weeks ago
Your Name d62c589881 Rename media:maintenance to media:filtercleanup
Rename the command signature (media:maintenance -> media:filtercleanup), class
(MediaMaintenance -> MediaFilterCleanup), and file to match. Behavior
unchanged.
3 weeks ago
Your Name 4dfb34de75 Drop live from --status on media:maintenance
Orphaned media never references a live status, so --status only accepts soft
and hard. --profile still accepts live/soft/hard. Removes the now-redundant
live short-circuit and makes valid values per-option.
3 weeks ago
Your Name 4ad6e91ef9 Add --status and --profile state filters to media:maintenance
--status live|soft|hard and --profile live|soft|hard narrow orphaned media by
the lifecycle state of the referenced status/profile row. Filters are applied
at the SQL level (whereExists/whereNotExists on deleted_at) so they compose
correctly with --limit. --status=live short-circuits since orphaned media never
has a live status. Options are validated up front.
3 weeks ago
Your Name 838a6b999c Add TODO.md; enhance media:maintenance with --server filter and state annotations
- TODO.md: capture follow-ups (centralized status media teardown, DM leak fix,
  no-DB-cascade rationale, remote-edit orphaning, media:gc re-check).
- media:maintenance: add --server remote|local|both (default both) to filter
  orphaned media by origin.
- Annotate each row with status state (live/soft-deleted/hard-deleted) next to
  status_id and profile state (live/soft-deleted/hard-deleted) next to
  profile_id, in both dry-run table and verbose run output. States are resolved
  in batched, trashed-aware queries.
3 weeks ago
Your Name 48a1fe5e5e Add verbose output to media:maintenance
With -v, print per-row detail (media_id, original status_id, remote_media,
profile_id, mime, size, path) as each orphaned row is processed instead of the
progress bar, and expand the dry-run table with extra columns. Uses Laravel's
built-in verbosity flag.
3 weeks ago
Your Name 87dad44d09 Add media:maintenance command with orphanedMedia scope
media:maintenance --scope orphanedMedia cleans up media whose status_id
references a status that no longer exists (hard-deleted) or is soft-deleted.
These dangling references predate the delete-path fix and MediaDeletePipeline's
attached guard would otherwise refuse to delete them, leaking files.

Detaches (status_id = null) before dispatching deletion via MediaStorageService,
so the guard sees a genuinely orphaned row. Supports --limit (batched),
--dry-run, and --force. The --scope map is extensible for future routines.
3 weeks ago
Your Name 69869536a1 Fix remote status deletion leaking attached media, add status:media command
MediaDeletePipeline skips deletion when media->status_id is set. status_id has
no FK/cascade, so deleting a status never clears it, and the delete jobs
dispatched by the status-delete paths were always skipped, leaking media files.

Detach media (status_id = null) before dispatching the delete in StatusDelete,
RemoteStatusDelete and DeleteRemoteStatusPipeline, so the row is genuinely
orphaned by the time the guard checks it and the deletion proceeds.

Also adds a status:media diagnostic command that dumps all metadata for a
media id (DB columns, computed URLs, attachment state, parent status including
the dangling status_id case, owner, metadata, and an optional live URL check).
3 weeks ago
Your Name 9214e9680a Add admin:resyncemoji command to re-download remote emoji locally
Adds CustomEmojiService::resync() which re-fetches a remote custom emoji's
media from its origin (image_remote_url) via the SSRF-hardened
SecureMediaFetchService and stores it locally under public/{media_path},
reusing the existing headCheck validation and cache busting.

The admin:resyncemoji command takes a comma-separated list of emoji
filenames, looks each up by media_path, and resyncs remote ones. Supports
--missingonly, --dry-run and --force.
3 weeks ago
Your Name fce75030e0 refactor: simplify storage:maintenance flags and make it quiet by default
- Drop --except (--only already covers task selection)
- Quiet by default; per-root/per-item and summary lines now require -v/--verbose
- Errors are always shown regardless of verbosity
- Document the command in the console README
3 weeks ago
Your Name 8ca3ac4dcc polish 3 weeks ago
Your Name 400f00c5e1 feat: storage:maintenance command + in-flow cleanup of emptied dirs
Replace the remcache GC (GarbageCollectorRemcache / gc:remcache) with a
broader storage:maintenance command that sweeps stale remcache temp files and
recursively removes the random empty directories accumulated under the media,
story, avatar and import trees (--hours/--only/--except/--dry-run), scheduled
daily.

Fix the root causes so flows clean up after themselves rather than relying on
the sweep:
- MediaDeletePipeline removes its own emptied m/_v2 leaf dir
- AvatarOptimize logs the previously-swallowed exception, still cleans up the
  old avatar on failure, and removes the old file's now-empty splayed dir
- AvatarController::deleteAvatar removes the emptied splayed dir
- StoryExpire/StoryDelete remove the story's own emptied leaf dir
- TransformImports removes imports/{userId} once its files are moved out
- StoryFetch cleans up its remcache temp file in a finally block
3 weeks ago
Your Name a81270770c refactor: keep MediaMoveStorageLocalToCloud as a stable admin command
MediaMoveStorageLocalToCloud is stable, so move it back out of the Unstable
namespace: restore App\Console\Commands\Admin\MediaMoveStorageLocalToCloud and
its admin:MediaMoveStorageLocalToCloud signature, and update the scheduler,
README, and feature test. CloudToLocal and CloudToCloud remain under unstable:.
3 weeks ago
Your Name 41c9b8830f refactor: rename MigrateLocalS3MediaURL class and move media move-storage commands to unstable
- Rename App\Console\Commands\Admin\MigrateLocalS3MediaURL to MediaUpdateS3CDNUrl
  (class + filename only; the admin:MigrateLocalS3MediaURL signature is unchanged)
- Move the three MediaMoveStorage{LocalToCloud,CloudToLocal,CloudToCloud} commands
  into App\Console\Commands\Admin\Unstable and change their signatures from
  admin: to unstable:
- Update the scheduler in bootstrap/app.php, the command README, the
  config/filesystems.php reference comment, and the affected feature tests
3 weeks ago
Your Name 40b323bca4 revert: remove emoji local-to-cloud storage changes
Back out all emoji cloud-storage work from staging so it can be reworked and
re-landed separately (the URL resolution flips to cloud on a global config
flag, which created a broken-URL window, and the migration approach needs
revisiting).

Reverts to pre-emoji state:
- CustomEmoji model URL/storage helpers (urlForPath, storageTarget, storeMedia,
  storeMediaFromFile, deleteMedia, url) and callers in ImportEmojis,
  CustomEmojiService, AdminController
- admin custom-emoji blade views back to local /storage URLs
- Remove admin:EmojiMoveStorageLocalToCloud command
- Remove the deploy migration and its scheduler entry

Media (and the already-reverted story) scheduler entries are untouched.
4 weeks ago
Shlee 948da00f0c
Update EmojiMoveStorageLocalToCloud.php 4 weeks ago
Shlee ef8cf16cfa
Merge pull request #6968 from pixelfed/debug/emoji-cloud-migration
perf: async S3 SDK upload path for emoji migration
4 weeks ago
Your Name a0a262f07f perf: async S3 SDK upload path for emoji migration
Process-level workers plateaued at ~7.5 uploads/sec against Fastly Object
Storage because each PUT is high-latency and only a handful ran concurrently.

Add --concurrency=N which uses the AWS SDK CommandPool to keep N PutObject
requests in flight from a single process. A successful PutObject response is
the confirmation (no separate HEAD verify), and the local file is deleted on
success. Commands are yielded lazily so memory stays flat over large runs.

--no-acl escape hatch for S3-compatible stores that reject the ACL header.
4 weeks ago
Shlee 2c0fe5db2f
Merge pull request #6967 from pixelfed/debug/emoji-cloud-migration
feat: add uploads/sec throughput counter to emoji migration
4 weeks ago
Your Name f17a88e16c feat: add uploads/sec throughput counter to emoji migration
- Live 'up/s' rate shown on the progress bar during single-worker runs
- Final summary reports elapsed time and uploads/sec
- Parallel runs tally moved across workers and report aggregate uploads/sec

Makes it easy to compare --workers counts and decide whether async S3
(option 2) is worth pursuing.
4 weeks ago
Shlee 67ae799cb4
Merge pull request #6966 from pixelfed/debug/emoji-cloud-migration
perf: parallelise emoji cloud migration with worker processes
4 weeks ago
Your Name 00564ac221 perf: parallelise emoji cloud migration with worker processes
The migration was ~1-2s/file due to sequential S3 round-trips (HEAD + PUT +
verify HEAD). Speed it up:

- --workers=N spawns N child processes, each handling a strided slice of the
  files (index % N == shard) for real concurrency on the I/O-bound uploads
- --skip-cloud-check skips the upfront HEAD (always upload, idempotent)
- --skip-verify skips the post-upload size re-check
- --offset for manual chunking

Storage/Flysystem has no batch or async upload API, so process-level
concurrency is the pragmatic lever here.
4 weeks ago
Shlee b6e8571009
Merge pull request #6965 from pixelfed/debug/emoji-cloud-migration
fix: skip missing.png in emoji cloud migration
4 weeks ago
Your Name 9e5fcb9a90 fix: skip missing.png in emoji cloud migration
The frontend renders a hardcoded /storage/emoji/missing.png local onerror
fallback for emoji, so that placeholder must stay on local disk. Skip it in
the migration so it is never moved to cloud or deleted locally.
4 weeks ago
Shlee e79d5c828b
Merge pull request #6964 from pixelfed/debug/emoji-cloud-migration
fix: make emoji cloud migration disk-driven + add --debug
4 weeks ago
Your Name 79eef56be0 fix: make emoji cloud migration disk-driven + add --debug
The migration was DB-driven (whereNull('uri')), which excluded federated
emoji whose media is stored locally but have a uri set -> the disk was never
scanned, resulting in moved=0.

- Drive the migration by enumerating local files under public/emoji/ instead
  of a DB query; the local file is the source of truth for what needs moving
- Add --debug to print config, custom_emoji table breakdown, local emoji dir
  contents, and per-file decisions
4 weeks ago
Your Name 9f55f7204c revert: story cloud-migration work
Back out the story local->cloud migration so we can land and verify the
emoji cloud work first, one change at a time.

Reverts:
- 9f110bb74 feat: migrate local story media to cloud storage
- 842681b99 fix: schedule StoryMoveStorageLocalToCloud command

Removes StoryMoveStorageLocalToCloud command, its scheduler entry, and the
StoryExpire explicit-disk changes. Remcache and emoji work are untouched.
Will revisit story once emoji is confirmed.
4 weeks ago
Your Name aa9bb868dd fix: emoji admin URLs and cloud-migration guard
Two issues prevented emoji from serving/migrating correctly on cloud:

- Admin custom-emoji views hardcoded url('storage/'.media_path), so they
  always showed local URLs and bypassed cloud resolution. Use $emoji->url().
- The migration/command guard relied solely on config_cache('pixelfed.cloud_storage'),
  which is DB/12h-cached and can read stale-false right after cloud is
  enabled, causing the migration to silently no-op. Treat cloud as enabled
  when either live config() or config_cache() is true.
4 weeks ago
Your Name 979df6e39e feat: migrate all local emoji to cloud in one pass by default
Change --limit default to 0 (no limit) so the emoji migration processes
every local emoji in a single run instead of capping at 1000, and drop the
limit from the scheduled invocation. Avoids a multi-run window where
not-yet-migrated emoji resolve to missing cloud URLs.
4 weeks ago
Your Name fa76e1014a feat: store custom emoji on cloud storage when enabled
Custom emoji were always written locally and served via hardcoded /storage
URLs, so they never used S3 even on cloud instances.

- CustomEmoji: centralize URL + storage on the active disk (cloud when
  pixelfed.cloud_storage is enabled, else local public/ disk) via
  urlForPath/url/storageTarget/storeMedia/storeMediaFromFile/deleteMedia
- Route emoji writes/deletes and URL generation (scan, CustomEmojiService::all)
  through those helpers in ImportEmojis, CustomEmojiService::import and
  AdminController
- Add admin:EmojiMoveStorageLocalToCloud to migrate existing local emoji to
  cloud: copy, verify by size, delete local, bust caches
- Schedule it daily when cloud storage is enabled
4 weeks ago
Your Name 9f110bb74c feat: migrate local story media to cloud storage
Ensure story media lands on and stays on cloud storage for S3 instances.

- StoryExpire: archive expiring story media on the same explicit disk the
  media lives on (S3 move is a server-side copy+delete), with error handling
- Add admin:StoryMoveStorageLocalToCloud to migrate local story media
  (active + story_archives) to cloud: copy, verify by size, then delete local
- --orphans option relocates untracked story_archives/ files to cloud using
  the same copy/verify/delete flow (media is moved, never discarded)
- Schedule it hourly alongside the media migration when cloud storage is on
4 weeks ago
Your Name 9704fd5a36 refactor: use GarbageCollector prefix for GC console command classes
Rename the internal garbage collector commands to a consistent
GarbageCollector* naming scheme (file + class). Command signatures are
unchanged, so the scheduler and cron entries are unaffected.

- MediaGarbageCollector          -> GarbageCollectorMedia
- DatabaseSessionGarbageCollector -> GarbageCollectorDatabaseSession
- FailedJobGC                    -> GarbageCollectorFailedJob
- PasswordResetGC                -> GarbageCollectorPasswordReset
- GCRemcache                     -> GarbageCollectorRemcache
- StoryGC                        -> GarbageCollectorStory
- ImportUploadGarbageCollection  -> GarbageCollectorImportUpload
4 weeks ago
Your Name 57b3bf140b refactor: rename RemcacheGarbageCollector to GCRemcache 4 weeks ago
Your Name 3232761a74 fix: prevent remcache temp file leaks and add GC command
The remote avatar/media fetchers wrote temp files to storage/app/remcache/
and only unlinked them on the happy path. Any exception between the write
and the unlink (e.g. a cloud upload failure) leaked the file, and nothing
swept the directory.

- Wrap post-write logic in fetchAvatar() and remoteToCloud() in try/finally
  so the temp file is always removed, even on failure
- Add gc:remcache command to delete stale remcache files (default >24h old,
  preserves .gitignore, supports --hours and --dry-run)
- Schedule gc:remcache daily to clean up any stragglers

StoryFetch already handled cleanup via try/catch and was left unchanged.
4 weeks ago
Your Name 878775cab9 chore: resolve psalm issues in admin commands and auth
- Add return type hints (void) and final class markers
- Guard null returns from newestBackup() and putFileAs() in BackupToCloud
- Type ask() default values as strings
- Fix uses_left fallback condition for null/zero max_uses
- Annotate AdminInvite::whereInviteCode and cast Str::uuid() to string
- Ignore local redis-data and mysql-9-data dev directories
4 weeks ago
Your Name de850836ca fix: make admin:fixPostCounts summary report only changed metrics
The resynced summary printed all three counts unconditionally, which
made an untouched metric (e.g. an already-correct comments count) look
like it had been resynced. Drive the summary from the drifted set and
show before->after values, so it matches the drift detection exactly.
4 weeks ago
Your Name d50024a578 fix: display comments count as 0 instead of blank in admin:fixPostCounts
reply_count is a nullable column, so NULL rendered as an empty string in
the resync summary. Cast the summary output to int so a null/absent
comment count prints as 0. No behavior change to the reconcile logic.
4 weeks ago
Your Name 73b8353dab refactor: move admin:fix*Counts commands to Admin/
FixProfileCounts and FixPostCounts use the admin: signature prefix and
are operator-run maintenance tools, so move them from FixBugs/ to Admin/
(namespace updated) and refresh the README tables to match.
4 weeks ago