Compare commits

...

5 Commits

Author SHA1 Message Date
davalanche
a11755de5d
Update cloudflared.json (#266)
Some checks are pending
Deploy Next.js site to Pages / build (push) Waiting to run
Deploy Next.js site to Pages / deploy (push) Blocked by required conditions
Did I just break https://community-scripts.github.io/ProxmoxVE/scripts?
2024-11-15 18:46:37 +01:00
Bram Suurd
10fe784e1c
Optimize website json-editor page and components (#265)
* Update mariadb.json

* Update vaultwarden.json

* Update vaultwarden.json

* Update keycloak.json

* Update json/keycloak.json

Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com>

* Update mariadb.json

Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com>

* Add canonical link to layout for improved SEO and page indexing

* Fix image source fallback for script logos to use a consistent relative path

* Fix image source for script logos across components to consistently use the "/ProxmoxVE/logo.png" path

* Update image source for script logos to use basePath for consistent paths across all components

* Fix image source for script logos to ensure leading slash is consistent for all components' paths

* Add JSON generator component with validation and UI elements for managing scripts, categories, and installation methods

* Add calendar and label components; enhance JSON generator with date selection and script path updates for installation methods

* Enhance Alerts component with dynamic colored notes using AlertColors from config for better visibility and consistency

* Remove MultiSelect component

* Update JSON generator: streamline install methods, enhance note type selection, and refine button behavior for better UX

* Refactor AlertColors: unify warning and danger styles for consistency and improved visual hierarchy in alerts

* Enhance JSONGenerator: improve SelectItem layout with color indicators for better visual representation of alert types

* Refactor JSON schema definitions in JSONGenerator: separate InstallMethod and Note schemas for better structure and readability

* Fix JSONGenerator: streamline SelectItem markup and enhance JSON display layout for improved readability and user experience

* Refactor JSON schema handling: move schema definitions to separate file

* Enhance error handling in JSONGenerator: display Zod validation errors on user input for better feedback and debugging

* Export InstallMethodSchema and integrate into JSONGenerator for better validation of install method data input

* Add Categories and Note components to JSONGenerator for better organization and modularity in the JSON editing interface

* Remove unused imports

* Add JSON Editor route to sitemap for improved SEO and navigation

* Refactor JSON Editor components to improve performance with memoization and streamline state updates with useCallback

---------

Co-authored-by: CanbiZ <47820557+MickLesk@users.noreply.github.com>
Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com>
2024-11-15 18:16:19 +01:00
davalanche
f6cc26af3a
Update cloudflared-install.sh (#264)
Added the option to configure cloudflared as a DNS-over-HTTPS (DoH) proxy
2024-11-15 18:15:25 +01:00
CanbiZ
a29ed78ae3
Add Option "check_storage" and "check_container_ressources" (#249)
* Add Option "check_storage" and "check_container_ressources"

* Update misc/build.func

Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com>

* Update misc/build.func

Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com>

* Update misc/build.func

Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com>

* Final Version

- remove locals
- use var_variables
- show storage in percent
- harmonize some texts
- optimize Prompt (Continue anyway)

---------

Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com>
2024-11-15 18:13:14 +01:00
davalanche
f8d302c096
Update mysql.json (#263)
* Updated to reflect PR #123 where the MySQL 8.4 LTS or MySQL 8.0 release can now be be selected.

* Updated the "documentation" and "website" values for good measure.
2024-11-15 17:21:12 +01:00
8 changed files with 355 additions and 249 deletions

View File

@ -10,6 +10,7 @@ import { Category } from "@/lib/types";
import { cn } from "@/lib/utils";
import { z } from "zod";
import { ScriptSchema } from "../_schemas/schemas";
import { memo } from "react";
type Script = z.infer<typeof ScriptSchema>;
@ -21,7 +22,42 @@ type CategoryProps = {
categories: Category[];
};
export default function Categories({
const CategoryTag = memo(({
category,
onRemove
}: {
category: Category;
onRemove: () => void;
}) => (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{category.name}
<button
type="button"
className="ml-1 inline-flex text-blue-400 hover:text-blue-600"
onClick={onRemove}
>
<span className="sr-only">Remove</span>
<svg
className="h-3 w-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</span>
));
CategoryTag.displayName = 'CategoryTag';
function Categories({
script,
setScript,
categories,
@ -40,64 +76,44 @@ export default function Categories({
});
};
const categoryMap = new Map(categories.map(c => [c.id, c]));
return (
<>
<div>
<Label>
Category <span className="text-red-500">*</span>
</Label>
<Select onValueChange={(value) => addCategory(Number(value))}>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category.id} value={category.id.toString()}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
<div
className={cn(
"flex flex-wrap gap-2",
script.categories.length !== 0 && "mt-2",
)}
>
{script.categories.map((categoryId) => {
const category = categories.find((c) => c.id === categoryId);
return category ? (
<span
key={categoryId}
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
>
{category.name}
<button
type="button"
className="ml-1 inline-flex text-blue-400 hover:text-blue-600"
onClick={() => removeCategory(categoryId)}
>
<span className="sr-only">Remove</span>
<svg
className="h-3 w-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</span>
) : null;
})}
</div>
<div>
<Label>
Category <span className="text-red-500">*</span>
</Label>
<Select onValueChange={(value) => addCategory(Number(value))}>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category.id} value={category.id.toString()}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
<div
className={cn(
"flex flex-wrap gap-2",
script.categories.length !== 0 && "mt-2",
)}
>
{script.categories.map((categoryId) => {
const category = categoryMap.get(categoryId);
return category ? (
<CategoryTag
key={categoryId}
category={category}
onRemove={() => removeCategory(categoryId)}
/>
) : null;
})}
</div>
</>
</div>
);
}
export default memo(Categories);

View File

@ -10,6 +10,7 @@ import { PlusCircle, Trash2 } from "lucide-react";
import { Input } from "@/components/ui/input";
import { z } from "zod";
import { InstallMethodSchema, ScriptSchema } from "../_schemas/schemas";
import { memo, useCallback } from "react";
type Script = z.infer<typeof ScriptSchema>;
@ -20,13 +21,13 @@ type InstallMethodProps = {
setZodErrors: (zodErrors: z.ZodError | null) => void;
};
export default function InstallMethod({
function InstallMethod({
script,
setScript,
setIsValid,
setZodErrors,
}: InstallMethodProps) {
const addInstallMethod = () => {
const addInstallMethod = useCallback(() => {
setScript((prev) => {
const method = InstallMethodSchema.parse({
type: "default",
@ -44,9 +45,9 @@ export default function InstallMethod({
install_methods: [...prev.install_methods, method],
};
});
};
}, [setScript]);
const updateInstallMethod = (
const updateInstallMethod = useCallback((
index: number,
key: keyof Script["install_methods"][number],
value: Script["install_methods"][number][keyof Script["install_methods"][number]],
@ -82,14 +83,35 @@ export default function InstallMethod({
}
return updated;
});
};
}, [setScript, setIsValid, setZodErrors]);
const removeInstallMethod = (index: number) => {
const removeInstallMethod = useCallback((index: number) => {
setScript((prev) => ({
...prev,
install_methods: prev.install_methods.filter((_, i) => i !== index),
}));
};
}, [setScript]);
const ResourceInput = memo(({
placeholder,
value,
onChange,
type = "text"
}: {
placeholder: string;
value: string | number | null;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
type?: string;
}) => (
<Input
placeholder={placeholder}
type={type}
value={value || ""}
onChange={onChange}
/>
));
ResourceInput.displayName = 'ResourceInput';
return (
<>
@ -109,33 +131,33 @@ export default function InstallMethod({
</SelectContent>
</Select>
<div className="flex gap-2">
<Input
<ResourceInput
placeholder="CPU in Cores"
type="number"
value={method.resources.cpu || ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
value={method.resources.cpu}
onChange={(e) =>
updateInstallMethod(index, "resources", {
...method.resources,
cpu: e.target.value ? Number(e.target.value) : null,
})
}
/>
<Input
<ResourceInput
placeholder="RAM in MB"
type="number"
value={method.resources.ram || ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
value={method.resources.ram}
onChange={(e) =>
updateInstallMethod(index, "resources", {
...method.resources,
ram: e.target.value ? Number(e.target.value) : null,
})
}
/>
<Input
<ResourceInput
placeholder="HDD in GB"
type="number"
value={method.resources.hdd || ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
value={method.resources.hdd}
onChange={(e) =>
updateInstallMethod(index, "resources", {
...method.resources,
hdd: e.target.value ? Number(e.target.value) : null,
@ -144,21 +166,21 @@ export default function InstallMethod({
/>
</div>
<div className="flex gap-2">
<Input
<ResourceInput
placeholder="OS"
value={method.resources.os || ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
value={method.resources.os}
onChange={(e) =>
updateInstallMethod(index, "resources", {
...method.resources,
os: e.target.value || null,
})
}
/>
<Input
<ResourceInput
placeholder="Version"
type="number"
value={method.resources.version || ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
value={method.resources.version}
onChange={(e) =>
updateInstallMethod(index, "resources", {
...method.resources,
version: e.target.value ? Number(e.target.value) : null,
@ -168,7 +190,7 @@ export default function InstallMethod({
</div>
<Button
variant="destructive"
size={"sm"}
size="sm"
type="button"
onClick={() => removeInstallMethod(index)}
>
@ -178,7 +200,7 @@ export default function InstallMethod({
))}
<Button
type="button"
size={"sm"}
size="sm"
disabled={script.install_methods.length >= 2}
onClick={addInstallMethod}
>
@ -187,3 +209,5 @@ export default function InstallMethod({
</>
);
}
export default memo(InstallMethod);

View File

@ -12,6 +12,7 @@ import { cn } from "@/lib/utils";
import { PlusCircle, Trash2 } from "lucide-react";
import { z } from "zod";
import { ScriptSchema } from "../_schemas/schemas";
import { memo, useCallback } from "react";
type Script = z.infer<typeof ScriptSchema>;
@ -21,95 +22,98 @@ type NoteProps = {
setIsValid: (isValid: boolean) => void;
setZodErrors: (zodErrors: z.ZodError | null) => void;
};
export default function Note({
function Note({
script,
setScript,
setIsValid,
setZodErrors,
}: NoteProps) {
const addNote = () => {
const newScript: Script = {
const addNote = useCallback(() => {
setScript({
...script,
notes: [...script.notes, { text: "", type: "" }],
};
setScript(newScript);
};
});
}, [script, setScript]);
const updateNote = (
const updateNote = useCallback((
index: number,
key: keyof Script["notes"][number],
value: string,
) => {
const updated: Script = {
...script,
notes: script.notes.map((note: Script["notes"][number], i: number) =>
notes: script.notes.map((note, i) =>
i === index ? { ...note, [key]: value } : note,
),
};
const result = ScriptSchema.safeParse(updated);
setIsValid(result.success);
if (!result.success) {
setZodErrors(result.error);
} else {
setZodErrors(null);
}
setZodErrors(result.success ? null : result.error);
setScript(updated);
};
}, [script, setScript, setIsValid, setZodErrors]);
const removeNote = (index: number) => {
const newScript: Script = {
const removeNote = useCallback((index: number) => {
setScript({
...script,
notes: script.notes.filter((_: Script["notes"][number], i: number) => i !== index),
};
setScript(newScript);
};
notes: script.notes.filter((_, i) => i !== index),
});
}, [script, setScript]);
const NoteItem = memo(({ note, index }: { note: Script["notes"][number], index: number }) => (
<div className="space-y-2 border p-4 rounded">
<Input
placeholder="Note Text"
value={note.text}
onChange={(e) => updateNote(index, "text", e.target.value)}
/>
<Select
value={note.type}
onValueChange={(value) => updateNote(index, "type", value)}
>
<SelectTrigger className="flex-1">
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
{Object.keys(AlertColors).map((type) => (
<SelectItem key={type} value={type}>
<span className="flex items-center gap-2">
{type.charAt(0).toUpperCase() + type.slice(1)}{" "}
<div
className={cn(
"size-4 rounded-full border",
AlertColors[type as keyof typeof AlertColors],
)}
/>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
<Button
size="sm"
variant="destructive"
type="button"
onClick={() => removeNote(index)}
>
<Trash2 className="mr-2 h-4 w-4" /> Remove Note
</Button>
</div>
));
NoteItem.displayName = 'NoteItem';
return (
<>
<h3 className="text-xl font-semibold">Notes</h3>
{script.notes.map((note, index) => (
<div key={index} className="space-y-2 border p-4 rounded">
<Input
placeholder="Note Text"
value={note.text}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateNote(index, "text", e.target.value)}
/>
<Select
value={note.type}
onValueChange={(value: string) => updateNote(index, "type", value)}
>
<SelectTrigger className="flex-1">
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
{Object.keys(AlertColors).map((type) => (
<SelectItem key={type} value={type}>
<span className="flex items-center gap-2">
{type.charAt(0).toUpperCase() + type.slice(1)}{" "}
<div
className={cn(
"size-4 rounded-full border",
AlertColors[type as keyof typeof AlertColors],
)}
/>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
<Button
size={"sm"}
variant="destructive"
type="button"
onClick={() => removeNote(index)}
>
<Trash2 className="mr-2 h-4 w-4" /> Remove Note
</Button>
</div>
<NoteItem key={index} note={note} index={index} />
))}
<Button type="button" size={"sm"} onClick={addNote}>
<Button type="button" size="sm" onClick={addNote}>
<PlusCircle className="mr-2 h-4 w-4" /> Add Note
</Button>
</>
);
}
export default memo(Note);

View File

@ -20,7 +20,7 @@ import { Category } from "@/lib/types";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon, Check, Clipboard } from "lucide-react";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { z } from "zod";
import Categories from "./_components/Categories";
@ -30,66 +30,98 @@ import { ScriptSchema } from "./_schemas/schemas";
type Script = z.infer<typeof ScriptSchema>;
export default function JSONGenerator() {
const [script, setScript] = useState<Script>({
name: "",
slug: "",
categories: [],
date_created: "",
type: "ct",
updateable: false,
privileged: false,
interface_port: null,
documentation: null,
website: null,
logo: null,
description: "",
install_methods: [],
default_credentials: {
username: null,
password: null,
},
notes: [],
});
const [isCopied, setIsCopied] = useState(false);
const initialScript: Script = {
name: "",
slug: "",
categories: [],
date_created: "",
type: "ct",
updateable: false,
privileged: false,
interface_port: null,
documentation: null,
website: null,
logo: null,
description: "",
install_methods: [],
default_credentials: {
username: null,
password: null,
},
notes: [],
};
export default function JSONGenerator() {
const [script, setScript] = useState<Script>(initialScript);
const [isCopied, setIsCopied] = useState(false);
const [isValid, setIsValid] = useState(false);
const [categories, setCategories] = useState<Category[]>([]);
const [zodErrors, setZodErrors] = useState<z.ZodError | null>(null);
useEffect(() => {
fetchCategories()
.then((data) => {
setCategories(data);
})
.then(setCategories)
.catch((error) => console.error("Error fetching categories:", error));
}, []);
const updateScript = (key: keyof Script, value: Script[keyof Script]) => {
const updateScript = useCallback((key: keyof Script, value: Script[keyof Script]) => {
setScript((prev) => {
const updated = { ...prev, [key]: value };
// Update script paths for install methods if `type` or `slug` changed
if (key === "type" || key === "slug") {
updated.install_methods = updated.install_methods.map((method) => ({
...method,
script:
method.type === "alpine"
? `/${updated.type}/alpine-${updated.slug}.sh`
: `/${updated.type}/${updated.slug}.sh`,
script: method.type === "alpine"
? `/${updated.type}/alpine-${updated.slug}.sh`
: `/${updated.type}/${updated.slug}.sh`,
}));
}
const result = ScriptSchema.safeParse(updated);
setIsValid(result.success);
if (!result.success) {
setZodErrors(result.error);
} else {
setZodErrors(null);
}
setZodErrors(result.success ? null : result.error);
return updated;
});
};
}, []);
const handleCopy = useCallback(() => {
navigator.clipboard.writeText(JSON.stringify(script, null, 2));
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
toast.success("Copied metadata to clipboard");
}, [script]);
const handleDateSelect = useCallback((date: Date | undefined) => {
updateScript(
"date_created",
format(date || new Date(), "yyyy-MM-dd")
);
}, [updateScript]);
const formattedDate = useMemo(() =>
script.date_created ? format(script.date_created, "PPP") : undefined,
[script.date_created]
);
const validationAlert = useMemo(() => (
<Alert className={cn("text-black", isValid ? "bg-green-100" : "bg-red-100")}>
<AlertTitle>{isValid ? "Valid JSON" : "Invalid JSON"}</AlertTitle>
<AlertDescription>
{isValid
? "The current JSON is valid according to the schema."
: "The current JSON does not match the required schema."}
</AlertDescription>
{zodErrors && (
<div className="mt-2 space-y-1">
{zodErrors.errors.map((error, index) => (
<AlertDescription key={index} className="p-1 text-red-500">
{error.path.join(".")} - {error.message}
</AlertDescription>
))}
</div>
)}
</Alert>
), [isValid, zodErrors]);
return (
<div className="flex h-screen mt-20">
@ -155,11 +187,7 @@ export default function JSONGenerator() {
!script.date_created && "text-muted-foreground",
)}
>
{script.date_created ? (
format(script.date_created, "PPP")
) : (
<span>Pick a date</span>
)}
{formattedDate || <span>Pick a date</span>}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
@ -167,12 +195,7 @@ export default function JSONGenerator() {
<Calendar
mode="single"
selected={new Date(script.date_created)}
onSelect={(date) =>
updateScript(
"date_created",
format(date || new Date(), "yyyy-MM-dd"),
)
}
onSelect={handleDateSelect}
initialFocus
/>
</PopoverContent>
@ -199,18 +222,14 @@ export default function JSONGenerator() {
<div className="flex items-center space-x-2">
<Switch
checked={script.updateable}
onCheckedChange={(checked) =>
updateScript("updateable", checked)
}
onCheckedChange={(checked) => updateScript("updateable", checked)}
/>
<label>Updateable</label>
</div>
<div className="flex items-center space-x-2">
<Switch
checked={script.privileged}
onCheckedChange={(checked) =>
updateScript("privileged", checked)
}
onCheckedChange={(checked) => updateScript("privileged", checked)}
/>
<label>Privileged</label>
</div>
@ -219,12 +238,7 @@ export default function JSONGenerator() {
placeholder="Interface Port"
type="number"
value={script.interface_port || ""}
onChange={(e) =>
updateScript(
"interface_port",
e.target.value ? Number(e.target.value) : null,
)
}
onChange={(e) => updateScript("interface_port", e.target.value ? Number(e.target.value) : null)}
/>
<div className="flex gap-2">
<Input
@ -235,9 +249,7 @@ export default function JSONGenerator() {
<Input
placeholder="Documentation URL"
value={script.documentation || ""}
onChange={(e) =>
updateScript("documentation", e.target.value || null)
}
onChange={(e) => updateScript("documentation", e.target.value || null)}
/>
</div>
<InstallMethod
@ -250,22 +262,18 @@ export default function JSONGenerator() {
<Input
placeholder="Username"
value={script.default_credentials.username || ""}
onChange={(e) =>
updateScript("default_credentials", {
...script.default_credentials,
username: e.target.value || null,
})
}
onChange={(e) => updateScript("default_credentials", {
...script.default_credentials,
username: e.target.value || null,
})}
/>
<Input
placeholder="Password"
value={script.default_credentials.password || ""}
onChange={(e) =>
updateScript("default_credentials", {
...script.default_credentials,
password: e.target.value || null,
})
}
onChange={(e) => updateScript("default_credentials", {
...script.default_credentials,
password: e.target.value || null,
})}
/>
<Note
script={script}
@ -276,36 +284,13 @@ export default function JSONGenerator() {
</form>
</div>
<div className="w-1/2 p-4 bg-background overflow-y-auto">
<Alert
className={cn("text-black", isValid ? "bg-green-100" : "bg-red-100")}
>
<AlertTitle>{isValid ? "Valid JSON" : "Invalid JSON"}</AlertTitle>
<AlertDescription>
{isValid
? "The current JSON is valid according to the schema."
: "The current JSON does not match the required schema."}
</AlertDescription>
{zodErrors && (
<div className="mt-2 space-y-1">
{zodErrors.errors.map((error, index) => (
<AlertDescription key={index} className="p-1 text-red-500">
{error.path.join(".")} - {error.message}
</AlertDescription>
))}
</div>
)}
</Alert>
{validationAlert}
<div className="relative">
<Button
className="absolute right-2 top-2"
size="icon"
variant="outline"
onClick={() => {
navigator.clipboard.writeText(JSON.stringify(script, null, 2));
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
toast.success("Copied metadata to clipboard");
}}
onClick={handleCopy}
>
{isCopied ? (
<Check className="h-4 w-4" />

View File

@ -28,6 +28,41 @@ $STD apt-get update
$STD apt-get install -y cloudflared
msg_ok "Installed Cloudflared"
read -r -p "Would you like to configure cloudflared as a DNS-over-HTTPS (DoH) proxy? <y/N> " prompt
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
msg_info "Creating Service"
cat <<EOF >/usr/local/etc/cloudflared/config.yml
proxy-dns: true
proxy-dns-address: 0.0.0.0
proxy-dns-port: 53
proxy-dns-max-upstream-conns: 5
proxy-dns-upstream:
- https://1.1.1.1/dns-query
- https://1.0.0.1/dns-query
#- https://8.8.8.8/dns-query
#- https://8.8.4.4/dns-query
#- https://9.9.9.9/dns-query
#- https://149.112.112.112/dns-query
EOF
cat <<EOF >/etc/systemd/system/cloudflared.service
[Unit]
Description=cloudflared DNS-over-HTTPS (DoH) proxy
After=syslog.target network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/cloudflared --config /usr/local/etc/cloudflared/config.yml
Restart=on-failure
RestartSec=10
KillMode=process
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now cloudflared.service
msg_ok "Created Service"
fi
motd_ssh
customize

View File

@ -9,7 +9,7 @@
"updateable": false,
"privileged": false,
"interface_port": null,
"documentation": null,
"documentation": "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/",
"website": "https://www.cloudflare.com/",
"logo": "https://raw.githubusercontent.com/loganmarchione/homelab-svg-assets/main/assets/cloudflare.svg",
"description": "Cloudflared is a command-line tool that allows you to securely access resources on the Cloudflare network, such as websites and APIs, from your local computer. It works by creating a secure tunnel between your computer and the Cloudflare network, allowing you to access resources as if they were on your local network.",
@ -30,5 +30,10 @@
"username": null,
"password": null
},
"notes": []
"notes": [
{
"text": "With an option to configure cloudflared as a DNS-over-HTTPS (DoH) proxy",
"type": "info"
}
]
}

View File

@ -9,8 +9,8 @@
"updateable": true,
"privileged": false,
"interface_port": null,
"documentation": null,
"website": null,
"documentation": "https://dev.mysql.com/doc/",
"website": "https://www.mysql.com/",
"logo": "https://1000logos.net/wp-content/uploads/2020/08/MySQL-Logo.png",
"description": "MySQL is an open-source relational database management system (RDBMS) that uses SQL for managing and manipulating data. It is known for its scalability, reliability, and high performance, making it suitable for small to large-scale applications. Key features include support for ACID transactions, data replication for high availability, and compatibility with various programming languages like Python, PHP, and Java.",
"install_methods": [
@ -34,6 +34,10 @@
{
"text": "Database credentials: `cat mysql.creds`",
"type": "warning"
},
{
"text": "With an option to install the MySQL 8.4 LTS release instead of MySQL 8.0",
"type": "info"
}
]
}

View File

@ -7,6 +7,7 @@ variables() {
# This function sets various color variables using ANSI escape codes for formatting text in the terminal.
color() {
YW=$(echo "\033[33m")
YWB=$(echo "\033[93m")
BL=$(echo "\033[36m")
RD=$(echo "\033[01;31m")
BGN=$(echo "\033[4;92m")
@ -501,6 +502,38 @@ install_script() {
fi
}
check_container_resources() {
# Check actual RAM & Cores
current_ram=$(free -m | awk '/^Mem:/{print $2}')
current_cpu=$(nproc)
# Check whether the current RAM is less than the required RAM or the CPU cores are less than required
if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then
echo -e "\n⚠${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}"
echo -e "${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\n"
exit 1
else
echo -e ""
fi
}
check_container_storage() {
# Check if the /boot partition is more than 80% full
total_size=$(df /boot --output=size | tail -n 1)
local used_size=$(df /boot --output=used | tail -n 1)
usage=$(( 100 * used_size / total_size ))
if (( usage > 80 )); then
# Prompt the user for confirmation to continue
echo -e "⚠️${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}"
read -r -p "Continue anyway? <y/N> " prompt
# Check if the input is 'y' or 'yes', otherwise exit with status 1
if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then
echo -e "❌${HOLD} ${YWB}Exiting based on user input.${CL}"
exit 1
fi
fi
}
start() {
if command -v pveversion >/dev/null 2>&1; then
if ! (whiptail --backtitle "Proxmox VE Helper Scripts" --title "${APP} LXC" --yesno "This will create a New ${APP} LXC. Proceed?" 10 58); then