Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3902eb6
feat: add registry credential backend and runtime
mstrhakr Sep 13, 2026
f07c4d0
feat: add credential manager and stack selector UI
mstrhakr Sep 13, 2026
e080b63
fix: lock GitHub OAuth credential fields
mstrhakr Sep 13, 2026
d9351d8
fix: copy GitHub device code before authorization
mstrhakr Sep 13, 2026
5d60bf4
fix: support credential encryption without sodium
mstrhakr Sep 13, 2026
019aed6
feat: add registry provider presets and token helper links
mstrhakr Sep 14, 2026
15ff79f
fix: validate GitHub device session file write and chmod
mstrhakr Sep 14, 2026
d19a431
fix: catch credential vault errors in setStackSettings
mstrhakr Sep 14, 2026
96f70cf
fix: serialize vault read-modify-write transactions with flock
mstrhakr Sep 14, 2026
082748d
fix: sweep stale docker-config temp dirs and write vault/key atomically
mstrhakr Sep 14, 2026
126f2af
fix: reject client-supplied ids for new credentials and lock authMeth…
mstrhakr Sep 14, 2026
a352fab
fix: merge onto existing Docker config instead of replacing it wholesale
mstrhakr Sep 14, 2026
d39690c
perf: single-pass credential-to-stack mapping instead of per-credenti…
mstrhakr Sep 14, 2026
ba0081f
fix: sweep expired GitHub device-auth session files on start
mstrhakr Sep 14, 2026
acf332b
feat(credentials): add copy button and QR code for GitHub device sign-in
mstrhakr Sep 14, 2026
f59e39b
feat(credentials): log which registry credential is used for compose …
mstrhakr Sep 14, 2026
ad252a6
fix(credentials): surface credential name in operation output; add cr…
mstrhakr Sep 14, 2026
2e451fd
feat(credentials): add one-click renewal for OAuth GitHub credentials
mstrhakr Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion source/compose.manager/compose.manager.settings.page
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js'
<script src="<?php autov('/webGui/javascript/jquery.switchbutton.js'); ?>"></script>
<script src="<?php autov('/webGui/javascript/jquery.filetree.js'); ?>" charset="utf-8"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/common.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/qrcode/qrcode.min.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/credentialManager.js'); ?>" type="text/javascript"></script>
<style>
/* Toggle switch overrides */
#compose-tab-settings .switch-button-background,
Expand Down Expand Up @@ -1437,7 +1439,7 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js'
}, 'user', 'debug', 'settings');
}

if (settingsUnifiedState.activeTab === 'credits' || settingsUnifiedState.activeTab === 'log') {
if (settingsUnifiedState.activeTab === 'credits' || settingsUnifiedState.activeTab === 'log' || settingsUnifiedState.activeTab === 'credentials') {
$('#global-settings-actions').addClass('hidden');
} else {
$('#global-settings-actions').removeClass('hidden');
Expand Down Expand Up @@ -1649,6 +1651,9 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js'
} else {
stopLogRefresh();
}
if (tabId === 'credentials') {
ComposeCredentialManager.load();
}
// Always load backup archives when switching to backup tab
if (tabId === 'backup') {
loadBackupArchives();
Expand Down Expand Up @@ -1779,6 +1784,10 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js'
});
refreshCronStatus(finalizeInitialUpdates);
}

if (savedTab === 'credentials') {
ComposeCredentialManager.load();
}
});

function loadLogs() {
Expand Down Expand Up @@ -2666,10 +2675,21 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js'
<div class="compose-tab active" data-tab="settings"><i class="fa fa-cog fa-fw"></i> _(Settings)_</div>
<div class="compose-tab" data-tab="updates"><i class="fa fa-refresh fa-fw"></i> _(Updates)_</div>
<div class="compose-tab" data-tab="backup"><i class="fa fa-archive fa-fw"></i> _(Backup / Restore)_</div>
<div class="compose-tab" data-tab="credentials"><i class="fa fa-key fa-fw"></i> _(Credentials)_</div>
<div class="compose-tab" data-tab="log"><i class="fa fa-file-text-o fa-fw"></i> _(Log)_</div>
<div class="compose-tab" data-tab="credits"><i class="fa fa-star fa-fw"></i> _(Credits)_</div>
</div>

<div id="compose-tab-credentials" class="compose-tab-content">
<div class="title"><span class="left"><i class="fa fa-key icon"></i>_(Registry Credentials)_</span></div>
<p>Store pull credentials locally and assign them to individual stacks.</p>
<button type="button" id="add-credential-button"><i class="fa fa-plus"></i> Add credential</button>
<table class="tablesorter" style="width:100%;margin-top:16px;">
<thead><tr><th>Name</th><th>Provider</th><th>Registry</th><th>Account</th><th>Used by</th><th>Actions</th></tr></thead>
<tbody id="credentials-tbody"><tr><td colspan="6" class="credential-empty">Open this tab to load credentials.</td></tr></tbody>
</table>
</div>

<!-- Settings Tab -->
<div id="compose-tab-settings" class="compose-tab-content active">
<form name="compose_manager_settings" method="POST" action="/update.php" target="progressFrame">
Expand Down
1 change: 1 addition & 0 deletions source/compose.manager/default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ ONLY_EXPAND_RUNNING_STACKS="false"
COMPOSE_STATS_RATE_MODE="live"
COMPOSE_STATS_CUSTOM_INTERVAL_MS="1000"
DONT_CLOSE_EDITOR_MODAL_ON_OUTSIDE_CLICK="false"
GITHUB_OAUTH_CLIENT_ID="Ov23lip7HwI4IX2ueC97"
3 changes: 3 additions & 0 deletions source/compose.manager/event/docker_started
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ start_stack() {
if [ -n "$COMPOSE_SPEC_ENV_FILE_PATH" ]; then
cmd_args+=(-e "$COMPOSE_SPEC_ENV_FILE_PATH")
fi
if [ -n "$COMPOSE_SPEC_CREDENTIAL_ID" ]; then
cmd_args+=(--credential-id "$COMPOSE_SPEC_CREDENTIAL_ID")
fi
for profile in "${COMPOSE_SPEC_PROFILES[@]}"; do
cmd_args+=(-g "$profile")
done
Expand Down
5 changes: 5 additions & 0 deletions source/compose.manager/include/AutoUpdateRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,12 @@
$composeFileList = $stackInfo->buildComposeFileList();
$envFilePath = $args['envFilePath'] ?? null;
$projectDirectory = $args['projectDirectory'];
$credentialId = $stackInfo->getCredentialId();
} else {
$composeFileList = findComposeFile($path);
$envFilePath = null;
$projectDirectory = $path;
$credentialId = null;
}

// Allow overriding the shell command via environment for tests; default to sh
Expand All @@ -147,6 +149,9 @@
if ($composeFileList === '' && $projectDirectory !== '') {
$envPrefix .= 'COMPOSE_PROJECT_DIR=' . escapeshellarg($projectDirectory) . ' ';
}
if ($credentialId !== null && $credentialId !== '') {
$envPrefix .= 'COMPOSE_CREDENTIAL_ID=' . escapeshellarg($credentialId) . ' ';
}

$cmd = $envPrefix . $shCmd . ' ' . escapeshellarg($script) . " " . escapeshellarg($projectName) . " >/dev/null 2>&1 &";
exec($cmd);
Expand Down
4 changes: 4 additions & 0 deletions source/compose.manager/include/ComposeCommandBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public static function buildForAction(StackInfo $stackInfo, string $action, ?str
self::assertResolvedIdentity($stackInfo, $action);

$args = $stackInfo->buildComposeArgs();
$credentialId = in_array($action, ['up', 'update', 'pull'], true)
? trim((string) ($stackInfo->getCredentialId() ?? ''))
: '';

return [
'action' => $action,
Expand All @@ -37,6 +40,7 @@ public static function buildForAction(StackInfo $stackInfo, string $action, ?str
'useDefaultFileDiscovery' => $args['useDefaultFileDiscovery'],
'profiles' => self::resolveProfilesForAction($stackInfo, $action),
'stackPath' => $stackPath ?? $stackInfo->path,
'credentialId' => $credentialId,
];
}

Expand Down
11 changes: 11 additions & 0 deletions source/compose.manager/include/ComposeManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ function compose_manager_cpu_spec_count($cpuSpec)
</script>
<script src="<?php autov('/plugins/compose.manager/javascript/composeManagerPageInit.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/composeColumnCustomizer.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/qrcode/qrcode.min.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/credentialManager.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/composeManagerMain.js'); ?>" type="text/javascript"></script>

<HTML>
Expand Down Expand Up @@ -767,6 +769,15 @@ function compose_manager_cpu_spec_count($cpuSpec)
</div>
</div>

<div class="settings-field">
<label for="settings-credential-id">Registry Credential</label>
<div style="display:flex;gap:8px;align-items:center;">
<select id="settings-credential-id" style="flex:1;"><option value="">Anonymous / no credential</option></select>
<button type="button" id="settings-add-credential" title="Add credential"><i class="fa fa-plus"></i></button>
</div>
<div class="settings-field-help">Used only for pull-capable operations for this stack.</div>
</div>

<div class="settings-field">
<label for="settings-wait-for-healthy">Wait for Healthy</label>
<label style="display:flex;align-items:center;gap:8px;font-weight:normal;">
Expand Down
Loading
Loading