Commit Graph
105 Commits
Author SHA1 Message Date
Maxim Shulga 84519cf7cc Run a container smoke test against the real seafile daemon
Startup, shutdown and secret handling were only ever checked by hand, and the
unit tests cannot check them: they stub seaf-cli, so nothing exercised the
daemon that actually ships. The GitHub runner is x86_64, which matches the
AppImage, so the image build job can run the real thing.

tests/smoke.sh starts and stops the real daemon, sends SIGTERM through docker
stop and asserts the container exits 0 within the grace period with the daemon
shut down, checks that a configuration error exits 2 naming the variable, that
a secret mounted under /run/secrets is read and stays out of the environment,
and that a default container gets exactly the documented defaults.

The pinned client version is exposed as SEAFILE_CLI_VERSION in the image,
since seaf-cli has no --version flag and there is no package manager entry
for an AppImage.
2026-08-02 19:37:26 -07:00
Maksim Shulga 2f93e4cdbc Merge pull request #26 from snegov/docs/record-certificate-flag-fix
Record why DISABLE_VERIFY_CERTIFICATE never worked
2026-08-03 01:32:23 +00:00
Maxim Shulga ca4c906814 Name the stored strings instead of implying booleans
The note explained a string comparison but wrote the values as if they were
booleans, which is the exact confusion it exists to clear up. Both places now
say which string was stored and which one the daemon accepts.
2026-08-02 18:24:43 -07:00
Maxim Shulga 3c8d247b28 Record why DISABLE_VERIFY_CERTIFICATE never worked
The daemon reads the key with seafile_session_config_get_bool, which is a
case sensitive g_strcmp0(value, "true"), so the capitalised True stored by
released images up to 0.0.13 was read as false and verification stayed on for
anyone who asked to disable it. The default was never affected, since any
value other than "true" is false.

Rendering booleans in lower case fixed this; this only writes down what the
change actually repaired, for anyone who reported the flag as broken.
2026-08-02 18:16:43 -07:00
Maksim Shulga 872585833b Merge pull request #25 from snegov/feature/typed-env-parsing
Read the environment with typed parsers and apply the documented defaults
2026-08-03 01:12:06 +00:00
Maxim Shulga 3c7daea3c1 Annotate env_str as returning an optional string
It returns the default when a variable is unset, and that default is None,
so the previous annotation was wrong for both the parameter and the result.
2026-08-02 18:08:38 -07:00
Maxim Shulga e6034a69a6 Read the environment with typed parsers and apply the documented defaults
set_defaults() assigned os.getenv() results directly, which overwrote the
argparse defaults with None whenever a variable was unset. A default container
therefore ran "seaf-cli config -k delete_confirm_threshold -v None", and the
same for upload_limit and download_limit: none of the three documented
defaults were ever applied.

SEAFILE_UID and SEAFILE_GID had a second problem. argparse type=int applies
only to command line flags, never to set_defaults, so both stayed strings.
The check in setup_uid compares them against pwd integers, and a string is
never equal to one, so usermod ran on every start.

dsc/config.py reads each variable with its documented default and rejects a
malformed or negative value at startup, naming the variable, instead of
passing it on. An empty variable counts as unset.

Booleans are now rendered as true and false rather than Python's True and
False. The daemon reads disable_verify_certificate with its boolean accessor,
which does not recognise the capitalised form. I could not run the daemon to
confirm the previous value's effect, since the AppImage is x86_64 only.

seaf-cli is no longer given a None value for any option.
2026-08-02 17:44:57 -07:00
Maksim Shulga 87b76b80f6 Merge pull request #24 from snegov/feature/parse-library-list-as-json
Read the local library list as JSON instead of splitting columns
2026-08-03 00:36:38 +00:00
Maxim Shulga 2e18a774f2 Fail the same way on JSON of an unexpected shape
json.loads succeeding does not mean the result is a list of objects with an
id. A dict, an entry without an id, or a scalar raised TypeError or KeyError,
which escaped unhandled and defeated the point of the change. Both cases now
raise DaemonError, and the original exception is chained so the cause is
visible.
2026-08-02 17:33:45 -07:00
Maxim Shulga b75863ba51 Read the local library list as JSON instead of splitting columns
get_local_libraries unpacked "line.rsplit(maxsplit=3)" into three names, but
maxsplit=3 yields up to four fields. seaf-cli prints "name id path" separated
by whitespace, so any library whose name contains a space produced a fourth
field and raised ValueError, which nothing caught.

The failure only appeared after the first run: the list is empty until a
library has been synced, so a container would work when it was set up and then
crash on every restart.

seaf-cli list --json returns name, id and path as structured fields, which
display output cannot represent unambiguously for names or paths containing
whitespace. Its exit code is now checked and unreadable output is reported as
a daemon error instead of a traceback.

The image smoke check asserts that the installed seaf-cli offers --json and
--token, so a future version bump that drops either fails the build rather
than the container.
2026-08-02 17:29:51 -07:00
Maksim Shulga 8043ac1409 Merge pull request #23 from snegov/feature/secrets-support
Authenticate from mounted secrets and stop passing the password to seaf-cli
2026-08-03 00:24:28 +00:00
Maxim Shulga eef0e453bf Authenticate from mounted secrets and stop passing the password to seaf-cli
The password could only be given through the PASSWORD variable, so Docker
Compose and Swarm secrets were unusable and the password sat in the container
environment.

PASSWORD_FILE and TOKEN_FILE read a secret from a mounted file. Only trailing
newlines are stripped, since secret files are usually written with one and a
password that silently carries it just fails to authenticate; everything else,
including surrounding spaces, belongs to the secret. Setting both a variable
and a file for one secret is rejected rather than resolved by precedence,
because the two are meant to be the same value and quietly picking one hides
the mistake until authentication fails.

The installed seaf-cli supports token authentication and ignores the password
when a token is given, so sync now passes -T instead of -p. The account
password no longer appears in any command line. The token does, because
seaf-cli accepts credentials only as arguments, but a token can be revoked on
the server. This is documented in the README.

Both secrets are masked before a command is logged.

Tests cover newline handling, empty and missing files, conflicting sources,
and that neither secret reaches the logs. Requests are now blocked in tests:
the client retries with backoff, so a test that reached the network appeared
to hang instead of failing.
2026-08-02 17:21:46 -07:00
Maksim Shulga 1e59da6193 Merge pull request #22 from snegov/feature/deterministic-startup-shutdown
Bound daemon startup and shutdown and exit with a useful code
2026-08-03 00:12:50 +00:00
Maxim Shulga f3804b7e2e Bound daemon startup and shutdown and exit with a useful code
start_daemon and stop_daemon polled "while True" with no bound, and no
seaf-cli exit code was checked. A daemon that failed to start left the
container spinning forever with nothing pointing at the cause, and there was
no signal handling, so "docker stop" waited the full 10 second grace period
and then SIGKILLed the process with the daemon still running.

Readiness polling is now bounded by DAEMON_START_TIMEOUT and
DAEMON_STOP_TIMEOUT. The stop timeout is 8 seconds so a graceful shutdown
fits inside the docker stop grace period, and a test asserts that relation.

SIGTERM and SIGINT raise GracefulShutdown, and the lifecycle moved into
start.run(), which always stops the daemon on the way out and returns the
process exit code: 0 on a clean finish or a stop signal, 1 on a daemon or
configuration failure.

seaf-cli exit codes are checked. A failed sync is reported per library and
does not stop the remaining libraries, since one bad library should not take
the container down.

Verified in the built image: "docker stop" now returns in 0s with exit code 0
and the daemon stopped, against 10s and exit 137 before. A daemon that cannot
start exits 1 with the failing command and its exit code in the log.

Also bumps actions/setup-python to v7, which clears the last Node 20
deprecation warning; v7 only drops the unused pip-install input.
2026-08-02 17:10:20 -07:00
Maksim Shulga d1c0be2c24 Merge pull request #17 from snegov/dependabot/pip/cached-property-2.0.1
pip: bump cached-property from 1.5.2 to 2.0.1
2026-08-03 00:02:50 +00:00
dependabot[bot] 8ce30e6194 pip: bump cached-property from 1.5.2 to 2.0.1
Bumps [cached-property](https://github.com/pydanny/cached-property) from 1.5.2 to 2.0.1.
- [Release notes](https://github.com/pydanny/cached-property/releases)
- [Changelog](https://github.com/pydanny/cached-property/blob/main/HISTORY.md)
- [Commits](https://github.com/pydanny/cached-property/compare/1.5.2...2.0.1)

---
updated-dependencies:
- dependency-name: cached-property
  dependency-version: 2.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 00:01:34 +00:00
Maksim Shulga d2d6621add Merge pull request #15 from snegov/dependabot/github_actions/docker/login-action-4
ci: bump docker/login-action from 3 to 4
2026-08-03 00:00:37 +00:00
Maksim Shulga f0e5c1fc06 Merge pull request #20 from snegov/dependabot/github_actions/docker/metadata-action-6
ci: bump docker/metadata-action from 5 to 6
2026-08-03 00:00:31 +00:00
Maksim Shulga 5546fe5317 Merge pull request #18 from snegov/dependabot/pip/requests-2.34.2
pip: bump requests from 2.33.0 to 2.34.2
2026-08-02 23:59:58 +00:00
Maksim Shulga fad6baf28a Merge pull request #16 from snegov/dependabot/github_actions/actions/checkout-7
ci: bump actions/checkout from 4 to 7
2026-08-02 23:59:42 +00:00
Maksim Shulga 964ab2c46f Merge pull request #19 from snegov/dependabot/github_actions/docker/build-push-action-7
ci: bump docker/build-push-action from 6 to 7
2026-08-02 23:59:39 +00:00
dependabot[bot] 048c6bf131 ci: bump actions/checkout from 4 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 23:57:15 +00:00
Maksim Shulga 02e703ffc3 Merge pull request #21 from snegov/feature/no-shell-and-path-injection
Stop server-provided values from reaching a shell or escaping /dsc/seafile
2026-08-02 23:55:20 +00:00
Maxim Shulga c15763d9f8 Stop server-provided values from reaching a shell or escaping /dsc/seafile
Commands were joined into a string and passed to "su -c", so a login shell
parsed every interpolated value. A library name is chosen on the server, and
"sync -d /dsc/seafile/<name>" put it straight into that string: a library
named 'Docs; touch /tmp/pwned; #' ran as an arbitrary command. Confirmed
against the built image before and after this change.

Every command is now an argument list executed by "runuser --", which execs
directly with no shell. runuser -u sets the same HOME and a PATH that still
finds the seaf-cli wrapper, so the daemon environment is unchanged.

The password no longer needs its surrounding quotes, which also fixes
passwords containing quotes, dollar signs, and backticks.

Library names are untrusted, so dsc/paths.py reduces each one to a single
path component and proves the result stays under the libraries directory
with symlinks resolved. Empty, ".", "..", and names containing a separator
are skipped with a warning; names that would collide are suffixed with their
library ID rather than sharing a directory.

Tests cover quotes, command substitutions, separators, newlines, absolute
paths, parent traversal, Unicode, and colliding names. They run in a separate
CI job so application failures stay distinguishable from packaging failures.
2026-08-02 16:53:19 -07:00
dependabot[bot] 90d1c8d1bb ci: bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 23:34:48 +00:00
dependabot[bot] d2adc54132 ci: bump docker/build-push-action from 6 to 7
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 23:34:37 +00:00
dependabot[bot] 2b29865b9c pip: bump requests from 2.33.0 to 2.34.2
Bumps [requests](https://github.com/psf/requests) from 2.33.0 to 2.34.2.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.33.0...v2.34.2)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.34.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 23:34:33 +00:00
dependabot[bot] e3fb2cb255 ci: bump docker/login-action from 3 to 4
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 23:34:25 +00:00
Maksim Shulga 50ede0b2f9 Merge pull request #14 from snegov/feature/dependabot-updates
Track dependency updates with Dependabot
2026-08-02 23:33:39 +00:00
Maxim ShulgaandClaude Opus 5 bef866fb6a Track dependency updates with Dependabot
Add a weekly Dependabot configuration for pip, the Dockerfile base image,
and the GitHub Actions used by the publish workflow.

The Seafile AppImage is fetched by URL with a pinned checksum, which
Dependabot cannot resolve, so it stays a manual bump.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 16:29:35 -07:00
Maksim Shulga dc1a9b6f03 Merge pull request #13 from snegov/feature/dockerignore-and-unused-imports
Trim the build context and drop unused imports
2026-08-02 23:27:44 +00:00
Maxim ShulgaandClaude Opus 5 2126808cee Trim the build context and drop unused imports
Add a .dockerignore that whitelists only requirements.txt, start.py and
dsc/. The build context previously included .git, .idea, __pycache__ and
any local .env, which slowed builds and put a secrets file within reach
of any future COPY of the whole tree.

Remove the unused urlparse import from dsc/client.py and the redundant
import os.path from start.py; importing os already binds os.path.

Record both in IMPROVEMENTS.md, including the part still outstanding:
binutils and squashfs-tools remain in the runtime image because the
AppImage is unpacked in the same stage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 16:23:04 -07:00
Maksim Shulga ff22f05576 Merge pull request #12 from snegov/feature/ci-release-pipeline
Publish one release image digest from a modernised workflow
2026-08-02 23:20:15 +00:00
Maxim ShulgaandClaude Opus 5 9b9d4f0577 Publish one release image digest from a modernised workflow
The push job called build-push-action twice, once for the ref tag and once
for latest. Two builds meant the two tags could resolve to different manifest
digests. Build once and let docker/metadata-action supply both tags and the
OCI labels for that single build.

Replace the deprecated actions/checkout@v2 and docker/build-push-action@v1,
default workflow permissions to contents: read so pull requests cannot reach
publication credentials, and authenticate with a scoped DOCKER_TOKEN through
docker/login-action instead of DOCKER_PASSWORD.

The test job's docker-compose.test.yml branch was dead; that file does not
exist in the repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 16:18:03 -07:00
Maxim Shulga 6abd3323d2 Reconcile the improvement plan with the AppImage change
The signature acceptance criterion assumed the signed APT repository.
Upstream ships a bare AppImage with no digest or signature, so as written
it could never be met and would block the release gate indefinitely.
State what the build actually enforces, and track the weakened supply
chain as its own item rather than only a note.
2026-08-02 16:07:18 -07:00
Maksim Shulga b71a29b6ef Merge pull request #10 from snegov/dependabot/pip/requests-2.33.0
Bump requests from 2.32.4 to 2.33.0
2026-08-02 23:05:58 +00:00
dependabot[bot] cd2983af52 Bump requests from 2.32.4 to 2.33.0
Bumps [requests](https://github.com/psf/requests) from 2.32.4 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.4...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 23:03:35 +00:00
Maksim Shulga f8a8e12add Merge pull request #11 from snegov/feature/appimage-seafile-cli
Install Seafile CLI from AppImage instead of the APT repository
2026-08-02 23:00:29 +00:00
Maxim Shulga c558caba5b Keep the working directory off the CLI wrapper's search paths
Neither LD_LIBRARY_PATH nor PYTHONPATH is set in the base image, so
appending them unconditionally left a trailing ':'. An empty entry in a
colon-separated search path means the working directory, and Python
resolved it to /dsc on sys.path. Append the separator only when the
variable is already non-empty.

Reported by the automated review on #11.
2026-08-02 15:55:55 -07:00
Maxim Shulga 523a0955c4 Install Seafile CLI from AppImage instead of the APT repository
Seafile discontinued linux-clients.seafile.com: it resolves but accepts
no connections on 80 or 443, and the official docs no longer mention APT
at all. Every `docker build` has failed since. Upstream now ships the CLI
as an AppImage, so install 9.0.19 from the download site.

The AppImage is unpacked rather than executed. Running one needs FUSE and
so a privileged container, and its self-extraction also fails when the
build host cannot execute the payload's architecture. Unpacking the
appended squashfs directly avoids both.

Also add the bundled seafile/pysearpc bindings to the venv path. The APT
package installed them system-wide, where `venv --system-site-packages`
picked them up; dsc imports seafile directly for RPC, not only through
seaf-cli, so it broke when that implicit dependency went away.

Drops apt-key, pins the version, and verifies a SHA256 before unpacking.
Build-time smoke checks assert the seaf-cli subcommands and the app's own
imports.

Note: the AppImage is x86_64-only, and upstream publishes no digest or
signature, so the pinned checksum is trust-on-first-use.
2026-08-02 15:39:29 -07:00
Maxim Shulga e7fce80239 Correct smoke-check item: seaf-cli has no --version flag 2026-08-02 15:07:33 -07:00
Maxim Shulga e3c6f28182 Pin Compose example to 0.0.13, the current release 2026-08-02 15:00:48 -07:00
Maxim Shulga d500ddc7ab Fix and validate Compose quick start
The list-form environment syntax in the README passed quote characters
through as part of the value, so LIBRARY_ID was read as '"<id>"' and no
matching library was found. Switch to mapping syntax, where YAML strips
the quotes.

- Pin the example to a versioned image instead of implicit latest
- Add a checked-in compose.example.yaml
- Validate it with `docker compose config` in CI
- Link the Docker Hub image from the README
2026-08-02 14:53:08 -07:00
snegov a7abdab1f7 Add HTTP retry strategy for server connection failures
Implement automatic retry with exponential backoff (up to 30 attempts)
for 502/503/504 errors. Prevents container crash-loop during server
outages, reducing kernel log spam from repeated restarts.
0.0.13
2026-02-03 00:31:09 -08:00
snegov fe9f5a9272 Improve library status output formatting
Add dynamic column width alignment for library names up to 30 chars.
Prevents excessive whitespace when library names vary significantly
in length while maintaining readability for typical names.
2026-02-02 21:11:10 -08:00
Maks Snegov 42a0f3f05c Merge pull request #9 from snegov/dependabot/pip/requests-2.32.4
Bump requests from 2.32.0 to 2.32.4
2026-02-02 21:10:45 -08:00
dependabot[bot] 9860ee9b40 Bump requests from 2.32.0 to 2.32.4
Bumps [requests](https://github.com/psf/requests) from 2.32.0 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.0...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 05:09:23 +00:00
Maks Snegov 446eb8e1ef Merge pull request #7 from snegov/dependabot/pip/requests-2.32.0
Bump requests from 2.31.0 to 2.32.0
2026-02-02 21:07:11 -08:00
dependabot[bot] 9f8eb92e5b ---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 06:22:09 +00:00
snegov a7f351a75f Use RPC to fetch sync status 2023-10-24 22:12:02 -07:00