From 35ed499cd3767d1e6fd13dc69e5030a0b15ee948 Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 09:51:43 -0600 Subject: [PATCH 01/16] start work on updating copy --- src/lib/components/about/AboutCTA.svelte | 6 ++-- src/lib/components/about/AboutHero.svelte | 0 src/lib/components/about/AboutMission.svelte | 13 ++++----- src/lib/components/about/AboutQuote.svelte | 30 -------------------- src/lib/components/about/AboutTeam.svelte | 2 +- src/routes/+page.svelte | 6 ++-- src/routes/about/+page.svelte | 14 ++++----- src/routes/services/vps/+page.svelte | 6 ++-- 8 files changed, 22 insertions(+), 55 deletions(-) delete mode 100644 src/lib/components/about/AboutHero.svelte delete mode 100644 src/lib/components/about/AboutQuote.svelte diff --git a/src/lib/components/about/AboutCTA.svelte b/src/lib/components/about/AboutCTA.svelte index e9f02b5..8cd94f1 100644 --- a/src/lib/components/about/AboutCTA.svelte +++ b/src/lib/components/about/AboutCTA.svelte @@ -28,11 +28,9 @@ >

- Built by engineers, not a board. + Built by engineers.

-

- Small team, on purpose. We live with the decisions we make. -

+

Small team, on purpose. Everyone here ships.

- Why we exist. + Why we exist

The honest version, not the marketing one.

@@ -19,7 +19,7 @@ ecosystem pulling you in twelve directions.

- We started this because we wanted infrastructure we'd actually want to use — something we + We started this because we wanted infrastructure we'd actually like to use. Something we could recommend to friends without caveat. That's the bar we hold ourselves to.

$5/mo.

@@ -27,21 +27,20 @@

- We're proud supporters of open source. Fyra Stack is the commercial arm of Fyra Labs, a + We're proud supporters of open source. Fyra Stack is the commercial (?) arm of Fyra Labs, a small group building tools and systems around Linux, community, and the idea that software should be made by people who care what happens to it.

- If that sounds like something you want to be a part of, we're building in the open — and the - team page is short enough that you can actually read it. + If that sounds like something you want to be a part of, we're building in the open.

- Fyra Labs on GitHub + Fyra Stack on GitHub
diff --git a/src/lib/components/about/AboutQuote.svelte b/src/lib/components/about/AboutQuote.svelte deleted file mode 100644 index 6d31e98..0000000 --- a/src/lib/components/about/AboutQuote.svelte +++ /dev/null @@ -1,30 +0,0 @@ - -
-
-
-
-
-

- "We were tired of infrastructure that charges you for the privilege of existing. Good - hardware, fair pricing, and engineers who actually pick up the phone — that's what we set - out to build. Nothing more complicated than that." -

-
- Katherine Cole -
-

Katherine Cole

-

- Founder · Infrastructure -

-
-
-
-
-
-
diff --git a/src/lib/components/about/AboutTeam.svelte b/src/lib/components/about/AboutTeam.svelte index 57e7aac..0c2e318 100644 --- a/src/lib/components/about/AboutTeam.svelte +++ b/src/lib/components/about/AboutTeam.svelte @@ -48,7 +48,7 @@

Fyra Stack | Infrastructure without breaking the bank, or your mind @@ -17,7 +17,7 @@ @@ -28,7 +28,7 @@ diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte index c5550b3..706f502 100644 --- a/src/routes/about/+page.svelte +++ b/src/routes/about/+page.svelte @@ -14,7 +14,7 @@ About | Fyra Stack @@ -22,7 +22,7 @@ @@ -33,7 +33,7 @@ @@ -103,9 +103,9 @@ running our own.

- Stack is what came out of that. Real hardware, flat pricing, no surprise fees. We charge you - for what you use. We don't sell your data, we don't lock you in, and we don't have a - "retention team." + Stack is what came out of that. Open software, transparent pricing, and a team who cares as + much as you. We charge you for what you use. We don't sell your data, we don't lock you in, + and we don't have a "retention team."

@@ -141,7 +141,7 @@

The team

- Small, on purpose. Everyone here ships and everyone here answers the support queue. + Small, on purpose. Everyone here ships, and everyone here answers the support queue.

diff --git a/src/routes/services/vps/+page.svelte b/src/routes/services/vps/+page.svelte index d257e53..81a741a 100644 --- a/src/routes/services/vps/+page.svelte +++ b/src/routes/services/vps/+page.svelte @@ -9,7 +9,7 @@ VPS Hosting | Fyra Stack @@ -17,7 +17,7 @@ @@ -28,7 +28,7 @@ From 97f34d11bdb97064e5763d599296c7367634d7d9 Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 10:02:42 -0600 Subject: [PATCH 02/16] more stuff --- src/lib/components/Comparison.svelte | 4 ++-- src/lib/components/about/AboutMission.svelte | 3 ++- .../components/services/colocation/ColocationFeatures.svelte | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/components/Comparison.svelte b/src/lib/components/Comparison.svelte index a0940c0..e68e520 100644 --- a/src/lib/components/Comparison.svelte +++ b/src/lib/components/Comparison.svelte @@ -75,11 +75,11 @@
- +
diff --git a/src/lib/components/about/AboutMission.svelte b/src/lib/components/about/AboutMission.svelte index 7280706..722c0c9 100644 --- a/src/lib/components/about/AboutMission.svelte +++ b/src/lib/components/about/AboutMission.svelte @@ -27,7 +27,8 @@

- We're proud supporters of open source. Fyra Stack is the commercial (?) arm of Fyra Labs, a + + We're proud supporters of open source. Fyra Stack is the commercial arm of Fyra Labs, a small group building tools and systems around Linux, community, and the idea that software should be made by people who care what happens to it.

diff --git a/src/lib/components/services/colocation/ColocationFeatures.svelte b/src/lib/components/services/colocation/ColocationFeatures.svelte index 780b424..65cb84d 100644 --- a/src/lib/components/services/colocation/ColocationFeatures.svelte +++ b/src/lib/components/services/colocation/ColocationFeatures.svelte @@ -64,7 +64,7 @@

Real support

- By the engineers who have actually racked servers before. + By engineers who have actually racked servers before.

From a4239fe22bf12b9ffdc4ec0b0567e0411f4be192 Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 10:12:44 -0600 Subject: [PATCH 03/16] i think i prefer this stuff without the periods --- src/lib/components/Features.svelte | 10 ++++----- .../services/vps/VPSFeatures.svelte | 22 ++++++++++--------- .../components/services/vps/VPSHero.svelte | 2 +- .../components/services/vps/VPSPlans.svelte | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/lib/components/Features.svelte b/src/lib/components/Features.svelte index 02d3182..d30c40b 100644 --- a/src/lib/components/Features.svelte +++ b/src/lib/components/Features.svelte @@ -16,8 +16,8 @@

- We focus on bare-metal that just works, built from the ground up to support the open source - projects you love; all without breaking the bank, or your mind. + We focus on bare-metal that just works, built from the ground up to support and use the open + source projects you love; all without breaking the bank, or your mind.

@@ -44,11 +44,11 @@ - +

Midwest proud

- Operating out of Chicago, Illinois since 2026. + Operating out of Chicago, Illinois.

@@ -56,7 +56,7 @@

Independent

- Built on infrastructure we own, we don't resell another provider. + Built on infrastructure we own. We don't resell another provider.

diff --git a/src/lib/components/services/vps/VPSFeatures.svelte b/src/lib/components/services/vps/VPSFeatures.svelte index 6bc6688..979a6ed 100644 --- a/src/lib/components/services/vps/VPSFeatures.svelte +++ b/src/lib/components/services/vps/VPSFeatures.svelte @@ -10,14 +10,15 @@

- Most budget providers cut corners on hardware to hit their price points, we don't. + Most budget providers cut corners on hardware to hit their price points. We don't.

Powered by Cascade Lake series CPUs, Dell PowerEdge servers, and enterprise SSDs. We buy - good hardware, run it efficiently, run a small focused team, don't oversell, and charge a - fair price. + + good hardware, run it efficiently, run a small focused team, don't oversell, and charge a fair + price.

-

It's boring and it works.

+

It's boring, and it works.

@@ -33,7 +34,8 @@

Simple control panel

- Developed with engineers in mind from the beginning. + + Developed with you in mind, from the beginning

@@ -42,15 +44,15 @@

Linux native

- Run Ultramarine, Debian, Alma, or whatever your stack needs. + Run Ultramarine, Debian, Alma, or whatever your stack needs

- +

Fast by default

- Powered by Cascade Lake CPUs that are fast, real fast. + Powered by Cascade Lake CPUs that are fast, really fast

@@ -58,7 +60,7 @@

Real support

- By the system engineers that actually maintain the servers. + By the system engineers that actually maintain the servers

@@ -66,7 +68,7 @@

Honest pricing

- No hidden fees, no egress charges, no surprise bills. + No hidden fees, no egress charges, no surprise bills

diff --git a/src/lib/components/services/vps/VPSHero.svelte b/src/lib/components/services/vps/VPSHero.svelte index ed1d726..6d578fc 100644 --- a/src/lib/components/services/vps/VPSHero.svelte +++ b/src/lib/components/services/vps/VPSHero.svelte @@ -22,7 +22,7 @@ You run your stack

- Our mission is simple: Provide hosting you can trust, without breaking the bank, or your mind. + Our mission is simple: Provide hosting you can trust, without breaking the bank, or your mind

diff --git a/src/lib/components/services/vps/VPSPlans.svelte b/src/lib/components/services/vps/VPSPlans.svelte index 37fb8d0..b6bc827 100644 --- a/src/lib/components/services/vps/VPSPlans.svelte +++ b/src/lib/components/services/vps/VPSPlans.svelte @@ -13,7 +13,7 @@ Priced fairly -

Simple, flat-rate plans with no surprise fees.

+

Simple, flat-rate plans with no surprise fees

From 7ba90f11a723aa199b36ea11db96625295a7a02f Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 10:18:55 -0600 Subject: [PATCH 04/16] a bunch of the colo stuff was annoying me --- .../colocation/ColocationDocsLink.svelte | 18 ++++++++++-------- .../colocation/ColocationFeatures.svelte | 12 ++++++------ .../services/colocation/ColocationHero.svelte | 2 +- .../services/colocation/ColocationPlans.svelte | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/lib/components/services/colocation/ColocationDocsLink.svelte b/src/lib/components/services/colocation/ColocationDocsLink.svelte index ce490dc..3eb358e 100644 --- a/src/lib/components/services/colocation/ColocationDocsLink.svelte +++ b/src/lib/components/services/colocation/ColocationDocsLink.svelte @@ -7,13 +7,15 @@

Need a hand getting started?

-

Our docs walk you through it.

- - Read the documentation - +

Our docs walk you through it!

+
diff --git a/src/lib/components/services/colocation/ColocationFeatures.svelte b/src/lib/components/services/colocation/ColocationFeatures.svelte index 65cb84d..c9c442a 100644 --- a/src/lib/components/services/colocation/ColocationFeatures.svelte +++ b/src/lib/components/services/colocation/ColocationFeatures.svelte @@ -33,13 +33,13 @@ aria-hidden="true" />

Midwest proud

- Providing low-latency access to the entire US on our blended network. + Providing low-latency access to the entire US on our blended network

@@ -48,7 +48,7 @@

IPMI remote access

- Full out-of-band management over a simple VPN connection. + Full out-of-band management over a simple VPN connection

@@ -56,7 +56,7 @@

Redundant power

- A+B feeds, UPS, and generator backup keeps your server on. + A+B feeds, UPS, and a generator backup keeps your server on

@@ -64,7 +64,7 @@

Real support

- By engineers who have actually racked servers before. + By engineers who have actually racked servers before

@@ -72,7 +72,7 @@

Honest pricing

- No hidden power fees, no egress charges, no surprise bills. + No hidden power fees, no egress charges, no surprise bills

diff --git a/src/lib/components/services/colocation/ColocationHero.svelte b/src/lib/components/services/colocation/ColocationHero.svelte index 7024994..97a1c33 100644 --- a/src/lib/components/services/colocation/ColocationHero.svelte +++ b/src/lib/components/services/colocation/ColocationHero.svelte @@ -27,7 +27,7 @@ We'll handle the rest

- Colocation without the enterprise markup, mandatory contracts, or fees for asking questions. + Colocation without the enterprise markup, mandatory contracts, or fees for asking questions

-
+ diff --git a/src/lib/components/services/colocation/ColocationSignup.svelte b/src/lib/components/services/colocation/ColocationSignup.svelte index 14ef527..aebd86d 100644 --- a/src/lib/components/services/colocation/ColocationSignup.svelte +++ b/src/lib/components/services/colocation/ColocationSignup.svelte @@ -103,7 +103,7 @@ Chicago, IL 60625`; Get started with colocation

- Select your plan, provide your details, and we'll get you set up. + Select your plan, provide your details, and we'll get you set up

From 1a150875f96fad12aea15e94ae93000845f60add Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 10:25:03 -0600 Subject: [PATCH 06/16] also update spacing on VPS --- src/lib/components/services/vps/VPSPlans.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/services/vps/VPSPlans.svelte b/src/lib/components/services/vps/VPSPlans.svelte index b6bc827..6367cc1 100644 --- a/src/lib/components/services/vps/VPSPlans.svelte +++ b/src/lib/components/services/vps/VPSPlans.svelte @@ -31,7 +31,7 @@

CPU

@@ -73,7 +73,7 @@ >
-
+

{plan.cpu} vCPU • From 4949907983434fc28e458e1b690e55514f9d0609 Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 10:37:31 -0600 Subject: [PATCH 07/16] run prettier and update root page --- .github/workflows/accessibility.yml | 40 +++++++++---------- src/lib/components/Features.svelte | 21 ++++++---- src/lib/components/Pricing.svelte | 6 +-- src/lib/components/about/AboutMission.svelte | 8 ++-- .../services/vps/VPSFeatures.svelte | 2 +- src/lib/remote/colocation-reserve.remote.ts | 4 +- 6 files changed, 44 insertions(+), 37 deletions(-) diff --git a/.github/workflows/accessibility.yml b/.github/workflows/accessibility.yml index 320b52a..0e21901 100644 --- a/.github/workflows/accessibility.yml +++ b/.github/workflows/accessibility.yml @@ -6,31 +6,31 @@ on: - main - next paths: - - ".github/workflows/accessibility.yml" - - "package.json" - - "playwright.config.*" - - "svelte.config.*" - - "vite.config.*" - - "wrangler.jsonc" - - "pnpm-lock.yaml" - - "src/**" - - "static/**" - - "tests/**" + - '.github/workflows/accessibility.yml' + - 'package.json' + - 'playwright.config.*' + - 'svelte.config.*' + - 'vite.config.*' + - 'wrangler.jsonc' + - 'pnpm-lock.yaml' + - 'src/**' + - 'static/**' + - 'tests/**' push: branches: - main - next paths: - - ".github/workflows/accessibility.yml" - - "package.json" - - "playwright.config.*" - - "svelte.config.*" - - "vite.config.*" - - "wrangler.jsonc" - - "pnpm-lock.yaml" - - "src/**" - - "static/**" - - "tests/**" + - '.github/workflows/accessibility.yml' + - 'package.json' + - 'playwright.config.*' + - 'svelte.config.*' + - 'vite.config.*' + - 'wrangler.jsonc' + - 'pnpm-lock.yaml' + - 'src/**' + - 'static/**' + - 'tests/**' workflow_dispatch: permissions: contents: read diff --git a/src/lib/components/Features.svelte b/src/lib/components/Features.svelte index d30c40b..9074cb4 100644 --- a/src/lib/components/Features.svelte +++ b/src/lib/components/Features.svelte @@ -12,12 +12,17 @@ Stack is provided by Fyra Labs, an open-source community that develops Terra and Ultramarine Linux. + >, the open source community that develops Terra and Ultramarine Linux.

- We focus on bare-metal that just works, built from the ground up to support and use the open - source projects you love; all without breaking the bank, or your mind. + Everything we do is open source, and you can view and contribute to the very code that + powers your server. +

+ +

+ We focus on servers that just work, built to support and use the open source projects you + love.

@@ -39,7 +44,7 @@

Fast by default

- Fast storage, fast compute, fast networking,
fast everything. + Fast storage, fast compute, fast networking,
fast everything

@@ -48,7 +53,7 @@

Midwest proud

- Operating out of Chicago, Illinois. + Operating out of Chicago, Illinois

@@ -56,7 +61,7 @@

Independent

- Built on infrastructure we own. We don't resell another provider. + Built on infrastructure we own, we don't resell another provider

@@ -64,7 +69,7 @@

Real support

- By the system engineers that actually maintain the servers. + By the engineers that actually maintain the servers

@@ -72,7 +77,7 @@

Honest pricing

- No hidden fees, no egress charges, no surprise bills. + No hidden fees, no egress charges, no surprise bills

diff --git a/src/lib/components/Pricing.svelte b/src/lib/components/Pricing.svelte index 2194c66..5cd11d5 100644 --- a/src/lib/components/Pricing.svelte +++ b/src/lib/components/Pricing.svelte @@ -10,7 +10,7 @@ name: 'Virtual Private Servers', href: '/services/vps' as ServiceHref, price: '5', - description: 'Scales to $48/mo.', + description: 'Scales to $48/mo', features: [ 'Cascade Lake vCPUs', 'SAS3 SSD storage', @@ -26,7 +26,7 @@ name: 'Server Colocation', href: '/services/colocation' as ServiceHref, price: '50', - description: 'Scales to $280/mo.', + description: 'Scales to $280/mo', features: [ 'Up to 6U rackspace', 'Up to 700W power budget', @@ -47,7 +47,7 @@

Priced fairly

-

Simple, flat-rate plans. No surprise fees.

+

Simple, flat-rate plans with no surprise fees

diff --git a/src/lib/components/about/AboutMission.svelte b/src/lib/components/about/AboutMission.svelte index 722c0c9..d4da2b1 100644 --- a/src/lib/components/about/AboutMission.svelte +++ b/src/lib/components/about/AboutMission.svelte @@ -27,10 +27,10 @@

- - We're proud supporters of open source. Fyra Stack is the commercial arm of Fyra Labs, a - small group building tools and systems around Linux, community, and the idea that software - should be made by people who care what happens to it. + + We're proud supporters of open source. Fyra Stack is the commercial arm of Fyra Labs, a small + group building tools and systems around Linux, community, and the idea that software should be + made by people who care what happens to it.

If that sounds like something you want to be a part of, we're building in the open. diff --git a/src/lib/components/services/vps/VPSFeatures.svelte b/src/lib/components/services/vps/VPSFeatures.svelte index 979a6ed..c904ed0 100644 --- a/src/lib/components/services/vps/VPSFeatures.svelte +++ b/src/lib/components/services/vps/VPSFeatures.svelte @@ -18,7 +18,7 @@ good hardware, run it efficiently, run a small focused team, don't oversell, and charge a fair price.

-

It's boring, and it works.

+

It's boring, and it works!

diff --git a/src/lib/remote/colocation-reserve.remote.ts b/src/lib/remote/colocation-reserve.remote.ts index d5b2f71..2096357 100644 --- a/src/lib/remote/colocation-reserve.remote.ts +++ b/src/lib/remote/colocation-reserve.remote.ts @@ -33,7 +33,9 @@ async function sendDiscordNotification(webhookUrl: string, webhookBody: unknown) }); if (!response.ok) { - throw new Error(`Response status: ${response.status}, Response content: ${await response.text()}`); + throw new Error( + `Response status: ${response.status}, Response content: ${await response.text()}` + ); } } From 935984723ba670c8608249f4151c9c6b04b4452a Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Tue, 1 Sep 2026 16:09:28 -0600 Subject: [PATCH 08/16] add documentation for nat64 (#97) * add documentation for nat64 * remove redundant connection up thing --- src/routes/docs/+page.svelte | 8 +- src/routes/docs/vps/+page.svelte | 5 + src/routes/docs/vps/nat64/+page.svx | 145 ++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/routes/docs/vps/nat64/+page.svx diff --git a/src/routes/docs/+page.svelte b/src/routes/docs/+page.svelte index 837f5ff..3fe1797 100644 --- a/src/routes/docs/+page.svelte +++ b/src/routes/docs/+page.svelte @@ -16,6 +16,7 @@ | '/docs/vps/hardening' | '/docs/vps/networking' | '/docs/vps/choosing-a-distro' + | '/docs/vps/nat64' | '/docs/account-billing/support'; type Step = { @@ -93,7 +94,12 @@ { label: 'Adding a Non-Root User', soon: false, href: '/docs/vps/user-setup' }, { label: 'Initial Server Hardening', soon: false, href: '/docs/vps/hardening' }, { label: 'VPS Networking', soon: false, href: '/docs/vps/networking' }, - { label: 'Choosing a Linux Distribution', soon: false, href: '/docs/vps/choosing-a-distro' } + { + label: 'Choosing a Linux Distribution', + soon: false, + href: '/docs/vps/choosing-a-distro' + }, + { label: 'IPv6-only networking with NAT64', soon: false, href: '/docs/vps/nat64' } ] }, { diff --git a/src/routes/docs/vps/+page.svelte b/src/routes/docs/vps/+page.svelte index b4fa66d..606cbe8 100644 --- a/src/routes/docs/vps/+page.svelte +++ b/src/routes/docs/vps/+page.svelte @@ -27,6 +27,11 @@ label: 'Choosing a Linux Distribution', description: 'Pick the operating system that fits your workload.', href: '/docs/vps/choosing-a-distro' + }, + { + label: 'IPv6-only networking with NAT64', + description: 'Use IPv6-only VPS networking to reach IPv4-only services.', + href: '/docs/vps/nat64' } ] as const; diff --git a/src/routes/docs/vps/nat64/+page.svx b/src/routes/docs/vps/nat64/+page.svx new file mode 100644 index 0000000..0ec1955 --- /dev/null +++ b/src/routes/docs/vps/nat64/+page.svx @@ -0,0 +1,145 @@ + + + + +# IPv6-only VPS networking + +Fyra Stack supports IPv6-only VPS networking, now with NAT64. NAT64 allows an IPv6-only VPS to reach services that are otherwise available only over IPv4. NAT64 is configured automatically **only for IPv6-only VPSes**. + +**Note:** NAT64 does not give the VPS a public IPv4 address, and it does not make an IPv4-only service reachable from the public internet over IPv6. + +--- +## How it works + +The VPS uses: + +- IPv6 for its normal default route. +- `64:ff9b::/96` for IPv4-embedded IPv6 destinations. +- Fyra Stack's DNS64 resolver to synthesize IPv6 `AAAA` records for IPv4-only hostnames. + +New VPSes receive the NAT64 route and DNS64 resolver through cloud-init. If you have an existing VPS, you'll have to do some configuration manually, as detailed below. + +--- +## Check whether NAT64 is working + +From an IPv6-only VPS, resolve and connect to a hostname that has an IPv4 address: + +```sh +getent ahosts example.com +curl -6 https://example.com +``` + +On an IPv6-only VPS, `curl -4` is expected to fail because the VM has no IPv4 connectivity. + +The `curl -6` request should work when DNS64 and NAT64 are configured. A literal IPv4 address cannot be used directly by an IPv6-only host; use a hostname so DNS64 can synthesize the IPv6 destination. + +You can also test the well-known NAT64 prefix by embedding an IPv4 address. Replace `192.0.2.1` with an IPv4 address you are authorized to test: + +```sh +ping -6 64:ff9b::192.0.2.1 +``` + +--- +## New VPSes + +New IPv6-only VPSes receive the NAT64 route automatically. VPSes with IPv4 do not receive this route or the NAT64 DNS resolver. The route is: + +``` +64:ff9b::/96 via the IPv6 gateway +``` + +The generated configuration also places the Fyra Stack DNS64 resolver before the public fallback resolvers. You normally do not need to edit the network configuration manually. + +--- +## Existing IPv6-only VPSes using NetworkManager + +Add the NAT64 route to the active connection. Replace `CONNECTION` with the connection name and use the gateway shown in your VPS networking details: + +```sh +nmcli connection show +sudo nmcli connection modify "CONNECTION" +ipv6.routes "64:ff9b::/96 fe80::1040:ffff" +``` + +Configure the DNS64 resolver without discarding your existing resolvers: + +```sh +sudo nmcli connection modify "CONNECTION" ipv6.dns "2602:f41f:10:1040::ffff 2606:4700:4700::1111 2606:4700:4700::1001" +sudo nmcli connection up "CONNECTION" +``` + +Verify the route and DNS configuration: + +```sh +ip -6 route get 64:ff9b::1.1.1.1 +resolvectl status +curl -6 https://example.com +``` + +--- +## Existing IPv6-only Linux VPSes using netplan + +Back up the current configuration before editing it: + +```sh +sudo cp -a /etc/netplan /etc/netplan.backup +``` + +Add the NAT64 route to the existing interface configuration. Do not remove your assigned addresses, default routes, or existing DNS settings. The exact file name may differ; common locations include `/etc/netplan/50-cloud-init.yaml` and `/etc/netplan/99-custom.yaml`. + +Use this route in the existing `routes:` list: + +```yaml +routes: + - to: 64:ff9b::/96 + via: fe80::1040:ffff + on-link: true +``` + +Configure the Fyra Stack DNS64 resolver in the existing `nameservers.addresses` list. The resolver address is provided in your VPS networking details. The current public VPS network uses: + +```yaml +nameservers: + addresses: + - 2602:f41f:10:1040::ffff + - 1.1.1.1 + - 1.0.0.1 + - 2606:4700:4700::1111 + - 2606:4700:4700::1001 +``` + +Apply and verify the configuration: + +```sh +sudo netplan try +sudo netplan apply +ip -6 route get 64:ff9b::1.1.1.1 +resolvectl status +curl -6 https://example.com +``` + +If your network uses a different IPv6 gateway, use the gateway shown in your VPS networking details instead of `fe80::1040:ffff`. + +--- +## Troubleshooting + +- If `curl -6` fails for a hostname but direct IPv6 sites work, check that the Fyra Stack DNS64 resolver is configured and reachable. +- If DNS returns only an IPv4 address, the resolver being used is not synthesizing DNS64 records. +- If DNS64 works but connections fail, check that `64:ff9b::/96` exists in `ip -6 route`. +- Do not add a second IPv6 default route just to enable NAT64. NAT64 uses the more specific `64:ff9b::/96` route. +- Do not replace your VPS's assigned IPv6 address or delegated prefix when adding this route. + +If you need help, send us an email at [support@fyrastack.com](mailto:support@fyrastack.com), or [join our Discord](https://fyra.to/discord). Send us the output of: + +```sh +ip -6 addr +ip -6 route +resolvectl status +``` + +You can remove your public IP addresses from the output if you don't want to share them. From b98c6a0ea43e865766ca3f00d731d670ffdd9739 Mon Sep 17 00:00:00 2001 From: Addison LeClair Date: Mon, 7 Sep 2026 02:09:55 +0000 Subject: [PATCH 09/16] fix(colocation): update plans to regular pricing - Remove limited-time pricing displays - Update navigation and comparison copy to reflect new rates --- src/lib/components/Nav.svelte | 2 +- .../colocation/ColocationComparison.svelte | 8 +------- .../services/colocation/ColocationPlans.svelte | 16 ---------------- src/lib/data/colocationPlans.ts | 18 ++++++------------ 4 files changed, 8 insertions(+), 36 deletions(-) diff --git a/src/lib/components/Nav.svelte b/src/lib/components/Nav.svelte index 678f197..7eb9396 100644 --- a/src/lib/components/Nav.svelte +++ b/src/lib/components/Nav.svelte @@ -23,7 +23,7 @@ }, { label: 'Colocation', - description: 'Colocation hosting, from $50/mo', + description: 'Colocation hosting, from $60/mo', href: '/services/colocation' as InternalHref } ]; diff --git a/src/lib/components/services/colocation/ColocationComparison.svelte b/src/lib/components/services/colocation/ColocationComparison.svelte index b167bc9..a9a647e 100644 --- a/src/lib/components/services/colocation/ColocationComparison.svelte +++ b/src/lib/components/services/colocation/ColocationComparison.svelte @@ -12,7 +12,6 @@ const rows: { label: string; stack: { - previousPrice?: string; text: string; }; values: string[]; @@ -20,8 +19,7 @@ { label: 'Entry price', stack: { - previousPrice: '$60', - text: '$50/mo' + text: '$60/mo' }, values: ['$200+/mo', '$80+/mo', '$0+/mo*'] }, @@ -115,10 +113,6 @@ {row.label}
{#each row.values as val, i (`${row.label}-${competitors[i]}`)} diff --git a/src/lib/components/services/colocation/ColocationPlans.svelte b/src/lib/components/services/colocation/ColocationPlans.svelte index 18e9bb1..e324dbf 100644 --- a/src/lib/components/services/colocation/ColocationPlans.svelte +++ b/src/lib/components/services/colocation/ColocationPlans.svelte @@ -22,22 +22,11 @@

- {#if currentPlan.originalPrice} - ${currentPlan.originalPrice} - {/if} ${currentPlan.price}/mo

- {#if currentPlan.originalPrice} - Limited time - {/if}
@@ -78,11 +67,6 @@

{plan.name}

- {#if plan.originalPrice} - ${plan.originalPrice} - {/if} ${plan.price}/mo diff --git a/src/lib/data/colocationPlans.ts b/src/lib/data/colocationPlans.ts index ad1c32f..34d6b41 100644 --- a/src/lib/data/colocationPlans.ts +++ b/src/lib/data/colocationPlans.ts @@ -1,8 +1,7 @@ export const colocationPlans = [ { name: 'COLO-1U', - price: '50', - originalPrice: '60', + price: '60', units: '1', power: '200', bandwidth: '1Gbps', @@ -10,8 +9,7 @@ export const colocationPlans = [ }, { name: 'COLO-2U', - price: '90', - originalPrice: '100', + price: '100', units: '2', power: '300', bandwidth: '1Gbps', @@ -19,8 +17,7 @@ export const colocationPlans = [ }, { name: 'COLO-3U', - price: '130', - originalPrice: '140', + price: '140', units: '3', power: '400', bandwidth: '1Gbps', @@ -28,8 +25,7 @@ export const colocationPlans = [ }, { name: 'COLO-4U', - price: '170', - originalPrice: '180', + price: '180', units: '4', power: '500', bandwidth: '1Gbps', @@ -37,8 +33,7 @@ export const colocationPlans = [ }, { name: 'COLO-5U', - price: '210', - originalPrice: '220', + price: '220', units: '5', power: '600', bandwidth: '1Gbps', @@ -46,8 +41,7 @@ export const colocationPlans = [ }, { name: 'COLO-6U', - price: '250', - originalPrice: '260', + price: '260', units: '6', power: '700', bandwidth: '1Gbps', From d06bc578945e72568abef34c540efad7c4d4e1e3 Mon Sep 17 00:00:00 2001 From: Addison LeClair Date: Mon, 7 Sep 2026 02:23:09 +0000 Subject: [PATCH 10/16] fix some spacing stuff --- src/lib/components/Nav.svelte | 2 +- .../colocation/ColocationComparison.svelte | 2 +- .../colocation/ColocationContact.svelte | 29 +++++ .../services/colocation/ColocationHero.svelte | 2 +- src/lib/data/colocationPlans.ts | 12 +-- src/lib/remote/colocation-reserve.remote.ts | 100 ------------------ src/routes/docs/+page.svelte | 2 +- src/routes/services/colocation/+page.svelte | 4 +- 8 files changed, 41 insertions(+), 112 deletions(-) create mode 100644 src/lib/components/services/colocation/ColocationContact.svelte delete mode 100644 src/lib/remote/colocation-reserve.remote.ts diff --git a/src/lib/components/Nav.svelte b/src/lib/components/Nav.svelte index 7eb9396..074c234 100644 --- a/src/lib/components/Nav.svelte +++ b/src/lib/components/Nav.svelte @@ -23,7 +23,7 @@ }, { label: 'Colocation', - description: 'Colocation hosting, from $60/mo', + description: 'Colocation hosting, from $70/mo', href: '/services/colocation' as InternalHref } ]; diff --git a/src/lib/components/services/colocation/ColocationComparison.svelte b/src/lib/components/services/colocation/ColocationComparison.svelte index a9a647e..62998a6 100644 --- a/src/lib/components/services/colocation/ColocationComparison.svelte +++ b/src/lib/components/services/colocation/ColocationComparison.svelte @@ -19,7 +19,7 @@ { label: 'Entry price', stack: { - text: '$60/mo' + text: '$70/mo' }, values: ['$200+/mo', '$80+/mo', '$0+/mo*'] }, diff --git a/src/lib/components/services/colocation/ColocationContact.svelte b/src/lib/components/services/colocation/ColocationContact.svelte new file mode 100644 index 0000000..5104133 --- /dev/null +++ b/src/lib/components/services/colocation/ColocationContact.svelte @@ -0,0 +1,29 @@ + + +
+
+

+ Get started with colocation +

+

+ Tell us what you're racking and we'll get you set up. +

+
+ +
+

+ Send us your plan, hardware details, and power needs. We'll confirm availability, share + shipping instructions, and send an invoice once your server is racked. +

+ + Contact us + +
+
diff --git a/src/lib/components/services/colocation/ColocationHero.svelte b/src/lib/components/services/colocation/ColocationHero.svelte index 97a1c33..f67de6d 100644 --- a/src/lib/components/services/colocation/ColocationHero.svelte +++ b/src/lib/components/services/colocation/ColocationHero.svelte @@ -31,7 +31,7 @@

Get started diff --git a/src/lib/data/colocationPlans.ts b/src/lib/data/colocationPlans.ts index 34d6b41..02c8760 100644 --- a/src/lib/data/colocationPlans.ts +++ b/src/lib/data/colocationPlans.ts @@ -1,7 +1,7 @@ export const colocationPlans = [ { name: 'COLO-1U', - price: '60', + price: '70', units: '1', power: '200', bandwidth: '1Gbps', @@ -9,7 +9,7 @@ export const colocationPlans = [ }, { name: 'COLO-2U', - price: '100', + price: '140', units: '2', power: '300', bandwidth: '1Gbps', @@ -17,7 +17,7 @@ export const colocationPlans = [ }, { name: 'COLO-3U', - price: '140', + price: '210', units: '3', power: '400', bandwidth: '1Gbps', @@ -25,7 +25,7 @@ export const colocationPlans = [ }, { name: 'COLO-4U', - price: '180', + price: '280', units: '4', power: '500', bandwidth: '1Gbps', @@ -33,7 +33,7 @@ export const colocationPlans = [ }, { name: 'COLO-5U', - price: '220', + price: '350', units: '5', power: '600', bandwidth: '1Gbps', @@ -41,7 +41,7 @@ export const colocationPlans = [ }, { name: 'COLO-6U', - price: '260', + price: '420', units: '6', power: '700', bandwidth: '1Gbps', diff --git a/src/lib/remote/colocation-reserve.remote.ts b/src/lib/remote/colocation-reserve.remote.ts deleted file mode 100644 index 2096357..0000000 --- a/src/lib/remote/colocation-reserve.remote.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { form, getRequestEvent } from '$app/server'; -import { type } from 'arktype'; -import { colocationPlans } from '$lib/data/colocationPlans'; -import { env } from '$env/dynamic/private'; -import { Autumn } from 'autumn-js'; - -const planNames = colocationPlans.map((p) => p.name); -const planType = type.enumerated(...planNames); - -const reserveSchema = type({ - plan: planType, - name: 'string.trim & /\\S/', - email: 'string.trim & string.email' -}); - -export type ReserveInput = typeof reserveSchema.infer; - -export type ReserveResult = - | { - ok: true; - paymentLink: string; - } - | { - ok: false; - error: string; - }; - -async function sendDiscordNotification(webhookUrl: string, webhookBody: unknown) { - const response = await fetch(webhookUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(webhookBody) - }); - - if (!response.ok) { - throw new Error( - `Response status: ${response.status}, Response content: ${await response.text()}` - ); - } -} - -async function createPaymentLink(plan: string, email: string, name: string): Promise { - const event = getRequestEvent(); - if (!event) { - throw new Error('No request event found'); - } - - const url = new URL(event.request.url); - const successUrl = `${url.origin}/services/colocation?success=true`; - - const user_id = crypto.randomUUID(); - const autumn = new Autumn({ secretKey: env.AUTUMN_SECRET_KEY }); - - await autumn.customers.getOrCreate({ - customerId: user_id, - name: name, - email: email - }); - - const response = await autumn.billing.attach({ - customerId: user_id, - planId: plan, - successUrl - }); - - const webhookBody = { - username: 'Fyra Stack', - embeds: [ - { - author: { name: 'New Colocation Payment Link Creation' }, - title: `${plan}`, - description: `${email} - ${name}`, - color: 0xc6716d, - footer: { text: `fyrastack.com` }, - timestamp: new Date().toISOString() - } - ] - }; - - if (env.COLOCATION_RESERVATION_DISCORD_WEBHOOK) { - const notification = sendDiscordNotification( - env.COLOCATION_RESERVATION_DISCORD_WEBHOOK, - webhookBody - ).catch((error) => { - console.error('Failed to send Discord colocation reservation notification', error); - }); - - event.platform?.ctx.waitUntil(notification); - } else { - console.log('would have called discord colocation webhook'); - console.log(webhookBody); - } - - return response.paymentUrl as string; -} - -export const reserve = form(reserveSchema, async (data): Promise => { - const paymentLink = await createPaymentLink(data.plan, data.email, data.name); - return { ok: true, paymentLink }; -}); diff --git a/src/routes/docs/+page.svelte b/src/routes/docs/+page.svelte index 3fe1797..4a256f0 100644 --- a/src/routes/docs/+page.svelte +++ b/src/routes/docs/+page.svelte @@ -35,7 +35,7 @@ n: '01', title: 'Contact us', body: "Pick your colocation plan and use the contact form, we'll then reach out to organize shipping your server.", - cta: { label: 'Sign up today →', href: '/services/colocation' } + cta: { label: 'Get in touch →', href: '/services/colocation' } }, { n: '02', diff --git a/src/routes/services/colocation/+page.svelte b/src/routes/services/colocation/+page.svelte index c4d3252..67502e6 100644 --- a/src/routes/services/colocation/+page.svelte +++ b/src/routes/services/colocation/+page.svelte @@ -4,7 +4,7 @@ import ColocationComparison from '$lib/components/services/colocation/ColocationComparison.svelte'; import ColocationPlans from '$lib/components/services/colocation/ColocationPlans.svelte'; import ColocationDocsLink from '$lib/components/services/colocation/ColocationDocsLink.svelte'; - import ColocationSignup from '$lib/components/services/colocation/ColocationSignup.svelte'; + import ColocationContact from '$lib/components/services/colocation/ColocationContact.svelte'; @@ -45,4 +45,4 @@ - + From b70cac15c80a69d8b011082f32ac5d094f3c9154 Mon Sep 17 00:00:00 2001 From: Addison LeClair Date: Mon, 7 Sep 2026 02:25:12 +0000 Subject: [PATCH 11/16] fix(pricing): update server colocation rates --- src/lib/components/Pricing.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/Pricing.svelte b/src/lib/components/Pricing.svelte index 5cd11d5..bf9b4a6 100644 --- a/src/lib/components/Pricing.svelte +++ b/src/lib/components/Pricing.svelte @@ -25,8 +25,8 @@ { name: 'Server Colocation', href: '/services/colocation' as ServiceHref, - price: '50', - description: 'Scales to $280/mo', + price: '70', + description: 'Scales to $420/mo.', features: [ 'Up to 6U rackspace', 'Up to 700W power budget', From 9c667882ddc2256359ae77ed113e6160a77f6db2 Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 10:37:31 -0600 Subject: [PATCH 12/16] run prettier and update root page --- src/lib/components/Pricing.svelte | 4 +- src/lib/remote/colocation-reserve.remote.ts | 100 ++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/lib/remote/colocation-reserve.remote.ts diff --git a/src/lib/components/Pricing.svelte b/src/lib/components/Pricing.svelte index bf9b4a6..59c85c4 100644 --- a/src/lib/components/Pricing.svelte +++ b/src/lib/components/Pricing.svelte @@ -25,8 +25,8 @@ { name: 'Server Colocation', href: '/services/colocation' as ServiceHref, - price: '70', - description: 'Scales to $420/mo.', + price: '50', + description: 'Scales to $280/mo.', features: [ 'Up to 6U rackspace', 'Up to 700W power budget', diff --git a/src/lib/remote/colocation-reserve.remote.ts b/src/lib/remote/colocation-reserve.remote.ts new file mode 100644 index 0000000..2096357 --- /dev/null +++ b/src/lib/remote/colocation-reserve.remote.ts @@ -0,0 +1,100 @@ +import { form, getRequestEvent } from '$app/server'; +import { type } from 'arktype'; +import { colocationPlans } from '$lib/data/colocationPlans'; +import { env } from '$env/dynamic/private'; +import { Autumn } from 'autumn-js'; + +const planNames = colocationPlans.map((p) => p.name); +const planType = type.enumerated(...planNames); + +const reserveSchema = type({ + plan: planType, + name: 'string.trim & /\\S/', + email: 'string.trim & string.email' +}); + +export type ReserveInput = typeof reserveSchema.infer; + +export type ReserveResult = + | { + ok: true; + paymentLink: string; + } + | { + ok: false; + error: string; + }; + +async function sendDiscordNotification(webhookUrl: string, webhookBody: unknown) { + const response = await fetch(webhookUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(webhookBody) + }); + + if (!response.ok) { + throw new Error( + `Response status: ${response.status}, Response content: ${await response.text()}` + ); + } +} + +async function createPaymentLink(plan: string, email: string, name: string): Promise { + const event = getRequestEvent(); + if (!event) { + throw new Error('No request event found'); + } + + const url = new URL(event.request.url); + const successUrl = `${url.origin}/services/colocation?success=true`; + + const user_id = crypto.randomUUID(); + const autumn = new Autumn({ secretKey: env.AUTUMN_SECRET_KEY }); + + await autumn.customers.getOrCreate({ + customerId: user_id, + name: name, + email: email + }); + + const response = await autumn.billing.attach({ + customerId: user_id, + planId: plan, + successUrl + }); + + const webhookBody = { + username: 'Fyra Stack', + embeds: [ + { + author: { name: 'New Colocation Payment Link Creation' }, + title: `${plan}`, + description: `${email} - ${name}`, + color: 0xc6716d, + footer: { text: `fyrastack.com` }, + timestamp: new Date().toISOString() + } + ] + }; + + if (env.COLOCATION_RESERVATION_DISCORD_WEBHOOK) { + const notification = sendDiscordNotification( + env.COLOCATION_RESERVATION_DISCORD_WEBHOOK, + webhookBody + ).catch((error) => { + console.error('Failed to send Discord colocation reservation notification', error); + }); + + event.platform?.ctx.waitUntil(notification); + } else { + console.log('would have called discord colocation webhook'); + console.log(webhookBody); + } + + return response.paymentUrl as string; +} + +export const reserve = form(reserveSchema, async (data): Promise => { + const paymentLink = await createPaymentLink(data.plan, data.email, data.name); + return { ok: true, paymentLink }; +}); From 23ce6cf0d48e45587592e4a7ae171032d16d6c4c Mon Sep 17 00:00:00 2001 From: Addison LeClair Date: Mon, 7 Sep 2026 02:23:09 +0000 Subject: [PATCH 13/16] feat(colocation): replace signup flow with contact inquiry - Update colocation pricing across navigation, comparison, and plans - Replace payment signup form with a contact CTA - Remove reservation payment backend and refresh related copy --- src/lib/remote/colocation-reserve.remote.ts | 100 -------------------- 1 file changed, 100 deletions(-) delete mode 100644 src/lib/remote/colocation-reserve.remote.ts diff --git a/src/lib/remote/colocation-reserve.remote.ts b/src/lib/remote/colocation-reserve.remote.ts deleted file mode 100644 index 2096357..0000000 --- a/src/lib/remote/colocation-reserve.remote.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { form, getRequestEvent } from '$app/server'; -import { type } from 'arktype'; -import { colocationPlans } from '$lib/data/colocationPlans'; -import { env } from '$env/dynamic/private'; -import { Autumn } from 'autumn-js'; - -const planNames = colocationPlans.map((p) => p.name); -const planType = type.enumerated(...planNames); - -const reserveSchema = type({ - plan: planType, - name: 'string.trim & /\\S/', - email: 'string.trim & string.email' -}); - -export type ReserveInput = typeof reserveSchema.infer; - -export type ReserveResult = - | { - ok: true; - paymentLink: string; - } - | { - ok: false; - error: string; - }; - -async function sendDiscordNotification(webhookUrl: string, webhookBody: unknown) { - const response = await fetch(webhookUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(webhookBody) - }); - - if (!response.ok) { - throw new Error( - `Response status: ${response.status}, Response content: ${await response.text()}` - ); - } -} - -async function createPaymentLink(plan: string, email: string, name: string): Promise { - const event = getRequestEvent(); - if (!event) { - throw new Error('No request event found'); - } - - const url = new URL(event.request.url); - const successUrl = `${url.origin}/services/colocation?success=true`; - - const user_id = crypto.randomUUID(); - const autumn = new Autumn({ secretKey: env.AUTUMN_SECRET_KEY }); - - await autumn.customers.getOrCreate({ - customerId: user_id, - name: name, - email: email - }); - - const response = await autumn.billing.attach({ - customerId: user_id, - planId: plan, - successUrl - }); - - const webhookBody = { - username: 'Fyra Stack', - embeds: [ - { - author: { name: 'New Colocation Payment Link Creation' }, - title: `${plan}`, - description: `${email} - ${name}`, - color: 0xc6716d, - footer: { text: `fyrastack.com` }, - timestamp: new Date().toISOString() - } - ] - }; - - if (env.COLOCATION_RESERVATION_DISCORD_WEBHOOK) { - const notification = sendDiscordNotification( - env.COLOCATION_RESERVATION_DISCORD_WEBHOOK, - webhookBody - ).catch((error) => { - console.error('Failed to send Discord colocation reservation notification', error); - }); - - event.platform?.ctx.waitUntil(notification); - } else { - console.log('would have called discord colocation webhook'); - console.log(webhookBody); - } - - return response.paymentUrl as string; -} - -export const reserve = form(reserveSchema, async (data): Promise => { - const paymentLink = await createPaymentLink(data.plan, data.email, data.name); - return { ok: true, paymentLink }; -}); From 87774af92f993c248c79f76cccff91624d18c1ba Mon Sep 17 00:00:00 2001 From: Addison LeClair Date: Mon, 7 Sep 2026 02:25:12 +0000 Subject: [PATCH 14/16] fix(pricing): update server colocation rates --- src/lib/components/Pricing.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/Pricing.svelte b/src/lib/components/Pricing.svelte index 59c85c4..bf9b4a6 100644 --- a/src/lib/components/Pricing.svelte +++ b/src/lib/components/Pricing.svelte @@ -25,8 +25,8 @@ { name: 'Server Colocation', href: '/services/colocation' as ServiceHref, - price: '50', - description: 'Scales to $280/mo.', + price: '70', + description: 'Scales to $420/mo.', features: [ 'Up to 6U rackspace', 'Up to 700W power budget', From 82a64e78e6057e20892e2a44989f4aa5b107579c Mon Sep 17 00:00:00 2001 From: Cypress Reed Date: Fri, 28 Aug 2026 10:37:31 -0600 Subject: [PATCH 15/16] run prettier and update root page --- src/lib/remote/colocation-reserve.remote.ts | 100 ++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/lib/remote/colocation-reserve.remote.ts diff --git a/src/lib/remote/colocation-reserve.remote.ts b/src/lib/remote/colocation-reserve.remote.ts new file mode 100644 index 0000000..2096357 --- /dev/null +++ b/src/lib/remote/colocation-reserve.remote.ts @@ -0,0 +1,100 @@ +import { form, getRequestEvent } from '$app/server'; +import { type } from 'arktype'; +import { colocationPlans } from '$lib/data/colocationPlans'; +import { env } from '$env/dynamic/private'; +import { Autumn } from 'autumn-js'; + +const planNames = colocationPlans.map((p) => p.name); +const planType = type.enumerated(...planNames); + +const reserveSchema = type({ + plan: planType, + name: 'string.trim & /\\S/', + email: 'string.trim & string.email' +}); + +export type ReserveInput = typeof reserveSchema.infer; + +export type ReserveResult = + | { + ok: true; + paymentLink: string; + } + | { + ok: false; + error: string; + }; + +async function sendDiscordNotification(webhookUrl: string, webhookBody: unknown) { + const response = await fetch(webhookUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(webhookBody) + }); + + if (!response.ok) { + throw new Error( + `Response status: ${response.status}, Response content: ${await response.text()}` + ); + } +} + +async function createPaymentLink(plan: string, email: string, name: string): Promise { + const event = getRequestEvent(); + if (!event) { + throw new Error('No request event found'); + } + + const url = new URL(event.request.url); + const successUrl = `${url.origin}/services/colocation?success=true`; + + const user_id = crypto.randomUUID(); + const autumn = new Autumn({ secretKey: env.AUTUMN_SECRET_KEY }); + + await autumn.customers.getOrCreate({ + customerId: user_id, + name: name, + email: email + }); + + const response = await autumn.billing.attach({ + customerId: user_id, + planId: plan, + successUrl + }); + + const webhookBody = { + username: 'Fyra Stack', + embeds: [ + { + author: { name: 'New Colocation Payment Link Creation' }, + title: `${plan}`, + description: `${email} - ${name}`, + color: 0xc6716d, + footer: { text: `fyrastack.com` }, + timestamp: new Date().toISOString() + } + ] + }; + + if (env.COLOCATION_RESERVATION_DISCORD_WEBHOOK) { + const notification = sendDiscordNotification( + env.COLOCATION_RESERVATION_DISCORD_WEBHOOK, + webhookBody + ).catch((error) => { + console.error('Failed to send Discord colocation reservation notification', error); + }); + + event.platform?.ctx.waitUntil(notification); + } else { + console.log('would have called discord colocation webhook'); + console.log(webhookBody); + } + + return response.paymentUrl as string; +} + +export const reserve = form(reserveSchema, async (data): Promise => { + const paymentLink = await createPaymentLink(data.plan, data.email, data.name); + return { ok: true, paymentLink }; +}); From ab35c2c77a1b503c6aa4806fcf7741921e33b4a8 Mon Sep 17 00:00:00 2001 From: Addison LeClair Date: Mon, 7 Sep 2026 02:23:09 +0000 Subject: [PATCH 16/16] fix some spacing stuff --- src/lib/remote/colocation-reserve.remote.ts | 100 -------------------- 1 file changed, 100 deletions(-) delete mode 100644 src/lib/remote/colocation-reserve.remote.ts diff --git a/src/lib/remote/colocation-reserve.remote.ts b/src/lib/remote/colocation-reserve.remote.ts deleted file mode 100644 index 2096357..0000000 --- a/src/lib/remote/colocation-reserve.remote.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { form, getRequestEvent } from '$app/server'; -import { type } from 'arktype'; -import { colocationPlans } from '$lib/data/colocationPlans'; -import { env } from '$env/dynamic/private'; -import { Autumn } from 'autumn-js'; - -const planNames = colocationPlans.map((p) => p.name); -const planType = type.enumerated(...planNames); - -const reserveSchema = type({ - plan: planType, - name: 'string.trim & /\\S/', - email: 'string.trim & string.email' -}); - -export type ReserveInput = typeof reserveSchema.infer; - -export type ReserveResult = - | { - ok: true; - paymentLink: string; - } - | { - ok: false; - error: string; - }; - -async function sendDiscordNotification(webhookUrl: string, webhookBody: unknown) { - const response = await fetch(webhookUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(webhookBody) - }); - - if (!response.ok) { - throw new Error( - `Response status: ${response.status}, Response content: ${await response.text()}` - ); - } -} - -async function createPaymentLink(plan: string, email: string, name: string): Promise { - const event = getRequestEvent(); - if (!event) { - throw new Error('No request event found'); - } - - const url = new URL(event.request.url); - const successUrl = `${url.origin}/services/colocation?success=true`; - - const user_id = crypto.randomUUID(); - const autumn = new Autumn({ secretKey: env.AUTUMN_SECRET_KEY }); - - await autumn.customers.getOrCreate({ - customerId: user_id, - name: name, - email: email - }); - - const response = await autumn.billing.attach({ - customerId: user_id, - planId: plan, - successUrl - }); - - const webhookBody = { - username: 'Fyra Stack', - embeds: [ - { - author: { name: 'New Colocation Payment Link Creation' }, - title: `${plan}`, - description: `${email} - ${name}`, - color: 0xc6716d, - footer: { text: `fyrastack.com` }, - timestamp: new Date().toISOString() - } - ] - }; - - if (env.COLOCATION_RESERVATION_DISCORD_WEBHOOK) { - const notification = sendDiscordNotification( - env.COLOCATION_RESERVATION_DISCORD_WEBHOOK, - webhookBody - ).catch((error) => { - console.error('Failed to send Discord colocation reservation notification', error); - }); - - event.platform?.ctx.waitUntil(notification); - } else { - console.log('would have called discord colocation webhook'); - console.log(webhookBody); - } - - return response.paymentUrl as string; -} - -export const reserve = form(reserveSchema, async (data): Promise => { - const paymentLink = await createPaymentLink(data.plan, data.email, data.name); - return { ok: true, paymentLink }; -});

- {#if row.stack.previousPrice} - {row.stack.previousPrice} - {/if} {row.stack.text}