feat: Updated marketing site
This commit is contained in:
+121
-120
@@ -1,131 +1,132 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "About",
|
||||
description:
|
||||
"Vantage began as a weekend fix for a lost laptop and grew into a fleet control plane. How it is built, and what it deliberately does not do.",
|
||||
title: "About",
|
||||
description: "Vantage is built by HostXtra, a small UK company that manages servers for a living. Who we are, why we built it, and how we plan to keep it going.",
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<section className="rail band band--open">
|
||||
<span className="tag">About</span>
|
||||
<h1 style={{ fontSize: "var(--s-3)", margin: "0.8rem 0 1.1rem", maxWidth: "20ch" }}>
|
||||
Built for the fleet nobody was given a budget to manage.
|
||||
</h1>
|
||||
return (
|
||||
<>
|
||||
<section className="rail band band--open">
|
||||
<span className="tag">About</span>
|
||||
<h1 style={{ fontSize: "var(--s-3)", margin: "0.8rem 0 1.1rem", maxWidth: "20ch" }}>We built this because we needed it.</h1>
|
||||
|
||||
<div className="split" style={{ marginTop: "2.4rem" }}>
|
||||
<div className="prose">
|
||||
<p>
|
||||
Vantage started as a weekend fix for a bad afternoon. A laptop was lost, and finding every server that
|
||||
trusted its key meant SSHing into each one with a text editor open. The list lived in someone's head.
|
||||
Two of the boxes were not on it.
|
||||
</p>
|
||||
<p>
|
||||
The obvious tools were all heavier than the problem. A configuration management stack to write one file. A
|
||||
bastion host that becomes the thing you now have to keep alive. A certificate authority with a rotation
|
||||
story nobody wanted to own.
|
||||
</p>
|
||||
<p>
|
||||
So it began with one job done properly: hold <code>authorized_keys</code> to a known state. Then the same
|
||||
agent turned out to be the right place to run a deploy script, check whether a service was answering, and
|
||||
open a shell when something was on fire. Each addition had to earn its place by riding the connection that
|
||||
already existed.
|
||||
</p>
|
||||
<p>
|
||||
Today it runs across homelabs, small hosting providers, and agencies who inherit client servers and need
|
||||
to prove who can reach them.
|
||||
</p>
|
||||
</div>
|
||||
<div className="split" style={{ marginTop: "2.4rem" }}>
|
||||
<div className="prose">
|
||||
<p>
|
||||
Vantage is made by HostXtra, a small company in the UK. We manage servers for a living our own and other people's and we built Vantage because we were doing that job
|
||||
badly with a spreadsheet and a folder of scripts.
|
||||
</p>
|
||||
<p>
|
||||
It started after a laptop went missing. Working out which servers that laptop could still get into meant logging into every one of them and reading a file by hand. The list
|
||||
of servers lived in somebody's head, and two of them weren't on it. That took an afternoon, and it should have taken a minute.
|
||||
</p>
|
||||
<p>
|
||||
Everything that existed to fix this was built for companies far larger than ours. We wanted one screen that answered "who can get into this machine" and let us
|
||||
change the answer. Once that worked, the rest followed: if there's already a way to talk to every server you own, you may as well use it to run your scripts, check
|
||||
things are still up, and open a terminal when something breaks at midnight.
|
||||
</p>
|
||||
<p>We use Vantage every day to run our own servers. When something about it is annoying, it's annoying us too, which is the main reason it keeps getting better.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="tag">How it is built</span>
|
||||
<div className="specs">
|
||||
<div className="spec">
|
||||
<span className="spec__k">SERVER</span>
|
||||
<div>
|
||||
<h3>Go, MongoDB, Redis</h3>
|
||||
<p>
|
||||
One Go binary serving REST for the interface and gRPC for agents. MongoDB holds everything durable;
|
||||
Redis holds sessions and nothing else.
|
||||
</p>
|
||||
<div>
|
||||
<span className="tag">The company</span>
|
||||
<div className="specs">
|
||||
<div className="spec">
|
||||
<span className="spec__k">WHO</span>
|
||||
<div>
|
||||
<h3>HostXtra</h3>
|
||||
<p>A small independent team in the United Kingdom. No investors, nobody to answer to but the people paying us.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">HOW WE EARN</span>
|
||||
<div>
|
||||
<h3>Subscriptions, and that's all</h3>
|
||||
<p>We're paid by our customers. We don't sell data, we don't sell ads, and there's no free tier being mined for anything.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">WHO YOU GET</span>
|
||||
<div>
|
||||
<h3>The people who wrote it</h3>
|
||||
<p>Support goes to the same small group that builds the product. There is no first line, and nobody reading from a script.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">IF WE STOP</span>
|
||||
<div>
|
||||
<h3>You can host it yourself</h3>
|
||||
<p>Every tier can run on your own hardware, and your data is exportable whenever you want it. You are not stuck with us.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">WEB</span>
|
||||
<div>
|
||||
<h3>Next.js</h3>
|
||||
<p>
|
||||
An operations interface, not a brochure: dense tables, live log streams, and state you can read at a
|
||||
glance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">AGENT</span>
|
||||
<div>
|
||||
<h3>Go, Linux and Windows</h3>
|
||||
<p>
|
||||
A single static binary under systemd or as a Windows service. No runtime, no dependencies, no
|
||||
package manager involved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">CONSOLE</span>
|
||||
<div>
|
||||
<h3>Guacamole</h3>
|
||||
<p>
|
||||
Protocol handling is a solved problem. We proxy the connection and manage the credentials around it.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section className="rail band">
|
||||
<span className="tag">Security posture</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "24ch" }}>
|
||||
The parts worth being specific about.
|
||||
</h2>
|
||||
<div className="caps">
|
||||
<article className="cap">
|
||||
<span className="cap__k">Tokens</span>
|
||||
<h3>Hashed, never stored plain</h3>
|
||||
<p>
|
||||
Agent tokens and the secrets read token are held as SHA-256 hashes. The plaintext exists on the
|
||||
agent's own disk at 0600 and nowhere else.
|
||||
</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">At rest</span>
|
||||
<h3>AES-256-GCM</h3>
|
||||
<p>
|
||||
Private keys, passphrases, vault secrets, identity provider secrets and console credentials are encrypted
|
||||
with a key held only by your deployment.
|
||||
</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">One-time</span>
|
||||
<h3>Tokens that expire and spend</h3>
|
||||
<p>
|
||||
Pre-registration tokens last an hour and work once. Console session tokens are consumed the moment the
|
||||
tunnel opens.
|
||||
</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">Recorded</span>
|
||||
<h3>Every mutation is audited</h3>
|
||||
<p>
|
||||
Assignments, revocations, runs, console sessions, secret reveals and settings changes are attributed and
|
||||
kept.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
<section className="rail band">
|
||||
<span className="tag">Who uses it</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "24ch" }}>People with more servers than time.</h2>
|
||||
<div className="caps">
|
||||
<article className="cap">
|
||||
<span className="cap__k">Home labs</span>
|
||||
<h3>A rack in the spare room</h3>
|
||||
<p>A handful of machines you built yourself, and no interest in running enterprise software to keep track of them.</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">Small teams</span>
|
||||
<h3>Two or three people, one estate</h3>
|
||||
<p>Everyone needs access to everything, and everyone needs to know what the others changed. Usually the point where the spreadsheet stops working.</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">Agencies</span>
|
||||
<h3>Servers you inherited</h3>
|
||||
<p>Machines set up by somebody who has since left, for a client who now wants a straight answer about who can get into them.</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">Hosting</span>
|
||||
<h3>Small providers</h3>
|
||||
<p>Lots of machines, thin margins, and a need to prove access is controlled without buying something priced per seat.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rail band">
|
||||
<span className="tag">How we work</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "24ch" }}>A few things we've decided.</h2>
|
||||
<div className="specs" style={{ maxWidth: "70ch" }}>
|
||||
<div className="spec">
|
||||
<span className="spec__k">SMALL</span>
|
||||
<div>
|
||||
<h3>We say no to most things</h3>
|
||||
<p>Every feature has to earn its place. A tool that manages access to your servers is one you have to be able to understand completely.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">HONEST</span>
|
||||
<div>
|
||||
<h3>The price is on the website</h3>
|
||||
<p>All three tiers, including the largest, can be bought without speaking to anybody. There is no hidden pricing and no "contact us" tier.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">OPEN</span>
|
||||
<div>
|
||||
<h3>Bugs are in public</h3>
|
||||
<p>Our issue tracker is open to read. If something is broken, you can see whether we already know about it.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">CAREFUL</span>
|
||||
<div>
|
||||
<h3>We take security reports seriously</h3>
|
||||
<p>Report something and you'll hear back from a person within three days. We would much rather you told us than didn't.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ContactForm } from "@/components/ContactForm";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Contact",
|
||||
description: "Sales questions, self-hosted licensing, security disclosures and bug reports.",
|
||||
description: "Questions about pricing, licensing for self-hosted installs, security reports and bugs.",
|
||||
};
|
||||
|
||||
const CHANNELS = [
|
||||
@@ -21,13 +21,13 @@ const CHANNELS = [
|
||||
},
|
||||
{
|
||||
title: "Bugs and feature requests",
|
||||
body: "Public tracker, read by the people who write the code.",
|
||||
body: "Our public issue tracker, read by the people who write the code.",
|
||||
link: "git.vantage.hostxtra.co.uk/vantage",
|
||||
href: "https://git.vantage.hostxtra.co.uk/vantage",
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
body: "Control plane uptime and incident history.",
|
||||
body: "Whether the hosted service is up, and a history of past incidents.",
|
||||
link: "status.vantage.hostxtra.co.uk",
|
||||
href: "https://status.vantage.hostxtra.co.uk",
|
||||
},
|
||||
@@ -37,7 +37,7 @@ export default function ContactPage() {
|
||||
return (
|
||||
<section className="rail band band--open">
|
||||
<span className="tag">Contact</span>
|
||||
<h1 style={{ fontSize: "var(--s-3)", margin: "0.8rem 0 1rem", maxWidth: "15ch" }}>Tell us what your fleet looks like.</h1>
|
||||
<h1 style={{ fontSize: "var(--s-3)", margin: "0.8rem 0 1rem", maxWidth: "15ch" }}>Get in touch.</h1>
|
||||
|
||||
<div className="split" style={{ marginTop: "2.4rem" }}>
|
||||
<div className="card">
|
||||
@@ -45,7 +45,7 @@ export default function ContactPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="prose">Pick the right door and you will get a faster answer.</p>
|
||||
<p className="prose">If one of these is a better fit than the form, use it you'll get an answer sooner.</p>
|
||||
{CHANNELS.map((channel) => (
|
||||
<div className="chan" key={channel.title}>
|
||||
<h3>{channel.title}</h3>
|
||||
|
||||
+5
-5
@@ -7,15 +7,15 @@ import { ThemeScript } from "@/components/ThemeScript";
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://vantage.hostxtra.co.uk"),
|
||||
title: {
|
||||
default: "Vantage one control plane for the whole fleet",
|
||||
template: "%s Vantage",
|
||||
default: "Vantage: one place to manage every server you look after",
|
||||
template: "%s · Vantage",
|
||||
},
|
||||
description: "Self-hosted fleet control: SSH key assignment, workflow execution, service monitoring, a secrets vault and a browser console, across every server you manage.",
|
||||
description: "Manage access to your servers, run your scripts, check your services are up, store your passwords and open a terminal in the browser. Host it yourself or let us host it.",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
siteName: "Vantage",
|
||||
title: "Vantage one control plane for the whole fleet",
|
||||
description: "Self-hosted fleet control: SSH keys, workflows, monitors, secrets and consoles, over one outbound agent connection.",
|
||||
title: "Vantage: one place to manage every server you look after",
|
||||
description: "Server access, scripts, uptime checks, stored passwords and a terminal in the browser. Host it yourself or let us host it.",
|
||||
},
|
||||
icons: { icon: "/images/vantage_logo.svg" },
|
||||
};
|
||||
|
||||
+53
-50
@@ -6,21 +6,21 @@ export default function OverviewPage() {
|
||||
<>
|
||||
<div className="heroband">
|
||||
<section className="rail hero">
|
||||
<span className="tag">Self-hosted fleet control plane</span>
|
||||
<h1>Your servers, under one pane of glass you actually own.</h1>
|
||||
<span className="tag">Server management you can host yourself</span>
|
||||
<h1>One place to manage every server you look after.</h1>
|
||||
<p className="lede">
|
||||
Vantage holds SSH keys, runs scripts, watches services, stores secrets and opens consoles across every machine you manage. One agent per server, outbound connections only, all
|
||||
state in your own database.
|
||||
Vantage keeps track of who has access to which server, runs your scripts, checks your services are still up, stores your passwords and gives you a terminal in the browser. You
|
||||
install one small program on each server, and you can run the whole thing on your own hardware.
|
||||
</p>
|
||||
<div className="hero__acts">
|
||||
<Link className="btn btn--solid" href="/start">
|
||||
Create your instance
|
||||
Get started free
|
||||
</Link>
|
||||
<Link className="btn btn--line" href="/platform">
|
||||
What it does
|
||||
</Link>
|
||||
</div>
|
||||
<p className="hero__foot">Free for 3 servers · Linux and Windows agents · Self-host the whole stack</p>
|
||||
<p className="hero__foot">Free for 3 servers · Linux and Windows · Hosted by us or by you</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -28,25 +28,25 @@ export default function OverviewPage() {
|
||||
|
||||
<section className="rail band band--open">
|
||||
<span className="tag">The problem</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "24ch" }}>Six tools, six sources of truth, one afternoon lost.</h2>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "24ch" }}>Nothing knows about anything else.</h2>
|
||||
<div className="split" style={{ marginTop: "2rem" }}>
|
||||
<p className="prose">
|
||||
Most small fleets end up with keys in a spreadsheet, scripts in someone's home directory, uptime checks in a separate service, secrets in a chat thread, and no record of
|
||||
who ran what. None of those systems know about each other, so every question who can reach this box, what ran on it last, is it even up gets answered by hand.
|
||||
Access ends up in a spreadsheet. Scripts end up in somebody's home directory. Uptime checks live in a separate service, passwords get pasted into a chat thread, and nobody
|
||||
writes down who ran what. So when you need to know who can get into a particular box, or what was done to it last week, you go and work it out by hand.
|
||||
</p>
|
||||
<div className="specs specs--flush">
|
||||
<div className="spec">
|
||||
<span className="spec__k">ONE AGENT</span>
|
||||
<div>
|
||||
<h3>Everything rides one connection</h3>
|
||||
<p>Keys, steps, checks and inventory all travel over the same outbound link. Installing a second thing is not the answer.</p>
|
||||
<h3>One thing to install</h3>
|
||||
<p>Access, scripts, uptime checks and hardware stats all come down the same connection. You don't install a second thing to get the second feature.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">ONE RECORD</span>
|
||||
<div>
|
||||
<h3>Every change is an audit event</h3>
|
||||
<p>Assignments, revocations, runs, console sessions and settings changes all land in the same log, attributed to a person.</p>
|
||||
<h3>Everything is written down</h3>
|
||||
<p>Who was given access and who took it away, who ran which script, who opened a session. It all goes in the same log with a name against it.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,73 +55,76 @@ export default function OverviewPage() {
|
||||
|
||||
<section className="rail band">
|
||||
<span className="tag">Capabilities</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "22ch" }}>One control plane, six jobs.</h2>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "22ch" }}>What it does.</h2>
|
||||
<div className="caps">
|
||||
<article className="cap">
|
||||
<span className="cap__k">Access</span>
|
||||
<h3>SSH keys</h3>
|
||||
<p>
|
||||
Assign public keys per server and revoke them softly. The agent diffs desired state against the file and rewrites <code>authorized_keys</code> atomically.
|
||||
Choose which servers a key can reach, and take that access away again when someone leaves. Each server is kept matching what you set, without you logging in to change it.
|
||||
</p>
|
||||
<ul>
|
||||
<li>fingerprint deduplication</li>
|
||||
<li>agent-side keypair generation</li>
|
||||
<li>revocation history preserved</li>
|
||||
<li>spots the same key added twice</li>
|
||||
<li>can create a key on the server itself</li>
|
||||
<li>keeps a record of access you removed</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article className="cap">
|
||||
<span className="cap__k">Execution</span>
|
||||
<span className="cap__k">Scripts</span>
|
||||
<h3>Workflows</h3>
|
||||
<p>A library of bash and PowerShell steps with declared inputs, outputs and secret references, composed into workflows that target a set of servers.</p>
|
||||
<p>Keep your shell and PowerShell scripts in one library instead of scattered across machines, then run them in order across a group of servers.</p>
|
||||
<ul>
|
||||
<li>live streamed step logs</li>
|
||||
<li>stop, continue or retry on failure</li>
|
||||
<li>values passed between steps</li>
|
||||
<li>watch the output as it happens</li>
|
||||
<li>stop, carry on or retry when a step fails</li>
|
||||
<li>pass values from one step to the next</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article className="cap">
|
||||
<span className="cap__k">Uptime</span>
|
||||
<h3>Monitors</h3>
|
||||
<p>HTTP, TCP, ICMP and TLS checks, run either from the control plane or from an agent inside the target network.</p>
|
||||
<p>
|
||||
Check that a site, a port, a machine or a certificate is still answering. Checks can run from us, or from an agent inside your own network for things the internet
|
||||
can't see.
|
||||
</p>
|
||||
<ul>
|
||||
<li>incidents and uptime history</li>
|
||||
<li>certificate expiry warnings</li>
|
||||
<li>alerts to five channel types</li>
|
||||
<li>a history of outages and uptime</li>
|
||||
<li>warnings before a certificate expires</li>
|
||||
<li>alerts by email, Slack, Discord or Telegram</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article className="cap">
|
||||
<span className="cap__k">Secrets</span>
|
||||
<span className="cap__k">Passwords</span>
|
||||
<h3>Vault</h3>
|
||||
<p>Grouped key/value secrets encrypted with AES-256-GCM, injected into workflow steps at execution and never written to logs.</p>
|
||||
<p>Keep passwords, tokens and licence keys in one encrypted place. Your scripts are handed what they need as they run, and it never shows up in the logs afterwards.</p>
|
||||
<ul>
|
||||
<li>read token for External Secrets Operator</li>
|
||||
<li>rotatable, hashed at rest</li>
|
||||
<li>reveal is an audited action</li>
|
||||
<li>grouped by the thing they belong to</li>
|
||||
<li>encrypted with a key only you hold</li>
|
||||
<li>looking at one is recorded</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article className="cap">
|
||||
<span className="cap__k">Access</span>
|
||||
<h3>Browser console</h3>
|
||||
<p>Open an SSH, RDP or VNC session in the browser. SSH authenticates with a stored key, and every session is recorded in the audit log.</p>
|
||||
<p>Open a terminal or a remote desktop from the browser. Nothing to install on the machine you're sitting at, and every session goes in the log.</p>
|
||||
<ul>
|
||||
<li>one-time session tokens</li>
|
||||
<li>credentials consumed on connect</li>
|
||||
<li>no client software</li>
|
||||
<li>links that only work once</li>
|
||||
<li>passwords used once, then discarded</li>
|
||||
<li>works from a borrowed laptop</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article className="cap">
|
||||
<span className="cap__k">Health</span>
|
||||
<h3>Inventory and updates</h3>
|
||||
<p>CPU, memory, swap, disks and kernel reported continuously, alongside pending OS package updates you can apply from the interface.</p>
|
||||
<h3>Hardware and updates</h3>
|
||||
<p>See processor, memory, disk and operating system for every server, along with the updates waiting to be installed and install them without logging in.</p>
|
||||
<ul>
|
||||
<li>metrics every 30 seconds</li>
|
||||
<li>one-click package updates</li>
|
||||
<li>agents update themselves</li>
|
||||
<li>refreshed every 30 seconds</li>
|
||||
<li>updates applied from the same screen</li>
|
||||
<li>agents keep themselves up to date</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
@@ -129,27 +132,27 @@ export default function OverviewPage() {
|
||||
|
||||
<section className="rail band">
|
||||
<span className="tag">Getting started</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "20ch" }}>Four steps, and the first three take a minute.</h2>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "20ch" }}>Getting your first server in.</h2>
|
||||
<div className="flow">
|
||||
<div className="flow__c">
|
||||
<span className="flow__n">FIRST</span>
|
||||
<h3>Create an instance</h3>
|
||||
<p>You become the owner. Everything inside is invisible to every other instance.</p>
|
||||
<h3>Set up your account</h3>
|
||||
<p>You're the owner. Nobody outside your account can see anything in it.</p>
|
||||
</div>
|
||||
<div className="flow__c">
|
||||
<span className="flow__n">THEN</span>
|
||||
<h3>Add a server</h3>
|
||||
<p>Copy the install one-liner. It expires in an hour and works exactly once.</p>
|
||||
<p>Copy the install command we show you and paste it into the server. It works once, then expires.</p>
|
||||
</div>
|
||||
<div className="flow__c">
|
||||
<span className="flow__n">THEN</span>
|
||||
<h3>Assign a key</h3>
|
||||
<p>Paste a public key and tick the servers. It lands inside 30 seconds.</p>
|
||||
<h3>Give yourself access</h3>
|
||||
<p>Paste your public key and tick the servers it should reach. It arrives within 30 seconds.</p>
|
||||
</div>
|
||||
<div className="flow__c">
|
||||
<span className="flow__n">AFTER</span>
|
||||
<h3>Build from there</h3>
|
||||
<p>Add checks, write a step, invite the team, connect your identity provider.</p>
|
||||
<h3>Add the rest as you need it</h3>
|
||||
<p>Set up an uptime check, save a script, invite the people you work with.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -168,7 +171,7 @@ export default function OverviewPage() {
|
||||
<div className="card card--cta">
|
||||
<div style={{ maxWidth: "48ch" }}>
|
||||
<h2 style={{ fontSize: "var(--s-1)" }}>Three servers, free, no card.</h2>
|
||||
<p style={{ color: "var(--ink-2)", marginTop: "0.4rem", fontSize: "0.94rem" }}>Create an instance, install one agent, and watch a key land on a real box.</p>
|
||||
<p style={{ color: "var(--ink-2)", marginTop: "0.4rem", fontSize: "0.94rem" }}>Set up an account, add one server, and see it working before you decide anything.</p>
|
||||
</div>
|
||||
<Link className="btn btn--solid" href="/start">
|
||||
Get started
|
||||
|
||||
+64
-65
@@ -2,7 +2,7 @@ import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Platform",
|
||||
description: "How Vantage fits together: a control plane you run, one agent per server, and a single outbound connection between them.",
|
||||
description: "How Vantage fits together: the part you run, a small program on each server, and one connection between them that the server opens itself.",
|
||||
};
|
||||
|
||||
export default function PlatformPage() {
|
||||
@@ -11,43 +11,43 @@ export default function PlatformPage() {
|
||||
<section className="rail band band--open">
|
||||
<span className="tag">Platform</span>
|
||||
<h1 style={{ fontSize: "var(--s-3)", margin: "0.8rem 0 1rem", maxWidth: "19ch" }}>How the pieces fit together.</h1>
|
||||
<p className="lede">Three moving parts: a control plane you run, an agent on each server, and one outbound connection between them.</p>
|
||||
<p className="lede">There are only three parts: Vantage itself, a small program on each of your servers, and one connection between them that your server opens.</p>
|
||||
|
||||
<div className="split" style={{ marginTop: "3rem" }}>
|
||||
<div>
|
||||
<h2 style={{ fontSize: "var(--s-2)", maxWidth: "18ch" }}>The agent never listens.</h2>
|
||||
<h2 style={{ fontSize: "var(--s-2)", maxWidth: "18ch" }}>Your servers call us, not the other way round.</h2>
|
||||
<div className="prose" style={{ marginTop: "1rem" }}>
|
||||
<p>
|
||||
Every agent dials out to the control plane over gRPC with TLS. Nothing needs an inbound port, nothing needs a static address, and a machine behind NAT is no different
|
||||
from one with a public IP.
|
||||
Each server reaches out to Vantage over an encrypted connection it opens itself. You don't open a port in your firewall, you don't need a fixed address, and a
|
||||
machine sitting behind a home router works exactly like one in a data centre.
|
||||
</p>
|
||||
<p>
|
||||
Key state is polled on a 30-second interval, because 30 seconds is fine for access control and polling is simple to reason about. Everything that should not wait
|
||||
running a step, opening a console, applying updates is pushed down a bidirectional command stream the agent holds open.
|
||||
Once that connection is open it stays open, so anything you ask for from the browser run this script, open a session, install these updates happens straight away rather
|
||||
than on a timer.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="specs specs--flush">
|
||||
<div className="spec">
|
||||
<span className="spec__k">POLL</span>
|
||||
<span className="spec__k">CHECKING</span>
|
||||
<div>
|
||||
<h3>SyncKeys, every 30s</h3>
|
||||
<p>The desired key set for this server. Unchanged state means no disk write at all.</p>
|
||||
<h3>Access, every 30 seconds</h3>
|
||||
<p>The server asks who should be able to reach it. If nothing has changed, nothing is touched.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">PUSH</span>
|
||||
<span className="spec__k">ASKING</span>
|
||||
<div>
|
||||
<h3>Command stream</h3>
|
||||
<p>Generate a key, run a step, apply updates, update the agent, clean up a workspace.</p>
|
||||
<h3>Anything you press</h3>
|
||||
<p>Run a script, open a session, install updates, create a key. Sent down the connection as you ask for it.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">REPORT</span>
|
||||
<span className="spec__k">REPORTING</span>
|
||||
<div>
|
||||
<h3>Inventory and checks</h3>
|
||||
<p>Metrics every 30 seconds, a full hardware snapshot every 15 minutes, and monitor results as they complete.</p>
|
||||
<h3>Health and uptime</h3>
|
||||
<p>Load and free space every 30 seconds, full hardware details every 15 minutes, uptime checks as they finish.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,91 +55,90 @@ export default function PlatformPage() {
|
||||
</section>
|
||||
|
||||
<section className="rail band">
|
||||
<span className="tag">Write path</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "24ch" }}>The file is never half-written.</h2>
|
||||
<div className="split split--even" style={{ marginTop: "2rem" }}>
|
||||
<span className="tag">Changing access</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "24ch" }}>Careful about the file it owns.</h2>
|
||||
<div className="split" style={{ marginTop: "2rem" }}>
|
||||
<p className="prose">
|
||||
The agent computes the desired <code>authorized_keys</code> content, compares it to what is on disk, and stops there if nothing changed. When it does need to write, it writes a
|
||||
temporary file in the same directory and renames it over the real one. A machine that loses power mid-write keeps the file it had.
|
||||
The only thing Vantage changes on your server is the list of keys allowed to log in. It compares that list against what you have set and leaves it alone when they already
|
||||
match, so most of the time it writes nothing at all. When it does need to make a change, it prepares the new list first and swaps it in one go a server that loses power halfway
|
||||
through keeps the list it already had, rather than ending up with half of one.
|
||||
</p>
|
||||
<pre className="code">
|
||||
<i>// agent poll, simplified</i>
|
||||
{"\n"}
|
||||
desired := client.SyncKeys(serverID, token){"\n"}
|
||||
current := keys.ReadAuthorizedKeys(){"\n\n"}
|
||||
<b>if</b> !keys.StateChanged(current, desired) {"{"}
|
||||
{"\n "}
|
||||
<i>// nothing to do</i>
|
||||
{"\n "}
|
||||
<b>return</b> nil{"\n"}
|
||||
{"}"}
|
||||
{"\n\n"}
|
||||
keys.WriteAuthorizedKeys(desired){"\n"}
|
||||
<i>// write .tmp, os.Rename(), chmod 0600</i>
|
||||
</pre>
|
||||
<div className="specs specs--flush">
|
||||
<div className="spec">
|
||||
<span className="spec__k">SCOPE</span>
|
||||
<div>
|
||||
<h3>One file, nothing else</h3>
|
||||
<p>It doesn't manage your packages, your configuration or your users. Anything else that happens is a script you wrote and asked it to run.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">REMOVING</span>
|
||||
<div>
|
||||
<h3>Taking access away</h3>
|
||||
<p>Untick a server and the key is gone from it within 30 seconds. We keep the record that it was there, and who removed it.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rail band">
|
||||
<span className="tag">Tenancy and identity</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "22ch" }}>Instances are the boundary.</h2>
|
||||
<span className="tag">People and access</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "22ch" }}>Who can see what.</h2>
|
||||
<div className="caps">
|
||||
<article className="cap">
|
||||
<span className="cap__k">Isolation</span>
|
||||
<h3>Scoped at the query</h3>
|
||||
<p>
|
||||
Every server, key, workflow, monitor and secret belongs to an instance, and every lookup is filtered by it. Uniqueness constraints are enforced by the database, not by
|
||||
application logic.
|
||||
</p>
|
||||
<span className="cap__k">Separation</span>
|
||||
<h3>Your account is its own world</h3>
|
||||
<p>Servers, keys, scripts, checks and passwords all belong to one account. There is no shared view, and no setting that could accidentally expose one account to another.</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">Roles</span>
|
||||
<h3>Owner, admin, member</h3>
|
||||
<p>Members operate the fleet. Admins and owners manage people, identity settings and the secrets read token.</p>
|
||||
<p>Members get on with the day-to-day work. Admins and owners are the ones who add and remove people and change how the account is set up.</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">Identity</span>
|
||||
<h3>Local or OIDC, per instance</h3>
|
||||
<p>Sign in with email and password, or connect your own provider. Each instance configures its own issuer and client.</p>
|
||||
<span className="cap__k">Signing in</span>
|
||||
<h3>Email, or your own login</h3>
|
||||
<p>Email and password works out of the box. If your organisation already has a single sign-on provider, you can point Vantage at it instead.</p>
|
||||
</article>
|
||||
<article className="cap">
|
||||
<span className="cap__k">Sessions</span>
|
||||
<h3>Server-side, 24 hours</h3>
|
||||
<p>Cookies carry an opaque identifier and nothing else. Session bodies live in Redis, so losing it signs everyone out and costs no durable data.</p>
|
||||
<span className="cap__k">Storage</span>
|
||||
<h3>Encrypted, and yours</h3>
|
||||
<p>Private keys, passwords and saved credentials are encrypted before they are stored. Host it yourself and the key that unlocks them is one only you have.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rail band">
|
||||
<span className="tag">What we do not build</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "22ch" }}>The scope is the feature.</h2>
|
||||
<span className="tag">What it deliberately doesn't do</span>
|
||||
<h2 style={{ fontSize: "var(--s-2)", marginTop: "0.7rem", maxWidth: "22ch" }}>Things we left out on purpose.</h2>
|
||||
<div className="specs" style={{ maxWidth: "70ch" }}>
|
||||
<div className="spec">
|
||||
<span className="spec__k">NOT A PROXY</span>
|
||||
<span className="spec__k">NOT IN THE WAY</span>
|
||||
<div>
|
||||
<h3>We are never in the SSH path</h3>
|
||||
<p>Vantage assigns keys; your client connects straight to the box. If our control plane is down, your SSH still works.</p>
|
||||
<h3>Your connections don't go through us</h3>
|
||||
<p>Vantage decides who is allowed in; when you log in you go straight to the server. If Vantage is down for any reason, you can still get to your machines.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">NO CUSTODY</span>
|
||||
<span className="spec__k">NOT A KEYRING</span>
|
||||
<div>
|
||||
<h3>Private keys stay put by default</h3>
|
||||
<p>Keys generated on a server stay on it unless you explicitly upload the private half, and anything stored is encrypted with a key only your deployment holds.</p>
|
||||
<h3>We'd rather not hold your private keys</h3>
|
||||
<p>A key created on a server stays there unless you choose to upload it, and we only ever need the public half to give someone access.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">NO PER-USER</span>
|
||||
<span className="spec__k">NOT PER-PERSON</span>
|
||||
<div>
|
||||
<h3>Root, not every account</h3>
|
||||
<p>Vantage manages one file per server. Per-user key management is a different product with a different failure mode.</p>
|
||||
<h3>Access to the server, not to accounts on it</h3>
|
||||
<p>Vantage manages administrator access to each machine. Handing individual people their own separate logins on every box is a different job, and a bigger one.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">NO PLUGINS</span>
|
||||
<span className="spec__k">NO ADD-ONS</span>
|
||||
<div>
|
||||
<h3>An agent you can read in an evening</h3>
|
||||
<p>A few thousand lines of Go with no extension system. Auditability beats extensibility on a binary that runs as root.</p>
|
||||
<h3>Nothing to extend, on purpose</h3>
|
||||
<p>The program on your servers is small and has no plugin system. It runs with full privileges, so we would rather it be something you could sit down and read.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+21
-16
@@ -40,15 +40,15 @@ export default function PricingPage() {
|
||||
Pick a tier, then pay per server.
|
||||
</h1>
|
||||
<p className="lede">
|
||||
People are free. Keys, workflows, monitors and secrets are free. You pay for a tier and for the servers running an agent, because that is the only number that grows with you. Every
|
||||
tier runs either in our cloud or on your own hardware, for the same money.
|
||||
You pay for the number of servers you manage, and nothing else. Adding people costs nothing, and neither does adding keys, scripts, uptime checks or stored passwords. Every tier costs
|
||||
the same whether we host it or you do.
|
||||
</p>
|
||||
|
||||
<div className="plans">
|
||||
<div className="plan">
|
||||
<div>
|
||||
<div className="plan__n">Free</div>
|
||||
<p className="plan__d">A homelab, a couple of VPSes, and the keys on your own laptop.</p>
|
||||
<p className="plan__d">For a home lab or a couple of servers you look after on your own.</p>
|
||||
</div>
|
||||
<div className="plan__p">
|
||||
£0 <span>3 servers</span>
|
||||
@@ -69,12 +69,12 @@ export default function PricingPage() {
|
||||
<div className="plan plan--pick">
|
||||
<div>
|
||||
<div className="plan__n">Professional</div>
|
||||
<p className="plan__d">Real infrastructure, and more than one person holding the keys.</p>
|
||||
<p className="plan__d">For servers people rely on, looked after by more than one person.</p>
|
||||
</div>
|
||||
<div className="plan__p">
|
||||
£19 <span>/ month, 3 servers</span>
|
||||
</div>
|
||||
<p className="plan__t">£5 per extra server. £190 a year saves two months. Self-hosted is £190 a year.</p>
|
||||
<p className="plan__t">£2.50 per extra server. £190 a year saves two months. Self-hosted is £190 a year.</p>
|
||||
<ul>
|
||||
<li>3 servers included, add as many as you like</li>
|
||||
<li>Unlimited monitors, secrets and channels</li>
|
||||
@@ -90,12 +90,12 @@ export default function PricingPage() {
|
||||
<div className="plan">
|
||||
<div>
|
||||
<div className="plan__n">Enterprise</div>
|
||||
<p className="plan__d">A fleet, a compliance question, and someone who wants a phone number.</p>
|
||||
<p className="plan__d">For larger estates, and for anyone who has to answer to an auditor.</p>
|
||||
</div>
|
||||
<div className="plan__p">
|
||||
£49 <span>/ month, 10 servers</span>
|
||||
</div>
|
||||
<p className="plan__t">£5 per extra server. £490 a year saves two months. Self-hosted is £490 a year.</p>
|
||||
<p className="plan__t">£2.50 per extra server. £490 a year saves two months. Self-hosted is £490 a year.</p>
|
||||
<ul>
|
||||
<li>10 servers included, add as many as you like</li>
|
||||
<li>Everything in Professional</li>
|
||||
@@ -158,7 +158,7 @@ export default function PricingPage() {
|
||||
<span className="spec__k">COUNTING</span>
|
||||
<div>
|
||||
<h3>What counts as a server</h3>
|
||||
<p>One running agent, one server. You choose the cap rather than being metered on what you ran, so the invoice is the same number you set.</p>
|
||||
<p>Anything with Vantage installed on it counts as one. You set the limit yourself rather than being metered, so the bill is always the number you chose.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
@@ -178,8 +178,11 @@ export default function PricingPage() {
|
||||
<div className="spec">
|
||||
<span className="spec__k">LIMITS</span>
|
||||
<div>
|
||||
<h3>Reaching the cap</h3>
|
||||
<p>Nothing is deleted. An agent beyond the cap still registers and heartbeats, but stops syncing keys until you raise the cap or remove a server.</p>
|
||||
<h3>Reaching your limit</h3>
|
||||
<p>
|
||||
Nothing gets deleted. A server past the limit still checks in and still shows up in your list, but access changes stop reaching it until you raise the limit or remove
|
||||
one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
@@ -187,18 +190,18 @@ export default function PricingPage() {
|
||||
<div>
|
||||
<h3>What happens when you stop paying</h3>
|
||||
<p>
|
||||
Your licence runs to the end of the period you paid for, then the instance goes read-only. Monitors keep checking, alerts keep firing and agents keep their keys — you
|
||||
just cannot change anything until you renew.
|
||||
You keep everything until the end of the period you paid for. After that it becomes read-only: your uptime checks carry on running, your alerts still arrive and your
|
||||
servers keep the access they have. You just can't change anything until you renew.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">SELF-HOSTED</span>
|
||||
<div>
|
||||
<h3>Why self-hosted is annual only</h3>
|
||||
<h3>Why self-hosted is yearly only</h3>
|
||||
<p>
|
||||
A self-hosted licence is a signed file your install checks offline, with no phone home. That means we cannot withdraw one early, so the term is the limit — and we would
|
||||
rather bound it at a year than sell it twelve times over.
|
||||
When you host it yourself, your licence is a file your installation checks on its own. It never contacts us, which is the point but it also means we can't switch
|
||||
one off partway through, so we sell it a year at a time.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -206,7 +209,9 @@ export default function PricingPage() {
|
||||
<span className="spec__k">EXIT</span>
|
||||
<div>
|
||||
<h3>Leaving</h3>
|
||||
<p>Export every server, key, workflow and secret group as JSON whenever you like. Agents keep their last synced state on disk, so nobody is locked out mid-migration.</p>
|
||||
<p>
|
||||
You can export your servers, keys, scripts and stored passwords at any time. Your servers keep the access they already have, so nobody gets locked out while you move.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AccountForm } from "@/components/AccountForm";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Vantage Cloud",
|
||||
description: "Your account is where instances, licences and billing live. Free for three servers, hosted or self-hosted.",
|
||||
description: "Set up an account, confirm your email, and get a free Vantage for three servers. Hosted by us, or installed on your own hardware.",
|
||||
};
|
||||
|
||||
export default function StartPage() {
|
||||
@@ -14,7 +14,7 @@ export default function StartPage() {
|
||||
<span className="tag">Vantage Cloud</span>
|
||||
<h1 style={{ fontSize: "var(--s-3)", margin: "0.8rem 0 1rem", maxWidth: "15ch" }}>Create your account.</h1>
|
||||
<p className="lede" style={{ fontSize: "var(--s-0)" }}>
|
||||
Your account is where instances, licences and billing live. Confirm your email and you can create a free instance straight away — three servers, hosted by us.
|
||||
This is where your billing and your Vantage sites live. Confirm your email address and you can set one up right away free, for three servers, hosted by us.
|
||||
</p>
|
||||
|
||||
<div className="card" style={{ marginTop: "1.9rem" }}>
|
||||
@@ -29,34 +29,34 @@ export default function StartPage() {
|
||||
<span className="spec__k">FIRST</span>
|
||||
<div>
|
||||
<h3>Confirm your email</h3>
|
||||
<p>We send a link that works once. Your account is created when you open it, not before.</p>
|
||||
<p>We send you a link that works once. Nothing is created until you open it.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">THEN</span>
|
||||
<div>
|
||||
<h3>Create your instance</h3>
|
||||
<p>One click in the portal. It gets its own subdomain and a free licence, and you are its owner.</p>
|
||||
<h3>Set up your Vantage</h3>
|
||||
<p>One click. It gets its own web address, and you're the owner of it.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">THEN</span>
|
||||
<div>
|
||||
<h3>Add a key and a server</h3>
|
||||
<p>Paste your public key, then run the install command as root. It expires in an hour and works once.</p>
|
||||
<p>Paste in your public key, then run the install command on the server. It works once and expires after an hour.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="spec">
|
||||
<span className="spec__k">THEN</span>
|
||||
<div>
|
||||
<h3>Watch it register</h3>
|
||||
<p>The server moves from pending to active on first sync, usually inside 30 seconds.</p>
|
||||
<h3>Watch it appear</h3>
|
||||
<p>The server turns from pending to active as soon as it checks in, usually within 30 seconds.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre className="code" style={{ marginTop: "1.8rem" }}>
|
||||
<b>curl</b> -fsSL https://vantage.hostxtra.co.uk/install | \{"\n"}
|
||||
<b>curl</b> -fsSL https://vantage.mydomain.com/install | \{"\n"}
|
||||
{" "}bash -s -- --server-id=<b>$ID</b> --token=<b>$TOKEN</b>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user