Slide 1

Slide 1 text

Moncef Belyamani / rubyonmac.dev / @monfresh Licensing and Distributing a Paid Ruby CLI on macOS Rocky Mountain Ruby / October 6, 2023

Slide 2

Slide 2 text

Moncef Belyamani @monfresh monfresh.com rubyonmac.dev

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

I spent hours trying to get Jekyll to work on my new M2 Mac. Every time I thought I had it something else would break. Scott Lewis

Slide 7

Slide 7 text

If only I could get those 10 hours back from the last few days spent trying to get 2.6.10 installed on this MacBook Air M1. Dan Weaver

Slide 8

Slide 8 text

I spent around 3 days trying to solve issues on a Rails app. I looked and tried and retried so many possible solutions from so many sources on the web... but nothing was working. Monica Randriamialy

Slide 9

Slide 9 text

I spent probably 4 hours trying to do exactly same setup as before and it never worked. Anton Podviaznikov

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

• Xcode/Command Line Tools version • macOS version • Intel vs Apple Silicon • Installing things with and without Rosetta at the same time • The desired Ruby version • The current active Ruby version • The version manager used • Which OpenSSL versions are installed and which one you're trying to compile with • Missing, miscon fi gured, and outdated dev tools • PATH issues • Setting ENV vars and other compilation settings globally in shell startup fi le What can affect Ruby installation?

Slide 12

Slide 12 text

LDFLAGS CPPFLAGS RUBY_CFLAGS PKG_CONFIG_PATH --with-openssl-dir -Wno-error=implicit-function-declaration

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

The script was such a life saver. Finally got my Flutter project working on my laptop after literally one year! Nicole Manson

Slide 15

Slide 15 text

This script is truly a lifesaver! I couldn't get any version managers to work properly. Gelo Rosel

Slide 16

Slide 16 text

You are a lifesaver, I was struggling with installing rails on my m1 MBP and your script saved all the hassle. Abdullah Ashraf

Slide 17

Slide 17 text

Your script was a lifesaver, thank you so much, I am incredibly grateful for your work. Justin Festa

Slide 18

Slide 18 text

• Painless Ruby dev setup in 15 minutes or less • Works with all version managers • Installs Ruby faster than any other version manager (2x as fast as rbenv/asdf) • Easily install Ruby versions as old as 2.1 • Set up a new Mac in minutes with all your dev tools, Mac apps, fonts, Git preferences, macOS preferences, and GitHub repos. • Reset mode backs up and cleans up your dev setup in 1 minute rubyonmac.dev Ruby on Mac

Slide 19

Slide 19 text

• Prime customers: 1 year of updates, 1 Mac at a time Gotta Have Limits

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

• Prime customers: 1 year of updates, 1 Mac at a time • Use Ruby for license veri fi cation code • License-related code can't be easily bypassed by customer • Scripts need to be able to run on a brand new Mac with no dev tools • Customer can reliably activate and deactivate Macs Gotta Have Limits

Slide 22

Slide 22 text

💰 👤

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

github.com/neurobin/shc

Slide 25

Slide 25 text

test.sh shc test.sh.x.c ./test

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

github.com/jruby/warbler

Slide 33

Slide 33 text

github.com/phusion/ traveling-ruby

Slide 34

Slide 34 text

github.com/pmq20/ruby-packer

Slide 35

Slide 35 text

• Supplies a patched Ruby • Compiles that Ruby locally (with native extensions) • Packs it into a squashfs archive • Ruby is patched to look inside the squashfs and the real fi lesystem How ruby-packer works

Slide 36

Slide 36 text

👍 single file, works with all native extensions

Slide 37

Slide 37 text

👎 no cross-packaging support

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

github.com/ericbeland/ ruby-packer

Slide 40

Slide 40 text

github.com/motor-admin/ ruby-packer

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

unable to find ecdh parameters

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

HOLD UP

Slide 51

Slide 51 text

WAIT A MINUTE

Slide 52

Slide 52 text

rubyc --openssl-dir=/opt/homebrew/etc/[email protected]

Slide 53

Slide 53 text

rom script -m reset

Slide 54

Slide 54 text

certificate verify failed (OpenSSL::SSL::SSLError)

Slide 55

Slide 55 text

➜ ls -la /opt/homebrew/etc/[email protected] total 0 drwxr-xr-x@ 3 moncef admin 96B Sep 12 10:59 ./ drwxrwxr-x@ 16 moncef admin 512B Sep 12 10:59 ../ lrwxr-xr-x@ 1 moncef admin 42B Sep 12 10:59 cert.pem@ -> /opt/homebrew/etc/ca-certificates/cert.pem

Slide 56

Slide 56 text

➜ openssl version -a LibreSSL 3.3.6 built on: date not available platform: information not available options: bn(64,64) rc4(ptr,int) des(idx,cisc,16,int) blowfish(idx) compiler: information not available OPENSSLDIR: "/private/etc/ssl"

Slide 57

Slide 57 text

rubyc --openssl-dir=/private/etc/ssl test.rb -o test

Slide 58

Slide 58 text

🎉

Slide 59

Slide 59 text

bundle exec rake rubyc

Slide 60

Slide 60 text

rubyc --openssl-dir=/private/etc/ssl test.rb -o test

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

• Sign up for Apple Developer Program ($99/year) Gotta Be Legit

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

• https://developer.apple.com/documentation/devicecheck One, Two, One, Two DeviceCheck

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

import DeviceCheck public func getDeviceToken(completion: @escaping (String?) -> ()) { let currentDevice = DCDevice.current if currentDevice.isSupported { currentDevice.generateToken(completionHandler: { (data, error) in if let tokenData = data { let tokenString = tokenData.base64EncodedString() print("Device token generated 🎉") completion(tokenString) } else { print("Error generating token: \(error!.localizedDescription)") } }) } else { print("Device is not supported") // Simulators or etc. } }

Slide 70

Slide 70 text

getDeviceToken { deviceToken in var request = URLRequest(url: "http://localhost:3000/test") request.httpMethod = "POST" // Header request.setValue(deviceToken, forHTTPHeaderField: "Device-Token") // Body request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") let json = ["device_token": deviceToken] as [String : Any] let jsonData = try! JSONSerialization.data(withJSONObject: json) request.httpBody = jsonData as Data // Send it to server let downloadTask = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in ... }) }

Slide 71

Slide 71 text

HOLD UP

Slide 72

Slide 72 text

WAIT A MINUTE

Slide 73

Slide 73 text

??

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

let homeDirectory = NSHomeDirectory() let fileURL = URL(fileURLWithPath: "\(homeDirectory)/token.txt" if let data = token.data(using: .utf8) { do { try data.write(to: fileURL) } catch { print("Error writing token to the file: \(error)") } }

Slide 79

Slide 79 text

DeviceCheck Token ✅

Slide 80

Slide 80 text

Ruby script binary ✅

Slide 81

Slide 81 text

Bash script binary ✅

Slide 82

Slide 82 text

if ! ./verify_license; then exit fi

Slide 83

Slide 83 text

codesign --timestamp \ --options=runtime \ -s "5NB2AVZQPV" \ -v verify_license

Slide 84

Slide 84 text

codesign --display --verbose=1 verify_license

Slide 85

Slide 85 text

Executable=/Users/moncef/projects/rubyonmac-prime/verify_license Identifier=verify_license Format=Mach-O thin (arm64) Signature size=8981 Timestamp=Sep 9, 2023 at 16:52:16 Info.plist=not bound TeamIdentifier=5NB2AVZQPV Runtime Version=13.3.0 Sealed Resources=none

Slide 86

Slide 86 text

require 'json' result = `git for-each-ref --sort=-creatordate --format='% (creatordate:short) | %(contents)' refs/tags` tags = result.split("\n\n") versions = tags.each_with_object([]) do |tag, array| date = tag.split(" | ").first version = tag.split("Release ")[1] array.push({date:, version:}) end File.write("prime-versions.json", JSON.generate(versions))

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

codesign \ --timestamp \ --options=runtime \ -s "5NB2AVZQPV" \ --prefix="com.rubyonmac.2.0.22." \ -v verify_license

Slide 89

Slide 89 text

Executable=/Users/moncef/projects/rubyonmac-prime/verify_license Identifier=com.rubyonmac.2.0.22.verify_license Format=Mach-O thin (arm64) Signature size=8981 Timestamp=Sep 9, 2023 at 16:52:16 Info.plist=not bound TeamIdentifier=5NB2AVZQPV Runtime Version=13.3.0 Sealed Resources=none

Slide 90

Slide 90 text

No content

Slide 91

Slide 91 text

No content

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

No content

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

No content

Slide 97

Slide 97 text

No content

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

No content

Slide 104

Slide 104 text

💰 👤

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

Customer.exists?(email:) ❌

Slide 107

Slide 107 text

Customer.find_by(email:).present? ❌

Slide 108

Slide 108 text

Customer.where(email:).reload.present? ❌

Slide 109

Slide 109 text

Customer.uncached { Customer.where(email:).to_a.present? } ✅

Slide 110

Slide 110 text

• Generate the license • Store it in S3 with ActiveStorage • Respond to Paddle with plain text containing link to the license • Paddle emails customer Go With the Flow

Slide 111

Slide 111 text

• Customer downloads license and latest Ruby on Mac release • Customer runs .pkg installer • Installer puts ROM Token Generator macOS app in /Applications • Installer unzips rubyonmac folder Go With the Flow

Slide 112

Slide 112 text

• Customer runs Bash script • Bash script checks that license veri fi cation binary exists and that it's signed by me • License is decrypted and email and product are extracted • ROM Token Generator app is opened in the background • Token gets saved to a fi le Go With the Flow

Slide 113

Slide 113 text

• Token is read from the fi le and stored in a variable • Token Generator app is quit • Script sends token, email, and product to the Rails app • Rails app performs validations Go With the Flow

Slide 114

Slide 114 text

• Send request to Apple's "query_two_bits" endpoint • "Failed to fi nd bit state" means ROM has not yet been run on this Mac • Send request to "update_two_bits" endpoint to set bit0 and bit1 • If device already activated, Apple returns current bit0 and bit1 values Go With the Flow

Slide 115

Slide 115 text

• Verify current version was released before license expiration • Bash script can continue • License veri fi cation takes ~1 second Go With the Flow

Slide 116

Slide 116 text

No content

Slide 117

Slide 117 text

• MacBook Pro with Touch Bar (2016 and 2017) and Apple T1 Chip • Intel-based Mac computers with Apple T2 Security Chip • Mac computers with Apple silicon Secure Enclave

Slide 118

Slide 118 text

Copying latest Ruby on Mac files... cp: rom_arm64: No such file or directory

Slide 119

Slide 119 text

No content

Slide 120

Slide 120 text

Copying latest Ruby on Mac files... The rom-prime_arm64 file is missing! You are most likely running SentinelOne, which removed the file. Ruby on Mac Prime is not compatible with SentinelOne. Only the Ultimate version is. Please upgrade to Ruby on Mac Ultimate with coupon code FOOBAR: https://www.rubyonmac.dev/pricing If you're not running SentinelOne, please report this issue to [email protected]

Slide 121

Slide 121 text

• Testing Ruby Rewrite Benefits

Slide 122

Slide 122 text

• Testing • User Experience Ruby Rewrite Benefits

Slide 123

Slide 123 text

• Testing • User Experience • Future Proof Ruby Rewrite Benefits

Slide 124

Slide 124 text

Thank You! And a Gift 5⃣ RMR50 4⃣ RMR40 3⃣ RMR30 2⃣ RMR20 1⃣ RMR10 rubyonmac.dev/pricing