Showing posts with label swift. Show all posts
Showing posts with label swift. Show all posts

Sunday, June 13, 2021

Deploying Swift/Vapor Apps to Cloudhost

My Swift/Vapor deploying adventure with lots of words, because I stand in solidarity with the Recipe People. But no pictures, because I never think of that.

I thought it would be pretty straightforward. I’ve got Linux hosting at Cloudhost (not its real name) so I can just log in and install Swift and Vapor and build my app and run it, right?

Ha ha, good one.

But my hosting is modest - 1 GB of memory is plenty for my typical needs, which is usually limited to hosting an HTML site - Apache on Ubuntu 16.04.

So try and build a Vapor site? Nope, try and try and fail and fail - fatalError kind of stuff. Try adding -v to the build and re-run just that last command directly in Swift and all of a sudden I see the real problem: I’m running out of memory.

Well hell, of course I am!

So all I need to do is build my app off-site and copy the Run file, right? *Hey, don’t get ahead of me, this is my adventure.

Oh, I almost forgot this part. I’ve got a Linux system, and in fact I’ve already built my app there. Just copy the Run file, right? No, because this is Ubuntu 20.04 and so it depends on libraries and such that are four years newer than what’s running at Cloudhost.

But I’ve got this other PC and it’s got a bunch of memory. It might even ben running Linux already, who knows. Scrounge a keyboard and mouse and monitor. Plug it in. Beep beep beep. Won’t even POST.

No problem, pull the drive and drop it into yet another PC. Boot it up and wouldn’t you know, it’s actually running Ubuntu 16.04. In fact, there are ancient Swift directories from November 2016 when I thought I’d have time to play with the compiler. I didn’t. So get a network hooked up, get Swift installed, and…

$ swift -version

blah blah (I didn’t bother saving all the output)

Illegal Instruction

Oh yeah, if you are thinking about running Swift on Linux on an older PC, do this first:

$ cat /proc/cpuinfo | grep ssse3

because if it comes up blank, you’re going to have a bad time.

So no problem, that Ubuntu 20.04 PC is willing to sacrifice itself temporarily (and I know already it’s got ssse3) so unplug its drive and drop in the Ubuntu 16.04 drive.

Now here’s something I’m not 100% sure I needed to do, but I made sure that I installed Swift in the exact same place on both Ubuntu 16.04 systems - the one on my PC and the one at Cloudhost. In both cases, I have /opt/swift-5.4.1-RELEASE-ubuntu16.04. So if there are libraries that would cause a freakout because of different locations, that’s not going to happen.

And finally I see what I’ve been hoping for:

$ export PATH=/opt/swift-5.4.1-RELEASE-ubuntu16.04/usr/bin:"${PATH}"

$ swift -version

Swift version 5.4.1 (swift-5.4.1-RELEASE)

Target: x86_64-unknown-linux-gnu

So get Vapor installed, pull down my app, and (sudden thunder clap drum roll)

It works!!

*Oh wait… connect from the browser, no it doesn’t!! And this is something I didn’t realize about Vapor, you need to have the Resources/Views directory and all its .leaf (or .html) files. And, it turns out, your Public directory. No problem, so I did a clone of my whole app to get whatever files it needs. As they change, I can pull to refresh them. Then I re-copied the Run file, but this time dropped it in the same x86_64-linux-whatever location as where it came from. This way git doesn’t think I have any untracked files.

Now run it again, and it looks like it works, then hit it from the browser, and it actually does!!

So here are the takeaways:

  • Here are the links for installing swift and vapor
  • You'll have much better luck with your build system separate from your hosting
  • `cat /proc/cpuinfo | grep ssse3` # to make sure your build system is up to running Swift
  • Make sure you have enough memory on your build system (not sure how much that is tbh)
  • Make sure your hosting and build systems have the same exact Ubuntu version (major.minor is probably enough, but…???)
  • Install Swift to the same location on both build and hosting (not 100% sure you need to do this, but I think you do and it can’t hurt, right?)
  • `git clone` your whole app, even though you don’t need the Sources, you do need Resources and Public folders
  • scp .build/x86_64-linux-whatever/debug/Run from your build system to the same spot, so it’s ignored by git

I think that’s it. I hope this helps somebody!

Wednesday, June 02, 2021

Vapor/Leaf #ifdef(sortof)

The Swift/Vapor site I'm working on has one index.leaf to rule them all. (index.html, actually, because I thought I might want to look at my templates in a browser, but tbh I never have - but I digress)

And I find it really handy sometimes to throw in an error message just wherever, like this:

<div class="container">
    #ifdef(error):
    <p class="text-danger">#(error)</p>
    #endif
    #bodyBlock()
</div>

Of course there's no such thing as #ifdef. And I searched and I searched and even thought about implementing #ifdef. 

And then just when I was about to ask on Discord (and I hate doing that)... I don't even know what made me think of this, but here's what works:

<div class="container">
    #if(error??false):
    <p class="text-danger">#(error)</p>
    #endif
    #bodyBlock()
</div>

And tbh I don't know why it works, because if error doesn't exist, why should ?? even help? But it does. And it does!

So if "error" is in my render context, I'll get a nice red dangerous error message. Sweet!

Sunday, May 16, 2021

(not) Auto Vapor Restart

 I was thinking it might be fun to have auto restart of Vapor, like you can do with NodeJS. But then I thought more about what happens when you need to change multiple files, and you make change after change, and it won't build & run until you've made all of the changes.

Anyway, if you add this to routes.swift:

app.delete("kill-switch") { (req: Request) -> Response in
    exit(0)
}

And you start the server like so:

$ while true ; do vapor run ; sleep 3 ; done

Then you can restart it using curl or Postman or (if you use app.get) your browser:

$ curl -s 'http://localhost:8081/kill-switch' -X DELETE

Wait, what about reloading the page when the server restarts? That should be doable, right?

Friday, May 14, 2021

Vapor 4 and MySQL

tldr; if you get connection reset/connection refused errors, try using unixDomainSocketPath instead of hostname.

I've had a heck of a time connecting Vapor 4 to MySQL. I know, maybe I should be using PostgreSQL, but I've got this old database I'd like to be able to connect to and provide new functionality with Swift code.

So I put this code in configure.swift on Linux:

    app.databases.use(.mysql(
            hostname: "localhost",
            username: "dbuser",
            password: "passW0rd",
            database: "whichdb",
            tlsConfiguration: .forClient(certificateVerification: .none)
    ), as: .mysql)

and that worked great.

But connecting to my Linux system is sometimes a pain in the neck, but it turns out that for some reason I have the same database on my Mac. I connect from the command line and `show tables;` lists every table I have on the Linux database. So copy the project to the Mac and `vapor run migrate` (because I'm going to play with the galaxies sample) and I get this error:

connection reset (error set): Connection refused (errno: 61)

Well to make a long story short, I ran lsof and saw that mysqld was listening on port 3306 but also had the file /tmp/mysql.sock open. Then I knew what to search for, and discovered that there is indeed an option to connect by socket:

    try app.databases.use(.mysql(
            unixDomainSocketPath: "/tmp/mysql.sock",
            username: "dbuser",
            password: "passW0rd",
            database: "whichdb"
    ), as: .mysql)

So basically you replace hostname with unixDomainSocketPath and you don't need the TLS configuration nonsense. Oh, and that init throws, so add try.

Hope this helps somebody!

Monday, February 22, 2021

Play my slides, please!

Did you ever need to take notes while you watch your slides play, but you don't want to switch back and forth between your Keynote and your text editor? Here you go... Save this to your Mac as main.swift - no other name will work, then run it in a terminal like so:

$ swift main.swift


import Cocoa

var pid = pid_t(-1)

let bid = "com.apple.iWork.Keynote"

for x in NSWorkspace.shared.runningApplications {
    if x.bundleIdentifier == bid {
        pid = x.processIdentifier
        break
    }
}

guard let keynote = NSRunningApplication(processIdentifier: pid) else {
    print("Can't find Keynote")
    exit(-1)
}

let src = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)

let key: UInt16 = 0x79

guard let keydown = CGEvent(keyboardEventSource: src, virtualKey: key, keyDown: true) else {
    fatalError("Can't get keydown")
}

guard let keyup = CGEvent(keyboardEventSource: src, virtualKey: key, keyDown: false) else {
    fatalError("Can't get keydown")
}

while (true) {
    keydown.postToPid(pid)
    usleep(10_000)
    keyup.postToPid(pid)
    usleep(15_000_000)
}

Saturday, December 24, 2016

#selector vs Selector

TLDR: In Swift 3, use #selector instead of Selector (see here).

The warning (No method declared with Objective-C selector 'swipedLeft:') should be your first clue that there's a problem...



If you use this code in Swift 3, the left-swipe will result in an error like this:

2016-12-24 08:15:12.645 SpriteSimpleGame[65853:2987246] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SpriteSimpleGame.GameScene swipedLeft:]: unrecognized selector sent to instance 0x7fb99e4101f0'

Worst of all, examples like this are all over the place because leaving out the colon results in a similar error.

Thanks to this answer I learned that what you want instead is this:

let swipeLeft = UISwipeGestureRecognizer(
    target: self,
    action: #selector(swipedLeft(sender:))
)

Thanks, internets!!

Friday, January 01, 2016

Swift selector argument - yes you can!

Selectors are an Objective C thing, but sometimes you have to use them in Swift.

For example, if you are calling performSelectorOnMainThread to work around the problem between GCD and UIWebView that causes hangs in stringByEvaluatingJavaScriptFromString (see here) you don't have a choice.

But how to pass an argument? I defined my selector function to take AnyObject? just as I assumed I should since withObject:AnyObject?

So tl;dr - here's what I was doing wrong. The colon at the end of the selector DOES MATTER. Well duh, of course it does, because it indicates there's an argument. Here's an example of everything working correctly...

  private func caller() {
    let arg = 42
    performSelectorOnMainThread("myfunc:", withObject: arg, waitUntilDone: false)
  }

  func myfunc(arg: AnyObject?) {
    guard let n = arg as? Int else {
      print("how did we get in this cornfield?")
      return
    }
    print("and here we are with our arg n: \(n)")
  }

...and the proof of the pudding - my, how tasty!

and here we are with our arg n: 42

I hope this saves somebody else a few hours of struggle!