Jekyll2018-06-23T18:52:25-05:00https://blog.bryantluk.com/blog.bryantluk.comrandom thoughts
Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlGoogle Cloud and Terraform Setup2018-06-23T07:00:00-05:002018-06-23T07:00:00-05:00https://blog.bryantluk.com/devops/2018/06/23/google-cloud-terraform-setup<p>To setup <a href="https://cloud.google.com/">Google Cloud</a> for use with
<a href="https://www.terraform.io/">Terraform</a>, create a Google Cloud project
using the <a href="https://console.cloud.google.com/">Google Console</a>.</p>
<p>Then, I create a <a href="https://cloud.google.com/iam/docs/creating-managing-service-accounts">Google Cloud Service Account</a>.
After creating the account with the appropriate role, I create the
<a href="https://cloud.google.com/iam/docs/creating-managing-service-account-keys">service account key</a>
and download and save the private key data in JSON format.</p>
<p>Once you have the service account key, then you can export an environment
variable like <code class="highlighter-rouge">GOOGLE_CREDENTIALS</code> with the path to the JSON file.
See the <a href="https://www.terraform.io/docs/providers/google/index.html">Google Cloud Provider Terraform documentation</a>
for more information.</p>
<p>Finally, you can create and run your <code class="highlighter-rouge">terraform</code> commands which will
use the service account to create your Google Cloud resources.</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlSetup for Terraform and Google CloudWWDC 20182018-06-03T07:00:00-05:002018-06-03T07:00:00-05:00https://blog.bryantluk.com/dev/2018/06/03/wwdc-2018<p><a href="https://developer.apple.com/wwdc/">WWDC</a> is tomorrow! For the first time in
a while, the rumor mill is pretty quiet. However, it has led to even more
wishful thinking and speculation.</p>
<p>The general theme and expectation is Apple will focus on reliability. Bug fixes
will be prioritized which would be great. Another possible theme may be
“Digital Health”, which can mean information about how you use your phone to
better notification and Do Not Disturb management. After spending some time
with health and meditation apps, I think it would be helpful to have features
that make more efficient usage of your device and make you acknowledge that
it is your responsibility to monitor and manage your time.</p>
<p>watchOS needs more independence from iOS whether that be apps that can
operate more independently (when there’s Wi-Fi, use it instead of the
Bluetooth connection) to more capabilities (e.g. able to streaming podcasts)
for third party apps. There needs to be some justification for investing
energy into watchOS apps considering most developers are abandoning the
platform.</p>
<p>It would be wonderful if tvOS gained user profiles and some sort of
acknowledgement that it is not meant for just a single user’s preferences.
For instance, you can only log into one Game Center profile (even though
Game Center might as well not exist itself). More intriguingly, I think it
would be useful to detect the presence of multiple people (say some people
had their iPhones or Apple Watches near the Apple TV), and then tvOS would
give recommendations tailored to those people. It seems far-fetched at this
point given Apple’s direction, but it would be interesting conceptually.</p>
<p>macOS stability and feature parity is a sore spot. There are many capabilities
that are just not present (e.g. HomeKit, iMessage apps, TV app). If feature
parity is not on the table, then slowly breaking apart iTunes must be on
Apple’s priority list hopefully.</p>
<p>Perhaps the most important area of focus is Apple’s services. Siri is the poster
child of an Apple service needing improvement. The speech recognition and
feedback seem to be fine but actually interpreting the commands seems to be
difficult with its open ended nature. Compared to Google Assistant and Amazon
Echo, Siri might work slightly subpar but the constant embarrassing screenshots
of incidents where Siri makes wild interpretations of questions is alarming.</p>
<p>Personally, I would prefer if Apple makes a few acknowledgements in their
process more than anything. A public announcement that they will deliver
iOS 12 over the course of a few months instead of a 12.0 release with everything
in it. Whether intentional or not, iOS 11 just got the last of its announced
features a week before WWDC 2018 (Messages in the Cloud and AirPlay 2). It would
be even better if all of their services like Siri were continuously publicly
improved every week/month/quarter so that customers feel things are getting
better constantly at a sustainable pace.</p>
<p>The other major change I would like to see happen is a “service” mentality for
every one of their introduced features. Instead of features being tied to
an OS, the APIs should be made available on as many platforms as possible with
the ability for apps to integrate with the service. iMessage, Photos, HomeKit,
Siri, Apple Music and in general iCloud should become independent platforms
themselves without needing to build apps to use OS APIs to integrate with them.
Building ecosystems for these systems without requiring an iOS device would be a
huge change in strategy but if Apple is focusing on service revenue, building
great independent services would drive them forward. Instead of catching up
to WeChat, Dropbox, Amazon Echo, Spotify, Apple could independently move forward
without being tied to iOS.</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlWWDC 2018Terraform and Let’s Encrypt on Google Cloud Platform2018-06-02T07:00:00-05:002018-06-02T07:00:00-05:00https://blog.bryantluk.com/devops/2018/06/02/gcp-terraform-lets-encrypt<p><a href="https://letsencrypt.org/">Let’s Encrypt</a> is a service that offers free TLS
(aka SSL) certificates. The certificates are recognized by all modern browsers.
The only “disadvantage” of using Let’s Encrypt is that the certificates have to
be renewed every few months but the process can be automated.</p>
<p>Depending on your environment, there are various ways to get initially setup with
their certificates. You can get specific domain (e.g.
<code class="highlighter-rouge">www.example.com</code> or <code class="highlighter-rouge">staging.example.com</code>) or wildcard (<code class="highlighter-rouge">*.example.com</code>)
certificates. Visit the Let’s Encrypt website to understand all of your options.</p>
<p>Assuming that you already have a <a href="https://cloud.google.com/">Google Cloud</a> project,
have setup
<a href="https://www.terraform.io/docs/providers/google/">Google Cloud provider credentials for Terraform</a>
and have bought a domain name, you can use the Let’s Encrypt Docker certbot
image to get a wildcard certificate using the following process.</p>
<p>Note the assumption is that this is a new domain name which does not have
an existing DNS setup. If you are migrating a domain name, you should
read the <a href="https://cloud.google.com/dns/migrating">Google Cloud documentation</a>
instead.</p>
<h2 id="terraform">Terraform</h2>
<h3 id="resource-code">Resource Code</h3>
<p>First, create a new directory and then create a <a href="https://www.terraform.io">Terraform</a>
file like:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>provider "google" {
version = "~&gt; 1.0"
project = "${var.google_project}"
region = "${var.google_region}"
zone = "${var.google_zone}"
}
variable "google_project" {
description = "The Google Cloud project to use"
}
variable "google_region" {
description = "The Google Cloud region to use"
}
variable "google_zone" {
description = "The Google Cloud zone to use"
}
variable "domain_name" {
description = "The domain name to use"
}
resource "google_dns_managed_zone" "example_com" {
name = "example-com"
dns_name = "${var.domain_name}."
description = "${var.domain_name} domain"
}
resource "google_project_iam_custom_role" "dns_owner" {
role_id = "dns_owner"
title = "DNS Owner"
description = "Allows service account to manage DNS."
permissions = [
"dns.changes.create",
"dns.changes.get",
"dns.managedZones.list",
"dns.resourceRecordSets.create",
"dns.resourceRecordSets.delete",
"dns.resourceRecordSets.list",
"dns.resourceRecordSets.update",
]
}
resource "google_service_account" "letsencrypt_dns" {
account_id = "dns-letsencrypt"
display_name = "Lets Encrypt DNS Service Account"
}
resource "google_project_iam_member" "project" {
role = "projects/${var.google_project}/roles/${google_project_iam_custom_role.dns_owner.role_id}"
member = "serviceAccount:${google_service_account.letsencrypt_dns.email}"
}
resource "google_service_account_key" "letsencrypt_dns" {
service_account_id = "${google_service_account.letsencrypt_dns.name}"
public_key_type = "TYPE_X509_PEM_FILE"
}
resource "local_file" "letsencrypt_credentials_json" {
content = "${google_service_account_key.letsencrypt_dns.private_key}"
filename = "letsencrypt-credentials.json.base64"
}
</code></pre></div></div>
<p>The above config sets up the Google Cloud provider with a domain name, project,
region, and zone via variables to be set later. It creates a DNS managed zone on
Google Cloud. You may want to rename some of the resource names like
<code class="highlighter-rouge">example_com</code> to your specific setup.</p>
<p>Note that for the <code class="highlighter-rouge">dns_name</code>, the value will need a trailing <code class="highlighter-rouge">.</code> (so the final
value will be like <code class="highlighter-rouge">example.com.</code>).</p>
<p>The above config also creates a service account with a custom role which
allows the service account to modify DNS records. Once the account is
created, it will store the credentials in a local <code class="highlighter-rouge">letsencrypt-credentials.json.base64</code>
file.</p>
<h3 id="variable-config">Variable Config</h3>
<p>Create a <code class="highlighter-rouge">terraform.tfvars</code> file to fill in the variables with your specific
config.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>google_project = "project-id"
google_zone = "us-central1-a"
google_region = "us-central1"
domain_name = "example.com"
</code></pre></div></div>
<h3 id="plan-and-apply">Plan and Apply</h3>
<p>To do the one-time initial Terraform provider setup, run:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform init
</code></pre></div></div>
<p>Then to create a plan for creating the resources:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform plan -out=terraform.plan
</code></pre></div></div>
<p>You may want to inspect the output of <code class="highlighter-rouge">terraform plan</code> to understand
what resources are being created.</p>
<p>Run the following when ready to create the resources:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform apply terraform.plan
</code></pre></div></div>
<h2 id="setup-dns-nameserver">Setup DNS Nameserver</h2>
<p>You will need to have your domain registar use the Google Cloud DNS nameservers.
After applying the Terraform config, you can go to the
<a href="https://console.cloud.google.com/home/dashboard">Google Cloud Console</a>
under <code class="highlighter-rouge">Networks services &gt; Cloud DNS</code>. Find your domain name and get the DNS
nameservers. Go to your domain registar and use all of the DNS nameservers
(under the NS record like <code class="highlighter-rouge">ns-cloud-b1.googledomains.com.</code>).</p>
<p>You may have to wait a few minutes to a day for the nameserver change to
propagate.</p>
<h2 id="lets-encrypt-certbot-in-docker">Let’s Encrypt Certbot in Docker</h2>
<p>Running <a href="https://certbot.eff.org/docs/install.html#running-with-docker">Let’s Encrypt Certbot in Docker</a>,
you can finally get and renew a wildcard certificate.</p>
<p>Make 2 directories for Let’s Encrypt config and logs.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p certs/config
mkdir -p certs/logs
</code></pre></div></div>
<p>Base64 decode the service account credentials into a file, and move the file
into the config directory.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat letsencrypt-credentials.json.base64 | base64 -D &gt; letsencrypt-credentials.json
mv letsencrypt-credentials.json certs/config/google-cloud-service-account-credentials.json
</code></pre></div></div>
<p>Then run the following replacing the <code class="highlighter-rouge">&lt;absolute path&gt;</code> with the actual
absolute paths:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -it --rm --name certbot -v "&lt;absolute path to&gt;/certs/config:/etc/letsencrypt" -v "&lt;absolute path to&gt;/certs/logs:/var/lib/letsencrypt" certbot/dns-google certonly --dns-google-credentials /etc/letsencrypt/google-cloud-service-account-credentials.json --server https://acme-v02.api.letsencrypt.org/directory
</code></pre></div></div>
<p>After running the command and answering a few questions, the certbot will use
the service account to create a DNS entry to verify domain ownership. Then it
will issue a wildcard certificate for your domain. The certificate files and
credentials will be stored in your <code class="highlighter-rouge">certs/config</code> directory.</p>
<p>You can then re-run the certbot when it is time to renew the certificates.
Be sure to keep (and backup) a copy of the <code class="highlighter-rouge">certs/*</code> directories to
re-use them later.</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlHow to generate Let's Encrypt wildcard certificates on Google Cloud using Terraform and DockerTerminating Sidecar Containers in Kubernetes Job Specs2018-05-13T07:00:00-05:002018-05-13T07:00:00-05:00https://blog.bryantluk.com/devops/2018/05/13/kubernetes-sidecar-termination<p>Kubernetes <a href="https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/">Jobs</a>
are useful for one-off tasks. However, there are some problems when
you have to define <a href="https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/">sidecar containers</a>
in your job spec. Primarily, the job’s pod <a href="https://github.com/kubernetes/kubernetes/issues/25908">will not terminate</a>
when the sidecar containers are still running. If your sidecar container is a
logging agent or a <a href="https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/128">proxy for other services</a>,
they usually do not terminate. Furthermore, the sidecar container must terminate
with an exit code of <code class="highlighter-rouge">0</code> or else the job may restart.</p>
<p>One suggested solution is to have a script watch for the creation of a file on
a shared volume. When the script detects a file, the script will terminate the
container. For instance, here is a sample job spec which waits for a file to be
created in a shared volume in a sidecar container:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apiVersion: v1
kind: ConfigMap
metadata:
name: watchfile-config-map
labels:
name: watchfile-config-map
data:
watchfile.sh: |-
apk update &amp;&amp; apk add inotify-tools
echo "waiting for file..."
file=/var/lib/sharedwatchfile/file.unlock
while [ ! -f "$file" ]
do
inotifywait -qqt 10 -e create -e moved_to "$(dirname $file)"
done
echo "found file"
---
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
spec:
template:
spec:
containers:
- name: db-migration
image: &lt;your job image&gt;
command: ["/bin/sh",
"-c",
"&lt;run db migration script&gt; &amp;&amp; touch /var/lib/sharedwatchfile/file.unlock"]
volumeMounts:
- name: varlibsharedwatchfile
mountPath: /var/lib/sharedwatchfile
- name: cloudsql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:1.11
command: ["/bin/sh",
"-c",
"/cloud_sql_proxy -instances=&lt;your db instance&gt;=tcp:5432 -credential_file=/secrets/cloudsql/credentials.json &amp; /bin/sh /var/lib/watchfile/watchfile.sh"]
volumeMounts:
- name: cloudsql-instance-credentials
mountPath: /secrets/cloudsql
readOnly: true
- name: varlibwatchfile
mountPath: /var/lib/watchfile
readOnly: true
- name: varlibsharedwatchfile
mountPath: /var/lib/sharedwatchfile
readOnly: true
volumes:
- name: cloudsql-instance-credentials
secret:
secretName: sql-kubernetes-proxy-credentials
- name: varlibwatchfile
configMap:
name: watchfile-config-map
items:
- key: watchfile.sh
path: watchfile.sh
- name: varlibsharedwatchfile
restartPolicy: Never
backoffLimit: 4
</code></pre></div></div>
<p>The usage of <code class="highlighter-rouge">inotifywait</code> is to be a bit more efficient than just using
<code class="highlighter-rouge">sleep</code>. In the above example, instead of modifying an existing image,
the commands to run the sidecar container are slightly modified and a script is
mounted via a volume to the sidecar container.</p>
<p>While watching for a file to be created is not exactly ideal, it is a quick
workable hack until a general solution is available.</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlWatch files to determine sidecar container termination for job specsTerraform Remote State2018-05-12T07:00:00-05:002018-05-12T07:00:00-05:00https://blog.bryantluk.com/devops/2018/05/12/terraform-remote-state<p>When using <a href="https://www.terraform.io">Terraform</a>, I find that <a href="https://www.terraform.io/docs/state/remote.html">storing state
remotely</a> has great benefits.
If you work with others or on multiple machines, remote state allows re-using
Terraform defined infrastructure without copying the state manually to all other
users. More importantly, it allows a “core” set of resources to be defined and owned
by one project while
<a href="https://www.terraform.io/docs/providers/terraform/d/remote_state.html#root-outputs-only">the root level output resources are re-usable</a>
in other related Terraform projects.</p>
<p>To store state remotely, add a backend to store the state such as:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform {
backend "s3" {
bucket = "&lt;your bucket name&gt;"
key = "default"
region = "us-east-1"
}
}
</code></pre></div></div>
<p>Then you need to run <code class="highlighter-rouge">terraform init</code> after adding the backend to your Terraform
config.</p>
<p>To import remote state (say you have a core infrastructure Terraform project),
add another resource to import:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data "terraform_remote_state" "core_infrastructure" {
backend = "s3"
workspace = "${terraform.workspace}"
config {
bucket = "&lt;bucket with state to import&gt;"
key = "default"
region = "us-east-1"
}
}
</code></pre></div></div>
<p>The core infrastructure that I generally have are definitions for DNS zones
(so related projects can import the DNS managed zone identifier and create
subdomains), wildcard SSL certificates for test domains, and general repository
definitions for where the code is stored.</p>
<p>If you have multiple users, you will need to look into remote state locking
solutions as well with your backends,</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlStore Terraform state remotelySet Global Prefix Config for npm2018-05-06T07:00:00-05:002018-05-06T07:00:00-05:00https://blog.bryantluk.com/dev/2018/05/06/npm-global-prefix<p>For my <code class="highlighter-rouge">$HOME/.npmrc</code> config, I have the following config:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prefix=~/.npm-global
</code></pre></div></div>
<p>So when installing npm modules globally, they will be installed in a directory
that’s owned by my user instead of to a system directory. This prevents write
permission issues when trying to install modules not owned by my user.</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlSet prefix in .npmrc for global module installsYubiKey2017-04-25T07:00:00-05:002017-04-25T07:00:00-05:00https://blog.bryantluk.com/dev/2017/04/25/yubikey<p>I’ve had a couple of YubiKey devices for U2F auth for a while now. They are much
easier to use than digging out your two-factor authenticator app.</p>
<p>Recently, I purchased a <a href="https://www.yubico.com/products/yubikey-hardware/">YubiKey 4</a>.
The major feature addition to me is the support for <a href="https://gnupg.org">OpenPGP</a>
to make it easier to use OpenPGP subkeys to sign data, encrypt data, and
authenticate.</p>
<p>The feature list for YubiKey 4 is long, but after some usage, it is like there
are many separated and different functions all in one physical package.</p>
<h3 id="how-to-setup-and-use">How-To Setup and Use</h3>
<p>So far the best OpenPGP guide with YubiKey 4 has been
<a href="http://suva.sh/posts/gpg-ssh-smartcard-yubikey-keybase/">Suvash Thapaliya’s thorough step-by-step guide</a>.</p>
<p>The OpenPGP functionality works well. Instead of a long password to
remember and enter every time, you can insert the YubiKey, enter in the PIN
to unlock, and then remove the key when done.</p>
<p>My main issue now is key management. I’m still experimenting with how to
update my subkeys’ expiration times.</p>
<h3 id="random-notes">Random Notes</h3>
<h4 id="lock-out">Lock-out</h4>
<p>For functionality that requires a PIN, you can control how many wrong PINs it
takes before blocking the device. You actually have 2 PINs to remember. One is
the normal PIN used for daily use. The other is the <code class="highlighter-rouge">PUK</code> (Personal
Unlocking Key) which unlocks the PIN if the wrong PIN was entered
too many times.</p>
<p>If you want to reset your device, you may need to force your PIN and <code class="highlighter-rouge">PUK</code> to
both be blocked, and then you can perform a device reset.</p>
<h4 id="different-pins-for-different-functions">Different PINs for Different Functions</h4>
<p>You may find the <a href="https://www.yubico.com/support/knowledge-base/categories/articles/use-yubikey-openpgp/">official OpenPGP documentation from Yubico</a>
helpful, but what I really needed was their <a href="https://www.yubico.com/support/knowledge-base/categories/articles/reset-applet-yubikey/">Reset OpenPGP applet</a>
instructions. I managed to lock my YubiKey because I did not understand that
each functionality of the key has unique PINs.</p>
<p>For instance, using their <a href="https://www.yubico.com/support/knowledge-base/categories/articles/piv-tools/">PIV Tool</a>,
you need to set a PIN to be able to log in to macOS using the YubiKey. However,
it is not the same PIN that the OpenPGP applet uses. So be careful to remember
the default PINs (<code class="highlighter-rouge">123456</code> for normal entry and <code class="highlighter-rouge">12345678</code> for admin) when doing
the initial setup for each functionality and to change them.</p>
<p>Their <a href="https://forum.yubico.com/index-2.html">forum</a> also has posts explaining
how to reset the OpenPGP applet and other helpful advice.</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlExperience with YubiKeyWWDC 2016 Wishlist2016-05-21T07:00:00-05:002016-05-21T07:00:00-05:00https://blog.bryantluk.com/dev/2016/05/21/wwdc-2016-wish-list<p>Here’s my list of new things that I would like to see at <a href="https://developer.apple.com/wwdc/">WWDC 2016</a>:</p>
<h3 id="software">Software</h3>
<ul>
<li>Siri 3rd party integration</li>
<li>Remote view controllers</li>
<li>Split view with multiple views of the same app (e.g. 2 Safari instances) in iOS</li>
</ul>
<h3 id="hardware">Hardware</h3>
<ul>
<li>Separate Apple 4K/5K Display</li>
<li>Re-assign what the buttons on the Apple Watch do</li>
<li>HomeKit device similar to Amazon Echo / Google Home</li>
</ul>
<h3 id="services">Services</h3>
<ul>
<li>Street view for Maps</li>
<li>A way to download or at least queue apps for download from the web. If someone
runs across a news article for an app, s/he can click on a link to download
the app to their device. Google’s Play Store has had this feature for years. For
Apple TV in particular.</li>
<li>Upgrade pricing for major product updates for apps</li>
</ul>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlWishlist for WWDC 2016iOS Automate Build Version Number2016-02-23T06:00:00-06:002016-02-23T06:00:00-06:00https://blog.bryantluk.com/dev/2016/02/23/ios-build-version-number<p>If you have a continuous integration server, you might want to build your app
with unique version numbers tied to the build.</p>
<p>The <code class="highlighter-rouge">agvtool</code> (Apple-generic versioning tool for Xcode projects) is an easy
way to update the marketing and build version number within your app.</p>
<p>In your CI system, you can run something like the following to add a build
version number where the major version is the same but the
<code class="highlighter-rouge">.jenkins.$BUILD_NUMBER</code> is appended. <code class="highlighter-rouge">$BUILD_NUMBER</code> is provided by Jenkins.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MARKETING_VERSION=$(agvtool what-marketing-version -terse1); agvtool new-version -all $MARKETING_VERSION.jenkins.$BUILD_NUMBER
</code></pre></div></div>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlHow to increment the build version numberiOS Enterprise Distribution2016-01-10T06:00:00-06:002016-01-10T06:00:00-06:00https://blog.bryantluk.com/dev/2016/01/10/ios-enterprise-distribution<p>If you have an enterprise iOS account, you can distribute apps internally using an enterprise distribution certificate. Here are some instructions on how to get an installable IPA via shell commands which you can automate as part of a build process.</p>
<p>You need to have your code signing production certificate downloaded and installed into your Keychain on your build machine. Xcode can do this, or you can download the certificate from the <a href="https://developer.apple.com/">Apple Developer</a> site.</p>
<p>Also, you need to have a mobile provisioning profile installed. The mobile provisioning’s “App ID” should be a prefix (say <code class="highlighter-rouge">com.example.*</code>) of your app’s bundle ID (such as <code class="highlighter-rouge">com.example.LoremIpsum</code>). Again, you can download via Xcode or via the developer site.</p>
<h2 id="gather-optional-information">Gather (Optional) Information</h2>
<p>While the code signing and provisioning information can be set in the Xcode project’s build configuration, you may want to override the information in your build.</p>
<h4 id="code-sign-identity">Code Sign Identity</h4>
<p>If you need to specify your code signing identity during the build, find your certificate in the Keychain and get the Common Name of the certificate.</p>
<p>The production certificate’s Common Name will be the <code class="highlighter-rouge">CODE_SIGN_IDENTITY</code> value.</p>
<h4 id="mobile-provisioning-profile">Mobile Provisioning Profile</h4>
<p>If you downloaded the mobile provisioning file, you can open the provisioning profile and find the UUID value in the plist.</p>
<p>If you have downloaded the profile in Xcode, you can go back to Xocde and open the <code class="highlighter-rouge">Accounts</code> panel in the Xcode Preferences, look at your logged in Apple ID and <code class="highlighter-rouge">View Details</code>, and then find your Provisioning Profile. Right click on the name of the Provisioning Profile and click <code class="highlighter-rouge">Show in Finder</code>. You will see a file selected in <code class="highlighter-rouge">~/Library/MobileDevice/Provisioning Profiles</code>. The filename is usually named <code class="highlighter-rouge">&lt;UUID&gt;.mobileprovision</code>, or you can verify by opening up the file and find the UUID value.</p>
<p>The UUID will be the <code class="highlighter-rouge">PROVISIONING_PROFILE</code> value.</p>
<h2 id="build-an-xcarchive">Build an xcarchive</h2>
<p>Assuming that <code class="highlighter-rouge">LoremIpsum</code> is your Xcode project, you can run the following in your shell:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export BUILD_DIR=$(pwd)/build
mkdir -p $BUILD_DIR
xcrun xcodebuild -project LoremIpsum.xcodeproj -scheme LoremIpsum clean
xcrun xcodebuild CODE_SIGN_IDENTITY="$CODE_SIGN_IDENTITY" PROVISIONING_PROFILE="$PROVISIONING_PROFILE" -project LoremIpsum.xcodeproj -scheme LoremIpsum archive -archivePath LorenIpsum.xcarchive
</code></pre></div></div>
<p>You now have an Xcode archive which is similar to what is submitted to the App Store.</p>
<h2 id="export-your-app-from-xcarchive">Export your app from xcarchive</h2>
<p>The export archive option in <code class="highlighter-rouge">xcodebuild</code> allows a few options which will be saved in a plist file. The <code class="highlighter-rouge">$TEAM_ID</code> is the prefix in the <code class="highlighter-rouge">App IDs</code> in the Apple Developer portal. It can also be found in the mobile provisioning profile file as <code class="highlighter-rouge">TeamIdentifier</code>.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="cp">&lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;</span>
<span class="nt">&lt;plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">&gt;</span>
<span class="nt">&lt;dict&gt;</span>
<span class="nt">&lt;key&gt;</span>method<span class="nt">&lt;/key&gt;</span>
<span class="nt">&lt;string&gt;</span>enterprise<span class="nt">&lt;/string&gt;</span>
<span class="nt">&lt;key&gt;</span>embedOnDemandResourcesAssetPacksInBundle<span class="nt">&lt;/key&gt;</span>
<span class="nt">&lt;true/&gt;</span>
<span class="nt">&lt;key&gt;</span>compileBitcode<span class="nt">&lt;/key&gt;</span>
<span class="nt">&lt;true/&gt;</span>
<span class="nt">&lt;key&gt;</span>teamID<span class="nt">&lt;/key&gt;</span>
<span class="nt">&lt;string&gt;</span>$TEAM_ID<span class="nt">&lt;/string&gt;</span>
<span class="nt">&lt;/dict&gt;</span>
<span class="nt">&lt;/plist&gt;</span></code></pre></figure>
<p>The above options compile the Bitcode enabled app and add all of the resources into the app (instead of having on-demand resources).</p>
<p>Then just run:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xcrun xcodebuild -exportArchive -archivePath LorenIpsum.xcarchive -exportPath $BUILD_DIR/ipa -exportOptionsPlist $PATH_TO_PLIST_FILE
</code></pre></div></div>
<p>You should finally have your app’s IPA in the <code class="highlighter-rouge">$BUILD_DIR/ipa</code> directory which you can install by dragging and dropping the IPA to iTunes and then syncing your device with iTunes.</p>Bryant Lukbryant.luk@gmail.comhttps://blog.bryantluk.com/about.htmlHow to export an iOS enterprise app with shell commands