namespace PulumiWebServer open System.Net.Http open Nager.PublicSuffix open Nager.PublicSuffix.RuleProviders open Newtonsoft.Json open Pulumi open Pulumi.DigitalOcean open System.IO module Program = let stripSubdomain (parser : IDomainParser) (DomainName str) = let info = parser.Parse str $"{info.Domain}.{info.TopLevelDomain}" |> DomainName let config = use file = FileInfo("/Users/patrick/Documents/GitHub/Pulumi/PulumiWebServer/Nix/config.json") .OpenRead () Configuration.get file [] let main _argv = use httpClient = new HttpClient () let cacheProvider = RuleProviders.CacheProviders.LocalFileSystemCacheProvider () let ruleProvider = CachedHttpRuleProvider (cacheProvider, httpClient) if not (ruleProvider.BuildAsync().Result) then failwith "did not build rules" let parser = DomainParser ruleProvider fun () -> output { let! existingKeys = DigitalOcean.storedSshKeys (Output.Create "") let keyContents = let (PublicKey file) = config.PublicKey File.ReadAllText file.FullName let key = existingKeys |> Seq.filter (fun key -> key.PublicKey = keyContents) |> Seq.tryHead let key = match key with | None -> (DigitalOcean.saveSshKey config.PublicKey).Name | Some key -> Output.Create key.Name let! keys = DigitalOcean.storedSshKeys key |> Output.map ( Seq.map (fun s -> { Fingerprint = SshFingerprint s.Fingerprint PublicKeyContents = s.PublicKey } ) >> Seq.sort >> Array.ofSeq ) let! droplet = keys |> Array.map (SshKey.fingerprint >> Input.lift) |> DigitalOcean.makeNixosServer config.Name Region.LON1 let! ipv4 = droplet.Ipv4Address let! ipv6 = droplet.Ipv6Address let address = { IPv4 = Option.ofObj ipv4 IPv6 = Option.ofObj ipv6 } let! zone = Cloudflare.getZone (stripSubdomain parser config.Domain) let dns = Cloudflare.addDns parser config.Domain config.Cnames config.Subdomains zone address let readyCommand = Server.waitForReady 1 config.PrivateKey address let! _ = readyCommand.Stdout let deps = let dnsDeps = dns |> Map.toList |> List.collect (fun (_, record) -> match record with | DnsRecord.ARecord record -> [ record.IPv4 ; record.IPv6 ] | DnsRecord.Cname _ -> [] ) |> List.choose id |> List.map (fun record -> record.Urn) dnsDeps |> Output.sequence |> Output.map (String.concat ",") let! _ = deps let infectNix = Server.infectNix config.PrivateKey address let! _ = infectNix.Stdout // Pull the configuration files down. let pullNetworking = Command.pullFile config.PrivateKey address infectNix.Stdout "pull-networking" (BashString.make "/etc/nixos/networking.nix") (BashString.make "/tmp/networking.nix") |> fun c -> c.Stdout // TODO: do this properly via Command keys |> Array.map (fun k -> k.PublicKeyContents) |> Array.collect (fun s -> s.Split "\n") |> JsonConvert.SerializeObject |> fun s -> File.WriteAllText ("/tmp/ssh-keys.json", s) Log.Info "Stored SSH keys at /tmp/ssh-keys.json" let pullHardware = Command.pullFile config.PrivateKey address infectNix.Stdout "pull-hardware" (BashString.make "/etc/nixos/hardware-configuration.nix") (BashString.make "/tmp/hardware-configuration.nix") |> fun c -> c.Stdout let! _ = pullNetworking Log.Info "Networking configuration at /tmp/networking.nix" let! _ = pullHardware Log.Info "Hardware configuration at /tmp/hardware.nix" // The nixos rebuild has blatted the known public key. let! _ = (Local.forgetKey (address.Get ())).Stdout let! _ = (Local.forgetKey (string config.Domain)).Stderr let readyCommand = Server.waitForReady 2 config.PrivateKey address // Reboot so that we're fully a NixOS system. let reboot = Server.reboot "initial-reboot" readyCommand.Stdout config.PrivateKey address let! _ = reboot.Stdout return address } |> ignore |> Deployment.RunAsync |> Async.AwaitTask |> Async.RunSynchronously