diff --git a/CMakeLists.txt b/CMakeLists.txt index 91f14fae4..432b0d52a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ project(lokinet if(APPLE) # Apple build number: must be incremented to submit a new build for the same lokinet version, # should be reset to 0 when the lokinet version increments. - set(LOKINET_APPLE_BUILD 0) + set(LOKINET_APPLE_BUILD 1) endif() set(RELEASE_MOTTO "Gluten Free Edition" CACHE STRING "Release motto") @@ -106,6 +106,7 @@ endif() include(cmake/solaris.cmake) include(cmake/win32.cmake) +include(cmake/macos.cmake) # No in-source building include(MacroEnsureOutOfSourceBuild) @@ -302,6 +303,10 @@ add_subdirectory(docs) include(cmake/gui.cmake) +if(APPLE) + macos_target_setup() +endif() + # uninstall target if(NOT TARGET uninstall) configure_file( diff --git a/cmake/gui.cmake b/cmake/gui.cmake index 3d13c32b0..f0ba30516 100644 --- a/cmake/gui.cmake +++ b/cmake/gui.cmake @@ -18,20 +18,34 @@ if (BUILD_GUI) find_program(YARN NAMES yarn yarnpkg REQUIRED) message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}") + set(wine_env) + if(WIN32) + set(wine_env WINEDEBUG=-all "WINEPREFIX=${PROJECT_BINARY_DIR}/wineprefix") + endif() + add_custom_target(lokinet-gui COMMAND ${YARN} install --frozen-lockfile && - WINEDEBUG=-all "WINEPREFIX=${PROJECT_BINARY_DIR}/wineprefix" ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} + ${wine_env} ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") if(APPLE) - add_custom_target(copy_gui ALL - DEPENDS lokinet lokinet-extension lokinet-gui - # FIXME: we really shouldn't be building inside the source directory but this is npm... - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${PROJECT_SOURCE_DIR}/lokinet-gui/release/mac/lokinet-gui.app - $ + add_custom_target(assemble_gui ALL + DEPENDS assemble lokinet-gui + COMMAND mkdir "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers" + COMMAND cp -a "${PROJECT_SOURCE_DIR}/gui/release/mac/Lokinet-GUI.app" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/" + COMMAND mkdir -p "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/en.lproj" + COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/en.lproj/" + COMMAND cp "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/icon.icns" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/icon.icns" + COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/en.lproj/" + COMMAND /usr/libexec/PlistBuddy + -c "Delete :CFBundleDisplayName" + -c "Add :LSHasLocalizedDisplayName bool true" + -c "Add :CFBundleDevelopmentRegion string en" + -c "Set :CFBundleShortVersionString ${lokinet_VERSION}" + -c "Set :CFBundleVersion ${lokinet_VERSION}.${LOKINET_APPLE_BUILD}" + "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/Lokinet-GUI.app/Contents/Info.plist" ) - add_dependencies(assemble copy_gui) + elseif(WIN32) file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui") add_custom_target(copy_gui ALL diff --git a/cmake/macos.cmake b/cmake/macos.cmake new file mode 100644 index 000000000..4b81c3c8d --- /dev/null +++ b/cmake/macos.cmake @@ -0,0 +1,176 @@ +if(NOT APPLE) + return() +endif() + + +option(MACOS_SYSTEM_EXTENSION + "Build the network extension as a system extension rather than a plugin. This must be ON for non-app store release builds, and must be OFF for dev builds and Mac App Store distribution builds" + OFF) +option(CODESIGN "codesign the resulting app and extension" ON) +set(CODESIGN_ID "" CACHE STRING "codesign the macos app using this key identity; if empty we'll try to guess") +set(default_profile_type "dev") +if(MACOS_SYSTEM_EXTENSION) + set(default_profile_type "release") +endif() +set(CODESIGN_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.${default_profile_type}.provisionprofile" CACHE FILEPATH + "Path to a .provisionprofile to use for the main app") + +if(CODESIGN AND NOT CODESIGN_ID) + if(MACOS_SYSTEM_EXTENSION) + set(codesign_cert_pattern "Developer ID Application") + else() + set(codesign_cert_pattern "Apple Development") + endif() + execute_process( + COMMAND security find-identity -v -p codesigning + COMMAND sed -n "s/^ *[0-9][0-9]*) *\\([A-F0-9]\\{40\\}\\) *\"\\(${codesign_cert_pattern}.*\\)\"\$/\\1 \\2/p" + RESULT_VARIABLE find_id_exit_code + OUTPUT_VARIABLE find_id_output) + if(NOT find_id_exit_code EQUAL 0) + message(FATAL_ERROR "Finding signing identities with security find-identity failed; try specifying an id using -DCODESIGN_ID=...") + endif() + + string(REGEX MATCHALL "(^|\n)[0-9A-F]+" find_id_sign_id "${find_id_output}") + if(NOT find_id_sign_id) + message(FATAL_ERROR "Did not find any \"${codesign_cert_pattern}\" identity; try specifying an id using -DCODESIGN_ID=...") + endif() + if (find_id_sign_id MATCHES ";") + message(FATAL_ERROR "Found multiple \"${codesign_cert_pattern}\" identities:\n${find_id_output}\nSpecify an identify using -DCODESIGN_ID=...") + endif() + set(CODESIGN_ID "${find_id_sign_id}" CACHE STRING "" FORCE) +endif() + +if(CODESIGN) + message(STATUS "Codesigning using ${CODESIGN_ID}") + + if (NOT MACOS_NOTARIZE_USER AND NOT MACOS_NOTARIZE_PASS AND NOT MACOS_NOTARIZE_ASC AND EXISTS "$ENV{HOME}/.notarization.cmake") + message(STATUS "Loading notarization info from ~/.notarization.cmake") + include("$ENV{HOME}/.notarization.cmake") + endif() + + if (MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC) + message(STATUS "Enabling notarization with account ${MACOS_NOTARIZE_ASC}/${MACOS_NOTARIZE_USER}") + else() + message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization will fail; see contrib/macos/README.txt") + endif() + +else() + message(WARNING "Codesigning disabled; the resulting build will not run on most macOS systems") +endif() + + +if(NOT CODESIGN_PROFILE) + message(WARNING "Missing a CODESIGN_PROFILE provisioning profile: Apple will most likely log an uninformative error message to the system log and then kill harmless kittens if you try to run the result") +endif() +if(NOT EXISTS "${CODESIGN_PROFILE}") + message(FATAL_ERROR "Provisioning profile ${CODESIGN_PROFILE} does not exist; fix your -DCODESIGN_PROFILE path") +endif() +message(STATUS "Using ${CODESIGN_PROFILE} provisioning profile") + + +if(MACOS_SYSTEM_EXTENSION) + set(lokinet_ext_dir Contents/Library/SystemExtensions) +else() + set(lokinet_ext_dir Contents/PlugIns) +endif() + +if(CODESIGN) + if(MACOS_SYSTEM_EXTENSION) + set(LOKINET_ENTITLEMENTS_TYPE sysext) + set(notarize_py_is_sysext True) + else() + set(LOKINET_ENTITLEMENTS_TYPE plugin) + set(notarize_py_is_sysext False) + endif() + + configure_file( + "${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in" + "${PROJECT_BINARY_DIR}/sign.sh" + @ONLY) + + add_custom_target( + sign + DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" + COMMAND "${PROJECT_BINARY_DIR}/sign.sh" + ) + + if(MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC) + configure_file( + "${PROJECT_SOURCE_DIR}/contrib/macos/notarize.py.in" + "${PROJECT_BINARY_DIR}/notarize.py" + @ONLY) + add_custom_target( + notarize + DEPENDS "${PROJECT_BINARY_DIR}/notarize.py" sign + COMMAND "${PROJECT_BINARY_DIR}/notarize.py" + ) + else() + message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization disabled") + endif() +else() + add_custom_target(sign COMMAND "true") + add_custom_target(notarize DEPENDS sign COMMAND "true") +endif() + + +# Called later to set things up, after the main lokinet targets are set up +function(macos_target_setup) + + if(MACOS_SYSTEM_EXTENSION) + target_compile_definitions(lokinet PRIVATE MACOS_SYSTEM_EXTENSION) + endif() + + set_target_properties(lokinet + PROPERTIES + OUTPUT_NAME Lokinet + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_STRING "Lokinet IP Packet Onion Router" + MACOSX_BUNDLE_BUNDLE_NAME "Lokinet" + MACOSX_BUNDLE_BUNDLE_VERSION "${lokinet_VERSION}" + MACOSX_BUNDLE_LONG_VERSION_STRING "${lokinet_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${lokinet_VERSION_MAJOR}.${lokinet_VERSION_MINOR}" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.lokinet" + MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.Info.plist.in" + MACOSX_BUNDLE_COPYRIGHT "© 2022, The Oxen Project" + ) + + add_custom_target(copy_bootstrap + DEPENDS lokinet-extension + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed + $/Contents/Resources/bootstrap.signed + ) + + set(mac_icon ${PROJECT_BINARY_DIR}/lokinet.icns) + add_custom_command(OUTPUT ${mac_icon} + COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet-mac.svg ${mac_icon} + DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh) + add_custom_target(icon DEPENDS ${mac_icon}) + + + add_dependencies(lokinet lokinet-extension icon) + + + if(CODESIGN_PROFILE) + add_custom_target(copy_prov_prof + DEPENDS lokinet + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CODESIGN_PROFILE} + $/Contents/embedded.provisionprofile + ) + else() + add_custom_target(copy_prov_prof COMMAND true) + endif() + + add_custom_target(assemble ALL + DEPENDS lokinet lokinet-extension icon copy_prov_prof copy_bootstrap + COMMAND rm -rf "${PROJECT_BINARY_DIR}/Lokinet.app" + COMMAND cp -a $ "${PROJECT_BINARY_DIR}/Lokinet.app" + COMMAND mkdir -p "${PROJECT_BINARY_DIR}/Lokinet.app/${lokinet_ext_dir}" + COMMAND cp -a $ "${PROJECT_BINARY_DIR}/Lokinet.app/${lokinet_ext_dir}/" + COMMAND mkdir -p "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources" + COMMAND cp -a "${mac_icon}" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/icon.icns" + ) + + if(CODESIGN) + add_dependencies(sign assemble) + endif() +endfunction() diff --git a/contrib/lokinet-mac.svg b/contrib/lokinet-mac.svg new file mode 100644 index 000000000..d84e604a4 --- /dev/null +++ b/contrib/lokinet-mac.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/contrib/mac.sh b/contrib/mac.sh index 9460fd3bc..08645149b 100755 --- a/contrib/mac.sh +++ b/contrib/mac.sh @@ -20,6 +20,7 @@ cmake \ -DBUILD_STATIC_DEPS=ON \ -DBUILD_LIBLOKINET=OFF \ -DWITH_TESTS=OFF \ + -DWITH_BOOTSTRAP=OFF \ -DNATIVE_BUILD=OFF \ -DWITH_LTO=ON \ -DCMAKE_BUILD_TYPE=Release \ diff --git a/contrib/macos/InfoPlist.strings b/contrib/macos/InfoPlist.strings new file mode 100644 index 000000000..873c6aed6 Binary files /dev/null and b/contrib/macos/InfoPlist.strings differ diff --git a/contrib/macos/lokinet.Info.plist.in b/contrib/macos/lokinet.Info.plist.in index 6608e7b30..1ca51c59e 100644 --- a/contrib/macos/lokinet.Info.plist.in +++ b/contrib/macos/lokinet.Info.plist.in @@ -5,11 +5,8 @@ CFBundleDevelopmentRegion en - CFBundleDisplayName - Lokinet - CFBundleExecutable - lokinet-gui + Lokinet CFBundleIdentifier org.lokinet @@ -20,6 +17,9 @@ CFBundleName Lokinet + CFBundleIconFile + icon.icns + CFBundlePackageType APPL @@ -32,8 +32,14 @@ LSMinimumSystemVersion 10.15 - NSHumanReadableCopyright - Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later + NSHumanReadableCopyright + Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later - + LSUIElement + + + LSHasLocalizedDisplayName + + + diff --git a/contrib/macos/mk-icns.sh b/contrib/macos/mk-icns.sh index 8ba62be49..d13f8504d 100755 --- a/contrib/macos/mk-icns.sh +++ b/contrib/macos/mk-icns.sh @@ -14,7 +14,7 @@ done mv "${outdir}/icon_1024x1024.png" "${outdir}/icon_512x512@2x.png" for size in 16 32 128 256; do double=$((size * 2)) - cp "${outdir}/icon_${double}x${double}.png" "${outdir}/icon_${size}x${size}@2x.png" + ln -f "${outdir}/icon_${double}x${double}.png" "${outdir}/icon_${size}x${size}@2x.png" done iconutil -c icns "${outdir}" diff --git a/contrib/macos/network.loki.lokinet.daemon.plist b/contrib/macos/network.loki.lokinet.daemon.plist deleted file mode 100644 index cfdaa48ae..000000000 --- a/contrib/macos/network.loki.lokinet.daemon.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Label - network.loki.lokinet.daemon - - ProgramArguments - - /var/lib/lokinet/lokinet_macos_daemon_script.sh - - - - KeepAlive - - PathState - - /var/lib/lokinet/suspend-launchd-service - - - - - StandardOutPath - /var/log/lokinet.log - - diff --git a/contrib/macos/notarize.py.in b/contrib/macos/notarize.py.in index 2ce222459..a9be5af41 100755 --- a/contrib/macos/notarize.py.in +++ b/contrib/macos/notarize.py.in @@ -24,11 +24,11 @@ if not all(("@MACOS_NOTARIZE_USER@", "@MACOS_NOTARIZE_PASS@", "@MACOS_NOTARIZE_A os.chdir("@PROJECT_BINARY_DIR@") app = "Lokinet.app" -zipfile = "Lokinet.app.notarize.zip" -print(f"Creating lokinet.app.notarize.zip from lokinet.app") +zipfile = f"{app}.notarize.zip" +print(f"Creating {zipfile} from {app}") if os.path.exists(zipfile): os.remove(zipfile) -subprocess.run(['zip', '-r', zipfile, app]) +subprocess.run(['ditto', '-v', '-c', '-k', '--sequesterRsrc', '--keepParent', app, zipfile]) userpass = ('--username', "@MACOS_NOTARIZE_USER@", '--password', "@MACOS_NOTARIZE_PASS@") print("Submitting {} for notarization; this may take a minute...".format(zipfile)) diff --git a/contrib/macos/sign.sh.in b/contrib/macos/sign.sh.in index a949f1ec5..73abd945c 100755 --- a/contrib/macos/sign.sh.in +++ b/contrib/macos/sign.sh.in @@ -2,24 +2,71 @@ set -e -if [ -z "@CODESIGN" ]; then +if [ "@CODESIGN@" != "ON" ]; then echo "Cannot codesign: this build was not configured with codesigning" >&2 exit 1 fi +signit() { + target="$1" + entitlements="$2" + echo -e "\n\e[33;1mSigning ${target/*\/Lokinet.app/Lokinet.app}...\e[0m" >&2 + codesign \ + --verbose=4 \ + --force \ + -s "@CODESIGN_ID@" \ + --entitlements "$entitlements" \ + --strict \ + --timestamp \ + --options=runtime \ + "$target" +} + +gui_entitlements="@PROJECT_SOURCE_DIR@/gui/node_modules/app-builder-lib/templates/entitlements.mac.plist" +ext_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" +app_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" + +SIGN_TARGET="@PROJECT_BINARY_DIR@/Lokinet.app" + for ext in systemextension appex; do - netext="@lokinet_ext_dir@/org.lokinet.network-extension.$ext" - if [ -e "@SIGN_TARGET@/$netext" ]; then - echo -e "\n\e[33;1mSigning $netext...\e[0m\n" >&2 - codesign --verbose=4 --force -s "@CODESIGN_ID@" \ - --entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" \ - --deep --strict --timestamp --options=runtime "@SIGN_TARGET@/$netext" + netext="$SIGN_TARGET/@lokinet_ext_dir@/org.lokinet.network-extension.$ext" + if [ -e "$netext" ]; then + signit "$netext" "$ext_entitlements" fi done -for sub in "/Contents/MacOS/Lokinet" "" ; do - echo -e "\n\e[33;1mSigning $(basename @SIGN_TARGET@)$sub...\e[0m\n" >&2 - codesign --verbose=4 --force -s "@CODESIGN_ID@" \ - --entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" \ - --deep --strict --timestamp --options=runtime "@SIGN_TARGET@$sub" -done +if [ "@BUILD_GUI@" == "ON" ]; then + gui_app="$SIGN_TARGET"/Contents/Helpers/Lokinet-GUI.app + gui_sign_targets=() + for bundle in \ + "$gui_app"/Contents/Frameworks/*.framework \ + "$gui_app"/Contents/Frameworks/*.app + do + + if [ -d "$bundle/Libraries" ]; then + gui_sign_targets+=("$bundle"/Libraries/*.dylib) + fi + if [ -d "$bundle/Helpers" ]; then + gui_sign_targets+=("$bundle"/Helpers/*) + fi + if [ -d "$bundle/Resources" ]; then + for f in "$bundle/Resources"/*; do + if [[ -f "$f" && -x "$f" && "$(file -b "$f")" == Mach-O* ]]; then + gui_sign_targets+=("$f") + fi + done + fi + + gui_sign_targets+=("$bundle") + done + + gui_sign_targets+=("$gui_app") + + for target in "${gui_sign_targets[@]}"; do + signit "$target" "$gui_entitlements" + done + + signit "$SIGN_TARGET"/Contents/MacOS/Lokinet "$app_entitlements" +fi + +signit "$SIGN_TARGET" "$app_entitlements" diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 0025afbc8..1701d4fb4 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -67,171 +67,6 @@ foreach(exe ${exetargets}) endif() endforeach() -if(APPLE) - option(MACOS_SYSTEM_EXTENSION - "Build the network extension as a system extension rather than a plugin. This must be ON for non-app store release builds, and must be OFF for dev builds and Mac App Store distribution builds" - OFF) - option(CODESIGN "codesign the resulting app and extension" ON) - set(CODESIGN_ID "" CACHE STRING "codesign the macos app using this key identity; if empty we'll try to guess") - set(default_profile_type "dev") - if(MACOS_SYSTEM_EXTENSION) - set(default_profile_type "release") - endif() - set(CODESIGN_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.${default_profile_type}.provisionprofile" CACHE FILEPATH - "Path to a .provisionprofile to use for the main app") - set(CODESIGN_EXT_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.${default_profile_type}.provisionprofile" CACHE FILEPATH - "Path to a .provisionprofile to use for the extension") - - if(CODESIGN AND NOT CODESIGN_ID) - if(MACOS_SYSTEM_EXTENSION) - set(codesign_cert_pattern "Developer ID Application") - else() - set(codesign_cert_pattern "Apple Development") - endif() - execute_process( - COMMAND security find-identity -v -p codesigning - COMMAND sed -n "s/^ *[0-9][0-9]*) *\\([A-F0-9]\\{40\\}\\) *\"\\(${codesign_cert_pattern}.*\\)\"\$/\\1 \\2/p" - RESULT_VARIABLE find_id_exit_code - OUTPUT_VARIABLE find_id_output) - if(NOT find_id_exit_code EQUAL 0) - message(FATAL_ERROR "Finding signing identities with security find-identity failed; try specifying an id using -DCODESIGN_ID=...") - endif() - - string(REGEX MATCHALL "(^|\n)[0-9A-F]+" find_id_sign_id "${find_id_output}") - if(NOT find_id_sign_id) - message(FATAL_ERROR "Did not find any \"${codesign_cert_pattern}\" identity; try specifying an id using -DCODESIGN_ID=...") - endif() - if (find_id_sign_id MATCHES ";") - message(FATAL_ERROR "Found multiple \"${codesign_cert_pattern}\" identities:\n${find_id_output}\nSpecify an identify using -DCODESIGN_ID=...") - endif() - set(CODESIGN_ID "${find_id_sign_id}" CACHE STRING "" FORCE) - endif() - - if(CODESIGN) - message(STATUS "Codesigning using ${CODESIGN_ID}") - else() - message(WARNING "Codesigning disabled; the resulting build will not run on most macOS systems") - endif() - - if(MACOS_SYSTEM_EXTENSION) - set(lokinet_ext_dir Contents/Library/SystemExtensions) - target_compile_definitions(lokinet PRIVATE MACOS_SYSTEM_EXTENSION) - if (NOT MACOS_NOTARIZE_USER AND NOT MACOS_NOTARIZE_PASS AND NOT MACOS_NOTARIZE_ASC AND EXISTS "$ENV{HOME}/.notarization.cmake") - message(STATUS "Loading notarization info from ~/.notarization.cmake") - include("$ENV{HOME}/.notarization.cmake") - endif() - if (MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC) - message(STATUS "Enabling notarization with account ${MACOS_NOTARIZE_ASC}/${MACOS_NOTARIZE_USER}") - else() - message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization will fail; see contrib/macos/README.txt") - endif() - else() - set(lokinet_ext_dir Contents/PlugIns) - endif() - - foreach(var CODESIGN_PROFILE CODESIGN_EXT_PROFILE) - if(NOT ${var}) - message(WARNING "Missing a ${var} provisioning profile, and not building a system extension: Apple will most likely log an uninformative error message to the system log and then kill harmless kittens if you try to run the result") - endif() - if(NOT EXISTS "${${var}}") - message(FATAL_ERROR "Provisioning profile ${${var}} does not exist; fix your -D${var} path") - endif() - endforeach() - message(STATUS "Using ${CODESIGN_PROFILE} provisioning profile") - message(STATUS "Using ${CODESIGN_EXT_PROFILE} extension provisioning profile") - - set(mac_icon ${CMAKE_CURRENT_BINARY_DIR}/lokinet.icns) - add_custom_command(OUTPUT ${mac_icon} - COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${mac_icon} - DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh) - add_custom_target(icons DEPENDS ${mac_icon}) - add_dependencies(lokinet icons lokinet-extension) - set(post_build_pp) - if(CODESIGN AND CODESIGN_PROFILE) - set(post_build_pp COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CODESIGN_PROFILE} - $/Contents/embedded.provisionprofile) - endif() - - set_target_properties(lokinet - PROPERTIES - OUTPUT_NAME Lokinet - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_INFO_STRING "Lokinet IP Packet Onion Router" - MACOSX_BUNDLE_BUNDLE_NAME "Lokinet" - MACOSX_BUNDLE_BUNDLE_VERSION "${lokinet_VERSION}" - MACOSX_BUNDLE_LONG_VERSION_STRING "${lokinet_VERSION}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${lokinet_VERSION_MAJOR}.${lokinet_VERSION_MINOR}" - MACOSX_BUNDLE_GUI_IDENTIFIER "org.lokinet" - MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.Info.plist.in" - MACOSX_BUNDLE_ICON_FILE "${mac_icon}" - MACOSX_BUNDLE_COPYRIGHT "© 2022, The Oxen Project" - RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}" - ) - - add_custom_target(assemble - DEPENDS lokinet lokinet-extension - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed - $/Contents/Resources/bootstrap.signed - COMMAND mkdir -p $/${lokinet_ext_dir} - COMMAND cp -a $ $/${lokinet_ext_dir} - ${post_build_pp}) - - if(TARGET lokinet-gui) - add_custom_target(copy_gui - DEPENDS lokinet lokinet-extension lokinet-gui - # FIXME: we really shouldn't be building inside the source directory but this is npm... - COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/lokinet-gui/release/mac/lokinet-gui.app $ - ) - add_dependencies(assemble copy_gui) - endif() - - if(NOT CODESIGN) - message(STATUS "codesigning disabled") - add_custom_target( - sign - DEPENDS assemble - COMMAND "true") - - elseif(CODESIGN) - set(SIGN_TARGET "${PROJECT_BINARY_DIR}/Lokinet.app") - if(MACOS_SYSTEM_EXTENSION) - set(LOKINET_ENTITLEMENTS_TYPE sysext) - else() - set(LOKINET_ENTITLEMENTS_TYPE plugin) - endif() - configure_file( - "${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in" - "${PROJECT_BINARY_DIR}/sign.sh" - @ONLY) - add_custom_target( - sign - DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" assemble - COMMAND "${PROJECT_BINARY_DIR}/sign.sh" - ) - - if(NOT (MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC)) - message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization disabled") - endif() - if (MACOS_SYSTEM_EXTENSION) - set(notarize_py_is_sysext True) - else() - set(notarize_py_is_sysext False) - endif() - configure_file( - "${PROJECT_SOURCE_DIR}/contrib/macos/notarize.py.in" - "${PROJECT_BINARY_DIR}/notarize.py" - @ONLY) - add_custom_target( - notarize - DEPENDS "${PROJECT_BINARY_DIR}/notarize.py" sign - COMMAND "${PROJECT_BINARY_DIR}/notarize.py" - ) - - else() - message(FATAL_ERROR "CODESIGN_APP (=${CODESIGN_APP}) and/or CODESIGN_EXT (=${CODESIGN_EXT}) are not set. To disable code signing use -DCODESIGN=OFF") - endif() -endif() - if(SETCAP) install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)") endif() diff --git a/daemon/lokinet.swift b/daemon/lokinet.swift index 319c16af5..9e3c67f35 100644 --- a/daemon/lokinet.swift +++ b/daemon/lokinet.swift @@ -8,7 +8,7 @@ let app = NSApplication.shared let START = "--start" let STOP = "--stop" -let HELP_STRING = "usage: lokinet [--start|--stop]" +let HELP_STRING = "usage: lokinet {--start|--stop}" class LokinetMain: NSObject, NSApplicationDelegate { var vpnManager = NETunnelProviderManager() @@ -46,9 +46,8 @@ class LokinetMain: NSObject, NSApplicationDelegate { if let savedManagers = savedManagers { for manager in savedManagers { if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId { - manager.isEnabled = false + manager.connection.stopVPNTunnel() self.result(msg: "Lokinet Down") - return } } } @@ -155,6 +154,7 @@ class LokinetMain: NSObject, NSApplicationDelegate { func request(_: OSSystemExtensionRequest, didFailWithError error: Error) { NSLog("System extension request failed: %@", error.localizedDescription) + self.bail() } func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { @@ -174,9 +174,60 @@ class LokinetMain: NSObject, NSApplicationDelegate { let args = CommandLine.arguments -if args.count <= 2 { +// If we are invoked with no arguments then exec the gui. This is dumb, but there doesn't seem to +// be a nicer way to do this on Apple's half-baked platform because: +// - we have three "bundles" we need to manage: the GUI app, the system extension, and the Lokinet +// app (this file) which loads the system extension. +// - if we embed the system extension directly inside the GUI then it fails to launch because the +// electron GUI's requirements (needed for JIT) conflict with the ability to load a system +// extensions. +// - if we embed Lokinet.app inside Lokinet-GUI.app and then the system extension inside Lokinet.app +// then it works, but macos loses track of the system extension and doesn't remove it when you +// remove the application. (It breaks your system, leaving an impossible-to-remove system +// extension, in just the same way it breaks if you don't use Finder to remove the Application. +// Apple used to say (around 2 years ago as of writing) that they would fix this situation "soon", +// but hasn't, and has stopped saying anything about it.) +// - if we try to use multiple executables (one to launch the system extension, one simple shell +// script to execs the embedded GUI app) inside the Lokinet.app and make the GUI the default for +// the application then Lokinet gets killed by gatekeeper because code signing only applies the +// (required-for-system-extensions) provisioningprofile to the main binary in the app. +// +// So we are left needing *one* single binary that isn't the GUI but has to do double-duty for both +// exec'ing the binary and loading lokinet, depending on how it is called. +// +// But of course there is no way to specify command-line arguments to the default binary macOS runs, +// so we can't use a `--gui` flag or anything so abhorrent to macos purity, thus this nasty +// solution: +// - no args -- exec the GUI +// - `--start` -- load the system extension and start lokinet +// - `--stop` -- stop lokinet +// +// macOS: land of half-baked implementations and nasty hacks to make anything work. + +if args.count == 1 { + let gui_path = Bundle.main.resourcePath! + "/../Helpers/Lokinet-GUI.app" + if !FileManager.default.fileExists(atPath: gui_path) { + NSLog("Could not find gui app at %@", gui_path) + exit(1) + } + let gui_url = URL(fileURLWithPath: gui_path, isDirectory: false) + let gui_app_conf = NSWorkspace.OpenConfiguration() + let group = DispatchGroup() + group.enter() + NSWorkspace.shared.openApplication(at: gui_url, configuration: gui_app_conf, + completionHandler: { (app, error) in + if error != nil { + NSLog("Error launching gui: %@", error!.localizedDescription) + } else { + NSLog("Lauched GUI"); + } + group.leave() + }) + group.wait() + +} else if args.count == 2 { let delegate = LokinetMain() - delegate.mode = args.count > 1 ? args[1] : START + delegate.mode = args[1] app.delegate = delegate app.run() } else { diff --git a/gui b/gui index 4861da59c..abcd94814 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit 4861da59c3f1251a002199e185cc8a638845e692 +Subproject commit abcd94814e261ffd88104357e3946201d0d2c8e0 diff --git a/llarp/apple/CMakeLists.txt b/llarp/apple/CMakeLists.txt index 30b984653..71d6c6651 100644 --- a/llarp/apple/CMakeLists.txt +++ b/llarp/apple/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.13) if (BUILD_SHARED_LIBS OR NOT BUILD_STATIC_DEPS OR NOT STATIC_LINK) - message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/macos.sh script to build?") + message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/mac.sh script to build?") endif() # god (steve jobs) made apple so that man may suffer