diff --git a/apps/dashboard/src/lib/remote/vms.remote.ts b/apps/dashboard/src/lib/remote/vms.remote.ts
index d29733b..b7b9ca3 100644
--- a/apps/dashboard/src/lib/remote/vms.remote.ts
+++ b/apps/dashboard/src/lib/remote/vms.remote.ts
@@ -15,6 +15,7 @@ import { requireProjectAccess } from '$lib/server/auth-context';
import { isProjectBillingExempt, requireProjectBillingActive } from '$lib/server/billing/autumn';
import { queueVmDeletion } from '$lib/server/vm-deletion';
import { provisionVm } from '$lib/server/vm-provisioning';
+import { isValidPtrHostname } from '$lib/ptr';
import { instrument, timingLog } from '$lib/server/observability';
import {
accessibilityFixtureEnabled,
@@ -578,6 +579,27 @@ export const createVm = command(createParams, async (params) => {
});
});
+const updateHostnameParams = type({ vmId: 'string', hostname: 'string' });
+export const updateVmHostname = command(updateHostnameParams, async (params) => {
+ const event = getRequestEvent();
+ if (!event?.locals.user) error(401, 'Authentication required');
+
+ const db = initDrizzle();
+ const row = await db.query.vms.findFirst({ where: eq(vms.id, params.vmId) });
+ if (!row) error(404, `VM "${params.vmId}" not found`);
+ if (!row.active) error(400, 'Cannot rename an inactive VM');
+ if (!row.ownerProjectId) error(400, 'VM is not attached to a project');
+ await requireProjectAccess(db, event.locals.user.id, row.ownerProjectId, 'read_write');
+
+ const hostname = params.hostname.trim();
+ if (!isValidPtrHostname(hostname)) error(400, 'Hostname must be a valid hostname');
+
+ const backend = getBackend(row.backend);
+ await backend.updateVmHostname(row.id, hostname, row.proxmoxId ?? undefined);
+ await db.update(vms).set({ name: hostname }).where(eq(vms.id, row.id));
+ return { id: row.id, name: hostname };
+});
+
const deleteParams = type({ vmId: 'string' });
export const deleteVm = command(deleteParams, async (params) => {
const event = getRequestEvent();
diff --git a/apps/dashboard/src/lib/server/backends/proxmox/index.ts b/apps/dashboard/src/lib/server/backends/proxmox/index.ts
index f1f2e13..4081b48 100644
--- a/apps/dashboard/src/lib/server/backends/proxmox/index.ts
+++ b/apps/dashboard/src/lib/server/backends/proxmox/index.ts
@@ -76,6 +76,7 @@ type ProxmoxBackendOptions = {
};
type CloudInitVendorConfigParams = {
+ hostname?: string;
enableSshPasswordAuth?: boolean;
};
@@ -95,6 +96,7 @@ function firstIpv6AddressInPrefix(prefix: string) {
function cloudInitVendorConfig(params: CloudInitVendorConfigParams) {
const yamlContents = `#cloud-config\n${stringifyYaml({
+ ...(params.hostname ? { hostname: params.hostname, manage_etc_hosts: true } : {}),
write_files: [
{
path: '/etc/sysctl.d/99-ipv6-forwarding.conf',
@@ -540,7 +542,10 @@ export class ProxmoxBackend implements VmBackend {
),
this.uploadSnippet(
cloudInitVendorConfigFilename,
- cloudInitVendorConfig({ enableSshPasswordAuth: Boolean(params.password) })
+ cloudInitVendorConfig({
+ hostname: params.name,
+ enableSshPasswordAuth: Boolean(params.password)
+ })
)
]);
@@ -644,6 +649,28 @@ export class ProxmoxBackend implements VmBackend {
};
}
+ async updateVmHostname(id: string, hostname: string, proxmoxId?: number): Promise
+ This updates the guest hostname and the server name shown in the dashboard. +
{settingsError}
+ {/if} {#if deleteError}{deleteError}
{/if}