Просмотр исходного кода

Rebuild install script generator (#4057)

* Fix Norwegian typo

* Rebuild install script generator

* Refactor to define <select> options within options.js

* Refactor to simplify

* Default PHP-FPM to no

* Fix padding

* Tidy

* Tidy Teams content
Alec Rust 2 лет назад
Родитель
Сommit
846c0e3013

+ 0 - 340
docs/.vitepress/theme/components/InstallOptions.vue

@@ -1,340 +0,0 @@
-<script>
-export default {
-	props: {
-		languages: {
-			required: true,
-			selected: "en",
-		},
-		items: {
-			required: true,
-		},
-	},
-	data() {
-		return {
-			pageloader: false,
-			hestia_wget:
-				"wget https://raw.githubusercontent.com/hestiacp/hestiacp/release/install/hst-install.sh",
-			hestia_install: "sudo bash hst-install.sh",
-			installStr: "",
-		};
-	},
-	methods: {
-		getOptionString(item) {
-			if (item.textField) {
-				return item.selected ? `${item.param} '${item.text}'` : "";
-			}
-
-			if (item.selectField) {
-				return item.selected ? `${item.param} '${item.text}'` : "";
-			}
-
-			return item.param.includes("force") && item.selected
-				? item.param
-				: `${item.param}${item.selected ? " yes" : " no"}`;
-		},
-		generateString() {
-			const installStr = this.items.map(this.getOptionString).filter(Boolean);
-
-			this.installStr = `${this.hestia_install} ${installStr.join(" ")}`;
-			this.$refs.dialog.showModal();
-		},
-		closeDialog(e) {
-			if (e.target === this.$refs.dialogClose || e.target === this.$refs.dialog) {
-				this.$refs.dialog.close();
-			}
-		},
-		checkNeedEnabled(e) {
-			if (e.target.value != "") {
-				let id = e.target.getAttribute("target");
-				if (!document.getElementById(id).checked) {
-					document.getElementById(id).click();
-				}
-			}
-		},
-		toggleOption(e) {
-			if (e.target.checked) {
-				let conflicts = e.target.getAttribute("conflicts");
-				if (conflicts) {
-					if (document.getElementById(conflicts).checked) {
-						document.getElementById(conflicts).click();
-					}
-				}
-				let depends = e.target.getAttribute("depends");
-				if (depends) {
-					if (!document.getElementById(depends).checked) {
-						document.getElementById(depends).click();
-					}
-				}
-			}
-		},
-		copyToClipboard(text, button) {
-			navigator.clipboard.writeText(text).then(
-				() => {
-					button.textContent = "Copied!";
-					setTimeout(() => {
-						button.textContent = "Copy";
-					}, 1000);
-				},
-				(err) => {
-					console.error("Could not copy to clipboard:", err);
-				},
-			);
-		},
-	},
-};
-</script>
-
-<template>
-	<div class="container">
-		<div class="grid">
-			<div class="form-group" v-for="item in items">
-				<div class="form-check u-mb10">
-					<input
-						@change="toggleOption"
-						type="checkbox"
-						class="form-check-input"
-						v-model="item.selected"
-						:value="item.value"
-						:id="item.id"
-						:conflicts="item.conflicts"
-						:depends="item.depends"
-					/>
-					<label :for="item.id">{{ item.id }}</label>
-				</div>
-				<template v-if="item.textField || item.selectField">
-					<label class="form-label" :for="'input-' + item.id">{{ item.desc }}</label>
-				</template>
-				<template v-else>
-					<p>{{ item.desc }}</p>
-				</template>
-				<div v-if="item.textField">
-					<input
-						@change="checkNeedEnabled"
-						type="text"
-						class="form-control"
-						v-model="item.text"
-						:target="item.id"
-						:id="'input-' + item.id"
-						:type="'+item.type+'"
-					/>
-				</div>
-				<div v-if="item.selectField">
-					<select class="form-select" v-model="item.text" :id="'input-' + item.id">
-						<option v-for="language in languages" :value="language.value" :key="language.value">
-							{{ language.text }}
-						</option>
-					</select>
-				</div>
-			</div>
-		</div>
-		<div class="u-text-center u-mb10">
-			<button @click="generateString" class="form-submit" type="button">Submit</button>
-		</div>
-		<dialog ref="dialog" class="modal" @click="closeDialog">
-			<button class="modal-close" @click="closeDialog" type="button" ref="dialogClose">
-				Close
-			</button>
-			<div ref="dialogContent" class="modal-content">
-				<h1 class="modal-heading">Installation instructions</h1>
-				<p class="u-mb10">
-					Log in to your server as root, either directly or via SSH:
-					<code>ssh root@your.server</code> and download the installation script:
-				</p>
-				<div class="u-pos-relative">
-					<input
-						type="text"
-						class="form-control u-monospace u-mb10"
-						v-model="hestia_wget"
-						readonly
-					/>
-					<button
-						class="button-positioned"
-						@click="copyToClipboard(hestia_wget, $event.target)"
-						type="button"
-						title="Copy to Clipboard"
-					>
-						Copy
-					</button>
-				</div>
-				<p class="u-mb10">Then run the following command:</p>
-				<div class="u-pos-relative">
-					<textarea class="form-control u-min-height100" v-model="installStr" readonly />
-					<button
-						class="button-positioned"
-						@click="copyToClipboard(installStr, $event.target)"
-						type="button"
-						title="Copy to Clipboard"
-					>
-						Copy
-					</button>
-				</div>
-			</div>
-		</dialog>
-	</div>
-</template>
-
-<style scoped>
-.container {
-	margin: 0px auto;
-	max-width: 1152px;
-}
-.grid {
-	display: grid;
-	grid-gap: 20px;
-	margin-top: 30px;
-	margin-bottom: 30px;
-
-	@media (min-width: 640px) {
-		grid-template-columns: 1fr 1fr;
-	}
-
-	@media (min-width: 960px) {
-		grid-template-columns: 1fr 1fr 1fr;
-	}
-}
-.form-group {
-	font-size: 0.9em;
-	border-radius: 10px;
-	padding: 15px 20px;
-	background-color: var(--vp-c-bg-alt);
-}
-.form-label {
-	display: inline-block;
-	margin-left: 2px;
-	padding-bottom: 5px;
-	text-transform: capitalize;
-}
-.form-control {
-	font-size: 0.9em;
-	border: 1px solid var(--vp-c-border);
-	border-radius: 4px;
-	background-color: var(--vp-c-bg);
-	width: 100%;
-	padding: 5px 10px;
-
-	&:hover {
-		border-color: var(--vp-c-border-hover);
-	}
-
-	&:focus {
-		border-color: var(--vp-c-brand);
-	}
-}
-.form-select {
-	appearance: auto;
-	font-size: 0.9em;
-	border: 1px solid var(--vp-c-border);
-	border-radius: 4px;
-	background-color: var(--vp-c-bg);
-	padding: 5px 10px;
-	width: 100%;
-
-	&:hover {
-		border-color: var(--vp-c-border-hover);
-	}
-
-	&:focus {
-		border-color: var(--vp-c-brand);
-	}
-}
-.form-check {
-	position: relative;
-	padding-left: 20px;
-	margin-left: 3px;
-	min-height: 24px;
-
-	& label {
-		font-weight: 600;
-	}
-}
-.form-check-input {
-	position: absolute;
-	margin-top: 5px;
-	margin-left: -20px;
-}
-.form-submit {
-	border: 1px solid transparent;
-	display: inline-block;
-	font-weight: 600;
-	transition:
-		color 0.25s,
-		border-color 0.25s,
-		background-color 0.25s;
-	border-radius: 20px;
-	font-size: 16px;
-	padding: 10px 20px;
-	background-color: var(--vp-button-brand-bg);
-	border-color: var(--vp-button-brand-border);
-	color: var(--vp-button-brand-text);
-
-	&:hover {
-		background-color: var(--vp-button-brand-hover-bg);
-		border-color: var(--vp-button-brand-hover-border);
-		color: var(--vp-button-brand-hover-text);
-	}
-
-	&:active {
-		background-color: var(--vp-button-brand-active-bg);
-		border-color: var(--vp-button-brand-active-border);
-		color: var(--vp-button-brand-active-text);
-	}
-}
-.button-positioned {
-	position: absolute;
-	right: 1px;
-	top: 1px;
-	border-top-right-radius: 3px;
-	border-bottom-right-radius: 3px;
-	color: var(--vp-c-brand);
-	font-weight: 600;
-	padding: 6px 10px;
-	background-color: var(--vp-c-bg);
-}
-.modal {
-	position: fixed;
-	border-radius: 10px;
-	border: 1px solid var(--vp-c-border);
-	box-shadow: 0 8px 40px 0 rgb(0 0 0 / 35%);
-	padding: 0;
-
-	&::backdrop {
-		background-color: rgb(0 0 0 / 50%);
-	}
-}
-.modal-close {
-	position: absolute;
-	top: 10px;
-	right: 15px;
-	font-weight: 600;
-	color: var(--vp-c-brand);
-}
-.modal-content {
-	padding: 30px;
-}
-.modal-heading {
-	font-weight: 600;
-	font-size: 1.3em;
-	text-align: center;
-	margin-bottom: 15px;
-}
-code {
-	background-color: var(--vp-c-bg-alt);
-	border-radius: 3px;
-	padding: 2px 5px;
-}
-.u-mb10 {
-	margin-bottom: 10px !important;
-}
-.u-min-height100 {
-	min-height: 100px;
-}
-.u-text-center {
-	text-align: center !important;
-}
-.u-monospace {
-	font-family: monospace !important;
-}
-.u-pos-relative {
-	position: relative !important;
-}
-</style>

+ 0 - 29
docs/.vitepress/theme/components/InstallOptionsSection.vue

@@ -1,29 +0,0 @@
-<template>
-	<form class="InstallForm" id="form">
-		<div class="InstallOptionsSection">
-			<slot name="list" />
-		</div>
-		<cite
-			>Based on: <a href="https://github.com/gabizz/hestiacp-scriptline-generator">@gabizz</a> and
-			<a href="https://github.com/turbopixel/HestiaCP-Command-Creator">@turbopixel</a></cite
-		>
-	</form>
-</template>
-
-<style scoped>
-.InstallForm {
-	margin: 0.55em 0;
-	padding: 0 1em;
-	line-height: 1.5;
-}
-cite {
-	font-size: small;
-	margin: 0.55em 0;
-	display: block;
-	text-align: center;
-
-	& a {
-		color: var(--vp-c-txt-1) !important;
-	}
-}
-</style>

+ 0 - 17
docs/.vitepress/theme/components/InstallPage.vue

@@ -8,21 +8,4 @@
 .InstallPage {
 	line-height: 1.5;
 }
-.InstallPage :deep(.container) {
-	display: flex;
-	flex-direction: column;
-	margin: 0 auto;
-	max-width: 1152px;
-}
-
-.InstallPage :deep(a) {
-	font-weight: 500;
-	color: var(--vp-c-brand);
-	text-decoration-style: dotted;
-	transition: color 0.25s;
-}
-
-.InstallPage :deep(a:hover) {
-	color: var(--vp-c-brand-dark);
-}
 </style>

+ 298 - 0
docs/.vitepress/theme/components/InstallScriptGenerator.vue

@@ -0,0 +1,298 @@
+<template>
+	<div class="container">
+		<div class="output-card">
+			<h2 class="u-text-center">Installation instructions</h2>
+			<p class="u-mb10">
+				Log in to your server e.g.
+				<code>ssh root@your.server</code> and download the installation script:
+			</p>
+			<div class="u-pos-relative u-mb10">
+				<input
+					type="text"
+					class="form-control u-monospace"
+					readonly
+					value="wget https://raw.githubusercontent.com/hestiacp/hestiacp/release/install/hst-install.sh"
+				/>
+				<button
+					type="button"
+					class="button-positioned"
+					@click="
+						copyToClipboard(
+							'wget https://raw.githubusercontent.com/hestiacp/hestiacp/release/install/hst-install.sh',
+							$event.target,
+						)
+					"
+					title="Copy to Clipboard"
+				>
+					Copy
+				</button>
+			</div>
+			<p class="u-mb10">
+				Check you are running as the <code>root</code> user, configure the options you want below,
+				then run:
+			</p>
+			<div class="u-pos-relative u-mb10">
+				<input type="text" class="form-control u-monospace" readonly :value="installCommand" />
+				<button
+					type="button"
+					class="button-positioned"
+					@click="copyToClipboard(installCommand, $event.target)"
+					title="Copy to Clipboard"
+				>
+					Copy
+				</button>
+			</div>
+		</div>
+		<h2 class="u-text-center">Configure options</h2>
+		<ul class="option-list">
+			<li
+				v-for="option in options"
+				:key="option.flag"
+				:class="{
+					'option-item': true,
+					'is-active': selectedOptions[option.flag].enabled,
+					'is-clickable': !option.type || !selectedOptions[option.flag].enabled,
+				}"
+				@click="toggleOption(option)"
+			>
+				<div class="form-check u-mb10">
+					<input
+						type="checkbox"
+						class="form-check-input"
+						:id="option.flag"
+						v-model="selectedOptions[option.flag].enabled"
+					/>
+					<label :for="option.flag" @click.stop>{{ option.label }}</label>
+				</div>
+				<div v-if="selectedOptions[option.flag].enabled">
+					<p v-if="!option.type">
+						{{ option.description }}
+					</p>
+					<label v-else class="form-label" :for="`${option.flag}-input`">
+						{{ option.description }}
+					</label>
+					<input
+						v-if="option.type === 'text'"
+						class="form-control"
+						type="text"
+						:id="`${option.flag}-input`"
+						v-model="selectedOptions[option.flag].value"
+					/>
+					<select
+						v-if="option.type === 'select'"
+						class="form-select"
+						:id="`${option.flag}-input`"
+						v-model="selectedOptions[option.flag].value"
+					>
+						<option v-for="opt in option.options" :key="opt.value" :value="opt.value">
+							{{ opt.label }}
+						</option>
+					</select>
+				</div>
+				<div v-else>
+					<p>{{ option.description }}</p>
+				</div>
+			</li>
+		</ul>
+	</div>
+</template>
+
+<script setup>
+import { ref, watchEffect } from "vue";
+
+const { options } = defineProps({
+	options: {
+		type: Array,
+		required: true,
+	},
+});
+
+// Initialize selectedOptions with default values
+const selectedOptions = ref({});
+options.forEach((option) => {
+	selectedOptions.value[option.flag] = {
+		enabled: option.default === "yes",
+		value: option.default !== "yes" && option.default !== "no" ? option.default : null,
+	};
+});
+
+// Handle clicking the entire option "card"
+const toggleOption = (option) => {
+	// Only toggle if option is a standard checkbox, or the option is unchecked
+	if (!option.type || !selectedOptions.value[option.flag].enabled) {
+		selectedOptions.value[option.flag].enabled = !selectedOptions.value[option.flag].enabled;
+	}
+};
+
+// Copy command to clipboard
+const copyToClipboard = (text, button) => {
+	navigator.clipboard.writeText(text).then(
+		() => {
+			button.textContent = "Copied!";
+			setTimeout(() => {
+				button.textContent = "Copy";
+			}, 1000);
+		},
+		(err) => {
+			console.error("Could not copy to clipboard:", err);
+		},
+	);
+};
+
+// Build the install command
+const installCommand = ref("bash hst-install.sh");
+watchEffect(() => {
+	let cmd = "bash hst-install.sh";
+	for (const [key, { enabled, value }] of Object.entries(selectedOptions.value)) {
+		const opt = options.find((o) => o.flag === key);
+
+		if (!opt.type || opt.type === "checkbox") {
+			if (enabled !== (opt.default === "yes")) {
+				cmd += ` --${key}=${enabled ? "yes" : "no"}`;
+			}
+		} else if (enabled && value !== opt.default) {
+			cmd += ` --${key}=${value}`;
+		}
+	}
+	installCommand.value = cmd;
+});
+</script>
+
+<style scoped>
+h2 {
+	font-size: 24px;
+	font-weight: 600;
+	margin-bottom: 25px;
+}
+.container {
+	display: flex;
+	flex-direction: column;
+	margin: 0 auto;
+	max-width: 1152px;
+}
+.output-card {
+	background-color: var(--vp-c-bg-alt);
+	border-radius: 10px;
+	padding: 30px 40px;
+	margin-top: 40px;
+	margin-bottom: 40px;
+
+	& .form-control {
+		padding-right: 53px;
+	}
+}
+.option-list {
+	display: grid;
+	grid-gap: 20px;
+	margin-bottom: 50px;
+
+	@media (min-width: 640px) {
+		grid-template-columns: 1fr 1fr;
+	}
+
+	@media (min-width: 960px) {
+		grid-template-columns: 1fr 1fr 1fr;
+	}
+}
+.option-item {
+	font-size: 0.9em;
+	border-radius: 10px;
+	border: 2px solid transparent;
+	padding: 15px 20px;
+	background-color: var(--vp-c-bg-alt);
+	transition: border-color 0.2s;
+
+	&:hover {
+		border-color: var(--vp-button-brand-hover-bg);
+	}
+
+	&.is-active {
+		border-color: var(--vp-button-brand-active-bg);
+	}
+}
+.form-label {
+	display: inline-block;
+	padding-bottom: 5px;
+}
+.form-control {
+	font-size: 0.9em;
+	border: 1px solid var(--vp-c-border);
+	border-radius: 4px;
+	background-color: var(--vp-c-bg);
+	width: 100%;
+	padding: 5px 10px;
+
+	&:hover {
+		border-color: var(--vp-c-border-hover);
+	}
+
+	&:focus {
+		border-color: var(--vp-c-brand);
+	}
+}
+.form-select {
+	appearance: auto;
+	font-size: 0.9em;
+	border: 1px solid var(--vp-c-border);
+	border-radius: 4px;
+	background-color: var(--vp-c-bg);
+	padding: 6px;
+	width: 100%;
+
+	&:hover {
+		border-color: var(--vp-c-border-hover);
+	}
+
+	&:focus {
+		border-color: var(--vp-c-brand);
+	}
+}
+.form-check {
+	position: relative;
+	padding-left: 20px;
+	margin-left: 3px;
+	min-height: 24px;
+
+	& label {
+		font-weight: 600;
+		display: block;
+
+		&:hover {
+			cursor: pointer;
+		}
+	}
+}
+.form-check-input {
+	cursor: pointer;
+	position: absolute;
+	margin-top: 5px;
+	margin-left: -20px;
+}
+.button-positioned {
+	position: absolute;
+	top: 1px;
+	right: 1px;
+	bottom: 1px;
+	border-top-right-radius: 3px;
+	border-bottom-right-radius: 3px;
+	color: var(--vp-c-brand);
+	font-weight: 600;
+	padding: 6px 10px;
+	background-color: var(--vp-c-bg);
+}
+.u-mb10 {
+	margin-bottom: 10px !important;
+}
+.u-text-center {
+	text-align: center !important;
+}
+.u-monospace {
+	font-family: monospace !important;
+}
+.u-pos-relative {
+	position: relative !important;
+}
+.is-clickable {
+	cursor: pointer;
+}
+</style>

+ 5 - 0
docs/.vitepress/theme/components/PageHeader.vue

@@ -12,6 +12,11 @@
 </template>
 
 <style scoped>
+.container {
+	display: flex;
+	margin: 0 auto;
+	max-width: 1152px;
+}
 .PageHeader {
 	padding: 0 24px;
 	background-color: var(--vp-c-bg-alt);

+ 0 - 44
docs/_data/languages.js

@@ -1,44 +0,0 @@
-export const languages = [
-	{ text: 'Albanian', value: 'sq' },
-	{ text: 'Arabic', value: 'ar' },
-	{ text: 'Armenian', value: 'hy' },
-	{ text: 'Azerbaijani', value: 'az' },
-	{ text: 'Bengali', value: 'bn' },
-	{ text: 'Bosnian', value: 'bs' },
-	{ text: 'Bulgarian', value: 'bg' },
-	{ text: 'Catalan', value: 'ca' },
-	{ text: 'Croatian', value: 'hr' },
-	{ text: 'Czech', value: 'cs' },
-	{ text: 'Danish', value: 'da' },
-	{ text: 'Dutch', value: 'nl' },
-	{ text: 'English', value: 'en' },
-	{ text: 'Finnish', value: 'fi' },
-	{ text: 'French', value: 'fr' },
-	{ text: 'Georgian', value: 'ka' },
-	{ text: 'German', value: 'de' },
-	{ text: 'Greek', value: 'el' },
-	{ text: 'Hungarian', value: 'hu' },
-	{ text: 'Indonesian', value: 'id' },
-	{ text: 'Italian', value: 'it' },
-	{ text: 'Japanese', value: 'ja' },
-	{ text: 'Korean', value: 'ko' },
-	{ text: 'Kurdish Sorani', value: 'ku' },
-	{ text: 'Norwegain', value: 'no' },
-	{ text: 'Persian', value: 'fa' },
-	{ text: 'Polish', value: 'pl' },
-	{ text: 'Portuguese', value: 'pt' },
-	{ text: 'Portuguese (Brasil)', value: 'pt-br' },
-	{ text: 'Romanian', value: 'ro' },
-	{ text: 'Russian', value: 'ru' },
-	{ text: 'Serbian', value: 'sr' },
-	{ text: 'Simplified Chinese (China)', value: 'zh-cn' },
-	{ text: 'Slovak', value: 'sk' },
-	{ text: 'Spanish', value: 'es' },
-	{ text: 'Swedish', value: 'sv' },
-	{ text: 'Thai', value: 'th' },
-	{ text: 'Traditional Chinese (Taiwan)', value: 'zh-tw' },
-	{ text: 'Turkish', value: 'tr' },
-	{ text: 'Ukrainian', value: 'uk' },
-	{ text: 'Urdu', value: 'ur' },
-	{ text: 'Vietnamese', value: 'vi' },
-];

+ 159 - 136
docs/_data/options.js

@@ -1,178 +1,201 @@
 export const options = [
 	{
-		name: ' --port',
-		id: 'port',
-		param: '--port',
-		desc: 'Change Hestia Port',
-		selected: true,
-		text: '8083',
-		textField: true,
-	},
-	{
-		name: ' --lang',
-		id: 'language',
-		param: '--lang',
-		desc: 'ISO 639-1 codes',
-		selected: true,
-		default: 'en',
-		selectField: true,
-		text: 'en',
-	},
-	{
-		name: ' --hostname',
-		id: 'hostname',
-		param: '--hostname',
-		desc: 'Set hostname',
-		selected: false,
-		text: '',
-		textField: true,
+		flag: 'port',
+		label: 'Port',
+		description: 'Change the port Hestia uses.',
+		type: 'text',
+		default: '8083',
 	},
 	{
-		name: ' --email',
-		id: 'email',
-		param: '--email',
-		desc: 'Set admin email',
-		selected: false,
-		text: '',
-		textField: true,
+		flag: 'lang',
+		label: 'Language',
+		description: 'Change the ISO 639-1 language code.',
+		type: 'select',
+		default: 'en',
+		options: [
+			{ label: 'Albanian', value: 'sq' },
+			{ label: 'Arabic', value: 'ar' },
+			{ label: 'Armenian', value: 'hy' },
+			{ label: 'Azerbaijani', value: 'az' },
+			{ label: 'Bengali', value: 'bn' },
+			{ label: 'Bosnian', value: 'bs' },
+			{ label: 'Bulgarian', value: 'bg' },
+			{ label: 'Catalan', value: 'ca' },
+			{ label: 'Croatian', value: 'hr' },
+			{ label: 'Czech', value: 'cs' },
+			{ label: 'Danish', value: 'da' },
+			{ label: 'Dutch', value: 'nl' },
+			{ label: 'English', value: 'en' },
+			{ label: 'Finnish', value: 'fi' },
+			{ label: 'French', value: 'fr' },
+			{ label: 'Georgian', value: 'ka' },
+			{ label: 'German', value: 'de' },
+			{ label: 'Greek', value: 'el' },
+			{ label: 'Hungarian', value: 'hu' },
+			{ label: 'Indonesian', value: 'id' },
+			{ label: 'Italian', value: 'it' },
+			{ label: 'Japanese', value: 'ja' },
+			{ label: 'Korean', value: 'ko' },
+			{ label: 'Kurdish Sorani', value: 'ku' },
+			{ label: 'Norwegian', value: 'no' },
+			{ label: 'Persian', value: 'fa' },
+			{ label: 'Polish', value: 'pl' },
+			{ label: 'Portuguese', value: 'pt' },
+			{ label: 'Portuguese (Brasil)', value: 'pt-br' },
+			{ label: 'Romanian', value: 'ro' },
+			{ label: 'Russian', value: 'ru' },
+			{ label: 'Serbian', value: 'sr' },
+			{ label: 'Simplified Chinese (China)', value: 'zh-cn' },
+			{ label: 'Slovak', value: 'sk' },
+			{ label: 'Spanish', value: 'es' },
+			{ label: 'Swedish', value: 'sv' },
+			{ label: 'Thai', value: 'th' },
+			{ label: 'Traditional Chinese (Taiwan)', value: 'zh-tw' },
+			{ label: 'Turkish', value: 'tr' },
+			{ label: 'Ukrainian', value: 'uk' },
+			{ label: 'Urdu', value: 'ur' },
+			{ label: 'Vietnamese', value: 'vi' },
+		],
+	},
+	{
+		flag: 'hostname',
+		label: 'Hostname',
+		description: 'Set a custom hostname.',
+		type: 'text',
+		default: '',
+	},
+	{
+		flag: 'email',
+		label: 'Email',
+		description: 'Set the admin account email.',
+		type: 'text',
+		default: '',
+	},
+	{
+		flag: 'password',
+		label: 'Password',
+		description: 'Set the admin account password.',
+		type: 'text',
+		default: '',
+	},
+	{
+		flag: 'apache',
+		label: 'Apache',
+		description: 'Web server with htaccess support.',
+		default: 'yes',
+	},
+	{
+		flag: 'phpfpm',
+		label: 'PHP-FPM',
+		description: 'Process manager for executing PHP scripts.',
+		default: 'no',
 	},
 	{
-		name: ' --password',
-		id: 'password',
-		param: '--password',
-		desc: 'Set admin password',
-		selected: false,
-		text: '',
-		textField: true,
+		flag: 'multiphp',
+		label: 'MultiPHP',
+		description: 'Allows installing multiple PHP versions.',
+		default: 'yes',
 	},
 	{
-		name: ' --apache',
-		id: 'apache',
-		param: '--apache',
-		desc: 'Web server with htaccess support.',
-		selected: true,
+		flag: 'vsftpd',
+		label: 'VSFTPD',
+		description: 'Lightweight, minimalist and secure FTP server.',
+		default: 'yes',
 	},
-	{ name: ' --phpfpm', id: 'phpfpm', param: '--phpfpm', desc: 'Install PHP-FPM.', selected: true },
 	{
-		name: ' --multiphp',
-		id: 'multiphp',
-		param: '--multiphp',
-		desc: 'Allows installing multiple PHP versions.',
-		selected: true,
+		flag: 'proftpd',
+		label: 'ProFTPD',
+		description: 'Advanced, modular FTP server that supports LDAP.',
+		default: 'no',
 	},
 	{
-		name: ' --vsftpd',
-		id: 'vsftpd',
-		param: '--vsftpd',
-		desc: 'Lightweight, minimalist and secure FTP server.',
-		selected: true,
-		conflicts: 'proftpd',
+		flag: 'named',
+		label: 'BIND',
+		description: 'Custom DNS name server.',
+		default: 'yes',
 	},
 	{
-		name: ' --proftpd',
-		id: 'proftpd',
-		param: '--proftpd',
-		desc: 'Advanced, modular FTP server that supports LDAP.',
-		selected: false,
-		conflicts: 'vsftpd',
+		flag: 'mysql',
+		label: 'MariaDB',
+		description: 'Fork of MySQL with additional features and improvements.',
+		default: 'yes',
 	},
 	{
-		name: ' --named',
-		id: 'named',
-		param: '--named',
-		desc: 'Custom DNS name server.',
-		selected: true,
+		flag: 'mysql8',
+		label: 'MySQL 8',
+		description: 'Open-source relational database management system.',
+		default: 'no',
 	},
 	{
-		name: ' --mariadb',
-		id: 'mariadb',
-		param: '--mariadb',
-		desc: 'Fork of MySQL with additional features and improvements.',
-		selected: true,
-		conflicts: 'mysql8',
+		flag: 'postgresql',
+		label: 'PostgreSQL',
+		description: 'Open-source relational database management system.',
+		default: 'no',
 	},
 	{
-		name: ' --mysql8',
-		id: 'mysql8',
-		param: '--mysql8',
-		desc: 'Open-source database system.',
-		selected: false,
-		conflicts: 'mariadb',
+		flag: 'exim',
+		label: 'Exim',
+		description: 'Allows sending emails from web-mail or via SMTP.',
+		default: 'yes',
 	},
 	{
-		name: ' --postgresql',
-		id: 'postgresql',
-		param: '--postgresql',
-		desc: 'Open-source database system.',
-		selected: false,
+		flag: 'dovecot',
+		label: 'Dovecot',
+		description: 'Receive emails and connect with email clients via IMAP/POP3.',
+		default: 'yes',
 	},
 	{
-		name: ' --exim',
-		id: 'exim',
-		param: '--exim',
-		desc: 'Allows sending emails from webmail or via SMTP.',
-		selected: true,
+		flag: 'sieve',
+		label: 'Sieve',
+		description: 'Language for managing your own custom email filters.',
+		default: 'no',
 	},
 	{
-		name: ' --dovecot',
-		id: 'dovecot',
-		param: '--dovecot',
-		desc: 'Receive emails and connect with email clients via IMAP/POP3.',
-		selected: true,
-		depends: 'exim',
+		flag: 'clamav',
+		label: 'ClamAV',
+		description: 'Scans your email inbox for viruses.',
+		default: 'yes',
 	},
 	{
-		name: ' --sieve',
-		id: 'sieve',
-		param: '--sieve',
-		desc: 'Manage your own custom email filters.',
-		selected: false,
-		depends: 'dovecot',
+		flag: 'spamassassin',
+		label: 'SpamAssassin',
+		description: 'Filter out spam emails from your inbox.',
+		default: 'yes',
 	},
 	{
-		name: ' --clamav',
-		id: 'clamav',
-		param: '--clamav',
-		desc: 'Scans your email inbox for viruses.',
-		selected: true,
-		depends: 'exim',
+		flag: 'iptables',
+		label: 'iptables',
+		description: 'Allows firewall rule management within Hestia.',
+		default: 'yes',
 	},
 	{
-		name: ' --spamassassin',
-		id: 'spamassassin',
-		param: '--spamassassin',
-		desc: 'Filter out spam emails from your inbox.',
-		selected: true,
-		depends: 'exim',
+		flag: 'fail2ban',
+		label: 'Fail2Ban',
+		description: 'Provides brute force protection for SSH, email, FTP and databases.',
+		default: 'yes',
 	},
 	{
-		name: ' --iptables',
-		id: 'iptables',
-		param: '--iptables',
-		desc: 'Manage your firewall within Hestia.',
-		selected: true,
+		flag: 'quota',
+		label: 'Filesystem quota',
+		description: 'Use hard disk space limits on user packages.',
+		default: 'no',
 	},
 	{
-		name: ' --fail2ban',
-		id: 'fail2ban',
-		param: '--fail2ban',
-		desc: 'Provides Bruteforce protection for SSH, Email, FTP, database.',
-		selected: true,
+		flag: 'api',
+		label: 'Hestia API',
+		description: "Enable Hestia's internal API.",
+		default: 'yes',
 	},
 	{
-		name: ' --quota',
-		id: 'quota',
-		param: '--quota',
-		desc: 'Use hard disk space limits on user packages.',
-		selected: false,
+		flag: 'interactive',
+		label: 'Interactive install',
+		description: 'Enable interactive install.',
+		default: 'yes',
 	},
-	{ name: ' --api', id: 'api', param: '--api', desc: 'Activate API.', selected: true },
 	{
-		name: ' --interactive',
-		id: 'interactive',
-		param: '--interactive',
-		desc: 'Interactive install.',
-		selected: true,
+		flag: 'force',
+		label: 'Force installation',
+		description: 'Force the installation.',
+		default: 'no',
 	},
-	{ name: ' --force', id: 'force', param: '--force', desc: 'Force installation.', selected: false },
 ];

+ 10 - 8
docs/_data/team.js

@@ -21,6 +21,10 @@ export const projectManagers = [
 	},
 ];
 
+const globeIcon = {
+	svg: '<svg role="img" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><title>Website</title><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /></svg>',
+};
+
 /** @type {import("vitepress").DefaultTheme.TeamMember[]} */
 export const teamMembers = [
 	{
@@ -31,13 +35,8 @@ export const teamMembers = [
 		orgLink: 'https://prosomo.com',
 		links: [
 			{ icon: 'github', link: 'https://github.com/jakobbouchard' },
-			{ icon: 'linkedin', link: 'https://linkedin.com/in/jakobbouchard' },
-			{
-				icon: {
-					svg: '<svg role="img" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><title>Website</title><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /></svg>',
-				},
-				link: 'https://jakobbouchard.dev',
-			},
+			{ icon: 'linkedin', link: 'https://www.linkedin.com/in/jakobbouchard' },
+			{ icon: globeIcon, link: 'https://jakobbouchard.dev' },
 		],
 	},
 	{
@@ -64,6 +63,9 @@ export const teamMembers = [
 	{
 		avatar: 'https://www.github.com/AlecRust.png',
 		name: 'Alec Rust 🇬🇧',
-		links: [{ icon: 'github', link: 'https://github.com/AlecRust' }],
+		links: [
+			{ icon: 'github', link: 'https://github.com/AlecRust' },
+			{ icon: globeIcon, link: 'https://www.alecrust.com/' },
+		],
 	},
 ];

+ 4 - 4
docs/docs/introduction/getting-started.md

@@ -82,11 +82,11 @@ To choose what software gets installed, you can provide flags to the installatio
 
 ```bash
 -a, --apache Install Apache [yes | no] default: yes
--w, --phpfpm Install PHP-FPM [yes | no] default: yes
--o, --multiphp Install Multi-PHP [yes | no] default: no
--v, --vsftpd Install Vsftpd [yes | no] default: yes
+-w, --phpfpm Install PHP-FPM [yes | no] default: no
+-o, --multiphp Install MultiPHP [yes | no] default: yes
+-v, --vsftpd Install VSFTPD [yes | no] default: yes
 -j, --proftpd Install ProFTPD [yes | no] default: no
--k, --named Install Bind [yes | no] default: yes
+-k, --named Install BIND [yes | no] default: yes
 -m, --mysql Install MariaDB [yes | no] default: yes
 -M, --mysql8 Install Mysql8 [yes | no] default: no
 -g, --postgresql Install PostgreSQL [yes | no] default: no

+ 2 - 8
docs/install.md

@@ -5,19 +5,13 @@ title: Install
 
 <script setup>
   import PageHeader from "./.vitepress/theme/components/PageHeader.vue";
-  import InstallOptions from "./.vitepress/theme/components/InstallOptions.vue";
-  import InstallOptionsSection from "./.vitepress/theme/components/InstallOptionsSection.vue";
+  import InstallScriptGenerator from "./.vitepress/theme/components/InstallScriptGenerator.vue";
   import { options } from "./_data/options";
-  import { languages } from "./_data/languages";
 </script>
 
 <InstallPage>
   <PageHeader>
     <template #title>Install</template>
   </PageHeader>
-  <InstallOptionsSection>
-    <template #list>
-      <InstallOptions :items="options" :languages="languages"></InstallOptions>
-    </template>
-  </InstallOptionsSection>
+  <InstallScriptGenerator :options="options"></InstallScriptGenerator>
 </InstallPage>

+ 1 - 1
web/locale/languages.json

@@ -41,7 +41,7 @@
 	"pl_locale": ["Polski"],
 	"pl": ["Polish"],
 	"no_locale": ["Norsk"],
-	"no": ["Norwegain"],
+	"no": ["Norwegian"],
 	"nl_locale": ["Nederlands"],
 	"nl": ["Dutch"],
 	"ko_locale": ["\ud55c\uad6d\uc5b4"],