Merge branch 'main' of https://github.com/community-scripts/ProxmoxVED
This commit is contained in:
commit
7b203a10d8
3
.github/workflows/frontend-cicd.yml
vendored
3
.github/workflows/frontend-cicd.yml
vendored
@ -45,6 +45,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci --prefer-offline --legacy-peer-deps
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
|
||||
- name: Configure Next.js for pages
|
||||
uses: actions/configure-pages@v5
|
||||
with:
|
||||
|
@ -4,17 +4,7 @@ import path from "path";
|
||||
import { ScriptSchema, type Script } from "@/app/json-editor/_schemas/schemas";
|
||||
import { Metadata } from "@/lib/types";
|
||||
|
||||
const publicJsonPath = path.join(process.cwd(), 'public/json');
|
||||
const getJsonDirectory = async () => {
|
||||
if (!(await fs.stat(publicJsonPath).catch(() => null))) {
|
||||
throw new Error(`JSON path file not found: ${publicJsonPath}`);
|
||||
}
|
||||
const jsonPath = (await fs.readFile(publicJsonPath, "utf-8")).trim();
|
||||
return path.resolve(process.cwd(), jsonPath);
|
||||
};
|
||||
|
||||
|
||||
const jsonDir = await getJsonDirectory();
|
||||
const jsonDir = "public/json";
|
||||
const metadataFileName = "metadata.json";
|
||||
const encoding = "utf-8";
|
||||
|
||||
@ -25,7 +15,7 @@ describe.each(fileNames)("%s", async (fileName) => {
|
||||
let script: Script;
|
||||
|
||||
beforeAll(async () => {
|
||||
const filePath = path.resolve(jsonDir, fileName);
|
||||
const filePath = path.resolve(jsonDir, fileName);
|
||||
const fileContent = await fs.readFile(filePath, encoding)
|
||||
script = JSON.parse(fileContent);
|
||||
})
|
||||
@ -46,7 +36,7 @@ describe(`${metadataFileName}`, async () => {
|
||||
let metadata: Metadata;
|
||||
|
||||
beforeAll(async () => {
|
||||
const filePath = path.resolve(jsonDir, metadataFileName);
|
||||
const filePath = path.resolve(jsonDir, metadataFileName);
|
||||
const fileContent = await fs.readFile(filePath, encoding)
|
||||
metadata = JSON.parse(fileContent);
|
||||
})
|
||||
@ -55,9 +45,9 @@ describe(`${metadataFileName}`, async () => {
|
||||
// TODO: create zod schema for metadata. Move zod schemas to /lib/types.ts
|
||||
assert(metadata.categories.length > 0);
|
||||
metadata.categories.forEach((category) => {
|
||||
assert.isString(category.name)
|
||||
assert.isNumber(category.id)
|
||||
assert.isNumber(category.sort_order)
|
||||
assert.isString(category.name)
|
||||
assert.isNumber(category.id)
|
||||
assert.isNumber(category.sort_order)
|
||||
});
|
||||
});
|
||||
})
|
||||
|
@ -5,16 +5,7 @@ import path from "path";
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
const publicJsonPath = path.join(process.cwd(), 'public/json');
|
||||
const getJsonDirectory = async () => {
|
||||
if (!(await fs.stat(publicJsonPath).catch(() => null))) {
|
||||
throw new Error(`JSON path file not found: ${publicJsonPath}`);
|
||||
}
|
||||
const jsonPath = (await fs.readFile(publicJsonPath, "utf-8")).trim();
|
||||
return path.resolve(process.cwd(), jsonPath);
|
||||
};
|
||||
|
||||
const jsonDir = await getJsonDirectory();
|
||||
const jsonDir = "public/json";
|
||||
const metadataFileName = "metadata.json";
|
||||
const encoding = "utf-8";
|
||||
|
||||
|
@ -1,198 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { JSX, useEffect, useState } from "react";
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import ApplicationChart from "../../components/ApplicationChart";
|
||||
|
||||
interface DataModel {
|
||||
id: number;
|
||||
ct_type: number;
|
||||
disk_size: number;
|
||||
core_count: number;
|
||||
ram_size: number;
|
||||
os_type: string;
|
||||
os_version: string;
|
||||
disableip6: string;
|
||||
nsapp: string;
|
||||
created_at: string;
|
||||
method: string;
|
||||
pve_version: string;
|
||||
status: string;
|
||||
error: string;
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface SummaryData {
|
||||
total_entries: number;
|
||||
status_count: Record<string, number>;
|
||||
nsapp_count: Record<string, number>;
|
||||
}
|
||||
|
||||
const DataFetcher: React.FC = () => {
|
||||
const [data, setData] = useState<DataModel[]>([]);
|
||||
const [summary, setSummary] = useState<SummaryData | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(25);
|
||||
const [sortConfig, setSortConfig] = useState<{ key: string; direction: 'ascending' | 'descending' } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSummary = async () => {
|
||||
try {
|
||||
const response = await fetch("https://api.htl-braunau.at/data/summary");
|
||||
if (!response.ok) throw new Error(`Failed to fetch summary: ${response.statusText}`);
|
||||
const result: SummaryData = await response.json();
|
||||
setSummary(result);
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSummary();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPaginatedData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? '' : itemsPerPage}`);
|
||||
if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`);
|
||||
const result: DataModel[] = await response.json();
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPaginatedData();
|
||||
}, [currentPage, itemsPerPage]);
|
||||
|
||||
const sortedData = React.useMemo(() => {
|
||||
if (!sortConfig) return data;
|
||||
const sorted = [...data].sort((a, b) => {
|
||||
if (a[sortConfig.key] < b[sortConfig.key]) {
|
||||
return sortConfig.direction === 'ascending' ? -1 : 1;
|
||||
}
|
||||
if (a[sortConfig.key] > b[sortConfig.key]) {
|
||||
return sortConfig.direction === 'ascending' ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return sorted;
|
||||
}, [data, sortConfig]);
|
||||
|
||||
if (loading) return <p>Loading...</p>;
|
||||
if (error) return <p>Error: {error}</p>;
|
||||
|
||||
const requestSort = (key: string) => {
|
||||
let direction: 'ascending' | 'descending' = 'ascending';
|
||||
if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') {
|
||||
direction = 'descending';
|
||||
}
|
||||
setSortConfig({ key, direction });
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const timezoneOffset = dateString.slice(-6);
|
||||
return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 mt-20">
|
||||
<h1 className="text-2xl font-bold mb-4 text-center">Created LXCs</h1>
|
||||
<ApplicationChart data={summary} />
|
||||
<p className="text-lg font-bold mt-4"> </p>
|
||||
<div className="mb-4 flex justify-between items-center">
|
||||
<p className="text-lg font-bold">{summary?.total_entries} results found</p>
|
||||
<p className="text-lg font">Status Legend: 🔄 installing {summary?.status_count["installing"] ?? 0} | ✔️ completed {summary?.status_count["done"] ?? 0} | ❌ failed {summary?.status_count["failed"] ?? 0} | ❓ unknown</p>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<div className="overflow-y-auto lg:overflow-y-visible">
|
||||
<table className="min-w-full table-auto border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('status')}>Status</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('type')}>Type</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('nsapp')}>Application</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_type')}>OS</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_version')}>OS Version</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('disk_size')}>Disk Size</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('core_count')}>Core Count</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('ram_size')}>RAM Size</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('method')}>Method</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('pve_version')}>PVE Version</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('error')}>Error Message</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('created_at')}>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-4 py-2 border-b">
|
||||
{item.status === "done" ? (
|
||||
"✔️"
|
||||
) : item.status === "failed" ? (
|
||||
"❌"
|
||||
) : item.status === "installing" ? (
|
||||
"🔄"
|
||||
) : (
|
||||
item.status
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 border-b">{item.type === "lxc" ? (
|
||||
"📦"
|
||||
) : item.type === "vm" ? (
|
||||
"🖥️"
|
||||
) : (
|
||||
item.type
|
||||
)}</td>
|
||||
<td className="px-4 py-2 border-b">{item.nsapp}</td>
|
||||
<td className="px-4 py-2 border-b">{item.os_type}</td>
|
||||
<td className="px-4 py-2 border-b">{item.os_version}</td>
|
||||
<td className="px-4 py-2 border-b">{item.disk_size}</td>
|
||||
<td className="px-4 py-2 border-b">{item.core_count}</td>
|
||||
<td className="px-4 py-2 border-b">{item.ram_size}</td>
|
||||
<td className="px-4 py-2 border-b">{item.method}</td>
|
||||
<td className="px-4 py-2 border-b">{item.pve_version}</td>
|
||||
<td className="px-4 py-2 border-b">{item.error}</td>
|
||||
<td className="px-4 py-2 border-b">{formatDate(item.created_at)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-between items-center">
|
||||
<button onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage === 1} className="p-2 border">Previous</button>
|
||||
<span>Page {currentPage}</span>
|
||||
<button onClick={() => setCurrentPage(prev => prev + 1)} className="p-2 border">Next</button>
|
||||
<select
|
||||
value={itemsPerPage}
|
||||
onChange={(e) => setItemsPerPage(Number(e.target.value))}
|
||||
className="p-2 border"
|
||||
>
|
||||
<option value={10}>10</option>
|
||||
<option value={20}>20</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={100}>100</option>
|
||||
<option value={250}>250</option>
|
||||
<option value={500}>500</option>
|
||||
<option value={5000}>5000</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataFetcher;
|
@ -31,13 +31,13 @@
|
||||
"password": "admin123"
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "This LXC is not interchangeable with the Calibre-Web LXC",
|
||||
"type": "warning"
|
||||
},
|
||||
{
|
||||
"text": "Options enabled by default: Kobo sync; Goodreads author info; metadata extaction from epub, fb2, pdf; cover extraction from cbr, cbz, cbt files"
|
||||
"type": "info"
|
||||
}
|
||||
{
|
||||
"text": "This LXC is not interchangeable with the Calibre-Web LXC",
|
||||
"type": "warning"
|
||||
},
|
||||
{
|
||||
"text": "Options enabled by default: Kobo sync; Goodreads author info; metadata extaction from epub, fb2, pdf; cover extraction from cbr, cbz, cbt files",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "InvenTree",
|
||||
"slug": "inventree",
|
||||
"categories": [
|
||||
25
|
||||
],
|
||||
"date_created": "2025-03-05",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 80,
|
||||
"documentation": "https://docs.inventree.org/en/latest/",
|
||||
"website": "https://inventree.org",
|
||||
"logo": "https://docs.inventree.org/en/latest/assets/logo.png",
|
||||
"description": "InvenTree is an open-source inventory management system which provides intuitive parts management and stock control. It is designed to be lightweight and easy to use for SME or hobbyist applications.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/inventree.sh",
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 2048,
|
||||
"hdd": 6,
|
||||
"os": "debian",
|
||||
"version": "12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": "admin",
|
||||
"password": "`cat /etc/inventree/admin_password.txt`"
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "Please read the documentation for your configuration needs.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "Seafile",
|
||||
"slug": "Seafile",
|
||||
"categories": [
|
||||
12
|
||||
],
|
||||
"date_created": "2025-02-25",
|
||||
"type": "ct",
|
||||
"updateable": false,
|
||||
"privileged": false,
|
||||
"interface_port": 8000,
|
||||
"documentation": "https://manual.seafile.com/11.0/deploy",
|
||||
"website": "https://seafile.com",
|
||||
"logo": "https://manual.seafile.com/11.0/media/seafile-transparent-1024.png",
|
||||
"description": "Seafile is an open source file sync and share platform, focusing on reliability and performance.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/seafile.sh",
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 2048,
|
||||
"hdd": 20,
|
||||
"os": "debian",
|
||||
"version": "12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "Application credentials: `cat ~/seafile.creds`",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "Change STORAGE_DIR value in `external-storage.sh` and run `bash external-storage.sh` to use your defined storage instead of internal.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "RevealJS",
|
||||
"slug": "revealjs",
|
||||
"categories": [
|
||||
12
|
||||
],
|
||||
"date_created": "2025-03-03",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 8000,
|
||||
"documentation": "https://github.com/hakimel/reveal.js/wiki",
|
||||
"website": "https://github.com/hakimel/reveal.js",
|
||||
"logo": "https://static.slid.es/reveal/logo-v1/reveal-white-text.svg",
|
||||
"description": "reveal.js is an open source HTML presentation framework. It's a tool that enables anyone with a web browser to create fully-featured and beautiful presentations for free.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/revealjs.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 1024,
|
||||
"hdd": 4,
|
||||
"os": "debian",
|
||||
"version": "12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "Config file is at `/opt/revealjs/gulpfile.js`. Check the documentation for more information.",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "LiveReload is on port: 35729",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
128
misc/switch_from_VED_to_VE.sh
Normal file
128
misc/switch_from_VED_to_VE.sh
Normal file
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2025 community-scripts
|
||||
# Author: MickLesk
|
||||
# License: MIT
|
||||
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
|
||||
set -eEuo pipefail
|
||||
BL=$(echo "\033[36m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
CL=$(echo "\033[m")
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
____ _ ____________ __ ____ _ ________
|
||||
/ __ \_________ _ ______ ___ ____ _ _| | / / ____/ __ \ / /_____ / __ \_________ _ ______ ___ ____ _ _| | / / ____/
|
||||
/ /_/ / ___/ __ \| |/_/ __ `__ \/ __ \| |/_/ | / / __/ / / / / / __/ __ \ / /_/ / ___/ __ \| |/_/ __ `__ \/ __ \| |/_/ | / / __/
|
||||
/ ____/ / / /_/ /> </ / / / / / /_/ /> < | |/ / /___/ /_/ / / /_/ /_/ / / ____/ / / /_/ /> </ / / / / / /_/ /> < | |/ / /___
|
||||
/_/ /_/ \____/_/|_/_/ /_/ /_/\____/_/|_| |___/_____/_____/ \__/\____/ /_/ /_/ \____/_/|_/_/ /_/ /_/\____/_/|_| |___/_____/
|
||||
EOF
|
||||
}
|
||||
|
||||
function update_container() {
|
||||
container=$1
|
||||
os=$(pct config "$container" | awk '/^ostype/ {print $2}')
|
||||
|
||||
if [[ "$os" == "ubuntu" || "$os" == "debian" || "$os" == "alpine" ]]; then
|
||||
echo -e "${BL}[Info]${GN} Checking /usr/bin/update in ${BL}$container${CL} (OS: ${GN}$os${CL})"
|
||||
|
||||
if pct exec "$container" -- [ -e /usr/bin/update ]; then
|
||||
pct exec "$container" -- bash -c "sed -i 's/ProxmoxVED/ProxmoxVE/g' /usr/bin/update"
|
||||
|
||||
if pct exec "$container" -- grep -q "ProxmoxVE" /usr/bin/update; then
|
||||
echo -e "${GN}[Success]${CL} /usr/bin/update updated in ${BL}$container${CL}.\n"
|
||||
else
|
||||
echo -e "${RD}[Error]${CL} /usr/bin/update in ${BL}$container${CL} could not be updated properly.\n"
|
||||
fi
|
||||
else
|
||||
echo -e "${RD}[Error]${CL} /usr/bin/update not found in container ${BL}$container${CL}.\n"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function update_motd() {
|
||||
container=$1
|
||||
os=$(pct config "$container" | awk '/^ostype/ {print $2}')
|
||||
|
||||
echo -e "${BL}[Debug]${GN} Processing container: ${BL}$container${CL} (OS: ${GN}$os${CL})"
|
||||
|
||||
if [[ "$os" == "ubuntu" || "$os" == "debian" ]]; then
|
||||
echo -e "${BL}[Debug]${GN} Updating Debian/Ubuntu MOTD in ${BL}$container${CL}"
|
||||
|
||||
pct exec "$container" -- bash -c "
|
||||
PROFILE_FILE='/etc/profile.d/00_motd.sh'
|
||||
echo 'echo -e \"\"' > \"\$PROFILE_FILE\"
|
||||
echo 'echo -e \"🌐 Provided by: community-scripts ORG | GitHub: https://github.com/community-scripts/ProxmoxVE\"' >> \"\$PROFILE_FILE\"
|
||||
echo 'echo -e \"🖥️ OS: \$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')\"' >> \"\$PROFILE_FILE\"
|
||||
echo 'echo -e \"🏠 Hostname: \$(hostname)\"' >> \"\$PROFILE_FILE\"
|
||||
echo 'echo -e \"💡 IP Address: \$(hostname -I | awk '\''{print \$1}'\'')\"' >> \"\$PROFILE_FILE\"
|
||||
chmod -x /etc/update-motd.d/*
|
||||
"
|
||||
|
||||
echo -e "${GN}[Debug] Finished Debian/Ubuntu MOTD update for ${BL}$container${CL}"
|
||||
|
||||
elif [[ "$os" == "alpine" ]]; then
|
||||
echo -e "${BL}[Debug]${GN} Updating Alpine MOTD in ${BL}$container${CL}"
|
||||
|
||||
pct exec "$container" -- /bin/sh -c '
|
||||
echo "[Debug] Alpine: Start updating MOTD" > /tmp/motd_debug.log
|
||||
echo "export TERM=\"xterm-256color\"" >> /root/.bashrc
|
||||
echo "[Debug] Alpine: Set TERM variable" >> /tmp/motd_debug.log
|
||||
|
||||
IP=$(ip -4 addr show eth0 | awk "/inet / {print \$2}" | cut -d/ -f1 | head -n 1)
|
||||
echo "[Debug] Alpine: Fetched IP: $IP" >> /tmp/motd_debug.log
|
||||
|
||||
PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
|
||||
echo "[Debug] Alpine: Writing to profile file" >> /tmp/motd_debug.log
|
||||
|
||||
echo "echo -e \"\"" > \"$PROFILE_FILE\"
|
||||
echo "echo -e \" LXC Container\"" >> \"$PROFILE_FILE\"
|
||||
echo "echo -e \" 🌐 Provided by: community-scripts ORG | GitHub: https://github.com/community-scripts/ProxmoxVE\"" >> \"$PROFILE_FILE\"
|
||||
echo "[Debug] Alpine: Wrote MOTD header" >> /tmp/motd_debug.log
|
||||
|
||||
echo "echo \"\"" >> \"$PROFILE_FILE\"
|
||||
echo "echo -e \" 🖥️ OS: $(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: $(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')\"" >> \"$PROFILE_FILE\"
|
||||
echo "[Debug] Alpine: Wrote OS details" >> /tmp/motd_debug.log
|
||||
|
||||
echo "echo -e \"🏠 Hostname: $(hostname)\"" >> \"$PROFILE_FILE\"
|
||||
echo "echo -e \"💡 IP Address: $IP\"" >> \"$PROFILE_FILE\"
|
||||
echo "[Debug] Alpine: Wrote hostname & IP" >> /tmp/motd_debug.log
|
||||
'
|
||||
|
||||
echo -e "${GN}[Debug] Finished Alpine MOTD update for ${BL}$container${CL}"
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_dev_tag() {
|
||||
container=$1
|
||||
current_tags=$(pct config "$container" | awk '/^tags/ {print $2}')
|
||||
|
||||
if [[ "$current_tags" == *"dev"* ]]; then
|
||||
new_tags=$(echo "$current_tags" | sed 's/,*dev,*//g' | sed 's/^,//' | sed 's/,$//')
|
||||
|
||||
if [[ -z "$new_tags" ]]; then
|
||||
pct set "$container" -delete tags
|
||||
else
|
||||
pct set "$container" -tags "$new_tags"
|
||||
fi
|
||||
|
||||
echo -e "${GN}[Success]${CL} 'dev' tag removed from ${BL}$container${CL}.\n"
|
||||
fi
|
||||
}
|
||||
|
||||
header_info
|
||||
echo "Searching for containers with 'dev' tag..."
|
||||
for container in $(pct list | awk '{if(NR>1) print $1}'); do
|
||||
tags=$(pct config "$container" | awk '/^tags/ {print $2}')
|
||||
if [[ "$tags" == *"dev"* ]]; then
|
||||
update_container "$container"
|
||||
update_motd "$container"
|
||||
remove_dev_tag "$container"
|
||||
fi
|
||||
done
|
||||
|
||||
header_info
|
||||
echo -e "${GN}The process is complete.${CL}\n"
|
Loading…
x
Reference in New Issue
Block a user