Background: is a last-mile logistics service offering home deliveries for e-commerce shoppers. We’re a tech startup based in Stockholm, Sweden. Budbee At Budbee we have several applications that are used by multiple stake holders, e.g. Drivers. Since we have to move in a fast pace in our development, we distribute these applications using (There’s no way we can wait 1–4 weeks for a deploy). iOS Apples Enterprise distribution I personally always thought that managing code signing assets and performing deploys of iOS applications is a hassle, so we decided to automate as much as possible of the process so we can focus on more important stuff. We use together with to continuously build and distribute our iOS applications to an S3 bucket. This bucket has a JSON file describing the latest version so we can prompt our users to upgrade if a new version is available. We use for crash analytics, and our dSYMs are automatically uploaded when we deploy a new version. CircleCI fastlane sentry Use match to manage code signing assets Every developer needs to have his own set of private and public keys for development, and whoever does the distribution must have the distribution certificate on their machine. When setting up a new developer or migrating to a new machine you always manage to mess something up. Bleh, we just want to get our app out there — so we use to manage our code signing assets for us. It keeps everything synced on git. match Create a new private github repo in which you will store the profiles Create a new Apple ID to use for code-signing that will be shared between your team (e.g ios-dev@company.com) Run match to generate new certificates and provisioning profiles $ MATCH_FORCE_ENTERPRISE=”1" match enterprise Match don’t allow Enterprise distribution by default, due to it’s dangerous nature (if your keys are out and about, anyone can misuse your company name) hence the variable. We made sure our github repo is private, only the iOS dev team has access to it, and they have 2-factor authentication on their accounts. MATCH_FORCE_ENTERPRISE 4. Follow the instructions in match, use your newly created Apple ID for authentication. Setup CircleCI In the root of your iOS project, create a circle.yml file and populate it with the template below: machine:environment:MATCH_FORCE_ENTERPRISE: "1"xcode:version: 8.2 dependencies:pre:- gem update fastlane- brew install getsentry/tools/sentry-clicache_directories:- 'vendor/bundle'- 'budbee-driver/Pods'- '~/.cocoapods' compile:override:- fastlane build test:override:- fastlane test deployment:staging:branch: developcommands:- fastlane staging release:tag: /v?[0-9]+(\.[0-9]+)*/commands:- fastlane deploy- fastlane dsym In our circle.yml we set the environment flag to allow enterprise distribution, set the Xcode version to 8.2 (Use whatever you need here), update to the latest version of fastlane and install (for uploading dSYMs) MATCH_FORCE_ENTERPRISE sentry-cli You will need an OS X plan on to build iOS/OSX projects. Follow your github repo and make sure starts a build using the circle.yml settings. CircleCI You need to populate environment variables in the CircleCI project, namely (Your password to unlock the code signing certificate, which you chose when setting up match), , (your IAM keys that has access to the S3 bucket) and (an auth token generated by ) MATCH_PASSWORD S3_ACCESS_KEY S3_SECRET_ACCESS_KEY AWS SENTRY_AUTH_TOKEN sentry.io We have separated our Fastfile with different lanes for compiling, testing and distributing. When we push code to any branch, the project is built and tested. When we push to the develop branch a staging version of the application is deployed to S3 and we push a new version tag (v1.2.3) a production build is deployed to S3 and the dSYM is uploaded to Sentry. Setup fastlane fastlane/Fastfile: fastlane_version "2.17.1" default_platform :ios platform :ios do lane :build domatchgymend desc "Runs all the tests"lane :test doscan(workspace: 'budbee-app/myapp.xcworkspace',scheme: 'myapp',devices: ['iPhone 5s'])end desc "Deploy new version to S3 bucket"lane :deploy doaws_s3(access_key: ENV['S3_ACCESS_KEY'],secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],bucket: 'budbee-apps',region: 'eu-west-1',ipa: 'myapp.ipa',dsym: 'myapp.app.dSYM.zip',app_directory: 'com.budbee.myapp',upload_metadata: true,html_template_path: 'fastlane/s3_ios_html_template.erb',version_template_path: 'fastlane/s3_ios_version_template.erb',version_file_name: 'app_version.json')end desc "Upload dSYM to Sentry"lane :dsym dosentry_upload_dsym(auth_token: ENV['SENTRY_AUTH_TOKEN'],org_slug: 'budbee-ab',project_slug: 'budbee-app',dsym_path: './myapp.app.dSYM.zip')end desc "Deploy new staging version to S3 Bucket (staging)"lane :staging doaws_s3(access_key: ENV['S3_ACCESS_KEY'],secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],bucket: 'budbee-apps',region: 'eu-west-1',ipa: 'myapp.ipa',dsym: 'myapp.app.dSYM.zip',app_directory: 'com.budbee.myapp.staging',upload_metadata: true,html_template_path: 'fastlane/s3_ios_html_template.erb',version_template_path: 'fastlane/s3_ios_version_template.erb',version_file_name: 'app_version.json')endend fastlane/Appfile: app_identifier "com.budbee.myapp" # The bundle identifier of your appapple_id " " # Your Apple email address ios-dev@company.com team_id "[[DEV_PORTAL_TEAM_ID]]" # Developer Portal Team ID fastlane/Gymfile: workspace "budbee-app/myapp.xcworkspace"scheme "myapp"export_method "enterprise" output_directory "./" fastlane/Matchfile: git_url " :budbee/ios-certificates.git" git@github.com type "enterprise" readonly true app_identifier ["com.budbee.myapp"]username " " # Your Apple Developer Portal username ios-dev@company.com Install the needed fastlane plugins $ fastlane add_plugin aws_s3$ fastlane add_plugin sentry fastlane/s3_ios_html_template.erb: <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Install <%= title %></title></head><body><style type="text/css">* {font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;text-align: center;background-color: #ffffff;}.oneRow {width: 100%;overflow: auto;overflow-y: hidden;white-space: nowrap;text-align: center;}.download {margin: 30px;font-size: 130%;}a {text-decoration: none;color: blue;}a:hover {text-decoration: underline;}#finished { display: none; }#tutorial {text-align: center;max-width: 300px;}#logo {text-align: center;max-width: 150px;margin-top: 10px;}</style> <h1 style="text-align: center;"><%= title %></h1> <div class="oneRow"><span class="download" id="ios"><a href="itms-services://?action=download-manifest&url=<%= plist_url %>" id="text" class="btn btn-lg btn-default" onclick="document.getElementById('finished').id = '';">Install <%= title %> <%= bundle_version %></a></span></div> <h3 id="desktop">Please open this page on your iPhone!</h3> <div id="finished"><p>App is being installed. You may close Safari.</p></div> <img src=" " id="logo" /></body> https://company.com/logo.png <script type='text/javascript'>if (/iPhone|iPad|iPod/i.test(navigator.userAgent)){document.getElementById("desktop").remove()}else{document.getElementById("ios").remove()}</script></html> fastlane/s3_ios_version_template.erb: {"latestVersion": "<%= bundle_version %>","updateUrl": "itms-services://?action=download-manifest&url=<%= plist_url %>"} Keeping the app up-to-date Upon start of our iOS application, it checks the S3 bucket if there is a new version available, and if so prompts the user to update. $ pod install Alamofire$ pod install Version UpdateManager.swift: import Foundationimport Alamofireimport Version class UpdateManager: NSObject { @objc func checkForUpdate() { let baseUrl = "[https://budbee-apps.s3-eu-west-1.amazonaws.com](https://budbee-enterprise-apps.s3-eu-west-1.amazonaws.com)" let bundleIdentifier = Bundle.main.bundleIdentifier! let url = "\\(baseUrl)/\\(bundleIdentifier)/app\_version.json" let currentVersion: Version = Version(Bundle.main.infoDictionary?\["CFBundleShortVersionString"\] as! String) Alamofire.request(url).responseJSON { response in if let result = response.result.value { let JSON = result as! NSDictionary if let version = JSON.value(forKey: "latestVersion") { let latestVersion: Version = Version(version as! String) let updateUrl = JSON.value(forKey: "updateUrl") as! String if (currentVersion < latestVersion) { self.displayUpdateAlert(url: updateUrl) } } } } } func displayUpdateAlert(url: String) { let alertController: UIAlertController = UIAlertController(title: "New Version", message: "There is a new App version available. You must update to continue", preferredStyle: .alert) let button: UIAlertAction = UIAlertAction(title: "Update App", style: .default) { (action) in UIApplication.shared.openURL(URL(string: url)!) } alertController.addAction(button) UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil) } } That’s it! You should be able to visit the url to your S3 bucket on any iOS device and install the enterprise application. When you deploy a new version, you will be prompted to update right from within the application. is how hackers start their afternoons. We’re a part of the family. We are now and happy to opportunities. Hacker Noon @AMI accepting submissions discuss advertising & sponsorship To learn more, , , or simply, read our about page like/message us on Facebook tweet/DM @HackerNoon. If you enjoyed this story, we recommend reading our and . Until next time, don’t take the realities of the world for granted! latest tech stories trending tech stories