v0.9.1
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Citizen Launcher 0.9.0
|
||||
# Citizen Launcher 0.9.1
|
||||
|
||||
Citizen Launcher is the distro-neutral successor to Omarchy Citizen.
|
||||
|
||||
@@ -79,3 +79,12 @@ For Omarchy plus bar integration:
|
||||
## Security boundaries
|
||||
|
||||
Citizen Launcher manages its Wine/DXVK/RSI stack entirely as the current user. It does not create passwordless sudo/pacman/apt rules. GPU/kernel/base-system updates remain owned by the distribution.
|
||||
|
||||
|
||||
## 0.9.1 fixes
|
||||
|
||||
- PowerShell is no longer a mandatory prefix component.
|
||||
- RSI `latest.yml` parser accepts Electron Builder `path:` and nested `files: - url:` formats.
|
||||
- Repair also refreshes DXVK.
|
||||
- Responsive dashboard prevents long GPU/distribution names from overflowing.
|
||||
- GUI shows concise user-facing errors with expandable technical details.
|
||||
|
||||
+7
-34
@@ -1,36 +1,9 @@
|
||||
# Citizen Launcher 0.9.0 — distro-neutral preview
|
||||
# Citizen Launcher 0.9.1
|
||||
|
||||
This release turns Omarchy Citizen into an optional integration instead of the product core.
|
||||
Maintenance/UX hotfix for the distro-neutral preview.
|
||||
|
||||
## New
|
||||
|
||||
- standalone distro-neutral `citizen-launcher` Go binary
|
||||
- embedded polished local GUI (`citizen-launcher gui`)
|
||||
- Debian/Ubuntu `.deb` packaging
|
||||
- generic Linux amd64 tarball
|
||||
- Fedora RPM spec template
|
||||
- distro detection via `/etc/os-release`
|
||||
- package-manager detection for apt/dnf/pacman/zypper/apk diagnostics
|
||||
- XDG-native config/data/state/cache paths with legacy Omarchy-Citizen migration
|
||||
- generic systemd user maintenance timer, with launch-time maintenance fallback on non-systemd desktops
|
||||
- support bundles generated entirely in Go
|
||||
- Omarchy moved to `integrations/omarchy/`
|
||||
- system tools preferred; LUG AppImage runtime is only a lazy portability fallback when cabextract/curl/unzip are unavailable
|
||||
|
||||
## Validation performed
|
||||
|
||||
- `go test ./...`
|
||||
- `go vet ./...`
|
||||
- static Linux amd64 Go build
|
||||
- shell syntax verification
|
||||
- embedded GUI HTTP/API smoke test
|
||||
- generic user installer smoke test in an isolated HOME
|
||||
- Omarchy updater integration test
|
||||
- Debian package build and metadata inspection
|
||||
|
||||
## Still needs real-hardware testing
|
||||
|
||||
- clean Debian/Ubuntu desktop → RSI login/install → Star Citizen launch
|
||||
- Fedora/openSUSE hardware testing
|
||||
- AMD/NVIDIA/Intel Vulkan permutations
|
||||
- production standalone self-update release channel once an official repository/release URL is chosen
|
||||
- Removed mandatory PowerShell Core from base Winetricks setup.
|
||||
- Robust RSI Electron Builder `latest.yml` parsing (`path`, nested `url`, version fallback).
|
||||
- DXVK is included in automatic repair.
|
||||
- Responsive GUI with safer grid sizing and readable diagnostics.
|
||||
- Added regression tests for RSI metadata formats and the minimal prefix contract.
|
||||
|
||||
Binary file not shown.
@@ -42,6 +42,30 @@ type jobStore struct {
|
||||
jobs map[string]*GUIJob
|
||||
}
|
||||
|
||||
func friendlyActionError(action string, err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
s := err.Error()
|
||||
switch {
|
||||
case strings.Contains(s, "Windows-Komponente"):
|
||||
return "Eine Windows-Komponente konnte nicht eingerichtet werden. Der technische Fehler ist unten verfügbar."
|
||||
case strings.Contains(s, "RSI latest.yml") || strings.Contains(s, "RSI Installer"):
|
||||
return "Die aktuelle RSI-Launcher-Version konnte nicht zuverlässig ermittelt werden."
|
||||
case strings.Contains(s, "RSI Launcher Installation"):
|
||||
return "Der RSI Launcher konnte nicht installiert oder aktualisiert werden."
|
||||
case strings.Contains(strings.ToLower(s), "vulkan"):
|
||||
return "Vulkan ist auf diesem System nicht spielbereit."
|
||||
case strings.Contains(strings.ToLower(s), "wine"):
|
||||
return "Der Wine-Stack konnte den Selbsttest oder die Reparatur nicht abschließen."
|
||||
default:
|
||||
if action == "repair" {
|
||||
return "Die automatische Reparatur konnte nicht vollständig abgeschlossen werden."
|
||||
}
|
||||
return "Die Aktion konnte nicht vollständig abgeschlossen werden."
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) runGUI(args []string) error {
|
||||
noOpen := hasArg(args, "--no-open")
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
@@ -143,6 +167,7 @@ func (a *App) runGUI(args []string) error {
|
||||
j.Finished = time.Now().Format(time.RFC3339)
|
||||
if err != nil {
|
||||
j.State = "error"
|
||||
j.Message = friendlyActionError(action, err)
|
||||
j.Error = err.Error()
|
||||
} else {
|
||||
j.State = "done"
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
appVersion = "0.9.0"
|
||||
appVersion = "0.9.1"
|
||||
pluginID = "local.omarchy-citizen"
|
||||
appID = "io.github.citizenlauncher.CitizenLauncher"
|
||||
)
|
||||
|
||||
@@ -27,6 +27,12 @@ const (
|
||||
rsiLatestYML = rsiBaseURL + "/latest.yml"
|
||||
)
|
||||
|
||||
// Keep the mandatory prefix small and boring. PowerShell is deliberately not
|
||||
// a base dependency: current LUG documentation exposes it as a separate
|
||||
// maintenance/troubleshooting action, and PowerShell Core MSI installation can
|
||||
// fail under otherwise healthy Wine prefixes.
|
||||
var basePrefixWinetricksVerbs = []string{"arial", "tahoma", "win11"}
|
||||
|
||||
type GameConfig struct {
|
||||
Prefix string `json:"prefix"`
|
||||
GameDir string `json:"game_dir"`
|
||||
@@ -427,6 +433,78 @@ func (a *App) syncWinetricks() (string, string, error) {
|
||||
return target, tag, nil
|
||||
}
|
||||
|
||||
func parseRSILatestYML(b []byte) (string, string, error) {
|
||||
var version, topPath, topSHA, firstURL, firstURLSHA string
|
||||
urlIndent := -1
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(b)))
|
||||
for scanner.Scan() {
|
||||
raw := strings.TrimRight(scanner.Text(), "\r\n")
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
indent := len(raw) - len(strings.TrimLeft(raw, " \t"))
|
||||
norm := strings.TrimSpace(strings.TrimPrefix(trimmed, "- "))
|
||||
key, value, ok := strings.Cut(norm, ":")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
value = yamlScalar(value)
|
||||
switch key {
|
||||
case "version":
|
||||
if version == "" {
|
||||
version = value
|
||||
}
|
||||
case "path":
|
||||
if indent == 0 && topPath == "" {
|
||||
topPath = value
|
||||
}
|
||||
case "url":
|
||||
if firstURL == "" {
|
||||
firstURL = value
|
||||
urlIndent = indent
|
||||
}
|
||||
case "sha512":
|
||||
if indent == 0 && topSHA == "" {
|
||||
topSHA = value
|
||||
} else if firstURL != "" && firstURLSHA == "" && indent > urlIndent {
|
||||
firstURLSHA = value
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
file := topPath
|
||||
sha := topSHA
|
||||
if file == "" {
|
||||
file = firstURL
|
||||
if sha == "" {
|
||||
sha = firstURLSHA
|
||||
}
|
||||
}
|
||||
// Electron-builder normally supplies path/url, but the version fallback
|
||||
// keeps us resilient if CIG trims redundant fields from latest.yml.
|
||||
if file == "" && version != "" {
|
||||
file = "RSI Launcher-Setup-" + version + ".exe"
|
||||
}
|
||||
if file == "" {
|
||||
return "", "", errors.New("RSI latest.yml enthält weder path, url noch version")
|
||||
}
|
||||
return file, sha, nil
|
||||
}
|
||||
|
||||
func yamlScalar(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
if len(v) >= 2 {
|
||||
if (v[0] == '\'' && v[len(v)-1] == '\'') || (v[0] == '"' && v[len(v)-1] == '"') {
|
||||
v = v[1 : len(v)-1]
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(v)
|
||||
}
|
||||
|
||||
func (a *App) latestRSIInstaller() (string, string, string, error) {
|
||||
req, _ := http.NewRequest("GET", rsiLatestYML, nil)
|
||||
req.Header.Set("User-Agent", "citizen-launcher/"+appVersion)
|
||||
@@ -443,22 +521,19 @@ func (a *App) latestRSIInstaller() (string, string, string, error) {
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
reURL := regexp.MustCompile(`(?m)^url:\s*(.+?)\s*$`)
|
||||
m := reURL.FindSubmatch(b)
|
||||
if len(m) < 2 {
|
||||
return "", "", "", errors.New("RSI latest.yml Format unbekannt")
|
||||
file, sha, err := parseRSILatestYML(b)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
file := strings.TrimSpace(string(m[1]))
|
||||
file = strings.Trim(file, "\"'")
|
||||
sha := ""
|
||||
reSHA := regexp.MustCompile(`(?m)^sha512:\s*(.+?)\s*$`)
|
||||
if sm := reSHA.FindSubmatch(b); len(sm) >= 2 {
|
||||
sha = strings.TrimSpace(string(sm[1]))
|
||||
sha = strings.Trim(sha, "\"'")
|
||||
ref, err := neturl.Parse(file)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("RSI Installer-Pfad ungültig: %w", err)
|
||||
}
|
||||
if ref.IsAbs() {
|
||||
return filepath.Base(ref.Path), ref.String(), sha, nil
|
||||
}
|
||||
base, _ := neturl.Parse(rsiBaseURL + "/")
|
||||
ref := &neturl.URL{Path: file}
|
||||
return file, base.ResolveReference(ref).String(), sha, nil
|
||||
return filepath.Base(ref.Path), base.ResolveReference(ref).String(), sha, nil
|
||||
}
|
||||
|
||||
func verifySHA512Base64(path, want string) error {
|
||||
@@ -609,18 +684,22 @@ func (a *App) ensurePrefixComponents(gc GameConfig) error {
|
||||
cache := filepath.Join(a.cacheDir, "winetricks-cache", tag)
|
||||
_ = os.MkdirAll(cache, 0o755)
|
||||
env = append(env, "W_CACHE="+cache, "WINETRICKS_DOWNLOADER=curl")
|
||||
cmd := exec.Command(wt, "-q", "arial", "tahoma", "powershell", "win11")
|
||||
cmd.Env = env
|
||||
out, err := cmd.CombinedOutput()
|
||||
a.logf("winetricks %s output=%s", tag, compactDiagnostic(string(out)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Prefix-Komponenten: %s", formatCommandFailure(err, out))
|
||||
|
||||
for _, verb := range basePrefixWinetricksVerbs {
|
||||
cmd := exec.Command(wt, "-q", verb)
|
||||
cmd.Env = env
|
||||
out, runErr := cmd.CombinedOutput()
|
||||
a.logf("winetricks %s verb=%s output=%s", tag, verb, compactDiagnostic(string(out)))
|
||||
if runErr != nil {
|
||||
return fmt.Errorf("Windows-Komponente %s: %s", verb, formatCommandFailure(runErr, out))
|
||||
}
|
||||
}
|
||||
|
||||
runner, _ := a.currentRunner()
|
||||
wine := filepath.Join(runner, "bin", "wine")
|
||||
reg := exec.Command(wine, "reg", "add", `HKEY_CURRENT_USER\Software\Wine\FileOpenAssociations`, `/v`, `Enable`, `/d`, `N`, `/f`)
|
||||
reg := exec.Command(wine, "reg", "add", `HKEY_CURRENT_USER\\Software\\Wine\\FileOpenAssociations`, `/v`, `Enable`, `/d`, `N`, `/f`)
|
||||
reg.Env = env
|
||||
out, err = reg.CombinedOutput()
|
||||
out, err := reg.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Wine Registry: %s", formatCommandFailure(err, out))
|
||||
}
|
||||
@@ -788,9 +867,6 @@ func (a *App) gameRepair() error {
|
||||
if hs.State == "blocked" {
|
||||
return errors.New(hs.Reason)
|
||||
}
|
||||
if err := a.syncLUGHelper(); err != nil {
|
||||
a.logf("LUG runtime warning: %v", err)
|
||||
}
|
||||
if err := a.syncWineRunner(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -803,6 +879,9 @@ func (a *App) gameRepair() error {
|
||||
if err := a.ensureRSILauncher(gc, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.syncDXVK(); err != nil {
|
||||
return fmt.Errorf("DXVK: %w", err)
|
||||
}
|
||||
if err := a.writeOwnedLaunchFiles(gc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseRSILatestElectronBuilder(t *testing.T) {
|
||||
y := `version: 2.15.0
|
||||
files:
|
||||
- url: RSI Launcher-Setup-2.15.0.exe
|
||||
sha512: nested-sha
|
||||
size: 123
|
||||
path: RSI Launcher-Setup-2.15.0.exe
|
||||
sha512: top-sha
|
||||
releaseDate: '2026-07-27T18:00:00.000Z'
|
||||
`
|
||||
file, sha, err := parseRSILatestYML([]byte(y))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file != "RSI Launcher-Setup-2.15.0.exe" || sha != "top-sha" {
|
||||
t.Fatalf("unexpected: file=%q sha=%q", file, sha)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRSILatestNestedOnly(t *testing.T) {
|
||||
y := `version: 2.15.0
|
||||
files:
|
||||
- url: "RSI Launcher-Setup-2.15.0.exe"
|
||||
sha512: "nested-sha"
|
||||
`
|
||||
file, sha, err := parseRSILatestYML([]byte(y))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file != "RSI Launcher-Setup-2.15.0.exe" || sha != "nested-sha" {
|
||||
t.Fatalf("unexpected: file=%q sha=%q", file, sha)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRSILatestVersionFallback(t *testing.T) {
|
||||
file, _, err := parseRSILatestYML([]byte("version: 2.15.0\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file != "RSI Launcher-Setup-2.15.0.exe" {
|
||||
t.Fatalf("unexpected file %q", file)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasePrefixDoesNotRequirePowerShell(t *testing.T) {
|
||||
for _, v := range basePrefixWinetricksVerbs {
|
||||
if strings.EqualFold(v, "powershell") {
|
||||
t.Fatalf("PowerShell must remain optional")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,11 @@
|
||||
const B=window.CITIZEN_BASE;let current=null;const $=id=>document.getElementById(id);async function api(path,opts){const r=await fetch(B+path,opts);if(!r.ok)throw new Error(await r.text());return r.json()}function cls(el,state){el.classList.remove('good','bad','warn');if(state)el.classList.add(state)}function txt(id,v,c){const e=$(id);e.textContent=v||'—';cls(e,c)}function primaryFor(h){if(h==='ready')return['STAR CITIZEN STARTEN','launch'];if(h==='hardware-blocked')return['HARDWARE-DETAILS','doctor'];if(h==='repair'||h==='launcher-repair')return['AUTOMATISCH REPARIEREN','repair'];return['EINRICHTEN & STARTKLAR MACHEN','setup']}async function refresh(){try{const s=await api('/api/status');current=s;$('version').textContent='v'+s.version;$('platform').textContent=[s.platform.name,s.platform.desktop].filter(Boolean).join(' · ');$('distro').textContent=s.platform.name;$('session').textContent=s.platform.session||'—';const g=s.game;let head='READY FOR SETUP',sub='Citizen Launcher übernimmt Wine, DXVK, Prefix und RSI Launcher.';if(g.health==='ready'){head='FLIGHT READY';sub='Star Citizen ist startbereit. Autopilot hält den Gaming-Stack aktuell.'}else if(g.health==='hardware-blocked'){head='HARDWARE CHECK';sub=g.hardware_reason||'Die Hardware erfüllt eine Voraussetzung nicht.'}else if(g.health==='repair'||g.health==='launcher-repair'){head='REPAIR AVAILABLE';sub='Ein reparierbarer Zustand wurde erkannt.'}$('headline').textContent=head;$('subline').textContent=sub;const [label,act]=primaryFor(g.health);$('primary').textContent=label;$('primary').dataset.action=act;txt('gpu',g.gpu||'nicht eindeutig erkannt',g.hardware==='blocked'?'bad':'good');txt('vulkan',g.vulkan,g.vulkan==='blocked'?'bad':g.vulkan==='ready'?'good':'warn');txt('ram',g.ram_gib+' GiB',g.ram_gib>=16?'good':'warn');txt('wine',g.wine_version||'wird eingerichtet',g.wine_version?'good':'warn');txt('dxvk',g.dxvk_version||'wird eingerichtet',g.dxvk_version?'good':'warn');txt('rsi',g.launcher_state==='ready'?'bereit':'Setup',g.launcher_state==='ready'?'good':'warn');txt('autopilot',g.autopilot?'aktiv':'aus',g.autopilot?'good':'warn');$('raw').textContent=JSON.stringify(s,null,2)}catch(e){$('headline').textContent='BACKEND ERROR';$('subline').textContent=e.message}}async function action(name){try{if(name==='launch'){await api('/api/action/launch',{method:'POST'});return}const j=await api('/api/action/'+name,{method:'POST'});$('job').classList.remove('hidden');$('jobTitle').textContent='Aktion: '+name;$('jobText').textContent='läuft …';poll(j.id)}catch(e){$('job').classList.remove('hidden');$('jobTitle').textContent='Fehler';$('jobText').textContent=e.message}}async function poll(id){try{const j=await api('/api/job/'+id);if(j.state==='running'){setTimeout(()=>poll(id),1000);return}$('jobText').textContent=j.state==='done'?(j.message||'Abgeschlossen'):j.error;$('job').querySelector('.spinner').classList.toggle('hidden',j.state!=='running');await refresh()}catch(e){$('jobText').textContent=e.message}}document.addEventListener('click',e=>{const a=e.target.dataset.action;if(a)action(a);const o=e.target.dataset.open;if(o)api('/api/open/'+o,{method:'POST'});});$('toggle').onclick=()=>$('advanced').classList.toggle('hidden');$('primary').onclick=()=>action($('primary').dataset.action);refresh();setInterval(refresh,10000);
|
||||
const B=window.CITIZEN_BASE;let current=null;const $=id=>document.getElementById(id);
|
||||
async function api(path,opts){const r=await fetch(B+path,opts);if(!r.ok)throw new Error(await r.text());return r.json()}
|
||||
function cls(el,state){el.classList.remove('good','bad','warn');if(state)el.classList.add(state)}
|
||||
function txt(id,v,c){const e=$(id);const value=(v===undefined||v===null||v==='')?'—':String(v);e.textContent=value;e.title=value;cls(e,c)}
|
||||
function primaryFor(h){if(h==='ready')return['STAR CITIZEN STARTEN','launch'];if(h==='hardware-blocked')return['HARDWARE-DETAILS','doctor'];if(h==='repair'||h==='launcher-repair')return['AUTOMATISCH REPARIEREN','repair'];return['EINRICHTEN & STARTKLAR MACHEN','setup']}
|
||||
function setHeroHealth(h){$('hero').dataset.health=h||'setup'}
|
||||
async function refresh(){try{const s=await api('/api/status');current=s;$('version').textContent='v'+s.version;$('platform').textContent=[s.platform.name,s.platform.desktop].filter(Boolean).join(' · ');$('distro').textContent=s.platform.name;$('session').textContent=s.platform.session||'—';const g=s.game;setHeroHealth(g.health);let head='READY FOR SETUP',sub='Citizen Launcher übernimmt Wine, DXVK, Prefix und RSI Launcher.';if(g.health==='ready'){head='FLIGHT READY';sub='Star Citizen ist startbereit. Autopilot hält den Gaming-Stack aktuell.'}else if(g.health==='hardware-blocked'){head='HARDWARE CHECK';sub=g.hardware_reason||'Die Hardware erfüllt eine Voraussetzung nicht.'}else if(g.health==='repair'||g.health==='launcher-repair'){head='REPAIR AVAILABLE';sub='Ein reparierbarer Zustand wurde erkannt. Die Reparatur verändert keine Spieldaten.'}else if(g.health==='install-game'){head='LAUNCHER READY';sub='Der RSI Launcher ist bereit. Melde dich an und installiere Star Citizen.'}$('headline').textContent=head;$('subline').textContent=sub;const [label,act]=primaryFor(g.health);$('primary').textContent=label;$('primary').dataset.action=act;txt('gpu',g.gpu||'nicht eindeutig erkannt',g.hardware==='blocked'?'bad':'good');txt('vulkan',g.vulkan,g.vulkan==='blocked'?'bad':g.vulkan==='ready'?'good':'warn');txt('ram',g.ram_gib+' GiB',g.ram_gib>=16?'good':'warn');txt('wine',g.wine_version||'wird eingerichtet',g.wine_version?'good':'warn');txt('dxvk',g.dxvk_version||'wird eingerichtet',g.dxvk_version?'good':'warn');txt('rsi',g.launcher_state==='ready'?'bereit':'Setup',g.launcher_state==='ready'?'good':'warn');txt('autopilot',g.autopilot?'aktiv':'aus',g.autopilot?'good':'warn');$('raw').textContent=JSON.stringify(s,null,2)}catch(e){$('headline').textContent='BACKEND ERROR';$('subline').textContent=e.message}}
|
||||
function showJob(state,title,message,raw=''){const box=$('job');box.classList.remove('hidden','error','done');if(state==='error')box.classList.add('error');if(state==='done')box.classList.add('done');$('jobTitle').textContent=title;$('jobText').textContent=message||'';const spin=box.querySelector('.spinner');const symbol=$('jobSymbol');spin.classList.toggle('hidden',state!=='running');symbol.classList.toggle('hidden',state==='running');symbol.textContent=state==='done'?'✓':'!';const details=$('jobDetails');if(raw&&raw!==message){details.classList.remove('hidden');$('jobRaw').textContent=raw}else{details.classList.add('hidden');$('jobRaw').textContent=''}}
|
||||
async function action(name){try{if(name==='launch'){await api('/api/action/launch',{method:'POST'});showJob('done','RSI Launcher gestartet','Der Launcher wurde gestartet.');return}const j=await api('/api/action/'+name,{method:'POST'});showJob('running','Aktion: '+name,'Wird ausgeführt …');poll(j.id)}catch(e){showJob('error','Aktion konnte nicht gestartet werden',e.message,e.message)}}
|
||||
async function poll(id){try{const j=await api('/api/job/'+id);if(j.state==='running'){setTimeout(()=>poll(id),800);return}if(j.state==='done')showJob('done','Abgeschlossen',j.message||'Die Aktion wurde erfolgreich abgeschlossen.');else showJob('error','Aktion fehlgeschlagen',j.message||'Die Aktion konnte nicht abgeschlossen werden.',j.error||'');await refresh()}catch(e){showJob('error','Statusfehler','Der Aktionsstatus konnte nicht gelesen werden.',e.message)}}
|
||||
document.addEventListener('click',e=>{const button=e.target.closest('[data-action],[data-open]');if(!button)return;const a=button.dataset.action;if(a)action(a);const o=button.dataset.open;if(o)api('/api/open/'+o,{method:'POST'});});$('toggle').onclick=()=>$('advanced').classList.toggle('hidden');$('primary').onclick=()=>action($('primary').dataset.action);refresh();setInterval(refresh,10000);
|
||||
@@ -2,15 +2,15 @@
|
||||
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Citizen Launcher</title><link rel="stylesheet" href="__BASE__/assets/style.css"></head>
|
||||
<body><div class="scan"></div><main>
|
||||
<header><div class="mark">✦</div><div><div class="eyebrow">LINUX FLIGHT SYSTEM</div><h1>Citizen Launcher</h1><p id="platform">Linux wird erkannt …</p></div><div class="version" id="version"></div></header>
|
||||
<section class="hero" id="hero"><div><div class="eyebrow">SYSTEM STATE</div><h2 id="headline">SYSTEM CHECK</h2><p id="subline">Hardware und Gaming-Stack werden geprüft.</p></div><button class="primary" id="primary">PRÜFEN …</button></section>
|
||||
<section class="grid three">
|
||||
<article><h3>Hardware</h3><div class="metric"><span>GPU</span><b id="gpu">—</b></div><div class="metric"><span>Vulkan</span><b id="vulkan">—</b></div><div class="metric"><span>RAM</span><b id="ram">—</b></div></article>
|
||||
<article><h3>Gaming Stack</h3><div class="metric"><span>Wine</span><b id="wine">—</b></div><div class="metric"><span>DXVK</span><b id="dxvk">—</b></div><div class="metric"><span>Launcher</span><b id="rsi">—</b></div></article>
|
||||
<article><h3>Autopilot</h3><div class="metric"><span>Wartung</span><b id="autopilot">—</b></div><div class="metric"><span>Distribution</span><b id="distro">—</b></div><div class="metric"><span>Session</span><b id="session">—</b></div></article>
|
||||
<header><div class="mark">✦</div><div class="brand"><div class="eyebrow">LINUX FLIGHT SYSTEM</div><h1>Citizen Launcher</h1><p id="platform">Linux wird erkannt …</p></div><div class="version" id="version"></div></header>
|
||||
<section class="hero" id="hero"><div class="hero-copy"><div class="eyebrow">SYSTEM STATE</div><h2 id="headline">SYSTEM CHECK</h2><p id="subline">Hardware und Gaming-Stack werden geprüft.</p></div><button class="primary" id="primary">PRÜFEN …</button></section>
|
||||
<section class="dashboard">
|
||||
<article class="card hardware"><h3>Hardware</h3><div class="metric"><span>GPU</span><b id="gpu">—</b></div><div class="metric"><span>Vulkan</span><b id="vulkan">—</b></div><div class="metric"><span>RAM</span><b id="ram">—</b></div></article>
|
||||
<article class="card"><h3>Gaming Stack</h3><div class="metric"><span>Wine</span><b id="wine">—</b></div><div class="metric"><span>DXVK</span><b id="dxvk">—</b></div><div class="metric"><span>Launcher</span><b id="rsi">—</b></div></article>
|
||||
<article class="card"><h3>Autopilot</h3><div class="metric"><span>Wartung</span><b id="autopilot">—</b></div><div class="metric"><span>Distribution</span><b id="distro">—</b></div><div class="metric"><span>Session</span><b id="session">—</b></div></article>
|
||||
</section>
|
||||
<section class="actions"><button data-action="maintain">Gaming-Stack aktualisieren</button><button data-action="repair">Problem automatisch beheben</button><button data-action="doctor">Wine-Selbsttest</button><button data-action="support">Support-Paket erstellen</button></section>
|
||||
<section class="job hidden" id="job"><div class="spinner"></div><div><b id="jobTitle">Aktion läuft</b><p id="jobText"></p></div></section>
|
||||
<section class="actions"><button data-action="maintain"><strong>Stack aktualisieren</strong><small>Wine, DXVK & Launcher prüfen</small></button><button data-action="repair"><strong>Automatisch reparieren</strong><small>Setup und Launcher heilen</small></button><button data-action="doctor"><strong>Wine-Selbsttest</strong><small>Prefix isoliert prüfen</small></button><button data-action="support"><strong>Support-Paket</strong><small>Diagnose für Hilfe erstellen</small></button></section>
|
||||
<section class="job hidden" id="job"><div class="job-icon"><div class="spinner"></div><span class="job-symbol hidden" id="jobSymbol">!</span></div><div class="job-body"><b id="jobTitle">Aktion läuft</b><p id="jobText"></p><details class="job-details hidden" id="jobDetails"><summary>Technische Details</summary><pre id="jobRaw"></pre></details></div></section>
|
||||
<section class="details"><button id="toggle">Erweiterte Informationen</button><div id="advanced" class="hidden"><pre id="raw"></pre><div class="small-actions"><button data-open="logs">Logs öffnen</button><button data-open="downloads">Downloads öffnen</button></div></div></section>
|
||||
<footer>Citizen Launcher verwaltet Star Citizen im Benutzerkonto. System-/GPU-Treiber bleiben Aufgabe der Distribution.</footer>
|
||||
<footer>Citizen Launcher verwaltet den Gaming-Stack im Benutzerkonto. System- und GPU-Treiber bleiben bei deiner Distribution.</footer>
|
||||
</main><script>window.CITIZEN_BASE="__BASE__";</script><script src="__BASE__/assets/app.js"></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -12,7 +12,7 @@ Panel {
|
||||
property var anchorItem: null
|
||||
property var hostWidget: null
|
||||
|
||||
property string pluginVersion: "0.9.0"
|
||||
property string pluginVersion: "0.9.1"
|
||||
property string health: "checking"
|
||||
property string depsState: "checking"
|
||||
property string depsMissing: ""
|
||||
@@ -106,7 +106,7 @@ Panel {
|
||||
values[lines[i].slice(0, p)] = lines[i].slice(p + 1)
|
||||
}
|
||||
|
||||
pluginVersion = values.plugin_version || "0.9.0"
|
||||
pluginVersion = values.plugin_version || "0.9.1"
|
||||
health = values.health || "setup"
|
||||
depsState = values.deps || "missing"
|
||||
depsMissing = values.deps_missing || ""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -u
|
||||
|
||||
PLUGIN_ID="local.omarchy-citizen"
|
||||
PLUGIN_VERSION="0.9.0"
|
||||
PLUGIN_VERSION="0.9.1"
|
||||
|
||||
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "local.omarchy-citizen",
|
||||
"name": "Citizen Launcher · Omarchy",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.1",
|
||||
"author": "Community prototype",
|
||||
"license": "MIT",
|
||||
"description": "Optional Omarchy bar integration for the distro-neutral Citizen Launcher.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VERSION="0.9.0"; ARCH="amd64"; WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
VERSION="0.9.1"; ARCH="amd64"; WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
PKG="$WORK/citizen-launcher_${VERSION}_${ARCH}"
|
||||
mkdir -p "$PKG/DEBIAN" "$PKG/usr/bin" "$PKG/usr/share/applications"
|
||||
install -m755 "$ROOT/backend/bin/citizen-launcher" "$PKG/usr/bin/citizen-launcher"
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
set -euo pipefail
|
||||
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
mkdir -p "$ROOT/dist"
|
||||
tar -C "$ROOT/backend/bin" -czf "$ROOT/dist/citizen-launcher-0.9.0-linux-amd64.tar.gz" citizen-launcher
|
||||
tar -C "$ROOT/backend/bin" -czf "$ROOT/dist/citizen-launcher-0.9.1-linux-amd64.tar.gz" citizen-launcher
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: citizen-launcher
|
||||
Version: 0.9.0
|
||||
Version: 0.9.1
|
||||
Release: 1%{?dist}
|
||||
Summary: Star Citizen launcher and self-maintaining Wine stack for Linux
|
||||
License: MIT
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
for f in build.sh install.sh install-omarchy.sh uninstall.sh packaging/build-deb.sh packaging/build-tarball.sh integrations/omarchy/citizenctl; do bash -n "$ROOT/$f"; done
|
||||
(cd "$ROOT/backend" && gofmt -w cmd/citizen-launcher/*.go && go test ./... && go vet ./... && ./build.sh)
|
||||
"$ROOT/backend/bin/citizen-launcher" --version | grep -qx '0.9.0'
|
||||
"$ROOT/backend/bin/citizen-launcher" --version | grep -qx '0.9.1'
|
||||
"$ROOT/backend/bin/citizen-launcher" platform --json | grep -q '"id"'
|
||||
# Ensure GUI assets are embedded by looking for a distinctive string in binary.
|
||||
grep -a -q 'FLIGHT READY' "$ROOT/backend/bin/citizen-launcher"
|
||||
|
||||
Reference in New Issue
Block a user