docs: verify against real WHP + capture real screenshots

Discovery against the demo account on whp01 surfaced several inaccuracies:

- Cache is Valkey (Redis wire-compatible), not Redis or Memcached.
  No Memcached is offered as a separate service.
- Site Monitoring is the sidebar label (not 'AI Monitor').
- 'Add a domain' has no Primary/Add-on distinction.
- Sites form: 'Container Type' (not 'Site type'), Number of Containers
  (1-10 for horizontal scaling), CPU per Container (default 0.25),
  Memory per Container (default 256MB), SSL inline on the same form.
- Backups: default retention 5 days / 10 backups; on-demand + scheduled;
  S3 backup targets are visible and configurable.
- Email: per-domain settings live behind 'Setup Instructions' on the
  Email page; mail server hostname is on the Dashboard (per-server,
  e.g. mail01.cloud-hosting.io), not per-domain.

Also reworked the screenshot pipeline:
- New shots.config.ts targets the real index.php?page=... URLs
- Added redactSensitive() step that runs before each screenshot to swap
  server names, IPs, mail hostnames, and demo-user-isms with neutral
  placeholders. This keeps docs portable across the fleet.
- Hides .brand-full and .navbar-text (top-bar server identifier and
  Welcome greeting).
- Captured 9 real WHP screenshots; removed stale placeholders.
This commit is contained in:
2026-05-17 17:00:13 -07:00
parent 53bc37fd0d
commit c602b8f8f3
32 changed files with 460 additions and 152 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

View File

@@ -18,6 +18,8 @@ Archival email keeps a long-term, searchable copy of your mail **outside** the l
- Compliance or policy requires you keep email for a fixed window.
- You want a recovery option for mail you accidentally delete from the live mailbox.
It's powered by our [Bichon](https://anhonesthost.com/bichon/) archival service.
## How it's different from backups
| | Backups | Archival email |
@@ -30,22 +32,18 @@ You can use both — they cover different problems.
## What's included
- **Per-mailbox archive** — enable on the mailboxes that need it, not the whole account.
- **14-day quick-restore window** — accidentally deleted mail is recoverable without staff help.
- **Configurable longer retention** — set retention to match your policy.
- **Independent password reset** on the archive without touching the live mailbox — so you can grant audit access without disturbing the user.
- **Per-mailbox archive.** Enable on the mailboxes that need it, not the whole account. Your plan has an **archival slots** quota; the Email page shows current usage (e.g. `Archival: 0 of 0 mailboxes archived (no archival slots in your plan)` if you haven't added the add-on yet).
- **14-day quick-restore window.** Accidentally deleted mail is recoverable without staff help.
- **Configurable longer retention.** Set retention to match your policy.
- **Independent password reset on the archive.** Grant audit access without disturbing the live mailbox.
## How to enable
From the [client portal](https://secure.anhonesthost.com/clientarea.php), open **Add-ons** under your hosting service and pick Archival email. Billing is per archived mailbox.
## How to search archived mail
## How to use it
In WHP, go to the sidebar: **Email → Archive**. Pick the mailbox, then search by date range, sender, subject, or body content.
![WHP Email Archive page](~/assets/screenshots/whp/whp-email-archive.png)
Click a result to view the message in place; click **Restore to mailbox** to send it back to the live mailbox.
The archive is managed alongside your regular mailboxes on the **Email** page in WHP — the **Email Accounts** section shows archive status per mailbox, and the per-mailbox menu lets you search and restore archived mail.
## Related

View File

@@ -1,5 +1,5 @@
---
title: Monitoring (AI Monitor)
title: Site Monitoring
description: Proactive alerts for site errors, brute-force attempts, and exploit signatures.
sidebar:
order: 2
@@ -12,9 +12,9 @@ import Support from '~/content/partials/support-link.mdx';
## What it does
AI Monitor watches your site's logs, error rates, and access patterns in the background. If something looks unusual — a sustained spike in 500 errors, a brute-force pattern against a login page, a request matching a known exploit signature — you get an alert.
Site Monitoring watches your site's logs, error rates, and access patterns in the background. If something looks unusual — a sustained spike in 500 errors, a brute-force pattern against a login page, a request matching a known exploit signature — you get an alert.
By default alerts go to the contact email on your account; SMS is available for critical-severity events if you opt in.
By default, alerts go to the contact email on your account. SMS is available for critical-severity events if you opt in.
## What's included
@@ -26,15 +26,17 @@ By default alerts go to the contact email on your account; SMS is available for
## How to enable
From the [client portal](https://secure.anhonesthost.com/clientarea.php), go to your hosting service → **Upgrade/Downgrade** pick Monitoring.
Site Monitoring is enabled per site. From the [client portal](https://secure.anhonesthost.com/clientarea.php), go to your hosting service → **Upgrade/Downgrade** and pick Site Monitoring.
## Where it lives in WHP
Once enabled, find it in the sidebar: **Security → AI Monitor**.
Once enabled, find it in the sidebar: **Site Monitoring**.
![AI Monitor in WHP](~/assets/screenshots/whp/whp-monitor.png)
![WHP Site Monitoring page](~/assets/screenshots/whp/whp-site-monitoring.png)
You'll see a feed of events, severity badges, and counts. Click an event for the full log context.
When the add-on isn't enabled, the page reads: *"AI monitoring is not enabled for any of your sites. Contact your hosting provider to enable this feature."*
Once enabled, the page shows a feed of events with severity badges and counts. Click an event for the full log context.
## Tuning false positives

View File

@@ -16,7 +16,7 @@ Add-ons are **optional extras** you can layer onto your base hosting plan. Each
## What's available
- **[Monitoring (AI Monitor)](/whp/add-ons/monitoring/)** — proactive alerts when something looks wrong (error spikes, brute-force attempts, exploit signatures).
- **[Site Monitoring](/whp/add-ons/monitoring/)** — proactive alerts when something looks wrong (error spikes, brute-force attempts, exploit signatures).
- **[Archival email](/whp/add-ons/archival-email/)** — long-term, searchable retention of your mail outside the live mailbox.
- **[Resource upgrades](/whp/add-ons/resource-upgrades/)** — extra CPU, RAM, or storage without changing plans.
- **[Email upgrades](/whp/add-ons/email-upgrades/)** — extra mailboxes or larger per-mailbox storage caps.

View File

@@ -18,31 +18,28 @@ Boost your container's **CPU**, **RAM**, or **persistent disk** without migratin
Each resource is independent — upgrade only what you need.
- **CPU cores** — add cores for sustained-compute workloads.
- **RAM** — for memory-hungry apps (caches, large WordPress sites with many plugins, Node apps holding big in-memory state).
- **Persistent disk** — for sites that store a lot of files (media libraries, user uploads, backups outside our managed backup).
- **CPU cores per container** — visible on the Sites page as **CPU per Container (cores)** (default `0.25`). Add cores for sustained-compute workloads.
- **RAM per container** — visible on the Sites page as **Memory per Container (MB)** (default `256`). For memory-hungry apps (caches, large WordPress sites with many plugins, Node apps holding big in-memory state).
- **Number of containers** — scale a busy site horizontally from 1 up to 10 replicas; WHP load-balances traffic across them.
- **Persistent disk** — for sites that store a lot of files (media libraries, user uploads, etc.).
## When you might need this
- **High CPU on busy days.** Your **Overview → Resource usage** chart consistently hits the cap during peak hours.
- **"Out of memory" errors** in your app log or `dmesg`.
- **Disk usage approaching 80%** of your allocation — get ahead of it; full disks cause backup failures and uploads to fail.
- **High CPU on busy days.** Sustained-load sites consistently hit the per-container CPU cap during peak hours.
- **"Out of memory" errors** in your app log.
- **Disk usage approaching 80%** of your allocation. Get ahead of it full disks cause backup failures and uploads to fail.
## How to enable
From the [client portal](https://secure.anhonesthost.com/clientarea.php), open your hosting service → **Upgrade/Downgrade** pick the resource amount. WHP applies the change usually within a minute, no downtime.
For per-container CPU/RAM upgrades, edit the values directly on the **Sites** page for the site you want to change, and click **Save**. For account-wide upgrades (more total RAM, more total disk), open the [client portal](https://secure.anhonesthost.com/clientarea.php), go to your hosting service → **Upgrade/Downgrade**, and pick the resource increment.
## See your current usage
In WHP, sidebar: **Overview → Resource usage**.
![WHP Resource Usage page](~/assets/screenshots/whp/whp-resources.png)
The chart shows CPU, RAM, and disk over the last 24 hours and 7 days. If you're consistently above 80% of any line, that's the one to upgrade.
Open the **Dashboard** page in WHP. The **Server Information** and account stats show what you're consuming. For per-site usage, open a site from the **Sites** page.
## Related
- [Monitoring (AI Monitor)](/whp/add-ons/monitoring/) — catches resource saturation before customers complain.
- [Site Monitoring](/whp/add-ons/monitoring/) — catches resource saturation before customers complain.
- [Add-ons overview](/whp/add-ons/overview/)
## Still stuck?

View File

@@ -27,35 +27,41 @@ import Support from '~/content/partials/support-link.mdx';
<Steps>
1. From the sidebar, open **Domains → Add Domain**.
![WHP Add Domain page](~/assets/screenshots/whp/whp-domains-add.png)
1. In the sidebar, click **Domains**.
![WHP Domains page](~/assets/screenshots/whp/whp-domains.png)
2. Enter your domain (for example, `example.com`). Don't include `http://` or `www.` — just the bare domain.
2. Under **Add New Domain** on the left, type your domain (for example, `example.com`). Don't include `http://` or `www.` — just the bare domain.
3. Choose the domain type:
- **Primary** — your account's main domain. Pick this if it's your first.
- **Add-on** — an extra domain alongside your primary.
4. Click **Add**. WHP confirms the domain and shows the nameserver values and an A record you can point at.
3. Click **Add Domain**.
</Steps>
WHP creates the standard set of DNS records automatically for the new domain:
- **A record** for the apex domain → your server's IP
- **CNAME** for `www` → the apex
- **NS records** for the nameservers
- **MX record** → the mail server
- **TXT record** for SPF
You can review and tweak any of these from the **DNS Management** panel on the right side of the Domains page (select the domain from the dropdown).
## Point your DNS at us
There are two paths depending on where the domain is registered:
**Registered with us:** nothing to do. We set DNS automatically when the domain was created. Skip ahead to verify.
**Registered with us.** Nothing to do — we set DNS automatically when the domain was added. Skip ahead to verify.
**Registered elsewhere:** at your registrar, do one of the following:
**Registered elsewhere.** At your registrar, do one of the following:
- Set the **nameservers** to `ns1.anhonesthost.com` and `ns2.anhonesthost.com` (recommended — gives us full DNS control, easier to support), **or**
- Keep your existing nameservers and add an **A record** pointing the domain to the IP address shown on the WHP confirmation screen.
- Set the **nameservers** to the values shown on the Domains page (recommended — gives us full DNS control, easier to support), **or**
- Keep your existing nameservers and add an **A record** pointing the domain to the IP shown on the **Dashboard** page under Server Information.
## Verify it worked
DNS changes can take up to 24 hours to propagate worldwide. To check status:
DNS changes can take up to 24 hours to propagate worldwide. To check:
- Run `dig example.com +short` from a terminal — once you see our IP in the result, you're live.
- Run `dig example.com +short` from a terminal — once you see our server's IP in the result, you're live.
- Or use a web tool like [whatsmydns.net](https://www.whatsmydns.net/) to see propagation across many regions.
Once DNS resolves, visiting your domain in a browser will reach WHP — though you'll see a default placeholder until you create a site.
@@ -64,9 +70,9 @@ Once DNS resolves, visiting your domain in a browser will reach WHP — though y
**"Pending DNS" for more than 24 hours.** Re-check the nameservers or A record at your registrar — typos and trailing dots cause silent failures. Clear your local DNS cache to rule out client-side caching.
**Domain returns a generic "site not found" page.** DNS works but you haven't created a site yet. [Create one](/whp/how-to/create-a-site/) and point it at this domain.
**Domain returns a generic placeholder.** DNS works but you haven't created a site yet. [Create one](/whp/how-to/create-a-site/) and bind this domain to it.
**Wrong site loads.** Another site on your account is set as the default. In **Sites**, make sure the correct site is bound to this domain.
**Wrong site loads.** Another site on your account is bound to this domain. Open **Sites** in the sidebar and make sure the correct site is bound to it.
## Related

View File

@@ -1,6 +1,6 @@
---
title: Backups
description: Confirm WHP is backing up your site, restore individual files, and download archives.
description: Run on-demand and scheduled backups of your sites and databases, and confirm they're succeeding.
sidebar:
order: 4
---
@@ -9,76 +9,79 @@ import { Steps, Aside } from '@astrojs/starlight/components';
import SignIn from '~/content/partials/signing-in.mdx';
import Support from '~/content/partials/support-link.mdx';
WHP runs **automatic daily backups** of your site files and databases. You don't have to set anything up for this to happen — but it's worth knowing how to confirm a backup ran, restore something, and download an archive when you need to.
WHP keeps automatic backups of your sites and databases. The **Backup Management** page lets you trigger an on-demand backup, add a scheduled backup, manage where backups are sent, and review history.
## What's backed up by default
## What's backed up
- **Site files** — everything under each domain folder.
- **Databases** — all MySQL databases attached to your account.
- **Email** — mailboxes and their contents (depending on your plan).
- **Sites** — files for each site.
- **Databases** — every MySQL and PostgreSQL database attached to your account.
Retention is shown on your plan page. The default tier keeps **7 daily backups**.
Default retention on built-in backup targets is **5 days, up to 10 backups**.
## Sign in to WHP
<SignIn />
## Configure your backup schedule
## Run an on-demand backup
<Steps>
1. From the sidebar, open **Backups → Settings**.
![WHP Backup Settings page](~/assets/screenshots/whp/whp-backups-settings.png)
1. In the sidebar, click **Backups**.
![WHP Backup Management page](~/assets/screenshots/whp/whp-backups.png)
2. Confirm the **schedule** (default: daily, overnight). Adjust if your plan permits.
2. Under **Create New Backup**, pick a **Backup Type** (Sites, Databases, or both) and a **Backup Target** from the dropdown.
3. (Optional) Enable **off-server backups** to a separate storage location — recommended for anything you can't afford to lose.
3. Click **Start Backup**. The run appears in **Backup History** below; status updates live.
</Steps>
## Verify a backup actually ran
## Schedule a backup
<Steps>
1. Open **Backups → History**.
![WHP Backup History page](~/assets/screenshots/whp/whp-backups-history.png)
1. Scroll down to **Scheduled Backups** and click **+ Add Schedule**.
2. Confirm the most recent entry is from **within the last 24 hours** and shows **Success**.
2. Pick the cadence (daily, weekly, etc.), the type (sites / databases / both), and the target.
3. Click the entry to see what was included — files, databases, email — and the total size.
3. Save. The schedule appears in the list and runs automatically.
</Steps>
## Where backups are stored
The **Backup Targets** table shows the destinations available to your account. Built-in targets are S3-backed (for example, `WHP01 S3 Backups`) with retention and a maximum backup count. **Global** targets are managed by us; you can also add your own external target (for example, your own S3 bucket) with **+ Add Backup Target**.
## Verify backups are succeeding
<Steps>
1. Open **Backups**.
2. Look at the **Total Backups** and **Total Size** tiles at the top. If Total Backups stays at 0 over time, no backups are running — open a [support ticket](https://secure.anhonesthost.com/submitticket.php).
3. Check **Backup History** for recent entries. Each should show **Success** within minutes of its scheduled time.
</Steps>
<Aside type="tip">
Add a calendar reminder to check this monthly. Backups that fail silently are the worst kind.
Add a calendar reminder to check the history monthly. Backups that fail silently are the worst kind.
</Aside>
## Test a restore (recommended quarterly)
The best time to discover your backup isn't working is *not* when you actually need it. Test surgically:
1. In **History**, click a recent backup → **Restore preview**.
2. Pick a single file — your site's `index.php`, for example — and restore just that one file.
3. Confirm it appears correctly. If yes, the restore pipeline works.
Find the time to do this before you actually need it. Open a recent backup in **Backup History** → preview the contents → restore a single file (your site's `index.php` is a fine target) → confirm it appears. If the surgical restore works, the pipeline works.
Don't do a full restore unless you genuinely need to — it rewrites your live site.
## Download a backup
To grab a copy off-server:
1. From **History**, click a backup → **Download**.
2. WHP packages it as `.tar.gz` and offers a one-time download link that's valid for 24 hours.
## Troubleshooting
**Backup failed.** Click the failed row to see the error. Common causes:
- Disk full or close to it — check **Overview → Resource usage** and consider a [resource upgrade](/whp/add-ons/resource-upgrades/).
- A very large mailbox slowed the run — consider the [archival email add-on](/whp/add-ons/archival-email/) to relieve mailbox pressure.
- Transient lock or maintenance window — let it retry overnight before opening a ticket.
- Transient lock or maintenance window — let it retry on the next schedule before opening a ticket.
**Restore "succeeded" but my file isn't there.** Check the **path** shown on the backup's detail page — your restore landed where the backup says it would, which may not match your current site layout if you've moved files around.
**"No targets available" when starting a backup.** Your account has no backup targets attached. Open a ticket; we'll get one wired up.
## Related

View File

@@ -1,6 +1,6 @@
---
title: Create a site
description: Spin up a PHP, Node, or static HTML site on a domain you've added to WHP.
description: Spin up a containerized site on a domain you've added to WHP.
sidebar:
order: 2
---
@@ -10,10 +10,12 @@ import SignIn from '~/content/partials/signing-in.mdx';
import Hostnames from '~/content/partials/service-hostnames.mdx';
import Support from '~/content/partials/support-link.mdx';
Every site on WHP runs in one or more containers. The Sites page lets you pick a container type (PHP, Node, static HTML, or one of the other options on the dropdown), bind one or more domains, and set how much CPU and RAM each container gets.
## Before you start
- A **domain already added** to your account ([Add a domain](/whp/how-to/add-a-domain/) covers that).
- Decide what kind of site you're building: **PHP**, **Node**, or **static HTML**.
- Decide what kind of site you're building. Common container types: **PHP** (WordPress, Laravel, most CMSes), **Node** (custom apps), **Static HTML** (prebuilt bundles from Astro, Hugo, Eleventy, etc.).
- About 5 minutes.
## Sign in to WHP
@@ -24,33 +26,33 @@ import Support from '~/content/partials/support-link.mdx';
<Steps>
1. From the sidebar, open **Sites → Add Site**.
![WHP Add Site page](~/assets/screenshots/whp/whp-sites-add.png)
1. In the sidebar, click **Sites**.
![WHP Sites page with Create New Site form](~/assets/screenshots/whp/whp-sites.png)
2. Pick the **site type**:
- **PHP** — best for WordPress, Joomla, Laravel, and most off-the-shelf CMSes.
- **Node** — for custom apps you build and deploy (Express, Next.js in standalone mode, Fastify, etc.).
- **Static HTML** — for prebuilt site bundles from tools like Astro, Hugo, Eleventy, or Jekyll. No runtime; just files.
2. Fill in the **Create New Site** form on the right:
- **Site Name** — a friendly label for you to recognise the site later.
- **Primary Domain or Subdomain** — pick from the dropdown of domains you've already added.
- **Container Type** — PHP, Node, static HTML, etc.
- **Number of Containers** — `1` is the right answer for most sites. Bump this up (110) if you need to scale a busy site horizontally; WHP load-balances traffic across the replicas.
- **Additional Domains/Subdomains (Optional)** — bind extra domains to the same site if you want them to serve the same content.
- **CPU per Container (cores)** — defaults to `0.25`. Raise this only if you need to.
- **Memory per Container (MB)** — defaults to `256`. Raise this if your app needs more RAM.
3. Pick the **domain**. The dropdown lists domains you've already added.
3. Scroll down to **SSL/HAProxy Configuration**. Before you enable HTTPS, make sure your domain's DNS already points to our server's IP — SSL certificates can't be issued for domains that aren't pointed correctly. Then check **Enable HTTPS** to have us request a Let's Encrypt certificate.
4. (PHP only) Pick the **PHP version**.
5. (Node only) Pick the **Node version** and the **start command** (typically `npm run start`).
6. Click **Create site**. WHP provisions the container — usually under a minute. You'll see the new site in the **Sites** list when it's ready.
4. Click **Create Site**. WHP provisions the container(s) — usually under a minute. The new site shows up in the **Manage Sites** list on the left of the page.
</Steps>
## Where your files go
When you connect via SFTP, your account home shows one folder per domain. Inside each domain folder, the layout depends on the site type:
When you connect via SFTP, your account home (`/docker/users/<your-user>/`) shows one folder per domain. Inside each domain folder, the layout depends on the container type:
| Site type | Layout | Docroot is... |
| ------------ | ----------------------------------------------------- | ------------------------------------------------------ |
| PHP | `public_html/`, `logs/`, `crontab` | `public_html/` |
| Node | `app/`, `logs/` | Whatever your start command serves; we run it from `app/` |
| Static HTML | files directly (e.g., `index.html`, `assets/`) | The domain folder itself |
| Container type | Layout inside the domain folder | Docroot is... |
| -------------- | ------------------------------------------ | -------------------------------------------------------- |
| PHP | `public_html/`, `logs/`, `crontab` | `public_html/` |
| Node | `app/`, `logs/` | Whatever your start command serves; we run it from `app/`|
| Static HTML | files directly (e.g., `index.html`, `assets/`) | The domain folder itself |
Upload your files to the right place and the site picks them up immediately.
@@ -63,16 +65,18 @@ Upload your files to the right place and the site picks them up immediately.
Open your domain in a browser. You should see your site, or the default "no content yet" placeholder if you haven't uploaded files. For Node sites, give it 1530 seconds after creation for the process to start.
<Aside type="tip">
Static HTML sites are the fastest path if your toolchain produces a bundle (Astro, Hugo, Eleventy, etc.) — you skip the runtime entirely.
Static HTML is the fastest path if your toolchain produces a bundle (Astro, Hugo, Eleventy, etc.) — you skip the runtime entirely.
</Aside>
## Troubleshooting
**You see a default "no content" page.** Upload your files via SFTP — for PHP into `public_html/`, for Node into `app/`, for static directly into the domain folder.
**Node site returns 502.** Check that your start command runs successfully locally, and that it binds to the port shown on the site's WHP detail page (we set it via an env var; see the site's environment in WHP).
**Node site returns 502.** Check that your start command runs successfully locally, and that it binds to the port WHP exposes via env var to your container.
**PHP site shows a blank page or 500.** Check your site's error log (sidebar → Logs → pick the site). Common cause: a syntax error in `index.php` or a missing PHP extension — request extensions via a [support ticket](https://secure.anhonesthost.com/submitticket.php) if needed.
**PHP site shows a blank page or 500.** Check **Sites → your site → Logs** (or the `logs/` folder in SFTP). Common cause: a syntax error in `index.php` or a missing PHP extension — open a [support ticket](https://secure.anhonesthost.com/submitticket.php) if you need a non-default extension enabled.
**SSL won't issue.** The "Important: ensure your domain is pointed to our server" warning means DNS hasn't propagated yet, or it's pointed somewhere else. Re-check with `dig` and retry.
## Related

View File

@@ -11,7 +11,7 @@ import Support from '~/content/partials/support-link.mdx';
## Before you start
- A **domain added** to your account.
- A **domain added** to your account ([Add a domain](/whp/how-to/add-a-domain/) covers that).
- Decide what local part you want — the bit before the `@`. For example, `jane` to get `jane@example.com`.
- About 5 minutes.
@@ -23,62 +23,58 @@ import Support from '~/content/partials/support-link.mdx';
<Steps>
1. From the sidebar, open **Email → Email Accounts → Add Account**.
![WHP Add Email Account page](~/assets/screenshots/whp/whp-email-add.png)
1. In the sidebar, click **Email**.
![WHP Email Management page](~/assets/screenshots/whp/whp-email.png)
2. Pick the **domain** for this mailbox.
2. Scroll to **Email Accounts** and use the form to create a new account on one of your domains. You'll be asked for the domain, the local part, a password, and an optional mailbox size cap.
3. Enter the **local part** (the bit before `@`).
3. Set a **strong password** — at least 12 characters with a mix of upper case, lower case, numbers, and symbols. Email accounts are common attack targets.
4. Set a **strong password** — at least 12 characters with a mix of upper case, lower case, numbers, and symbols. Email accounts are common attack targets.
5. Choose a **mailbox size cap**. The default is usually fine; you can raise it later or via an [email upgrade add-on](/whp/add-ons/email-upgrades/).
6. Click **Create**.
4. Click **Create**. The new account appears in the **Email Accounts** list.
</Steps>
## Set up your email client
Mail server hostnames are per-domain. Find the exact incoming and outgoing hostnames for your domain on the **Dashboard** page in WHP — that page lists the IMAP, POP3, and SMTP host names along with the recommended ports and security settings.
The exact IMAP, POP3, and SMTP hostnames are listed on the Email page — click **Setup Instructions → View Instructions** under **Mail Server Access** for a step-by-step that includes the right hostnames, ports, and security settings for your server.
The typical pattern is:
The typical settings look like this; substitute the hostname shown in the Setup Instructions:
```text
IMAP (incoming)
Host: <see Dashboard>
Host: <see Setup Instructions>
Port: 993
Security: SSL/TLS
Username: full email address (e.g., jane@example.com)
Password: the one you set above
SMTP (outgoing)
Host: <see Dashboard>
Host: <see Setup Instructions>
Port: 587
Security: STARTTLS
Username: full email address
Password: same as IMAP
```
For per-client walkthroughs (Outlook, Apple Mail, etc.), see the Email clients section — coming soon.
For per-client walkthroughs (Outlook, Apple Mail, Thunderbird, etc.), see the Email clients section — coming soon.
## Webmail
The webmail URL for your domain is also listed on the **Dashboard** page in WHP.
Click **Webmail Access → Open Webmail** on the Email page to sign in to webmail in a new tab.
## Verify it worked
Send yourself a test message from another account (your personal Gmail, for example). It should arrive within a minute or two and be retrievable from both your client and webmail.
<Aside type="caution">
**SPF and DKIM records matter.** Without them, your outgoing mail will get flagged or rejected by other providers. If your domain is registered with us, we add these records automatically. If it's registered elsewhere, copy the SPF and DKIM records shown on the email account page into your registrar's DNS.
**SPF and DKIM records matter.** Without them, your outgoing mail will get flagged or rejected by other providers. We add an SPF record automatically when you add a domain. DKIM records are listed in the **DKIM Records** section near the bottom of the Email page — make sure they're present at your registrar if the domain isn't using our nameservers.
</Aside>
## Troubleshooting
**Webmail isn't reachable.** DNS for the mail subdomain may still be propagating — wait an hour and try again. The exact URL is on the Dashboard page in WHP.
**Webmail isn't reachable.** DNS for the mail subdomain may still be propagating — wait an hour and try again.
**Outgoing mail is bouncing or going to spam.** Check the SPF and DKIM records at your registrar. The email account page in WHP shows the exact records you should have.
**Outgoing mail is bouncing or going to spam.** Check the SPF and DKIM records. The DKIM Records panel on the Email page shows whether DKIM is configured for each of your domains.
**Client can connect on IMAP but not SMTP.** Some ISPs and corporate networks block outgoing port 587. Try sending from a different network to confirm; if the issue is your network, your ISP is the place to ask.

View File

@@ -1,6 +1,6 @@
---
title: Service hostnames
description: Quick reference for connecting to MySQL, PostgreSQL, Memcached, and Redis from inside your container.
description: Quick reference for connecting to MySQL, PostgreSQL, and Valkey from inside your container.
sidebar:
order: 1
---
@@ -14,24 +14,23 @@ Use these hostnames from inside your WHP container — they resolve to the inter
## Where do I get credentials?
Each service's credentials live in a different section of the WHP panel:
- **MySQL** → **Databases → MySQL → Users**
- **PostgreSQL** → **Databases → PostgreSQL** (if enabled on your plan)
- **Redis** → **Caching → Redis**
- **Memcached** — no auth required; the service is isolated per container.
- **MySQL** — sidebar → **MySQL Management**. Create users and databases there; the **phpMyAdmin** link below it opens an SSO'd phpMyAdmin in a new tab.
- **PostgreSQL** — sidebar → **PostgreSQL**. Same shape as MySQL Management; the **Adminer** link below it opens an SSO'd Adminer.
- **Valkey** — sidebar → **Valkey Cache**. Click **Enable Valkey**, pick a memory cap and mode, and you're done — no password needed because connections are network-isolated to your account.
## Mail servers
Mail server hostnames are **per-domain**, not a single shared hostname. Find the exact IMAP / POP3 / SMTP hostnames for each domain on the **Dashboard** page in WHP.
Mail server hostnames are not per-domain. Your server has one mail server hostname (for example `mail01.cloud-hosting.io`) that handles IMAP, POP3, and SMTP for every domain on that server. Find your exact mail server hostname on the **Dashboard** page in WHP under **Server Information** — that's also where you'll find your server's IP, primary nameservers, and your home directory.
For step-by-step email-client setup, the Email page has a **Setup Instructions** button that lays out the exact host, port, and security settings.
## Connecting from your laptop
Direct external connections to these internal services are not exposed for security reasons. Use the panel's tools instead:
Direct external connections to these internal services are not exposed. Use the panel's tools instead:
- **MySQL** — **phpMyAdmin** in the panel for ad-hoc queries.
- **PostgreSQL** — **Adminer** in the panel.
- **Redis / Memcached** — use the **Caching** section's built-in CLI panel.
- **Valkey** — there's no first-party browser tool; use a Valkey/Redis client inside your site's **Terminal** (sidebar → **Terminal**).
## Related

View File

@@ -1,12 +1,11 @@
Some applications need to connect to databases or caches. Use these hostnames from inside your site — they only resolve within your hosting container, so they don't work from your laptop.
| Service | Hostname | Default port | Notes |
| ----------- | ----------- | ------------ | ------------------------------------------------------ |
| MySQL | `mysql` | 3306 | Username, password, and database from your WHP panel. |
| PostgreSQL | `postgres` | 5432 | Available on most plans; ask support if it's missing. |
| Memcached | `memcache` | 11211 | No auth; isolated per container. |
| Redis | `redis` | 6379 | Single DB per site; password in panel. |
| Service | Hostname | Default port | Notes |
| ----------- | ----------- | ------------ | -------------------------------------------------------------------------------- |
| MySQL | `mysql` | 3306 | Username, password, and database from the **MySQL Management** page. |
| PostgreSQL | `postgres` | 5432 | Username, password, and database from the **PostgreSQL** page. |
| Valkey | `valkey` | 6379 | Redis wire-compatible (phpredis, Predis, ioredis work unchanged). No password — the connection is network-isolated to your account. Enable on the **Valkey Cache** page first. |
For **email** (IMAP, POP3, SMTP), the mail server hostname is **per-domain** — check the **Dashboard** page in WHP for the exact hostname to use in your email client or app.
For **email**, the mail server hostname is a per-server value (e.g. `mail01.cloud-hosting.io`) and is shown on the **Dashboard** page under Server Information.
**Connecting from your laptop?** Use the panel's **phpMyAdmin** (MySQL) or **Adminer** (PostgreSQL) for ad-hoc queries — direct external connections to these internal services are not exposed for security reasons.

View File

@@ -0,0 +1,98 @@
[
{
"text": "Logout",
"href": "index.php?whp-action=logout"
},
{
"text": "Dashboard",
"href": "index.php?page=dashboard"
},
{
"text": "Sites",
"href": "index.php?page=sites"
},
{
"text": "Traffic",
"href": "index.php?page=site-traffic"
},
{
"text": "Site Builder BETA",
"href": "index.php?page=site-builder"
},
{
"text": "Domains",
"href": "index.php?page=domains"
},
{
"text": "WordPress",
"href": "index.php?page=wordpress"
},
{
"text": "File Manager",
"href": "./filemanager/"
},
{
"text": "Terminal",
"href": "index.php?page=terminal"
},
{
"text": "cPanel Import",
"href": "index.php?page=cpanel-import"
},
{
"text": "MySQL Management",
"href": "index.php?page=database-management"
},
{
"text": "phpMyAdmin",
"href": "./phpmyadmin-sso.php"
},
{
"text": "PostgreSQL",
"href": "index.php?page=postgresql-management"
},
{
"text": "Adminer",
"href": "./adminer-sso.php"
},
{
"text": "Valkey Cache",
"href": "index.php?page=account-valkey"
},
{
"text": "Email",
"href": "index.php?page=email-management"
},
{
"text": "Backups",
"href": "index.php?page=backups"
},
{
"text": "Site Monitoring",
"href": "index.php?page=ai-monitor-customer"
},
{
"text": "Delegated Users",
"href": "index.php?page=delegated-users"
},
{
"text": "Active Sessions",
"href": "index.php?page=active-sessions"
},
{
"text": "Cloud Apache Container",
"href": "https://repo.anhonesthost.net/cloud-hosting-platform/cloud-apache-container"
},
{
"text": "Cloud Node Container",
"href": "https://repo.anhonesthost.net/cloud-hosting-platform/cloud-node-container"
},
{
"text": "Manage Domains",
"href": "index.php?page=domains"
},
{
"text": "Manage Databases",
"href": "index.php?page=database-management"
}
]

View File

@@ -0,0 +1,109 @@
/**
* Discovery script — logs in, captures the dashboard, and probes the sidebar
* nav to learn the actual URLs of the sections referenced in our docs.
*
* Not committed for ongoing use; the canonical capture is run.ts.
*/
import { chromium, type Page } from 'playwright';
import { mkdir, writeFile } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const OUT_DIR = resolve(__dirname, '../../src/assets/screenshots/whp');
function need(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`missing env: ${name}`);
return v;
}
const BASE = need('WHP_BASE');
const USER = need('WHP_USER');
const PASS = need('WHP_PASS');
async function login(page: Page): Promise<void> {
console.log(`navigating ${BASE}/login.php`);
await page.goto(`${BASE}/login.php`, { waitUntil: 'domcontentloaded' });
console.log(`landed at ${page.url()}`);
await page.fill('input[name="user"]', USER);
await page.fill('input[name="password"]', PASS);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
console.log(`logged in, now at ${page.url()}`);
}
async function snapshotPage(page: Page, id: string): Promise<void> {
await mkdir(OUT_DIR, { recursive: true });
const path = resolve(OUT_DIR, `${id}.png`);
await page.screenshot({ path, fullPage: false });
console.log(`captured ${id} -> ${path}`);
}
async function listNavLinks(page: Page): Promise<{ text: string; href: string | null }[]> {
// Use locator API to collect all anchors with hrefs (no string-eval).
const anchors = page.locator('a[href]');
const count = await anchors.count();
const out: { text: string; href: string | null }[] = [];
for (let i = 0; i < count && out.length < 120; i++) {
const a = anchors.nth(i);
const href = await a.getAttribute('href');
if (!href || href.startsWith('#')) continue;
const text = ((await a.textContent()) ?? '').trim().slice(0, 60);
if (!text) continue;
out.push({ text, href });
}
return out;
}
async function probe(page: Page, label: string, hrefHint: string): Promise<string | null> {
const link = page.locator(`a[href*="${hrefHint}"]`).first();
if ((await link.count()) === 0) {
console.log(` NO MATCH for "${label}" (hint=${hrefHint})`);
return null;
}
const href = await link.getAttribute('href');
console.log(` ${label}: ${href}`);
return href;
}
async function main(): Promise<void> {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({
ignoreHTTPSErrors: true,
viewport: { width: 1440, height: 900 },
});
const page = await ctx.newPage();
try {
await login(page);
await snapshotPage(page, '_discovery-dashboard');
const navLinks = await listNavLinks(page);
await writeFile(
resolve(__dirname, '_discovered-nav.json'),
JSON.stringify(navLinks, null, 2),
);
console.log(`wrote ${navLinks.length} nav links to _discovered-nav.json`);
for (const [label, hint] of [
['Domains', 'domain'],
['Sites', 'site'],
['Email', 'email'],
['Backups', 'backup'],
['Monitor', 'monitor'],
['Resources', 'resource'],
['Dashboard', 'dashboard'],
] as const) {
await probe(page, label, hint);
}
} finally {
await browser.close();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

53
tools/screenshots/peek.ts Normal file
View File

@@ -0,0 +1,53 @@
/**
* One-shot peek script: log in, navigate to a specific URL, screenshot it.
* Usage: PEEK_URL=... npx tsx tools/screenshots/peek.ts
*/
import { chromium, type Page } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const OUT_DIR = resolve(__dirname, '../../src/assets/screenshots/whp');
function need(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`missing env: ${name}`);
return v;
}
const BASE = need('WHP_BASE');
const USER = need('WHP_USER');
const PASS = need('WHP_PASS');
const PEEK = need('PEEK_URL');
const ID = process.env.PEEK_ID ?? '_peek';
async function login(page: Page): Promise<void> {
await page.goto(`${BASE}/login.php`, { waitUntil: 'domcontentloaded' });
await page.fill('input[name="user"]', USER);
await page.fill('input[name="password"]', PASS);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
async function main(): Promise<void> {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1440, height: 900 } });
const page = await ctx.newPage();
try {
await login(page);
await page.goto(`${BASE}${PEEK}`);
await page.waitForLoadState('networkidle');
await mkdir(OUT_DIR, { recursive: true });
const out = resolve(OUT_DIR, `${ID}.png`);
await page.screenshot({ path: out, fullPage: true });
console.log(`captured ${PEEK} -> ${out}`);
} finally {
await browser.close();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -22,13 +22,57 @@ const WHP_USER = envOrDie('WHP_USER');
const WHP_PASS = envOrDie('WHP_PASS');
async function login(page: Page): Promise<void> {
await page.goto(`${WHP_BASE}/login`);
await page.fill('input[name="username"]', WHP_USER);
await page.goto(`${WHP_BASE}/login.php`);
await page.fill('input[name="user"]', WHP_USER);
await page.fill('input[name="password"]', WHP_PASS);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
/**
* Neutralise server-identifying and account-specific text/labels before the
* screenshot — we run a multi-server fleet, so docs shouldn't bake in a single
* host's name or IP.
*
* Runs in the page context. Self-contained: no external bindings, no eval.
*/
async function redactSensitive(page: Page): Promise<void> {
await page.addStyleTag({
content: `
.navbar-text { visibility: hidden !important; }
.brand-full { visibility: hidden !important; }
`,
});
// String swaps for inline text that mask-by-selector can't cover.
await page.evaluate(() => {
const root = document.body;
const swaps: [RegExp, string][] = [
// server / mail / nameserver hostnames (with possible -s3 etc. suffix)
[/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '<your-server>.cloud-hosting.io'],
[/mail\d+\.cloud-hosting\.io/gi, '<your-mail-server>.cloud-hosting.io'],
[/ns[12]\.whp\d+\.cloud-hosting\.io/gi, 'ns<n>.your-server.cloud-hosting.io'],
// backup target / bucket names that bake in server number
[/whp\d+(-[a-z0-9]+)?\b/gi, '<your-server>'],
[/WHP\d+(-[A-Z0-9]+)?\b/g, '<YOUR-SERVER>'],
// IP addresses
[/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<your-server-IP>'],
// home dir + demo user
[/\/docker\/users\/[a-z0-9-]+/g, '/docker/users/<your-username>'],
[/demo-user/g, 'your-username'],
];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const nodes: Text[] = [];
let n: Node | null = walker.nextNode();
while (n) { nodes.push(n as Text); n = walker.nextNode(); }
for (const node of nodes) {
let v = node.nodeValue ?? '';
for (const [re, replacement] of swaps) v = v.replace(re, replacement);
if (v !== node.nodeValue) node.nodeValue = v;
}
});
}
async function captureShot(page: Page, shot: Shot): Promise<void> {
const viewport = shot.viewport ?? { width: 1440, height: 900 };
await page.setViewportSize(viewport);
@@ -36,6 +80,7 @@ async function captureShot(page: Page, shot: Shot): Promise<void> {
await page.goto(`${WHP_BASE}${shot.path}`);
await page.waitForLoadState('networkidle');
if (shot.waitFor) await page.waitForSelector(shot.waitFor);
await redactSensitive(page);
const maskSelectors = [...DEFAULT_MASK, ...(shot.mask ?? [])];
const maskLocators: Locator[] = maskSelectors.map((sel) => page.locator(sel));

View File

@@ -15,19 +15,18 @@ export type Shot = {
/** Always-applied redactions. Per-shot mask is added on top of this list. */
export const DEFAULT_MASK: string[] = [
'[data-test="account-id"]',
'[data-test="server-hostname"]',
'[data-test="user-ip"]',
'.billing-column',
// Conservative defaults — the actual selectors may differ; verify by inspecting
// a captured shot and adding more selectors here if anything leaks.
];
export const shots: Shot[] = [
{ id: 'whp-domains-add', path: '/domains/add' },
{ id: 'whp-sites-add', path: '/sites/add' },
{ id: 'whp-email-add', path: '/email/add' },
{ id: 'whp-backups-settings', path: '/backups/settings' },
{ id: 'whp-backups-history', path: '/backups/history' },
{ id: 'whp-monitor', path: '/security/monitor' },
{ id: 'whp-email-archive', path: '/email/archive' },
{ id: 'whp-resources', path: '/overview/resources' },
{ id: 'whp-dashboard', path: '/index.php?page=dashboard' },
{ id: 'whp-domains', path: '/index.php?page=domains' },
{ id: 'whp-sites', path: '/index.php?page=sites' },
{ id: 'whp-email', path: '/index.php?page=email-management' },
{ id: 'whp-backups', path: '/index.php?page=backups' },
{ id: 'whp-site-monitoring', path: '/index.php?page=ai-monitor-customer' },
{ id: 'whp-mysql', path: '/index.php?page=database-management' },
{ id: 'whp-postgres', path: '/index.php?page=postgresql-management' },
{ id: 'whp-valkey', path: '/index.php?page=account-valkey' },
];