diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..2c3dc0e
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,83 @@
+name: Release
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build:
+ name: Build app bundle and upload it to the release
+ runs-on: macos-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Select latest stable Xcode version
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+ # https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development#creating-secrets-for-your-certificate-and-provisioning-profile
+ # https://defn.io/2023/09/22/distributing-mac-apps-with-github-actions
+ - name: Install the Apple certificate and provisioning profile
+ env:
+ # exported from Xcode
+ # base64 -i ID_CERTIFICATE.p12 > ID_CERTIFICATE_BASE64
+ ID_CERTIFICATE_BASE64: ${{ secrets.ID_CERTIFICATE_BASE64 }}
+ # openssl rand -hex 32 > ID_CERTIFICATE_PASSWORD
+ ID_CERTIFICATE_PASSWORD: ${{ secrets.ID_CERTIFICATE_PASSWORD }}
+ # exported from Xcode
+ # base64 -i BUILD_CERTIFICATE.p12 > BUILD_CERTIFICATE_BASE64
+ BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
+ # openssl rand -hex 32 > BUILD_CERTIFICATE_PASSWORD
+ BUILD_CERTIFICATE_PASSWORD: ${{ secrets.BUILD_CERTIFICATE_PASSWORD }}
+ # openssl rand -hex 32 > KEYCHAIN_PASSWORD
+ KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
+ run: |
+ # create variables
+ ID_CERTIFICATE_PATH=$RUNNER_TEMP/id_certificate.p12
+ BUILD_CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+
+ # import certificates from secrets
+ echo -n "$ID_CERTIFICATE_BASE64" | base64 --decode -o $ID_CERTIFICATE_PATH
+ echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $BUILD_CERTIFICATE_PATH
+
+ # create temporary keychain
+ security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+ security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+
+ # import certificate to keychain
+ security import $ID_CERTIFICATE_PATH -P "$ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security import $BUILD_CERTIFICATE_PATH -P "$BUILD_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security list-keychain -d user -s $KEYCHAIN_PATH
+ - name: Build app
+ run: |
+ mkdir -p dist
+
+ xcodebuild \
+ archive \
+ -project 'Nautik Helper.xcodeproj'/ \
+ -scheme 'Nautik Helper' \
+ -configuration Release \
+ -destination 'generic/platform=macOS' \
+ -archivePath 'dist/Nautik Helper.xcarchive'
+ -allowProvisioningUpdates
+
+ xcodebuild \
+ -exportArchive \
+ -archivePath 'dist/Nautik Helper.xcarchive' \
+ -exportOptionsPlist 'Nautik Helper/ExportOptions.plist' \
+ -exportPath dist/ \
+ -allowProvisioningUpdates
+
+ cd dist
+ zip -r helper-${{ github.ref }}.zip 'Nautik Helper.app'
+ cd ..
+ - name: Upload app bundle to release
+ uses: svenstaro/upload-release-action@v2
+ with:
+ file: dist/helper-${{ github.ref }}.zip
+ asset_name: helper-${{ github.ref }}.zip
+ tag: ${{ github.ref }}
+ overwrite: true
diff --git a/README.md b/README.md
index e4f8ee9..f102293 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,31 @@
-
+
# Nautik Helper
+[![CI](https://github.com/nautik-io/helper/actions/workflows/ci.yml/badge.svg)](https://github.com/nautik-io/helper/actions/workflows/ci.yml)
+[![Release](https://github.com/nautik-io/helper/actions/workflows/release.yml/badge.svg)](https://github.com/nautik-io/helper/actions/workflows/release.yml)
+
Helper app to bridge Nautik's compatibility with the full kubeconfig spec, including exec plugins, without having the main app exit the sandbox.
-This exists because kubeconfig auth scenarios are diverse and a lot of setups (including the standard kubeconfig files exported out of of GCP, AWS, Azure and DigitalOcean) rely on calling executables in a user's PATH for ephemeral Kubernetes credential issuing. Calling arbitrary executables isn't possible as a sandboxed Mac app, but exiting the sandbox just to call local executables would prevent us from distributing the app via the Mac App Store. So all things exec are happening in this helper app that can optionally be used by people whose auth scenarios aren't implemented in the main app and also can easily be audited, since the whole exec thing might be suspicious to some people. Credential data is shared between the helper app and the main app using the system keychain.
+This exists because kubeconfig auth scenarios are diverse and a lot of setups (including the standard kubeconfig files exported out of of GCP, AWS, Azure and DigitalOcean) rely on calling executables in a user's PATH for ephemeral Kubernetes credential issuing. Calling arbitrary executables isn't possible as a sandboxed Mac app, but exiting the sandbox just to call local executables would prevent us from distributing the app via the Mac App Store. So all things exec are happening in this helper app that can optionally be used by people whose auth scenarios aren't implemented in the main app and can also easily be audited, since the whole exec thing might be suspicious to some people. Additionally, there's support for token and certificate files. Credential data is shared between the helper app and the main app using the system keychain.
+
+If you can prevent using this, don't use it and try using auth scenarios implemented in the main app instead, as they will work better across platforms without having to have a Mac running just to refresh credentials.
+
+## How the helper app works
+
+The helper app allows you to add kubeconfig files to keep track of via a file picker UI. The file paths of tracked kubeconfig files are stored on the [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults) in a serialized array of paths under the key `kubeconfigs`. Every valid cluster on the tracked kubeconfig files can be added to (or removed from) the keychain with a checkbox UI on the helper app's settings.
+
+
+
+Clusters on the keychain are continuously evaluated by the helper app. If a cluster's corresponding kubeconfig entry includes `client-certificate`, `client-key` or `token-file` keys, the file contents of the corresponding files are copied into the `client-certificate-data`, `client-key-data` and `token` fields of the stored cluster to have them be consumed by the main app on macOS, iOS or iPadOS. If a cluster's corresponding kubeconfig entry includes an `exec` value, the helper app spawns a process as the user running the helper app, executing the corresponding exec-based authentication plugin and copying its output into the `client-certificate-data`, `client-key-data` and `token` fields of the stored cluster to have them be consumed by the main app.
+
+
+
+Support for the `auth-provider` field on the kubeconfig is currently unimplemented. But support for the `oidc` auth provider is planned to be included on the main app at a later point. Contributions to the helper app extending the range of supported auth methods are very welcome.
+
+The helper app evaluates the refresh timestamps on stored clusters every 10 seconds, refreshes `client-certificate`, `client-key` and `token-file` values every 15 minutes and re-evaluates `exec` plugin outputs 5 minutes before they expire.
-If you can prevent using this, don't use it and try to use the auth scenarios implemented in the main app instead, as they will work better across platforms without having to have a Mac running just to refresh credentials.
+To allow to be run on multiple Macs and user accounts in parallel without interference, the helper app stores the device UUID and user of the system it was added on with the cluster.
## Installation