AeroFoil
Personal Library Manager
Install / Use
/learn @luketanti/AeroFoilREADME
AeroFoil
AeroFoil is a Personal library manager that turns your library into a fully customizable, self-hosted Shop. The goal of this project is to manage your library, identify any missing content (DLCs or updates) and provide a user friendly way to browse your content. Some of the features include:
- multi user authentication
- web interface for configuration
- web interface for browsing the library
- content identification
- shop customization
The project is still in development, expect things to break or change without notice.
Table of Contents
Installation
Using Docker
Docker run
Running this command will start the shop on port 8465 with the library in /your/game/directory:
docker run -d -p 8465:8465 \
-v /your/game/directory:/games \
-v /your/config/directory:/app/config \
-v /your/data/directory:/app/data \
--name aerofoil \
luketanti/aerofoil:latest
The shop is now accessible with your computer/server IP and port, i.e. http://localhost:8465 from the same computer or http://192.168.1.100:8465 from a device in your network.
Docker compose
Create a file named docker-compose.yml with the following content:
version: "3"
services:
aerofoil:
container_name: aerofoil
image: luketanti/aerofoil:latest
# environment:
# # For write permission in config directory
# - PUID=1000
# - PGID=1000
# # to create/update an admin user at startup
# - USER_ADMIN_NAME=admin
# - USER_ADMIN_PASSWORD=asdvnf!546
# # to create/update a regular user at startup
# - USER_GUEST_NAME=guest
# - USER_GUEST_PASSWORD=oerze!@8981
# # cache TTLs (seconds): use none/unset for rebuild-only
# - SHOP_SECTIONS_CACHE_TTL_S=none
# - MEDIA_INDEX_TTL_S=none
volumes:
- /your/game/directory:/games
- ./config:/app/config
- ./data:/app/data
ports:
- "8465:8465"
[!NOTE] You can control the
UIDandGIDof the user running the app in the container with thePUIDandPGIDenvironment variables. By default the user is created with1000:1000. If you want to have the same ownership for mounted directories, you need to set those variables with the UID and GID returned by theidcommand.
You can then create and start the container with the command (executed in the same directory as the docker-compose file):
docker-compose up -d
This is usefull if you don't want to remember the docker run command and have a persistent and reproductible container configuration.
Environment variables
New AEROFOIL_* variables are preferred. Legacy OWNFOIL_* names are still accepted for backward compatibility.
PUID/PGID: control the user ID/group ID inside the container (default1000:1000).USER_ADMIN_NAME/USER_ADMIN_PASSWORD: create or update an admin user at startup (default: unset).USER_GUEST_NAME/USER_GUEST_PASSWORD: create or update a regular user at startup (default: unset).AEROFOIL_SECRET_KEY: Flask secret key used for sessions/cookies. Recommended to set a long random value in production (default: auto-generated at startup).AEROFOIL_TRUST_PROXY_HEADERS: enable trustingX-Forwarded-Forwhen the proxy is in the trusted list (true/false, default:false).AEROFOIL_TRUSTED_PROXIES: comma-separated proxy IPs/CIDRs (default: empty), for example172.16.0.0/12,192.168.0.0/16.AEROFOIL_CONVERSION_STAGING_ENABLED: enable fixed Docker staging path (/app/conversion-tmp) for temporary NSP/XCI conversion output (true/false, default: unset/disabled).AEROFOIL_CONVERSION_STAGING_DIR: absolute path for temporary NSP/XCI conversion output (default: empty, which keeps direct in-library conversion output).SHOP_SECTIONS_CACHE_TTL_S: cache TTL for/api/shop/sections(seconds). Usenone/unset for rebuild-only (default),0to disable caching. Recommended:nonefor stable libraries, or600-900for periodic refresh.SHOP_SECTIONS_ALL_ITEMS_CAP: max number of items retained per discovery section before per-request slicing (default:300).SHOP_SECTIONS_ALL_ITEMS_CAP_NO_TITLEDB: max number of items retained per discovery section when TitleDB is unavailable (default:120).MEDIA_INDEX_TTL_S: cache TTL for icon/banner media index (seconds). Usenone/unset for rebuild-only (default),0to disable caching. Recommended:noneor600-900.AEROFOIL_TITLES_TOTAL_CACHE_TTL_S: cache TTL for/api/titlestotal-count cache (seconds, default:300; legacyOWNFOIL_TITLES_TOTAL_CACHE_TTL_Salso supported).AEROFOIL_TITLES_TOTAL_CACHE_MAX_ENTRIES: max entries for/api/titlestotal-count cache (default:256; clamped to16..4096; legacyOWNFOIL_TITLES_TOTAL_CACHE_MAX_ENTRIESalso supported).AEROFOIL_HOST: bind host for the web server (default:0.0.0.0).AEROFOIL_PORT: bind port for the web server (default:8465).AEROFOIL_WSGI_THREADS: Waitress worker thread count (default:32).AEROFOIL_WSGI_CONNECTION_LIMIT: max concurrent Waitress channels (default:1000).AEROFOIL_WSGI_CHANNEL_TIMEOUT_S: idle channel timeout in seconds (default:120).AEROFOIL_WSGI_CLEANUP_INTERVAL_S: Waitress cleanup interval in seconds (default:30).AEROFOIL_WSGI_MAX_REQUEST_BODY_SIZE: max HTTP request body size in bytes for Waitress (default:68853694464, about64.125 GB).AEROFOIL_UPLOAD_TMP_DIR: directory for temporary multipart upload files during request parsing (default:<data>/tmp/uploads; ensure enough free disk space for very large uploads).AEROFOIL_USE_FLASK_DEV: set totrue/1to force Flask dev server instead of Waitress.
Using Python
Clone the repository using git, install the dependencies and you're good to go:
$ git clone https://github.com/luketanti/aerofoil
$ cd aerofoil
$ pip install -r requirements.txt
$ python app/app.py
To update the app you will need to pull the latest commits.
By default, python app/app.py runs AeroFoil with the Waitress WSGI server (production-oriented). Set AEROFOIL_USE_FLASK_DEV=true only if you need the Flask development server for debugging.
CyberFoil setup
In CyberFoil, set the AeroFoil eShop URL in Settings:
- URL:
http://<server-ip>:8465(orhttps://if using an SSL-enabled reverse proxy) - Username: username as created in AeroFoil settings (if the shop is Private)
- Password: password as created in AeroFoil settings (if the shop is Private)
Save backups (Save Sync)
AeroFoil supports per-user save backup management when the user has the Backup flag enabled:
- Save archives are stored per user under
data/saves/<username>/. - Multiple backup versions per title are supported.
- Each uploaded version can include a note.
- Backups can be downloaded or deleted from:
- CyberFoil
Savessection (upload/download/delete), - AeroFoil web page
Saves Files(download/delete).
- CyberFoil
Save sync API endpoints:
GET /api/saves/listPOST /api/saves/upload/<title_id>GET /api/saves/download/<title_id>/<save_id>.zipDELETE /api/saves/delete/<title_id>/<save_id>(also acceptsPOSTfor compatibility)
Usage
Once AeroFoil is running you can access the Shop Web UI by navigating to the http://<computer/server IP>:8465.
User administration
AeroFoil requires an admin user to be created to enable Authentication for your Shop. Go to the Settings to create a first user that will have admin rights. Then you can add more users to your shop the same way.
Library administration
In the Settings page under the Library section, you can add directories containing your content. You can then manually trigger the library scan: AeroFoil will scan the content of the directories and try to identify every supported file (currently nsp, nsz, xci, xcz).
There is watchdog in place for all your added directories: files moved, renamed, added or removed will be reflected directly in your library.
Library management
In the Manage page, you can organize your library structure, delete older update files, delete scoped library content, clean up orphaned add-ons, and convert nsp/xci to nsz.
Library browser UI
- Card view: the Base/Update/DLC status icons are displayed above the action buttons.
- Icon view: the
Game infobutton is shown as an overlay on the game tile.
Discovery sections (New and Recommended)
The home-page discovery rows are generated from owned BASE titles only (not update/DLC rows), and only when a real library file is linked.
New: sorted by most recent library file id (newest first), then the first items are used for the section.Recommended: sorted by highestdownload_countfirst. If every candidate hasdownload_count = 0, AeroFoil falls back to the same ordering asNew.
For the Web UI, these sections are returned through /api/titles as discovery.newest and discovery.recommended.
Game info (TitleDB)
The Game info modal uses TitleDB metadata:
description: shown as the game summary.screenshots: displayed in a grid; click a screenshot to open it larger.DLC search: admins can trigger a download search for related add-ons directly from the details flow.
AeroFoil will download the TitleDB descri
Related Skills
node-connect
354.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
