diff --git a/.gitignore b/.gitignore index b3c5bd85..30818d52 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,7 @@ package-lock.json # Dummy macOS files .DS_Store + +# Python +*.pyc +venv/ diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index a2260595..7b13168a 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -124,6 +124,11 @@ list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR_DEF} list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_TRAY=${SUNSHINE_TRAY}) +# Publisher metadata +list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_PUBLISHER_NAME="${SUNSHINE_PUBLISHER_NAME}") +list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_PUBLISHER_WEBSITE="${SUNSHINE_PUBLISHER_WEBSITE}") +list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_PUBLISHER_ISSUE_URL="${SUNSHINE_PUBLISHER_ISSUE_URL}") + include_directories("${CMAKE_SOURCE_DIR}") include_directories( diff --git a/cmake/prep/options.cmake b/cmake/prep/options.cmake index f358f727..151a22a7 100644 --- a/cmake/prep/options.cmake +++ b/cmake/prep/options.cmake @@ -1,3 +1,12 @@ +# Publisher Metadata +set(SUNSHINE_PUBLISHER_NAME "SudoMaker" + CACHE STRING "The name of the publisher (or fork developer) of the application.") +set(SUNSHINE_PUBLISHER_WEBSITE "https://www.sudomaker.com" + CACHE STRING "The URL of the publisher's website.") +set(SUNSHINE_PUBLISHER_ISSUE_URL "https://github.com/ClassicOldSong/Apollo/issues" + CACHE STRING "The URL of the publisher's support site or issue tracker. + If you provide a modified version of Sunshine, we kindly request that you use your own url.") + option(BUILD_DOCS "Build documentation" ON) option(BUILD_TESTS "Build tests" ON) option(TESTS_ENABLE_PYTHON_TESTS "Enable Python tests" ON) diff --git a/docker/archlinux.dockerfile b/docker/archlinux.dockerfile index 5edb4416..f628ac07 100644 --- a/docker/archlinux.dockerfile +++ b/docker/archlinux.dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 # artifacts: true # platforms: linux/amd64 # archlinux does not have an arm64 base image @@ -17,7 +17,7 @@ pacman -Syu --disable-download-timeout --noconfirm pacman -Scc --noconfirm _DEPS -FROM sunshine-base as sunshine-build +FROM sunshine-base AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -31,18 +31,19 @@ ENV COMMIT=${COMMIT} SHELL ["/bin/bash", "-o", "pipefail", "-c"] -# Setup builder user, arch prevents running makepkg as root -RUN useradd -m builder && \ - echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers - -# patch the build flags # hadolint ignore=SC2016 -RUN sed -i 's,#MAKEFLAGS="-j2",MAKEFLAGS="-j$(nproc)",g' /etc/makepkg.conf - -# install dependencies -RUN <<_DEPS +RUN <<_SETUP #!/bin/bash set -e + +# Setup builder user, arch prevents running makepkg as root +useradd -m builder +echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + +# patch the build flags +sed -i 's,#MAKEFLAGS="-j2",MAKEFLAGS="-j$(nproc)",g' /etc/makepkg.conf + +# install dependencies pacman -Syu --disable-download-timeout --needed --noconfirm \ base-devel \ cmake \ @@ -51,7 +52,7 @@ pacman -Syu --disable-download-timeout --needed --noconfirm \ namcap \ xorg-server-xvfb pacman -Scc --noconfirm -_DEPS +_SETUP # Setup builder user USER builder @@ -84,9 +85,11 @@ cmake \ _MAKE WORKDIR /build/sunshine/pkg -RUN mv /build/sunshine/build/PKGBUILD . -RUN mv /build/sunshine/build/sunshine.install . -RUN makepkg --printsrcinfo > .SRCINFO +RUN <<_PACKAGE +mv /build/sunshine/build/PKGBUILD . +mv /build/sunshine/build/sunshine.install . +makepkg --printsrcinfo > .SRCINFO +_PACKAGE # create a PKGBUILD archive USER root @@ -111,12 +114,12 @@ rm -f /build/sunshine/pkg/sunshine-debug*.pkg.tar.zst ls -a _PKGBUILD -FROM scratch as artifacts +FROM scratch AS artifacts COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /sunshine.pkg.tar.zst COPY --link --from=sunshine-build /build/sunshine/sunshine.pkg.tar.gz /sunshine.pkg.tar.gz -FROM sunshine-base as sunshine +FROM sunshine-base AS sunshine COPY --link --from=artifacts /sunshine.pkg.tar.zst / diff --git a/docker/clion-toolchain.dockerfile b/docker/clion-toolchain.dockerfile index 647a4bed..6acd82ff 100644 --- a/docker/clion-toolchain.dockerfile +++ b/docker/clion-toolchain.dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 # artifacts: false # platforms: linux/amd64 # platforms_pr: linux/amd64 @@ -9,7 +9,7 @@ FROM ${BASE}:${TAG} AS toolchain-base ENV DEBIAN_FRONTEND=noninteractive -FROM toolchain-base as toolchain +FROM toolchain-base AS toolchain ARG TARGETPLATFORM RUN echo "target_platform: ${TARGETPLATFORM}" @@ -17,7 +17,9 @@ RUN echo "target_platform: ${TARGETPLATFORM}" ENV DISPLAY=:0 SHELL ["/bin/bash", "-o", "pipefail", "-c"] + # install dependencies +# hadolint ignore=SC1091 RUN <<_DEPS #!/bin/bash set -e @@ -59,20 +61,14 @@ apt-get install -y --no-install-recommends \ xvfb apt-get clean rm -rf /var/lib/apt/lists/* -_DEPS -#Install Node -# hadolint ignore=SC1091 -RUN <<_INSTALL_NODE -#!/bin/bash -set -e -node_version="20.9.0" -wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash +# Install Node +wget --max-redirect=0 -qO- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash source "$HOME/.nvm/nvm.sh" -nvm install "$node_version" -nvm use "$node_version" -nvm alias default "$node_version" -_INSTALL_NODE +nvm install node +nvm use node +nvm alias default node +_DEPS # install cuda WORKDIR /build/cuda @@ -110,13 +106,13 @@ else exec "\$@" fi EOF -_ENTRYPOINT # Make the script executable -RUN chmod +x /entrypoint.sh +chmod +x /entrypoint.sh # Note about CLion -RUN echo "ATTENTION: CLion will override the entrypoint, you can disable this in the toolchain settings" +echo "ATTENTION: CLion will override the entrypoint, you can disable this in the toolchain settings" +_ENTRYPOINT # Use the shell script as the entrypoint ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/debian-bookworm.dockerfile b/docker/debian-bookworm.dockerfile index 8a6db148..84edc4c9 100644 --- a/docker/debian-bookworm.dockerfile +++ b/docker/debian-bookworm.dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 # platforms_pr: linux/amd64 @@ -9,7 +9,7 @@ FROM ${BASE}:${TAG} AS sunshine-base ENV DEBIAN_FRONTEND=noninteractive -FROM sunshine-base as sunshine-build +FROM sunshine-base AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -31,7 +31,11 @@ RUN <<_BUILD #!/bin/bash set -e chmod +x ./scripts/linux_build.sh -./scripts/linux_build.sh --sudo-off +./scripts/linux_build.sh \ + --publisher-name='LizardByte' \ + --publisher-website='https://app.lizardbyte.dev' \ + --publisher-issue-url='https://app.lizardbyte.dev/support' \ + --sudo-off apt-get clean rm -rf /var/lib/apt/lists/* _BUILD @@ -53,7 +57,7 @@ ARG TAG ARG TARGETARCH COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb -FROM sunshine-base as sunshine +FROM sunshine-base AS sunshine # copy deb from builder COPY --link --from=artifacts /sunshine*.deb /sunshine.deb diff --git a/docker/fedora-39.dockerfile b/docker/fedora-39.dockerfile index e50fffdb..579eec9e 100644 --- a/docker/fedora-39.dockerfile +++ b/docker/fedora-39.dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 # artifacts: true # platforms: linux/amd64 # platforms_pr: linux/amd64 @@ -7,7 +7,7 @@ ARG BASE=fedora ARG TAG=39 FROM ${BASE}:${TAG} AS sunshine-base -FROM sunshine-base as sunshine-build +FROM sunshine-base AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -29,7 +29,11 @@ RUN <<_BUILD #!/bin/bash set -e chmod +x ./scripts/linux_build.sh -./scripts/linux_build.sh --sudo-off +./scripts/linux_build.sh \ + --publisher-name='LizardByte' \ + --publisher-website='https://app.lizardbyte.dev' \ + --publisher-issue-url='https://app.lizardbyte.dev/support' \ + --sudo-off dnf clean all rm -rf /var/cache/yum _BUILD @@ -51,7 +55,7 @@ ARG TAG ARG TARGETARCH COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm -FROM sunshine-base as sunshine +FROM sunshine-base AS sunshine # copy deb from builder COPY --link --from=artifacts /sunshine*.rpm /sunshine.rpm diff --git a/docker/fedora-40.dockerfile b/docker/fedora-40.dockerfile index 24735eef..fc6362d4 100644 --- a/docker/fedora-40.dockerfile +++ b/docker/fedora-40.dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 # artifacts: true # platforms: linux/amd64 # platforms_pr: linux/amd64 @@ -7,7 +7,7 @@ ARG BASE=fedora ARG TAG=40 FROM ${BASE}:${TAG} AS sunshine-base -FROM sunshine-base as sunshine-build +FROM sunshine-base AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -29,7 +29,11 @@ RUN <<_BUILD #!/bin/bash set -e chmod +x ./scripts/linux_build.sh -./scripts/linux_build.sh --sudo-off +./scripts/linux_build.sh \ + --publisher-name='LizardByte' \ + --publisher-website='https://app.lizardbyte.dev' \ + --publisher-issue-url='https://app.lizardbyte.dev/support' \ + --sudo-off dnf clean all rm -rf /var/cache/yum _BUILD @@ -51,7 +55,7 @@ ARG TAG ARG TARGETARCH COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm -FROM sunshine-base as sunshine +FROM sunshine-base AS sunshine # copy deb from builder COPY --link --from=artifacts /sunshine*.rpm /sunshine.rpm diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile index bfb2c3d0..24ceda2b 100644 --- a/docker/ubuntu-22.04.dockerfile +++ b/docker/ubuntu-22.04.dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 # platforms_pr: linux/amd64 @@ -9,7 +9,7 @@ FROM ${BASE}:${TAG} AS sunshine-base ENV DEBIAN_FRONTEND=noninteractive -FROM sunshine-base as sunshine-build +FROM sunshine-base AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -31,7 +31,11 @@ RUN <<_BUILD #!/bin/bash set -e chmod +x ./scripts/linux_build.sh -./scripts/linux_build.sh --sudo-off +./scripts/linux_build.sh \ + --publisher-name='LizardByte' \ + --publisher-website='https://app.lizardbyte.dev' \ + --publisher-issue-url='https://app.lizardbyte.dev/support' \ + --sudo-off apt-get clean rm -rf /var/lib/apt/lists/* _BUILD @@ -53,7 +57,7 @@ ARG TAG ARG TARGETARCH COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb -FROM sunshine-base as sunshine +FROM sunshine-base AS sunshine # copy deb from builder COPY --link --from=artifacts /sunshine*.deb /sunshine.deb diff --git a/docker/ubuntu-24.04.dockerfile b/docker/ubuntu-24.04.dockerfile index cbadaa62..1b7c0d6e 100644 --- a/docker/ubuntu-24.04.dockerfile +++ b/docker/ubuntu-24.04.dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 # platforms_pr: linux/amd64 @@ -9,7 +9,7 @@ FROM ${BASE}:${TAG} AS sunshine-base ENV DEBIAN_FRONTEND=noninteractive -FROM sunshine-base as sunshine-build +FROM sunshine-base AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -31,7 +31,11 @@ RUN <<_BUILD #!/bin/bash set -e chmod +x ./scripts/linux_build.sh -./scripts/linux_build.sh --sudo-off +./scripts/linux_build.sh \ + --publisher-name='LizardByte' \ + --publisher-website='https://app.lizardbyte.dev' \ + --publisher-issue-url='https://app.lizardbyte.dev/support' \ + --sudo-off apt-get clean rm -rf /var/lib/apt/lists/* _BUILD @@ -53,7 +57,7 @@ ARG TAG ARG TARGETARCH COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb -FROM sunshine-base as sunshine +FROM sunshine-base AS sunshine # copy deb from builder COPY --link --from=artifacts /sunshine*.deb /sunshine.deb diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index 496ab699..e8eb8521 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -83,7 +83,10 @@ build() { -D BUILD_WERROR=ON \ -D CMAKE_INSTALL_PREFIX=/usr \ -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ - -D SUNSHINE_ASSETS_DIR="share/sunshine" + -D SUNSHINE_ASSETS_DIR="share/sunshine" \ + -D SUNSHINE_PUBLSIHER_NAME='LizardByte' \ + -D SUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \ + -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' make -C build } diff --git a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.yml index d2b71022..f5e90739 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.yml @@ -71,12 +71,15 @@ modules: - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_CUDA_COMPILER=/app/cuda/bin/nvcc - -DSUNSHINE_ASSETS_DIR=share/sunshine + - -DSUNSHINE_BUILD_FLATPAK=ON - -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine - -DSUNSHINE_ENABLE_WAYLAND=ON - -DSUNSHINE_ENABLE_X11=ON - -DSUNSHINE_ENABLE_DRM=ON - -DSUNSHINE_ENABLE_CUDA=ON - - -DSUNSHINE_BUILD_FLATPAK=ON + - -DSUNSHINE_PUBLSIHER_NAME='LizardByte' + -DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' + -DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' sources: - type: git url: "@GITHUB_CLONE_URL@" diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 1b9e1bda..a2a9eb5e 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -43,7 +43,10 @@ depends_lib port:curl \ configure.args -DBOOST_USE_STATIC=ON \ -DBUILD_WERROR=ON \ -DCMAKE_INSTALL_PREFIX=${prefix} \ - -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets + -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets \ + -DSUNSHINE_PUBLSIHER_NAME='LizardByte' \ + -DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \ + -DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' configure.env-append BRANCH=@GITHUB_BRANCH@ configure.env-append BUILD_VERSION=@BUILD_VERSION@ diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index cf6886b4..a8d9a661 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -71,6 +71,9 @@ class @PROJECT_NAME@ < Formula -DSUNSHINE_ASSETS_DIR=sunshine/assets -DSUNSHINE_BUILD_HOMEBREW=ON -DSUNSHINE_ENABLE_TRAY=OFF + -DSUNSHINE_PUBLSIHER_NAME='LizardByte' + -DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' + -DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' ] if build.with? "docs-off" diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index 0ad3aaf6..f0cd3d7f 100644 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -3,6 +3,9 @@ set -e # Default value for arguments appimage_build=0 +publisher_name="Third Party Publisher" +publisher_website="" +publisher_issue_url="https://app.lizardbyte.dev/support" skip_cleanup=0 skip_cuda=0 skip_libva=0 @@ -21,14 +24,18 @@ Usage: $0 [options] Options: - -h, --help Display this help message. - -s, --sudo-off Disable sudo command. - --appimage-build Compile for AppImage, this will not create the AppImage, just the executable. - --skip-cleanup Do not restore the original gcc alternatives, or the math-vector.h file. - --skip-cuda Skip CUDA installation. - --skip-libva Skip libva installation. This will automatically be enabled if passing --appimage-build. - --skip-package Skip creating DEB, or RPM package. - --ubuntu-test-repo Install ppa:ubuntu-toolchain-r/test repo on Ubuntu. + -h, --help Display this help message. + -s, --sudo-off Disable sudo command. + --appimage-build Compile for AppImage, this will not create the AppImage, just the executable. + --publisher-name The name of the publisher (not developer) of the application. + --publisher-website The URL of the publisher's website. + --publisher-issue-url The URL of the publisher's support site or issue tracker. + If you provide a modified version of Sunshine, we kindly request that you use your own url. + --skip-cleanup Do not restore the original gcc alternatives, or the math-vector.h file. + --skip-cuda Skip CUDA installation. + --skip-libva Skip libva installation. This will automatically be enabled if passing --appimage-build. + --skip-package Skip creating DEB, or RPM package. + --ubuntu-test-repo Install ppa:ubuntu-toolchain-r/test repo on Ubuntu. EOF exit "$exit_code" @@ -46,6 +53,15 @@ while getopts ":hs-:" opt; do appimage_build=1 skip_libva=1 ;; + publisher-name=*) + publisher_name="${OPTARG#*=}" + ;; + publisher-website=*) + publisher_website="${OPTARG#*=}" + ;; + publisher-issue-url=*) + publisher_issue_url="${OPTARG#*=}" + ;; skip-cleanup) skip_cleanup=1 ;; skip-cuda) skip_cuda=1 ;; skip-libva) skip_libva=1 ;; @@ -268,6 +284,17 @@ function run_install() { cmake_args+=("-DSUNSHINE_BUILD_APPIMAGE=ON") fi + # Publisher metadata + if [ -n "$publisher_name" ]; then + cmake_args+=("-DSUNSHINE_PUBLISHER_NAME='${publisher_name}'") + fi + if [ -n "$publisher_website" ]; then + cmake_args+=("-DSUNSHINE_PUBLISHER_WEBSITE='${publisher_website}'") + fi + if [ -n "$publisher_issue_url" ]; then + cmake_args+=("-DSUNSHINE_PUBLISHER_ISSUE_URL='${publisher_issue_url}'") + fi + # Update the package list $package_update_command diff --git a/src/crypto.h b/src/crypto.h index 20651ecb..859c6675 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -34,6 +34,11 @@ namespace crypto { using pkey_ctx_t = util::safe_ptr; using bignum_t = util::safe_ptr; + /** + * @brief Hashes the given plaintext using SHA-256. + * @param plaintext + * @return The SHA-256 hash of the plaintext. + */ sha256_t hash(const std::string_view &plaintext); diff --git a/src/entry_handler.cpp b/src/entry_handler.cpp index 1c057c80..0f05b301 100644 --- a/src/entry_handler.cpp +++ b/src/entry_handler.cpp @@ -109,6 +109,13 @@ namespace lifetime { } } // namespace lifetime +void +log_publisher_data() { + BOOST_LOG(info) << "Package Publisher: "sv << SUNSHINE_PUBLISHER_NAME; + BOOST_LOG(info) << "Publisher Website: "sv << SUNSHINE_PUBLISHER_WEBSITE; + BOOST_LOG(info) << "Get support: "sv << SUNSHINE_PUBLISHER_ISSUE_URL; +} + #ifdef _WIN32 bool is_gamestream_enabled() { diff --git a/src/entry_handler.h b/src/entry_handler.h index 7e680b43..a2c9735a 100644 --- a/src/entry_handler.h +++ b/src/entry_handler.h @@ -108,6 +108,12 @@ namespace lifetime { get_argv(); } // namespace lifetime +/** + * @brief Log the publisher metadata provided from CMake. + */ +void +log_publisher_data(); + #ifdef _WIN32 /** * @brief Check if NVIDIA's GameStream software is running. diff --git a/src/main.cpp b/src/main.cpp index 44c3791e..26a47d6e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,6 +114,9 @@ main(int argc, char *argv[]) { // the version should be printed to the log before anything else BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER; + // Log publisher metadata + log_publisher_data(); + if (!config::sunshine.cmd.name.empty()) { auto fn = cmd_to_func.find(config::sunshine.cmd.name); if (fn == std::end(cmd_to_func)) { diff --git a/src/network.cpp b/src/network.cpp index b7b18de8..6012e32e 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -206,4 +206,34 @@ namespace net { return mapped_port; } + + /** + * @brief Returns a string for use as the instance name for mDNS. + * @param hostname The hostname to use for instance name generation. + * @return Hostname-based instance name or "Sunshine" if hostname is invalid. + */ + std::string + mdns_instance_name(const std::string_view &hostname) { + // Start with the unmodified hostname + std::string instancename { hostname.data(), hostname.size() }; + + // Truncate to 63 characters per RFC 6763 section 7.2. + if (instancename.size() > 63) { + instancename.resize(63); + } + + for (auto i = 0; i < instancename.size(); i++) { + // Replace any spaces with dashes + if (instancename[i] == ' ') { + instancename[i] = '-'; + } + else if (!std::isalnum(instancename[i]) && instancename[i] != '-') { + // Stop at the first invalid character + instancename.resize(i); + break; + } + } + + return !instancename.empty() ? instancename : "Apollo"; + } } // namespace net diff --git a/src/network.h b/src/network.h index ffc0b2d2..dbae81b9 100644 --- a/src/network.h +++ b/src/network.h @@ -105,4 +105,12 @@ namespace net { */ int encryption_mode_for_address(boost::asio::ip::address address); + + /** + * @brief Returns a string for use as the instance name for mDNS. + * @param hostname The hostname to use for instance name generation. + * @return Hostname-based instance name or "Sunshine" if hostname is invalid. + */ + std::string + mdns_instance_name(const std::string_view &hostname); } // namespace net diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index d826d77e..74f2fcca 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -51,19 +51,38 @@ namespace nvhttp { static std::string otp_passphrase; static std::chrono::time_point otp_creation_time; - class SunshineHttpsServer: public SimpleWeb::Server { + class SunshineHTTPS: public SimpleWeb::HTTPS { public: - SunshineHttpsServer(const std::string &certification_file, const std::string &private_key_file): - SimpleWeb::Server::Server(certification_file, private_key_file) {} + SunshineHTTPS(boost::asio::io_service &io_service, boost::asio::ssl::context &ctx): + SimpleWeb::HTTPS(io_service, ctx) {} + + virtual ~SunshineHTTPS() { + // Gracefully shutdown the TLS connection + SimpleWeb::error_code ec; + shutdown(ec); + } + }; + + class SunshineHTTPSServer: public SimpleWeb::ServerBase { + public: + SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file): + ServerBase::ServerBase(443), + context(boost::asio::ssl::context::tls_server) { + // Disabling TLS 1.0 and 1.1 (see RFC 8996) + context.set_options(boost::asio::ssl::context::no_tlsv1); + context.set_options(boost::asio::ssl::context::no_tlsv1_1); + context.use_certificate_chain_file(certification_file); + context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem); + } std::function verify; std::function, std::shared_ptr)> on_verify_failed; protected: + boost::asio::ssl::context context; + void after_bind() override { - SimpleWeb::Server::after_bind(); - if (verify) { context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once); context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) { @@ -115,7 +134,7 @@ namespace nvhttp { } }; - using https_server_t = SunshineHttpsServer; + using https_server_t = SunshineHTTPSServer; using http_server_t = SimpleWeb::Server; struct conf_intern_t { @@ -150,7 +169,7 @@ namespace nvhttp { struct { util::Either< std::shared_ptr::Response>, - std::shared_ptr::Response>> + std::shared_ptr::Response>> response; std::string salt; } async_insert_pin; @@ -162,8 +181,8 @@ namespace nvhttp { std::atomic session_id_counter; using args_t = SimpleWeb::CaseInsensitiveMultimap; - using resp_https_t = std::shared_ptr::Response>; - using req_https_t = std::shared_ptr::Request>; + using resp_https_t = std::shared_ptr::Response>; + using req_https_t = std::shared_ptr::Request>; using resp_http_t = std::shared_ptr::Response>; using req_http_t = std::shared_ptr::Request>; @@ -496,7 +515,7 @@ namespace nvhttp { struct tunnel; template <> - struct tunnel { + struct tunnel { static auto constexpr to_string = "HTTPS"sv; }; @@ -715,7 +734,7 @@ namespace nvhttp { print_req(request); int pair_status = 0; - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { auto args = request->parse_query_string(); auto clientID = args.find("uniqueid"s); @@ -745,7 +764,7 @@ namespace nvhttp { // Only include the MAC address for requests sent from paired clients over HTTPS. // For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore. - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address()))); } else { @@ -826,7 +845,7 @@ namespace nvhttp { void applist(resp_https_t response, req_https_t request) { - print_req(request); + print_req(request); pt::ptree tree; @@ -855,7 +874,7 @@ namespace nvhttp { void launch(bool &host_audio, resp_https_t response, req_https_t request) { - print_req(request); + print_req(request); pt::ptree tree; auto g = util::fail_guard([&]() { @@ -952,7 +971,7 @@ namespace nvhttp { void resume(bool &host_audio, resp_https_t response, req_https_t request) { - print_req(request); + print_req(request); pt::ptree tree; auto g = util::fail_guard([&]() { @@ -1038,7 +1057,7 @@ namespace nvhttp { void cancel(resp_https_t response, req_https_t request) { - print_req(request); + print_req(request); pt::ptree tree; auto g = util::fail_guard([&]() { @@ -1069,7 +1088,7 @@ namespace nvhttp { void appasset(resp_https_t response, req_https_t request) { - print_req(request); + print_req(request); auto args = request->parse_query_string(); auto app_image = proc::proc.get_app_image(util::from_view(get_arg(args, "appid"))); @@ -1162,9 +1181,9 @@ namespace nvhttp { tree.put("root..status_message"s, "The client is not authorized. Certificate verification failed."s); }; - https_server.default_resource["GET"] = not_found; - https_server.resource["^/serverinfo$"]["GET"] = serverinfo; - https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; + https_server.default_resource["GET"] = not_found; + https_server.resource["^/serverinfo$"]["GET"] = serverinfo; + https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; https_server.resource["^/applist$"]["GET"] = applist; https_server.resource["^/appasset$"]["GET"] = appasset; https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); }; diff --git a/src/platform/common.h b/src/platform/common.h index 05a2f3bb..d9c9583c 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -11,6 +11,10 @@ #include #include +#ifndef _WIN32 + #include + #include +#endif #include "src/config.h" #include "src/logging.h" @@ -31,6 +35,7 @@ struct AVHWFramesContext; struct AVCodecContext; struct AVDictionary; +#ifdef _WIN32 // Forward declarations of boost classes to avoid having to include boost headers // here, which results in issues with Windows.h and WinSock2.h include order. namespace boost { @@ -50,6 +55,7 @@ namespace boost { typedef basic_environment environment; } // namespace process } // namespace boost +#endif namespace video { struct config_t; } // namespace video @@ -612,6 +618,23 @@ namespace platf { void restart(); + /** + * @brief Set an environment variable. + * @param name The name of the environment variable. + * @param value The value to set the environment variable to. + * @return 0 on success, non-zero on failure. + */ + int + set_env(const std::string &name, const std::string &value); + + /** + * @brief Unset an environment variable. + * @param name The name of the environment variable. + * @return 0 on success, non-zero on failure. + */ + int + unset_env(const std::string &name); + struct buffer_descriptor_t { const char *buffer; size_t size; @@ -837,6 +860,8 @@ namespace platf { /** * @brief Gets the supported gamepads for this platform backend. + * @details This may be called prior to `platf::input()`! + * @param input Pointer to the platform's `input_t` or `nullptr`. * @return Vector of gamepad options and status. */ std::vector & diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 9997c405..b3e31ec6 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -326,6 +326,16 @@ namespace platf { lifetime::exit_sunshine(0, true); } + int + set_env(const std::string &name, const std::string &value) { + return setenv(name.c_str(), value.c_str(), 1); + } + + int + unset_env(const std::string &name) { + return unsetenv(name.c_str()); + } + bool request_process_group_exit(std::uintptr_t native_handle) { if (kill(-((pid_t) native_handle), SIGTERM) == 0 || errno == ESRCH) { @@ -913,6 +923,10 @@ namespace platf { std::unique_ptr init() { + // enable low latency mode for AMD + // https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039 + set_env("AMD_DEBUG", "lowlatency"); + // These are allowed to fail. gbm::init(); diff --git a/src/platform/linux/publish.cpp b/src/platform/linux/publish.cpp index 29641411..91d49248 100644 --- a/src/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -426,7 +426,8 @@ namespace platf::publish { return nullptr; } - name.reset(avahi::strdup(SERVICE_NAME)); + auto instance_name = net::mdns_instance_name(boost::asio::ip::host_name()); + name.reset(avahi::strdup(instance_name.c_str())); client.reset( avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error)); diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index f03dd3bf..6eca7d2e 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -253,6 +253,16 @@ namespace platf { lifetime::exit_sunshine(0, true); } + int + set_env(const std::string &name, const std::string &value) { + return setenv(name.c_str(), value.c_str(), 1); + } + + int + unset_env(const std::string &name) { + return unsetenv(name.c_str()); + } + bool request_process_group_exit(std::uintptr_t native_handle) { if (killpg((pid_t) native_handle, SIGTERM) == 0 || errno == ESRCH) { diff --git a/src/platform/macos/publish.cpp b/src/platform/macos/publish.cpp index ec4f1f45..b8c977c0 100644 --- a/src/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -105,7 +105,8 @@ namespace platf::publish { &serviceRef, 0, // flags 0, // interfaceIndex - SERVICE_NAME, SERVICE_TYPE, + nullptr, // name + SERVICE_TYPE, nullptr, // domain nullptr, // host htons(net::map_port(nvhttp::PORT_HTTP)), diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index a252544b..5ff61689 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1728,15 +1728,18 @@ namespace platf { std::vector & supported_gamepads(input_t *input) { - bool enabled; - if (input) { - auto vigem = ((input_raw_t *) input)->vigem; - enabled = vigem != nullptr; - } - else { - enabled = false; + if (!input) { + static std::vector gps { + supported_gamepad_t { "auto", true, "" }, + supported_gamepad_t { "x360", false, "" }, + supported_gamepad_t { "ds4", false, "" }, + }; + + return gps; } + auto vigem = ((input_raw_t *) input)->vigem; + auto enabled = vigem != nullptr; auto reason = enabled ? "" : "gamepads.vigem-not-available"; // ds4 == ps4 diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index a4190e01..657807fa 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1313,6 +1313,16 @@ namespace platf { lifetime::exit_sunshine(0, true); } + int + set_env(const std::string &name, const std::string &value) { + return _putenv_s(name.c_str(), value.c_str()); + } + + int + unset_env(const std::string &name) { + return _putenv_s(name.c_str(), ""); + } + struct enum_wnd_context_t { std::set process_ids; bool requested_exit; diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index fe3352e2..05208a9c 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -37,7 +37,6 @@ constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; #define SERVICE_DOMAIN "local" -constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); #ifndef __MINGW32__ @@ -107,10 +106,11 @@ namespace platf::publish { service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) { auto alarm = safe::make_alarm(); - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - auto host = from_utf8(boost::asio::ip::host_name() + ".local"); + auto hostname = boost::asio::ip::host_name(); + auto name = from_utf8(net::mdns_instance_name(hostname) + '.') + domain; + auto host = from_utf8(hostname + ".local"); DNS_SERVICE_INSTANCE instance {}; instance.pszInstanceName = name.data(); diff --git a/src_assets/common/assets/web/public/assets/locale/es.json b/src_assets/common/assets/web/public/assets/locale/es.json index 17418c0a..dec0a264 100644 --- a/src_assets/common/assets/web/public/assets/locale/es.json +++ b/src_assets/common/assets/web/public/assets/locale/es.json @@ -20,7 +20,7 @@ "see_more": "Ver más", "success": "¡Éxito!", "undo_cmd": "Deshacer comando", - "username": "Usuario", + "username": "Nombre de usuario", "warning": "¡Advertencia!" }, "apps": { diff --git a/tests/tests_log_checker.h b/tests/tests_log_checker.h new file mode 100644 index 00000000..656151f6 --- /dev/null +++ b/tests/tests_log_checker.h @@ -0,0 +1,135 @@ +/** + * @file tests/tests_log_checker.h + * @brief Utility functions to check log file contents. + */ +#pragma once + +#include +#include +#include +#include + +#include + +namespace log_checker { + + /** + * @brief Remove the timestamp prefix from a log line. + * @param line The log line. + * @return The log line without the timestamp prefix. + */ + inline std::string + remove_timestamp_prefix(const std::string &line) { + static const std::regex timestamp_regex(R"(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]: )"); + return std::regex_replace(line, timestamp_regex, ""); + } + + /** + * @brief Check if a log file contains a line that starts with the given string. + * @param log_file Path to the log file. + * @param start_str The string that the line should start with. + * @return True if such a line is found, false otherwise. + */ + inline bool + line_starts_with(const std::string &log_file, const std::string_view &start_str) { + logging::log_flush(); + + std::ifstream input(log_file); + if (!input.is_open()) { + return false; + } + + for (std::string line; std::getline(input, line);) { + line = remove_timestamp_prefix(line); + if (line.rfind(start_str, 0) == 0) { + return true; + } + } + return false; + } + + /** + * @brief Check if a log file contains a line that ends with the given string. + * @param log_file Path to the log file. + * @param end_str The string that the line should end with. + * @return True if such a line is found, false otherwise. + */ + inline bool + line_ends_with(const std::string &log_file, const std::string_view &end_str) { + logging::log_flush(); + + std::ifstream input(log_file); + if (!input.is_open()) { + return false; + } + + for (std::string line; std::getline(input, line);) { + line = remove_timestamp_prefix(line); + if (line.size() >= end_str.size() && + line.compare(line.size() - end_str.size(), end_str.size(), end_str) == 0) { + return true; + } + } + return false; + } + + /** + * @brief Check if a log file contains a line that equals the given string. + * @param log_file Path to the log file. + * @param str The string that the line should equal. + * @return True if such a line is found, false otherwise. + */ + inline bool + line_equals(const std::string &log_file, const std::string_view &str) { + logging::log_flush(); + + std::ifstream input(log_file); + if (!input.is_open()) { + return false; + } + + for (std::string line; std::getline(input, line);) { + line = remove_timestamp_prefix(line); + if (line == str) { + return true; + } + } + return false; + } + + /** + * @brief Check if a log file contains a line that contains the given substring. + * @param log_file Path to the log file. + * @param substr The substring to search for. + * @param case_insensitive Whether the search should be case-insensitive. + * @return True if such a line is found, false otherwise. + */ + inline bool + line_contains(const std::string &log_file, const std::string_view &substr, bool case_insensitive = false) { + logging::log_flush(); + + std::ifstream input(log_file); + if (!input.is_open()) { + return false; + } + + std::string search_str(substr); + if (case_insensitive) { + // sonarcloud complains about this, but the solution doesn't work for macOS-12 + std::transform(search_str.begin(), search_str.end(), search_str.begin(), ::tolower); + } + + for (std::string line; std::getline(input, line);) { + line = remove_timestamp_prefix(line); + if (case_insensitive) { + // sonarcloud complains about this, but the solution doesn't work for macOS-12 + std::transform(line.begin(), line.end(), line.begin(), ::tolower); + } + if (line.find(search_str) != std::string::npos) { + return true; + } + } + return false; + } + +} // namespace log_checker diff --git a/tests/unit/platform/test_common.cpp b/tests/unit/platform/test_common.cpp new file mode 100644 index 00000000..6f1c9bec --- /dev/null +++ b/tests/unit/platform/test_common.cpp @@ -0,0 +1,49 @@ +/** + * @file tests/unit/platform/test_common.cpp + * @brief Test src/platform/common.*. + */ +#include + +#include "../../tests_common.h" + +struct SetEnvTest: ::testing::TestWithParam> { +protected: + void + TearDown() override { + // Clean up environment variable after each test + const auto &[name, value, expected] = GetParam(); + platf::unset_env(name); + } +}; + +TEST_P(SetEnvTest, SetEnvironmentVariableTests) { + const auto &[name, value, expected] = GetParam(); + platf::set_env(name, value); + + const char *env_value = std::getenv(name.c_str()); + if (expected == 0 && !value.empty()) { + ASSERT_NE(env_value, nullptr); + ASSERT_EQ(std::string(env_value), value); + } + else { + ASSERT_EQ(env_value, nullptr); + } +} + +TEST_P(SetEnvTest, UnsetEnvironmentVariableTests) { + const auto &[name, value, expected] = GetParam(); + platf::unset_env(name); + + const char *env_value = std::getenv(name.c_str()); + if (expected == 0) { + ASSERT_EQ(env_value, nullptr); + } +} + +INSTANTIATE_TEST_SUITE_P( + SetEnvTests, + SetEnvTest, + ::testing::Values( + std::make_tuple("SUNSHINE_UNIT_TEST_ENV_VAR", "test_value_0", 0), + std::make_tuple("SUNSHINE_UNIT_TEST_ENV_VAR", "test_value_1", 0), + std::make_tuple("", "test_value", -1))); diff --git a/tests/unit/test_entry_handler.cpp b/tests/unit/test_entry_handler.cpp new file mode 100644 index 00000000..d1e2b061 --- /dev/null +++ b/tests/unit/test_entry_handler.cpp @@ -0,0 +1,18 @@ +/** + * @file tests/unit/test_entry_handler.cpp + * @brief Test src/entry_handler.*. + */ +#include + +#include "../tests_common.h" +#include "../tests_log_checker.h" + +TEST(EntryHandlerTests, LogPublisherDataTest) { + // call log_publisher_data + log_publisher_data(); + + // check if specific log messages exist + ASSERT_TRUE(log_checker::line_starts_with("test_sunshine.log", "Info: Package Publisher: ")); + ASSERT_TRUE(log_checker::line_starts_with("test_sunshine.log", "Info: Publisher Website: ")); + ASSERT_TRUE(log_checker::line_starts_with("test_sunshine.log", "Info: Get support: ")); +} diff --git a/tests/unit/test_logging.cpp b/tests/unit/test_logging.cpp index 99b4b264..b53b493b 100644 --- a/tests/unit/test_logging.cpp +++ b/tests/unit/test_logging.cpp @@ -5,8 +5,8 @@ #include #include "../tests_common.h" +#include "../tests_log_checker.h" -#include #include namespace { @@ -40,25 +40,5 @@ TEST_P(LogLevelsTest, PutMessage) { auto test_message = std::to_string(rand_gen()) + std::to_string(rand_gen()); BOOST_LOG(logger) << test_message; - // Flush logger and search for the message in the log file - - logging::log_flush(); - - std::ifstream input(log_file); - ASSERT_TRUE(input.is_open()); - - bool found = false; - for (std::string line; std::getline(input, line);) { - if (line.find(test_message) != std::string::npos) { - // Assume that logger may change the case of log level label - std::transform(line.begin(), line.end(), line.begin(), - [](char c) { return std::tolower(c); }); - - if (line.find(label) != std::string::npos) { - found = true; - break; - } - } - } - ASSERT_TRUE(found); + ASSERT_TRUE(log_checker::line_contains(log_file, test_message)); } diff --git a/tests/unit/test_network.cpp b/tests/unit/test_network.cpp new file mode 100644 index 00000000..fc8384ad --- /dev/null +++ b/tests/unit/test_network.cpp @@ -0,0 +1,26 @@ +/** + * @file tests/unit/test_network.cpp + * @brief Test src/network.* + */ +#include + +#include "../tests_common.h" + +struct MdnsInstanceNameTest: testing::TestWithParam> {}; + +TEST_P(MdnsInstanceNameTest, Run) { + auto [input, expected] = GetParam(); + ASSERT_EQ(net::mdns_instance_name(input), expected); +} + +INSTANTIATE_TEST_SUITE_P( + MdnsInstanceNameTests, + MdnsInstanceNameTest, + testing::Values( + std::make_tuple("shortname-123", "shortname-123"), + std::make_tuple("space 123", "space-123"), + std::make_tuple("hostname.domain.test", "hostname"), + std::make_tuple("&", "Sunshine"), + std::make_tuple("", "Sunshine"), + std::make_tuple("😁", "Sunshine"), + std::make_tuple(std::string(128, 'a'), std::string(63, 'a'))));