diff --git a/Dockerfile-release b/Dockerfile-release index 67d64a08..40634e9b 100644 --- a/Dockerfile-release +++ b/Dockerfile-release @@ -155,6 +155,7 @@ ARG RUNTIME_PACKAGES=" \ boost-iostreams \ boost-program_options \ boost-thread \ + ca-certificates \ lame-libs \ libarchive \ libconfig++ \ @@ -170,6 +171,7 @@ ARG RUNTIME_PACKAGES=" \ opus \ pugixml \ sqlite-libs \ + su-exec \ zlib" ARG LMS_USER=lms @@ -191,12 +193,16 @@ RUN addgroup -S ${LMS_GROUP} && \ VOLUME /var/lms VOLUME /music VOLUME /usr/local/etc - -USER ${LMS_USER}:${LMS_GROUP} +VOLUME /usr/local/share/ca-certificates COPY --from=build /tmp/fakeroot/ /usr COPY --from=build /tmp/fakeroot/share/lms/lms.conf /etc/lms.conf +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh EXPOSE 5082 -ENTRYPOINT ["/usr/bin/lms"] +# Stays root so the entrypoint can run update-ca-certificates; it drops to +# ${LMS_USER}:${LMS_GROUP} via su-exec before running lms itself. +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["/usr/bin/lms"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 00000000..fbfb92b7 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# If the operator mounted extra trusted CA certificates (e.g. an internal +# PKI root, common when LMS's OIDC provider sits behind a private CA), +# pick them up before starting. update-ca-certificates needs root, so this +# runs before we drop privileges to the lms user. +set -e + +if [ -d /usr/local/share/ca-certificates ] && [ -n "$(ls -A /usr/local/share/ca-certificates 2>/dev/null)" ]; then + update-ca-certificates +fi + +exec su-exec lms:lms "$@" diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 8dddb9de..2f974f69 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -182,6 +182,67 @@ namespace lms::db throw e; } } + + // Workaround: on a genuinely fresh database, Wt::Dbo's createTables() + // above silently fails to create "share", "starred_artist", + // "starred_release" and "starred_track" (no exception raised, and no + // discernible difference from the ~57 other mapped classes that DO + // get created correctly). Root cause not identified. These four are + // otherwise only ever created via historical schema migrations + // (Migration.cpp), which never run for a fresh install (a new + // database is stamped directly at LMS_DATABASE_VERSION). Create them + // here defensively; IF NOT EXISTS makes this a no-op on databases + // where createTables() (or, on upgraded databases, the migration + // path) already created them correctly. + { + auto transaction{ createWriteTransaction() }; + execute(R"(CREATE TABLE IF NOT EXISTS "share" ( + "id" integer primary key autoincrement, + "version" integer not null, + "token" text not null, + "media_ids" text not null, + "description" text not null, + "created" text not null, + "expires" text, + "last_visited" text, + "visit_count" integer not null, + "user_id" bigint not null, + constraint "fk_share_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred +))"); + execute(R"(CREATE TABLE IF NOT EXISTS "starred_artist" ( + "id" integer primary key autoincrement, + "version" integer not null, + "backend" integer not null, + "sync_state" integer not null, + "date_time" text, + "artist_id" bigint, + "user_id" bigint, + constraint "fk_starred_artist_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred, + constraint "fk_starred_artist_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred +))"); + execute(R"(CREATE TABLE IF NOT EXISTS "starred_release" ( + "id" integer primary key autoincrement, + "version" integer not null, + "backend" integer not null, + "sync_state" integer not null, + "date_time" text, + "release_id" bigint, + "user_id" bigint, + constraint "fk_starred_release_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred, + constraint "fk_starred_release_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred +))"); + execute(R"(CREATE TABLE IF NOT EXISTS "starred_track" ( + "id" integer primary key autoincrement, + "version" integer not null, + "backend" integer not null, + "sync_state" integer not null, + "date_time" text, + "track_id" bigint, + "user_id" bigint, + constraint "fk_starred_track_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred, + constraint "fk_starred_track_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred +))"); + } } bool Session::migrateSchemaIfNeeded()