-
Notifications
You must be signed in to change notification settings - Fork 2
SSUI 5.14.x #169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
SSUI 5.14.x #169
Changes from all commits
7d08455
8fd1832
aca9ef1
9b2148b
2f4e855
21c4316
a2e35ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "features": { | ||
| "ghcr.io/devcontainers/features/go:1": { | ||
| "version": "1.3.4", | ||
| "resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032", | ||
| "integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,7 +53,7 @@ function setButtonLoading(buttonId, isLoading) { | |
| if (isLoading) { | ||
| button.disabled = true; | ||
| button.dataset.originalText = button.textContent; | ||
| button.textContent = '⏳ Please wait...'; | ||
| button.textContent = button.classList.contains('mod-update-button') ? '⏳' : '⏳ Please wait...'; | ||
| button.classList.add('loading'); | ||
| } else { | ||
| button.disabled = false; | ||
|
|
@@ -135,6 +135,32 @@ function reinstallSLP() { | |
| }); | ||
| } | ||
|
|
||
| function updateSingleMod(workshopHandle, index) { | ||
| const btnId = 'update-mod-btn-' + index; | ||
| setButtonLoading(btnId, true); | ||
| showPopup('info', 'Updating workshop mod ' + workshopHandle + '...\n\nPlease wait.'); | ||
|
|
||
| fetch('/api/v2/steamcmd/updatemod', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ workshopHandle: workshopHandle }) | ||
| }) | ||
| .then(response => response.json()) | ||
| .then(data => { | ||
| setButtonLoading(btnId, false); | ||
| if (data.success) { | ||
| showPopup('success', 'Workshop mod updated successfully!\n\nReloading mod list...'); | ||
| loadInstalledMods(); | ||
| } else { | ||
| showPopup('error', 'Failed to update mod:\n\n' + (data.error || 'Unknown error')); | ||
| } | ||
| }) | ||
| .catch(error => { | ||
| showPopup('error', 'Failed to update mod:\n\n' + (error.message || 'Network error')); | ||
| setButtonLoading(btnId, false); | ||
| }); | ||
| } | ||
|
|
||
| function updateWorkshopMods() { | ||
| setButtonLoading('updateWorkshopModsBtn', true); | ||
| showPopup('info', 'Updating workshop mods...\n\nThis may take some time depending on the number of mods. Please wait.'); | ||
|
|
@@ -424,12 +450,18 @@ function createModCard(mod, index) { | |
| `; | ||
| } | ||
|
|
||
| let updateButtonHtml = ''; | ||
| if (mod.WorkshopHandle) { | ||
| updateButtonHtml = `<button class="mod-update-button" id="update-mod-btn-${index}" onclick="updateSingleMod('${escapeHtml(mod.WorkshopHandle)}', ${index})">🔄 Update</button>`; | ||
| } | ||
|
|
||
| card.innerHTML = ` | ||
| ${imageHtml} | ||
| <div class="mod-title">${escapeHtml(mod.Name || 'Unknown Mod')}</div> | ||
| ${mod.Author ? `<div class="mod-author">By ${escapeHtml(mod.Author)}</div>` : ''} | ||
| ${mod.Version ? `<div class="mod-version">v${escapeHtml(mod.Version)}</div>` : ''} | ||
| ${descriptionHtml} | ||
| ${updateButtonHtml} | ||
| `; | ||
|
|
||
| return card; | ||
|
Comment on lines
+453
to
467
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems valid, if I understand correctly we don't control the workshop handle so this is a potential XSS vector. We should sanitize this |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,12 +17,19 @@ import ( | |
|
|
||
| // COMMAND HANDLERS WITH COMMANDS USEFUL FOR DEVELOPMENT AND DEBUGGING | ||
|
|
||
| func downloadWorkshopItemTest() { | ||
| workshopHandles := []string{"3505169479"} | ||
| func downloadWorkshopItem(args []string) error { | ||
| var workshopHandles []string | ||
| if len(args) == 0 { | ||
| workshopHandles = []string{"3672138641"} // blueprint mod | ||
| } else { | ||
| workshopHandles = args | ||
| } | ||
| logger.Core.Info(fmt.Sprintf("Downloading workshop items: %v", workshopHandles)) | ||
| _, err := steamcmd.DownloadWorkshopItems(workshopHandles) | ||
| if err != nil { | ||
| logger.Core.Error("Error downloading workshop items: " + err.Error()) | ||
| } | ||
| return nil | ||
|
Comment on lines
28
to
+32
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair, my bad |
||
| } | ||
|
|
||
| func listworkshophandles() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ import ( | |
| "os/exec" | ||
| "path/filepath" | ||
| "runtime" | ||
| "slices" | ||
| "strings" | ||
| "time" | ||
|
|
||
|
|
@@ -293,9 +294,64 @@ func untarWrapper(r io.ReaderAt, _ int64, dest string) error { | |
| return untar(dest, io.NewSectionReader(r, 0, 1<<63-1)) // Use a large size for the section reader | ||
| } | ||
|
|
||
| type distroFamily int | ||
|
|
||
| const ( | ||
| distroUnknown distroFamily = iota | ||
| distroDebian | ||
| distroRHEL | ||
| ) | ||
|
|
||
| // parseOSRelease parses a /etc/os-release file into a key-value map. | ||
| func parseOSRelease(content string) map[string]string { | ||
| fields := make(map[string]string) | ||
| for _, line := range strings.Split(content, "\n") { | ||
| parts := strings.SplitN(line, "=", 2) | ||
| if len(parts) != 2 { | ||
| continue | ||
| } | ||
| fields[parts[0]] = strings.Trim(parts[1], "\"'") | ||
| } | ||
| return fields | ||
| } | ||
|
|
||
| // detectDistroFamily reads /etc/os-release and returns the distro family. | ||
| // ID is checked first (single value); ID_LIKE is the fallback (space-separated list). | ||
| func detectDistroFamily() distroFamily { | ||
| data, err := os.ReadFile("/etc/os-release") | ||
| if err != nil { | ||
| return distroUnknown | ||
| } | ||
|
|
||
| debianIDs := []string{"ubuntu", "debian", "linuxmint", "pop", "elementary", "raspbian"} | ||
| rhelIDs := []string{"rhel", "centos", "fedora", "rocky", "almalinux", "ol"} | ||
|
|
||
| fields := parseOSRelease(string(data)) | ||
|
|
||
| // Check ID first — it's a single value identifying the primary distro. | ||
| id := strings.ToLower(fields["ID"]) | ||
| if slices.Contains(debianIDs, id) { | ||
| return distroDebian | ||
| } | ||
| if slices.Contains(rhelIDs, id) { | ||
| return distroRHEL | ||
| } | ||
|
|
||
| // Fall back to ID_LIKE — a space-separated list of closely related distros. | ||
| for _, like := range strings.Fields(strings.ToLower(fields["ID_LIKE"])) { | ||
| if slices.Contains(debianIDs, like) { | ||
| return distroDebian | ||
| } | ||
| if slices.Contains(rhelIDs, like) { | ||
| return distroRHEL | ||
| } | ||
| } | ||
|
|
||
| return distroUnknown | ||
| } | ||
|
|
||
| // installRequiredLibraries installs the required libraries for SteamCMD if they are not already installed. | ||
| func installRequiredLibraries() error { | ||
| // Check if the system is Debian-based | ||
| if runtime.GOOS != "linux" { | ||
| return nil // Only Linux systems need this | ||
| } | ||
|
|
@@ -306,8 +362,19 @@ func installRequiredLibraries() error { | |
| return nil | ||
| } | ||
|
|
||
| // According to https://developer.valvesoftware.com/wiki/SteamCMD#Manually only lib32gcc-s1 is needed | ||
| // List of required libraries | ||
| switch detectDistroFamily() { | ||
| case distroDebian: | ||
| return installRequiredLibrariesDebian() | ||
| case distroRHEL: | ||
| return installRequiredLibrariesRHEL() | ||
| default: | ||
| return fmt.Errorf("unsupported Linux distribution: only Ubuntu/Debian and RHEL-based distros are supported") | ||
| } | ||
|
Comment on lines
+370
to
+372
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also a fair point. Will revisit this PR..later today or tomorrow, possibly |
||
| } | ||
|
|
||
| // installRequiredLibrariesDebian installs SteamCMD dependencies on Ubuntu/Debian using apt-get. | ||
| // According to https://developer.valvesoftware.com/wiki/SteamCMD#Manually only lib32gcc-s1 is needed. | ||
| func installRequiredLibrariesDebian() error { | ||
| requiredLibs := []string{ | ||
| "lib32gcc-s1", | ||
| //"lib32stdc++6", | ||
|
|
@@ -316,23 +383,49 @@ func installRequiredLibraries() error { | |
| // Check and install each library | ||
| for _, lib := range requiredLibs { | ||
| // Check if the library is already installed | ||
| cmd := exec.Command("dpkg", "-s", lib) | ||
| if err := cmd.Run(); err == nil { | ||
| if err := exec.Command("dpkg", "-s", lib).Run(); err == nil { | ||
| logger.Install.Debug("✅ Library already installed: " + lib + "\n") | ||
| continue // Library is already installed, skip to the next one | ||
| continue | ||
| } | ||
|
|
||
| // Library is not installed, attempt to install it | ||
| logger.Install.Debug("🔄 Installing library: " + lib + "\n") | ||
| installCmd := exec.Command("sudo", "apt-get", "install", "-y", lib) | ||
| installCmd.Stdout = os.Stdout | ||
| installCmd.Stderr = os.Stderr | ||
|
|
||
| if err := installCmd.Run(); err != nil { | ||
| return fmt.Errorf("failed to install library %s: %w", lib, err) | ||
| } | ||
| logger.Install.Debug("✅ Installed library: " + lib + "\n") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // installRequiredLibrariesRHEL installs SteamCMD dependencies on RHEL-based distros using dnf. | ||
| // libgcc.i686 is the RHEL equivalent of lib32gcc-s1 on Debian-based distros. | ||
| func installRequiredLibrariesRHEL() error { | ||
| requiredLibs := []string{ | ||
| "libgcc.i686", | ||
| "libstdc++.i686", | ||
| } | ||
|
|
||
| // Check and install each library | ||
| for _, lib := range requiredLibs { | ||
| // Check if the library is already installed | ||
| if err := exec.Command("rpm", "-q", lib).Run(); err == nil { | ||
| logger.Install.Debug("✅ Library already installed: " + lib + "\n") | ||
| continue | ||
| } | ||
|
|
||
| // Library is not installed, attempt to install it | ||
| logger.Install.Debug("🔄 Installing library: " + lib + "\n") | ||
| installCmd := exec.Command("sudo", "dnf", "install", "-y", lib) | ||
| installCmd.Stdout = os.Stdout | ||
| installCmd.Stderr = os.Stderr | ||
| if err := installCmd.Run(); err != nil { | ||
| return fmt.Errorf("failed to install library %s: %w", lib, err) | ||
| } | ||
| logger.Install.Debug("✅ Installed library: " + lib + "\n") | ||
| } | ||
| return nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.