Skip to content

Commit

Permalink
Configure diagnostics (and MapLibre) using Pkl. (#3820)
Browse files Browse the repository at this point in the history
* Bump the version for the next release.

* Ignore all generated sources.

* Use Pkl+XcodeGen to inject secrets instead of the project.

* Inject the PostHog/Sentry/Rageshake configuration from the environment.

* Fix bad unicode.

* Fix unit tests.
  • Loading branch information
pixlwave authored Feb 24, 2025
1 parent 773d44c commit 6ded867
Show file tree
Hide file tree
Showing 18 changed files with 95 additions and 99 deletions.
4 changes: 2 additions & 2 deletions .githooks/post-checkout
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ git lfs post-checkout "$@"
#!/bin/bash
export PATH="$PATH:/opt/homebrew/bin"

# ignores updates of 'secrets.xcconfig' to avoid pushing sensitive data by mistake
git update-index --assume-unchanged ElementX/SupportingFiles/secrets.xcconfig
# ignores updates of 'Secrets.swift' to avoid pushing sensitive data by mistake
git update-index --assume-unchanged ElementX/SupportingFiles/Secrets.swift
4 changes: 4 additions & 0 deletions .github/workflows/unit_tests_enterprise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ jobs:
run: bundle exec fastlane config_enterprise
env:
MAPLIBRE_API_KEY: WeDontNeedOneForUnitTests
SENTRY_DSN: https://sentry.localhost
POSTHOG_HOST: https://posthog.localhost
POSTHOG_API_KEY: WeDontNeedOneForUnitTests
RAGESHAKE_SERVER_URL: https://rageshake.localhost

- name: SwiftFormat
run: swiftformat --lint .
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ build
## macOS Files
.DS_Store
._*

# This is a temporary file that is used to generate Secrets.swift
# That file doesn't need to be ignored, as it we assumed-unchanged it post-checkout.
Secrets/secrets.yml
2 changes: 1 addition & 1 deletion .swiftformat
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--swiftversion 5.6

--exclude ElementX/Sources/Generated,vendor,**/Package.swift,ElementX/Sources/Mocks/Generated
--exclude **/Sources/**/Generated,vendor,**/Package.swift,Secrets/**

--disable wrapMultiLineStatementBraces
--disable hoistPatternLet
Expand Down
22 changes: 16 additions & 6 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.swift */; };
21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD558A898847C179E4B7A237 /* SecureBackupKeyBackupScreen.swift */; };
22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; };
22B380C579C148BA0BFB5952 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3557ACB95D0F666EF5AF0CE /* Secrets.swift */; };
22C5483D01EEB290B8339817 /* HomeScreenInviteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FC33C3F6BF597E095CE9FA /* HomeScreenInviteCell.swift */; };
230981086F0199F913434D6B /* EncryptionSettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF8E5D4C95974B96A18C80E /* EncryptionSettingsUITests.swift */; };
2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */; };
Expand Down Expand Up @@ -2353,6 +2354,7 @@
D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFlowCoordinator.swift; sourceTree = "<group>"; };
D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModelProtocol.swift; sourceTree = "<group>"; };
D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
D3557ACB95D0F666EF5AF0CE /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = "<group>"; };
D38391154120264910D19528 /* PollMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollMock.swift; sourceTree = "<group>"; };
D39D7F513A36C9C1951DB44C /* AnalyticsSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreen.swift; sourceTree = "<group>"; };
D3F219838588C62198E726E3 /* LABiometryType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LABiometryType.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2439,7 +2441,6 @@
E5FDFAA04174CC99FB66391C /* EditRoomAddressScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenViewModel.swift; sourceTree = "<group>"; };
E60757AFE04391B43EA568B8 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersView.swift; sourceTree = "<group>"; };
E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = secrets.xcconfig; sourceTree = "<group>"; };
E66763BD54A3A1D9C6E6F2F1 /* PinnedItemsIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedItemsIndicatorView.swift; sourceTree = "<group>"; };
E6935A55AB3B0C94BC566DD6 /* EncryptionResetPasswordScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenCoordinator.swift; sourceTree = "<group>"; };
E6E6BDF9D26DB05C88901416 /* RedactedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineItem.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2500,6 +2501,7 @@
F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = "<group>"; };
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
F276F31C1AEC19E52B951B62 /* SendInviteConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendInviteConfirmationView.swift; sourceTree = "<group>"; };
F2DC502B1A566E99969D34DD /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = "<group>"; };
F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreen.swift; sourceTree = "<group>"; };
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2796,7 +2798,6 @@
9C5E81214D27A6B898FC397D /* ElementX.entitlements */,
81B17DB1BC3B0C62AF84D230 /* Info.plist */,
048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */,
E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */,
B050A6B233D95807A09289E7 /* Settings.bundle */,
F012CB5EE3F2B67359F6CC52 /* target.yml */,
);
Expand Down Expand Up @@ -2952,6 +2953,14 @@
path = View;
sourceTree = "<group>";
};
2197234282B4BC0CE79AAC74 /* Secrets */ = {
isa = PBXGroup;
children = (
D3557ACB95D0F666EF5AF0CE /* Secrets.swift */,
);
path = Secrets;
sourceTree = "<group>";
};
21E5BB69FC9058BED96B9EFD /* Sources */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -3488,6 +3497,7 @@
A8002CB4F20B6282850A614C /* DevelopmentAssets */,
B04B538A859CD012755DC19C /* NSE */,
1803CD2B96BF06009334BB61 /* PreviewTests */,
2197234282B4BC0CE79AAC74 /* Secrets */,
D0111119CDF3E28E6D7768E8 /* ShareExtension */,
681566846AF307E9BA4C72C6 /* Products */,
);
Expand Down Expand Up @@ -7450,6 +7460,7 @@
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */,
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */,
67160204A8D362BB7D4AD259 /* Search.swift in Sources */,
22B380C579C148BA0BFB5952 /* Secrets.swift in Sources */,
339BC18777912E1989F2F17D /* Section.swift in Sources */,
F833D5B5BE6707F961FA88DB /* SecureBackupController.swift in Sources */,
2D0E3983288E2D35613AD681 /* SecureBackupControllerMock.swift in Sources */,
Expand Down Expand Up @@ -7896,6 +7907,7 @@
7199693797B66245EF97BCF5 /* id */,
44C314C00533E2C297796B60 /* it */,
0BA7D6C94A50428463D09AF0 /* ka */,
F2DC502B1A566E99969D34DD /* nb */,
E60757AFE04391B43EA568B8 /* nl */,
997BF045585AF6DB2EBC5755 /* pl */,
A8DF55467ED4CE76B7AE9A33 /* pt */,
Expand Down Expand Up @@ -7982,7 +7994,6 @@
};
62E1B7866DF0ED442C39A83B /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = ElementX/SupportingFiles/ElementX.entitlements;
Expand Down Expand Up @@ -8011,7 +8022,6 @@
};
6897D5BC19A2EA6ABD57DE7E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = ElementX/SupportingFiles/ElementX.entitlements;
Expand Down Expand Up @@ -8135,7 +8145,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 25.02.1;
MARKETING_VERSION = 25.03.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCTION_APP_NAME = Element;
Expand Down Expand Up @@ -8212,7 +8222,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 25.02.1;
MARKETING_VERSION = 25.03.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down
27 changes: 10 additions & 17 deletions ElementX/Sources/Application/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,31 +200,24 @@ final class AppSettings {

// MARK: - Bug report

let bugReportServiceBaseURL: URL = "https://riot.im/bugreports"
let bugReportSentryURL: URL = "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44"
let bugReportServiceBaseURL: URL! = URL(string: Secrets.rageshakeServerURL)
let bugReportSentryURL: URL! = URL(string: Secrets.sentryDSN)
// Use the name allocated by the bug report server
let bugReportApplicationId = "element-x-ios"
/// The maximum size of the upload request. Default value is just below CloudFlare's max request size.
let bugReportMaxUploadSize = 50 * 1024 * 1024

// MARK: - Analytics

#if DEBUG
/// The configuration to use for analytics during development. Set `isEnabled` to false to disable analytics in debug builds.
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations.
let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.main.bundleIdentifier.starts(with: "io.element."),
host: "https://posthog.element.dev",
apiKey: "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN",
termsURL: "https://element.io/cookie-policy")
#else
/// The configuration to use for analytics. Set `isEnabled` to false to disable analytics.
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations.
///
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork you will
/// need to regenerate the Secrets file with your PostHog server and API key before enabling.
let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.main.bundleIdentifier.starts(with: "io.element."),
host: "https://posthog.element.io",
apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
termsURL: URL("https://element.io/cookie-policy"))
#endif

host: Secrets.postHogHost,
apiKey: Secrets.postHogAPIKey,
termsURL: "https://element.io/cookie-policy")

/// Whether the user has opted in to send analytics.
@UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store))
var analyticsConsentState
Expand Down Expand Up @@ -273,7 +266,7 @@ final class AppSettings {
let mapTilerBaseURL: URL = "https://api.maptiler.com/maps"

// maptiler api key
let mapTilerApiKey = InfoPlistReader.main.mapLibreAPIKey
let mapTilerApiKey = Secrets.mapLibreAPIKey

// MARK: - Presence

Expand Down
7 changes: 0 additions & 7 deletions ElementX/Sources/Other/InfoPlistReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ struct InfoPlistReader {
static let bundleShortVersion = "CFBundleShortVersionString"
static let bundleDisplayName = "CFBundleDisplayName"
static let productionAppName = "productionAppName"
static let mapLibreAPIKey = "mapLibreAPIKey"
static let utExportedTypeDeclarationsKey = "UTExportedTypeDeclarations"
static let utTypeIdentifierKey = "UTTypeIdentifier"
static let utDescriptionKey = "UTTypeDescription"
Expand Down Expand Up @@ -87,12 +86,6 @@ struct InfoPlistReader {
var productionAppName: String {
infoPlistValue(forKey: Keys.productionAppName)
}

// MARK: - MapLibre

var mapLibreAPIKey: String {
infoPlistValue(forKey: Keys.mapLibreAPIKey)
}

// MARK: - Custom App Scheme

Expand Down
2 changes: 0 additions & 2 deletions ElementX/SupportingFiles/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,6 @@
<string>$(BASE_BUNDLE_IDENTIFIER)</string>
<key>keychainAccessGroupIdentifier</key>
<string>$(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)</string>
<key>mapLibreAPIKey</key>
<string>$(MAPLIBRE_API_KEY)</string>
<key>productionAppName</key>
<string>$(PRODUCTION_APP_NAME)</string>
</dict>
Expand Down
21 changes: 0 additions & 21 deletions ElementX/SupportingFiles/secrets.xcconfig

This file was deleted.

7 changes: 1 addition & 6 deletions ElementX/SupportingFiles/target.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ targets:
type: application
platform: iOS

configFiles:
Debug: ../SupportingFiles/secrets.xcconfig
Release: ../SupportingFiles/secrets.xcconfig

info:
path: ../SupportingFiles/Info.plist
properties:
Expand Down Expand Up @@ -109,8 +105,6 @@ targets:
LSHandlerRank: Owner
LSItemContentTypes: $(PILLS_UT_TYPE_IDENTIFIER)
LSSupportsOpeningDocumentsInPlace: false
mapLibreAPIKey: $(MAPLIBRE_API_KEY)


settings:
base:
Expand Down Expand Up @@ -226,6 +220,7 @@ targets:
excludes:
- Other/Extensions/XCTestCase.swift
- Other/Extensions/XCUIElement.swift
- path: ../../Secrets/Secrets.swift
- path: ../Resources
- path: ../SupportingFiles
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/ElementX
Expand Down
17 changes: 17 additions & 0 deletions Secrets/Secrets.pkl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//

// Analytics and Diagnostics

sentryDSN = read("env:SENTRY_DSN")
postHogHost = read("env:POSTHOG_HOST")
postHogAPIKey = read("env:POSTHOG_API_KEY")
rageshakeServerURL = read("env:RAGESHAKE_SERVER_URL")

// Maps

mapLibreAPIKey = read("env:MAPLIBRE_API_KEY")
19 changes: 19 additions & 0 deletions Secrets/Secrets.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen

import Foundation

// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length

// MARK: - YAML Files

// swiftlint:disable identifier_name line_length number_separator type_body_length
internal enum Secrets {
internal static let mapLibreAPIKey: String = "your_key"
internal static let postHogAPIKey: String = "your_key"
internal static let postHogHost: String = "https://posthog.localhost"
internal static let rageshakeServerURL: String = "https://rageshake.localhost"
internal static let sentryDSN: String = "https://sentry.localhost"
}
// swiftlint:enable identifier_name line_length number_separator type_body_length
4 changes: 2 additions & 2 deletions ci_scripts/ci_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ setup_xcode_cloud_environment () {
}

install_xcode_cloud_brew_dependencies () {
brew update && brew install xcodegen pkl
brew update && brew install xcodegen swiftgen pkl
}

setup_github_actions_environment() {
unset HOMEBREW_NO_INSTALL_FROM_API
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1

brew update && brew install xcodegen swiftlint swiftformat git-lfs pkl a7ex/homebrew-formulae/xcresultparser
brew update && brew install xcodegen swiftlint swiftformat swiftgen git-lfs pkl a7ex/homebrew-formulae/xcresultparser

bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
Expand Down
9 changes: 5 additions & 4 deletions docs/FORKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ The location sharing feature on Element X is currently integrated with [MapLibre

The MapLibre SDK requires an API key to work, so you need to get one for yourself.

After you get an API key, you need to configure the project by adding it inside the file `secrets.xconfig` in the project root folder. After you are done, the file should contain a setting like this:
After you get an API key, you need to configure the project by exporting it and regenerating the `Secrets.swift` file:

```
MAPLIBRE_API_KEY = your_map_libre_key
export MAPLIBRE_API_KEY=your_map_libre_key
bundle exec fastlane config_secrets
```

It’s not recommended to push your API key in your repository since other people may get it.

One way to avoid pushing the API key by mistake is running on your machine the command:
```
git update-index assume-unchanged secrets.xcconfig
git update-index assume-unchanged Secrets/Secrets.swift
```
this will prevent pushing any update of the file`secrets.xcconfig`.
this will prevent pushing any update of the file `Secrets.swift`.

Finally you need to setup your map styles overriding the values you find in the code:

Expand Down
15 changes: 5 additions & 10 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -408,14 +408,9 @@ private_lane :create_simulator_if_necessary do |options|
end
end

private_lane :config_secrets do
maplibre_api_key = ENV["MAPLIBRE_API_KEY"]
UI.user_error!("Invalid Map Libre API key.") unless !maplibre_api_key.to_s.empty?

set_xcconfig_value(
path: './ElementX/SupportingFiles/secrets.xcconfig',
name: 'MAPLIBRE_API_KEY',
value: maplibre_api_key,
mask_value: true
)
lane :config_secrets do
Dir.chdir "../Secrets" do
sh("pkl eval -f yaml -o secrets.yml Secrets.pkl")
sh("swiftgen run yaml -n inline-swift5 --param enumName=Secrets -o Secrets.swift secrets.yml")
end
end
8 changes: 8 additions & 0 deletions fastlane/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do



### config_secrets

```sh
[bundle exec] fastlane config_secrets
```



----

This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
Expand Down
Loading

0 comments on commit 6ded867

Please sign in to comment.