Compare commits

..

No commits in common. "a11755de5d3bb9ce516d076b453bff4faba1b58c" and "659fa2edf7e9e988f42d47bb3b9e0108b4358d13" have entirely different histories.

8 changed files with 248 additions and 354 deletions

View File

@ -10,7 +10,6 @@ import { Category } from "@/lib/types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { z } from "zod"; import { z } from "zod";
import { ScriptSchema } from "../_schemas/schemas"; import { ScriptSchema } from "../_schemas/schemas";
import { memo } from "react";
type Script = z.infer<typeof ScriptSchema>; type Script = z.infer<typeof ScriptSchema>;
@ -22,42 +21,7 @@ type CategoryProps = {
categories: Category[]; categories: Category[];
}; };
const CategoryTag = memo(({ export default function Categories({
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, script,
setScript, setScript,
categories, categories,
@ -76,9 +40,8 @@ function Categories({
}); });
}; };
const categoryMap = new Map(categories.map(c => [c.id, c]));
return ( return (
<>
<div> <div>
<Label> <Label>
Category <span className="text-red-500">*</span> Category <span className="text-red-500">*</span>
@ -102,18 +65,39 @@ function Categories({
)} )}
> >
{script.categories.map((categoryId) => { {script.categories.map((categoryId) => {
const category = categoryMap.get(categoryId); const category = categories.find((c) => c.id === categoryId);
return category ? ( return category ? (
<CategoryTag <span
key={categoryId} key={categoryId}
category={category} className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
onRemove={() => removeCategory(categoryId)} >
{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; ) : null;
})} })}
</div> </div>
</div> </div>
</>
); );
} }
export default memo(Categories);

View File

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

View File

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

View File

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

View File

@ -28,41 +28,6 @@ $STD apt-get update
$STD apt-get install -y cloudflared $STD apt-get install -y cloudflared
msg_ok "Installed 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 motd_ssh
customize customize

View File

@ -9,7 +9,7 @@
"updateable": false, "updateable": false,
"privileged": false, "privileged": false,
"interface_port": null, "interface_port": null,
"documentation": "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/", "documentation": null,
"website": "https://www.cloudflare.com/", "website": "https://www.cloudflare.com/",
"logo": "https://raw.githubusercontent.com/loganmarchione/homelab-svg-assets/main/assets/cloudflare.svg", "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.", "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,10 +30,5 @@
"username": null, "username": null,
"password": 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, "updateable": true,
"privileged": false, "privileged": false,
"interface_port": null, "interface_port": null,
"documentation": "https://dev.mysql.com/doc/", "documentation": null,
"website": "https://www.mysql.com/", "website": null,
"logo": "https://1000logos.net/wp-content/uploads/2020/08/MySQL-Logo.png", "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.", "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": [ "install_methods": [
@ -34,10 +34,6 @@
{ {
"text": "Database credentials: `cat mysql.creds`", "text": "Database credentials: `cat mysql.creds`",
"type": "warning" "type": "warning"
},
{
"text": "With an option to install the MySQL 8.4 LTS release instead of MySQL 8.0",
"type": "info"
} }
] ]
} }

View File

@ -7,7 +7,6 @@ variables() {
# This function sets various color variables using ANSI escape codes for formatting text in the terminal. # This function sets various color variables using ANSI escape codes for formatting text in the terminal.
color() { color() {
YW=$(echo "\033[33m") YW=$(echo "\033[33m")
YWB=$(echo "\033[93m")
BL=$(echo "\033[36m") BL=$(echo "\033[36m")
RD=$(echo "\033[01;31m") RD=$(echo "\033[01;31m")
BGN=$(echo "\033[4;92m") BGN=$(echo "\033[4;92m")
@ -502,38 +501,6 @@ install_script() {
fi 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() { start() {
if command -v pveversion >/dev/null 2>&1; then 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 if ! (whiptail --backtitle "Proxmox VE Helper Scripts" --title "${APP} LXC" --yesno "This will create a New ${APP} LXC. Proceed?" 10 58); then