Better Files Manager
Better Files Manager V-A.1.0.0 installs in two layers. The panel package provides the interface and panel-backed NBT and database previews. Patched Wings adds native collaboration, streamed search and media, resumable uploads, archive tools, file history, bulk operations, and Git endpoints.
Back up the panel and commit or copy a modified Wings source tree before migration. Do not combine the retired standalone installer with the current Ainx or Blueprint package.
Run the package installer from the Pterodactyl panel root:
ainx install betterfilesmanager.ainxThat completes the panel layer. Do not run the old Artisan installer or paste the retired standalone routes afterward.
Optional translations
The package includes an English catalog at translations/en/betterfilesmanager.php. Better Files works without installing it and uses built-in English strings as its fallback.
Translation paths differ between Pterodactyl themes. Place the catalog using the theme developer's Laravel-to-React translation instructions, and preserve all placeholders when creating another locale. The extension installer does not overwrite or remove customer translation files.
What works without patched Wings
The package installs the panel routes, assets, settings, and frontend build. NBT and database previews run through the panel and do not require daemon changes.
Continue below for native collaboration, streamed path/content search, media streaming, resumable uploads, directory archives, revision history, bulk copy and rename, operation cancellation, fingerprints, largest-directory analysis, and Git operations.
Security requirements
Build source installations with Go 1.26.7 or newer and golang.org/x/text v0.39.0 or newer. The current smart script and both current patch files carry those versions. Updating only the panel package or Wings source files does not update the running service: rebuild Wings, replace /usr/local/bin/wings, and restart it. Prebuilt installations must download the current binary and restart Wings.
The hardened resumable-upload flow keeps the existing endpoint and request headers, but clients must follow these rules:
- Mint one signed upload token per file. Every
HEAD, chunk, and retry for that token must use the same server and canonical file path. - A successful
Upload-Complete: ?1request retires the token. Mint a new token for another file or any request after completion. PATCH /upload/filecan return408when the request body is idle,409for a competing same-file writer or an offset/length conflict, and429when an upload admission limit is full. HonorRetry-Afteron429.
Git clone and pull now accept only the final public HTTPS repository URL. Redirects, proxies, credentials in URLs, internal addresses, and unsafe repository-local Git configuration fail closed. Network fetches run through the isolated alpine/git:2.49.1 helper; clone checkout and pull's fast-forward merge run separately with networking disabled. Wings may need to download the helper image on first use.
Folder batch upload API
Current Go Wings supports nested folders in one POST /upload/file request. Before using this mode, inspect GET /openapi.json and require paths['/upload/file'].post['x-betterfiles-folder-upload'].version === 1. The capability specifies paths_field: "paths" and max_files: 512. A plain POST route alone does not prove support: older daemons may ignore the manifest and flatten filenames.
Use the existing signed upload URL and directory query parameter. Append each file under the repeated files field with its basename, then add exactly one text field named paths, containing a JSON array of relative paths in the same order:
const form = new FormData();
form.append("files", firstConfigFile, "config.yml");
form.append("files", secondConfigFile, "config.yml");
form.append(
"paths",
JSON.stringify(["mods/a/config.yml", "mods/b/config.yml"])
);
const url = new URL(signedUploadUrl);
url.searchParams.set("directory", "/uploads");
await fetch(url, { method: "POST", body: form });This creates /uploads/mods/a/config.yml and /uploads/mods/b/config.yml. Go strips directory components from multipart filenames, so sending a path only as the file's name does not preserve folders. The browser supplies the multipart Content-Type boundary automatically.
- Paths use
/and are relative todirectory. Their basenames must match the corresponding file parts. Paths are literal JSON strings, not URL-encoded values. - Empty paths, absolute paths, dot/parent components, repeated separators, backslashes, NUL, Windows drive prefixes, invalid UTF-8, duplicate destinations, and file/directory conflicts are rejected. Paths are limited to 4096 bytes and components to 255 bytes.
- Missing parent directories are created with server ownership. Ignored paths, symbolic links, hardlinks, and non-regular destinations cannot be overwritten through this API.
- At most 512 files fit in one folder-batch request. Individual and combined file bytes must fit the Wings upload limit and server quota. The request body is also bounded to the upload limit plus 8 MiB of metadata.
- Each POST uses one fresh single-use token for the whole batch, even when the request fails. Obtain another token before retrying. Batches are not transactional: a runtime failure may leave earlier files written.
- Success remains
200; invalid manifests or file limits return400, ignored paths403, unavailable/consumed tokens404, active same-file writers409, and oversized HTTP bodies413. - When the capability is absent, use the existing per-directory flat uploads or per-file resumable uploads. Omitting
pathskeeps flat multipart behavior, and the resumable HEAD/PATCH format is unchanged.
Install C-Wings
Wings modifications
The prebuilt binary is the shortest route. For source builds, the smart script is the recommended automatic method. Use the addon patch for a reviewable Better Files-only change, and use manual recovery only when another addon conflicts with the patch.
Run this in Bash on your existing Wings node. The download command automatically selects the x86-64 or ARM64 binary from our latest dev-latest build:
sudo systemctl stop wings
sudo curl -fL --retry 3 -o /usr/local/bin/wings "https://github.com/Tomaxikz/daemon/releases/download/dev-latest/wings_linux_$([[ "$(uname -m)" == "x86_64" ]] && echo "amd64" || echo "arm64")"
sudo chmod u+x /usr/local/bin/wings
sudo systemctl restart wingsRelease page: Tomaxikz/daemon dev-latest.
Build and verify
Before replacing the running service, confirm the Go version, format the touched packages, run focused tests, and build Wings:
go version # must report go1.26.7 or newer
gofmt -w router/*.go router/middleware/*.go router/websocket/*.go \
server/*.go server/filesystem/*.go config/*.go environment/docker/*.go
go test ./router ./router/middleware ./router/websocket ./server ./server/filesystem
go build -o wings .
sudo systemctl stop wings
sudo install -m 0755 ./wings /usr/local/bin/wings
sudo systemctl start wings
sudo systemctl is-active wingsIf the host has a working C compiler, also run the race detector before deploying a custom build:
go test -race ./router ./router/middleware ./router/websocket ./server ./server/filesystemInspect the capability document and recent service logs:
curl -fsS "http://127.0.0.1:8080/openapi.json"
sudo journalctl -u wings -n 100 --no-pagerVerify these capabilities from Better Files in the panel:
HEADandPATCH /upload/filefor resumable uploads with offset checks, bounded concurrency, fail-fast same-file locking, idle-body timeouts, and one completed file per signed token.POST /git/cloneandPOST /git/pullfor HTTPS-only network transfers in the isolated helper and network-disabled checkout or fast-forward merge.GET /download/directoryfor streamedtar,tar_gz,tar_xz,tar_bz2,tar_zstd, andziparchives.POST /files/searchfor structured path, size, and content search.POST /files/copy-manyandPUT /files/renamefor safe bulk operations.GET /files/largest-directoriesandGET /files/fingerprintsfor analysis.DELETE /files/operations/:operationfor background-operation cancellation.
Upload, download, and server-file routes require a signed token or panel authentication. An unauthenticated 401 or 403 from a copied route URL does not by itself mean the installation failed.