Commit Graph

4437 Commits (400f00c5e13c0e8b7fdbf22d2a506cb5adbc717a)

Author SHA1 Message Date
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
4 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:.
4 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
4 weeks ago
Your Name b52c3d7659 perf: fix N+1 queries; fix ComposeController lint and test namespace
Performance:
- TrendingHashtagService: batch-load hashtags with whereIn/keyBy instead
  of Hashtag::find() per trending row.
- DirectMessageController: eager-load status.media and read the in-memory
  collection instead of firstMedia() issuing a query per DM message.
- GroupsSearchController: batch Profile/Follower/GroupInvitation lookups
  with whereIn instead of per-invitee queries.

Lint/tests:
- ComposeController: whitespace formatting (Pint).
- ComposeControllerTest: correct App\User to App\Models\User, fixing the
  larastan class.notFound error and import ordering.

Full suite: 547 passed. Pint and PHPStan clean.
4 weeks ago
Shlee d9817840c1
Merge pull request #6955 from hohoho1886/location-country-search
Add country filtering to location search
4 weeks ago
Your Name 3c6ba88e6e chore: modernize service providers for Laravel 13 readiness
Remove deprecated Foundation\Support\Providers\AuthServiceProvider and
EventServiceProvider base classes. Move policy and event listener
registrations into AppServiceProvider using Gate::policy() and
Event::listen(). Behavior is unchanged (verified via event:list and
auth test suite).
4 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
Nguyen Ninh Dao 129778ef2e implement search by country 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 87ed60d675 Accept compacted Note attachments (#6588)
Normalize JSON-LD compacted single attachments (a bare object instead of a
one-item array) in getAttachments(), and route verifyAttachments() through it
so validation and import share one normalization path.

Includes PR #6589's tests plus additional edge-case coverage: list-form
preservation, bare-input normalization, and guards for missing/empty/scalar
attachments.
4 weeks ago
Your Name d86fd28e34 polish 4 weeks ago
Shlee 79af98f9d0
Merge pull request #6667 from vinzgreg/fix/blurhash-memory-strands-video-uploads
Fix videos never reaching cloud storage by downscaling in Blurhash (#2652)
4 weeks ago
Shlee 186fa7c860
Update VideoThumbnail.php 4 weeks ago
Shlee a82dc91295
Merge branch 'staging' into dev 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
Your Name 744e453606 feat: add admin:fixPostCounts to resync post like/boost/comment counts
Add a FixPostCounts command mirroring admin:fixProfileCounts (single-id,
--all --scope, --active, --type, --dry-run, --force). It reconciles the
statuses likes_count, reblogs_count, and reply_count columns against
source-of-truth tables.

Add canonical recompute helpers and reconcileStatusCounts() to
StatusService (mirroring AccountStatService), busting the status cache
only when a column actually drifted.
4 weeks ago
Shlee 4699c2fe91
Merge branch 'staging' into refactor/artisan-command-subfolders 1 month ago
Your Name cd353a8305 refactor: move resolved one-off migrations to Deprecated/
status:dedup and fix:avatars address historical data states that can no
longer occur (unique statuses.uri index since 2019; SVG identicon avatars
no longer generated). Move both to a Deprecated/ folder and update the
README audit accordingly. media:fix stays in FixBugs/ since image filters
are still an active feature.
1 month ago
Shlee cafec250fe
Update README.md 1 month ago
Your Name c99b8068a2 docs: add README for Artisan commands with listing and audit 1 month ago
Your Name 1eae4bbd43 refactor: organize Artisan commands into subfolders
Group console commands into Admin, Dev, FixBugs, Install, Internal, and
User subfolders (matching the earlier reorganization), and add a new
Status subfolder for the status:user, status:profile, and status:post
debug commands. Namespaces updated to match; command signatures and the
total command count are unchanged.
1 month ago
Your Name 16c7c5d2e3 refactor: rename status debug commands to status: prefix
Rename user:status, profile:status, and post:status console commands
to status:user, status:profile, and status:post. Rename the command
files and classes to match (StatusUser, StatusProfile, StatusPost) and
update the cross-reference tip in StatusProfile.
1 month ago
Your Name 0d01d5a963 Fix duplicate-key violation when importing remote media attachments
Helpers::importNoteAttachment unconditionally inserted a new Media row per
attachment, so re-importing a remote status (an Announce racing another
inbox job, a re-fetch, or a duplicate url within one activity) hit the
media_status_id_media_path_unique constraint and crashed the queue job with
a 1062 UniqueConstraintViolationException, dropping the boost/import.

Make createMediaAttachment idempotent on (status_id, media_path): skip when
a row already exists, and catch the unique-constraint violation as a
lost-race no-op, returning null so the caller skips re-dispatching storage.

Adds regression tests (re-import no-op, distinct urls still stored,
concurrent-insert returns null).
1 month ago
Shlee f017e03286
Merge pull request #6932 from pixelfed/feature/media-url-migrate
Feature/media url migrate
1 month ago
Your Name 70b4a05b5c Add admin:MediaMoveStorageCloudToCloud for cold S3->S3 migration
Cold-migrate existing media from an old S3 bucket to the current cloud
bucket, one media row at a time (like MigrateLocalS3MediaURL):
- Source = --sourceDisk (default s3-old, reads AWS_OLD_*); destination = the
  current cloud disk (config filesystems.cloud). No .env editing: operators
  point AWS_* at the new bucket first (restarting workers as usual) so new
  uploads/downloads land on the new bucket, then run this to backfill old data.
- Copies media (+thumbnail) source->destination, verifies by size and by
  sha256 of the freshly-written destination object (against original_sha256),
  rewrites cdn_url/optimized_url/thumbnail_url to the destination host, and
  GCs the source objects (unless --keep-source). Busts caches.
- Only touches rows whose cdn_url still points at the source host; idempotent.
- --sourceDisk / --limit / --dry-run / --force.
- Adds the s3-old disk (AWS_OLD_*) to config/filesystems.php and feature tests.
1 month ago
Your Name 6ff9ffbbb8 Add media storage migration commands (local<->cloud) with integrated GC
Add admin:MediaMoveStorageLocalToCloud and admin:MediaMoveStorageCloudToLocal:
- Copy media (+thumbnail) between local and cloud disks, verify by size (and
  sha256 against original_sha256 when present) before deleting the source.
- Integrated GC: delete the verified source copy (local on upload, cloud on
  download), set version=4 / reset to 3, and bust MediaService/StatusService
  caches. --keep-local / --keep-cloud opt out.
- Manage PF_ENABLE_CLOUD in .env AND the live runtime + config cache so new
  uploads route to the correct backend mid-migration on a hot server. Uses the
  installer's atomic .env writer (shared ManagesMediaStorageEnv trait).
- --limit / --dry-run / --force.

Replaces media:migrate2cloud (CloudMediaMigrate) and media:s3gc
(MediaS3GarbageCollector); scheduler now runs MediaMoveStorageLocalToCloud
hourly for straggler upload + GC. Keeps media:fix-nonlocal-driver.

Adds feature tests (download+GC, --keep-cloud, dry-run, env-flag flip both
directions, unknown-disk guard).
1 month ago
Shlee 137bc91b41
Merge pull request #6922 from pixelfed/security/ssrf-media-fetch
Fix media fetch
1 month ago
Your Name da9e73dd22 Rename to admin:MigrateLocalS3MediaURL and drop --avatars
Rename the command (and test) to admin:MigrateLocalS3MediaURL to reflect its
scope: rewriting stale S3/cloud media URLs only. Remove avatar handling and
the --avatars option; the command now focuses solely on status media
(cdn_url, thumbnail_url, optimized_url).
1 month ago
Your Name 04536a6e32 Add admin:MigrateLocalMediaURL; replace media:cloud-url-rewrite
Rebuilds stale local media URLs (cdn_url, thumbnail_url, optimized_url) and
avatar cdn_urls from their storage paths via the configured cloud disk.

- Default target host comes from the configured cloud disk (AWS_URL);
  requires confirmation (or --force) and can be overridden with --newDomain.
- Optional --oldDomain filters to a single old backend host; by default all
  stale hosts are rewritten.
- Refuses to run when PF_ENABLE_CLOUD is false (local storage) and, when
  auto-detecting, refuses a target equal to the app domain — so local-storage
  instances are never rewritten.
- Single status id / post URL, --all, --avatars; --dry-run; busts
  MediaService/StatusService caches for affected statuses.
- Removes the superseded media:cloud-url-rewrite command.
- Adds feature tests covering rewrite/skip/dry-run/oldDomain/newDomain/
  remote-skip/local-storage-refusal.
1 month ago