GTFS Schedule and GTFS-realtime hosting with Copyparty,
following the setup in ../files/. Production routing remains
https://feeds.simovi.org behind the existing Traefik instance.
- Visitors can browse all directories and download files without logging in, but cannot upload, overwrite, create folders, move, rename, or delete.
- The password-protected
adminaccount hasrwmdapermissions: read, write, move/rename, delete, and volume administration. Accounts are managed in the private config, not through public registration. GTFS_DATA_PATHstill defaults to./gtfs-data. Existing paths and direct URLs stay unchanged, e.g./incofer/schedule/gtfs.zip. Everything in this directory is public; keep configuration, state, and backups outside it.config/copyparty.confandstate/are private and ignored by Git. State includes authentication salts, sessions, and the file index.Cache-Control: no-cacheforces clients to revalidate feed downloads. Copyparty usesLast-Modifiedfor conditional requests.
The data mount is now writable by the service so an authenticated admin can manage feeds. Copyparty's permissions enforce read-only visitor access. The container runs non-root with a read-only root filesystem, dropped capabilities, and only the data, private state, and temporary scratch space writable.
Requires Docker and Docker Compose >= 2.24.4. Start Docker Desktop first. No Traefik, DNS, external network, or TLS is required for this local preview.
Run once (do not overwrite an existing .env or private config):
test -f .env || cp .env.example .env
test -f config/copyparty.conf || cp config/copyparty.conf.example config/copyparty.conf
mkdir -p gtfs-data state
chmod 700 state
chmod 600 .env config/copyparty.conf
docker compose -f compose.yaml -f compose.local.yaml config --quiet
docker compose -f compose.yaml -f compose.local.yaml up -dOpen http://127.0.0.1:3923. The port is bound only to loopback.
The template initially grants public reads only; management stays disabled
until you create the admin account below. Existing .env settings are retained;
check GTFS_DATA_PATH before starting so you serve the intended local directory.
Use the same state/ directory for hashing and for the running service.
With usernames enabled, enter admin:YOUR_PASSWORD at the hidden prompt,
not just the password. Do not put the password in shell arguments or .env.
docker compose -f compose.yaml -f compose.local.yaml run --rm --no-deps feed-server -c /cfg/copyparty.conf --ah-cliCopy the generated hash (including its leading +) into the private
config/copyparty.conf, and enable the admin permission line:
[accounts]
admin: +YOUR_GENERATED_HASH
[/]
/w
accs:
r: *
rwmda: adminLeave the hash tool with Ctrl-D, then recreate to pick up the edited config (including editors that save by replacing the file):
docker compose -f compose.yaml -f compose.local.yaml up -d --force-recreateLog in through Copyparty's UI as admin, using just your password in the
password field. Never grant visitors (*) w, m, d, or a permissions.
Back up all of state/ with the config: losing the authentication salt
invalidates the hashes.
Log in, navigate to the agency/feed-type directory, and upload the file under
its existing name, e.g. gtfs.zip. The browser uploader defaults to always
replace an existing filename (u2ow: 2), keeping subscriber URLs stable.
An existing browser preference can override this default; check the uploader's
overwrite setting. PUT/WebDAV replacement is also enabled for the administrator.
You can create directories, rename/move, and delete after login. Files copied onto disk directly are rescanned every five minutes. The optional existing SSH helper remains unchanged:
./upload.sh incofer schedule ./build/incofer-feed.zipThat helper targets the real server, not the local preview. Do not use it for local tests.
The integration test starts Copyparty on loopback with disposable files, private
state, and a random temporary admin password. It never uses .env, the real
config, or GTFS_DATA_PATH. It tests anonymous denials and admin operations,
including overwriting at the same URL and conditional downloads.
uv run --with copyparty==1.20.26 --with argon2-cffi python tests/test_access.pyFor a manual UI check, upload/replace a disposable file while logged in, then use a private browser window to confirm browsing/downloads work but management does not. Ensure the startup permission summary grants everyone read only.
Do not deploy until local testing is complete. The production Compose file
keeps the feed-server service, FEEDS_DOMAIN, TRAEFIK_NETWORK, certificate
resolver, and existing data mount. No public host port is published in production.
Before switching on the server:
- Back up the existing feed directory and
.envto a private location outside the public feed tree. Record the current image/revision for rollback. - Preserve the exact existing
GTFS_DATA_PATH. Do not rename, replace, empty, or copy over that directory; the new service mounts it in place at/w. - Set
COPYPARTY_UID/COPYPARTY_GIDto its existing non-root owner, and ensure this user can read/write the feeds and state and read the private config. Inspect existing permissions first; do not recursively change ownership blindly. - Create private config/state with that owner. Generate the admin hash on the
server using its own persistent state, or transfer the local config together
with its state securely. With production Compose, the hash command is
docker compose run --rm --no-deps feed-server -c /cfg/copyparty.conf --ah-cli. - Set
xff-srcin the config to Traefik's actual network IP with/32(or an exact subnet only if all its members are trusted). Update it if the proxy IP changes. The loopback-only example intentionally does not trust arbitrary proxy headers.rproxy: -1assumes Traefik is the sole proxy hop. - Match the Traefik network, entrypoints and certificate resolver in
.env. Do not add global BasicAuth: direct feed URLs must remain public. - Validate configuration, pull the pinned image, and recreate the service:
docker compose config --quiet
docker compose pull
docker compose up -d --force-recreate
docker compose ps
docker compose logs --follow feed-serverVerify existing feed URLs, bytes/checksums, cache headers, public read-only
access, admin management, HTTPS, and HTTP-to-HTTPS redirects. Do not include
compose.local.yaml on the server. Back up config, .env, feed data, and state;
stop the service or use a consistent snapshot when backing up databases.
For rollback, restore the old code/config and recreate Caddy against the same
feed directory; do not restore an old data backup over newer uploads blindly.
No server migration commands run automatically in this repository.