Skip to content

Commit ce79c73

Browse files
authored
All: add plugin system with autologin and server list (#73)
1 parent 1cd8122 commit ce79c73

23 files changed

Lines changed: 384 additions & 23 deletions

File tree

.github/workflows/docker.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,52 +21,52 @@ jobs:
2121
matrix:
2222
include:
2323
- dockerfile: ./adminer-dg/Dockerfile
24-
context: ./adminer-dg
24+
context: .
2525
tag: dg
2626
platforms: linux/amd64,linux/arm64
2727

2828
- dockerfile: ./adminer-editor/Dockerfile
29-
context: ./adminer-editor
29+
context: .
3030
tag: editor
3131
platforms: linux/amd64,linux/arm64
3232

3333
- dockerfile: ./adminer-full/Dockerfile
34-
context: ./adminer-full
34+
context: .
3535
tag: full
3636
platforms: linux/amd64,linux/arm64
3737

3838
- dockerfile: ./adminer-mongo/Dockerfile
39-
context: ./adminer-mongo
39+
context: .
4040
tag: mongo
4141
platforms: linux/amd64,linux/arm64
4242

4343
- dockerfile: ./adminer-mysql/Dockerfile
44-
context: ./adminer-mysql
44+
context: .
4545
tag: mysql
4646
platforms: linux/amd64,linux/arm64
4747

4848
- dockerfile: ./adminer-oracle-11/Dockerfile
49-
context: ./adminer-oracle-11
49+
context: .
5050
tag: oracle-11
5151
platforms: linux/amd64
5252

5353
- dockerfile: ./adminer-oracle-12/Dockerfile
54-
context: ./adminer-oracle-12
54+
context: .
5555
tag: oracle-12
5656
platforms: linux/amd64
5757

5858
- dockerfile: ./adminer-oracle-19/Dockerfile
59-
context: ./adminer-oracle-19
59+
context: .
6060
tag: oracle-19
6161
platforms: linux/amd64
6262

6363
- dockerfile: ./adminer-postgres/Dockerfile
64-
context: ./adminer-postgres
64+
context: .
6565
tag: postgres
6666
platforms: linux/amd64,linux/arm64
6767

6868
- dockerfile: ./adminer-full/Dockerfile
69-
context: ./adminer-full
69+
context: .
7070
tag: latest
7171
platforms: linux/amd64,linux/arm64
7272

.plugins/adminer-autologin.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/**
4+
* Auto-login to a single database server. Skips the login form entirely.
5+
*
6+
* Activated by: ADMINER_AUTOLOGIN_SERVER env var
7+
* DSN format: driver://username:password@host:port/database
8+
* Example: server://root:secret@mysql:3306/mydb
9+
*/
10+
class AdminerAutologin extends Adminer\Plugin {
11+
private $config;
12+
13+
function __construct() {
14+
$dsn = getenv('ADMINER_AUTOLOGIN_SERVER');
15+
if (!$dsn || !preg_match('#^(\w+)://(?:([^:@]*)(?::([^@]*))?@)?([^/:]+)(?::(\d+))?(?:/(.*))?$#', $dsn, $m)) {
16+
return;
17+
}
18+
$host = $m[4] . (!empty($m[5]) ? ':' . $m[5] : '');
19+
$this->config = [
20+
'driver' => $m[1],
21+
'username' => urldecode($m[2] ?? ''),
22+
'password' => urldecode($m[3] ?? ''),
23+
'server' => $host,
24+
'db' => urldecode($m[6] ?? ''),
25+
];
26+
27+
// Inject login data on first visit (before Adminer processes the request)
28+
if (!isset($_GET["username"]) && empty($_POST["auth"]) && !isset($_GET["server"])) {
29+
$_POST["auth"] = [
30+
"driver" => $this->config["driver"],
31+
"server" => $this->config["server"],
32+
"username" => $this->config["username"],
33+
"password" => $this->config["password"],
34+
"db" => $this->config["db"],
35+
"permanent" => "",
36+
];
37+
}
38+
}
39+
40+
function credentials() {
41+
if ($this->config) {
42+
return [$this->config["server"], $this->config["username"], $this->config["password"]];
43+
}
44+
}
45+
46+
function login($login, $password) {
47+
if ($this->config) {
48+
return true;
49+
}
50+
}
51+
}

.plugins/adminer-server-list.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
/**
4+
* Server list with optional stored credentials and Auto Sign-In button.
5+
* Credentials are handled server-side — never exposed to the browser.
6+
*
7+
* Activated by: ADMINER_SERVERS_* env vars
8+
* DSN format: driver://username:password@host:port/database
9+
* Examples:
10+
* ADMINER_SERVERS_MariaDB=server://root:secret@mysql:3306/mydb
11+
* ADMINER_SERVERS_PostgreSQL=pgsql://postgres:pwd@pg:5432/app
12+
* ADMINER_SERVERS_SQLite=sqlite:///data/db.sqlite (no credentials)
13+
*
14+
* The suffix after ADMINER_SERVERS_ becomes the display name in the dropdown.
15+
*/
16+
class AdminerServerList extends Adminer\Plugin {
17+
private $servers = [];
18+
private $credentials = [];
19+
20+
function __construct() {
21+
foreach (getenv() as $key => $value) {
22+
if (!str_starts_with($key, 'ADMINER_SERVERS_')) {
23+
continue;
24+
}
25+
$name = substr($key, strlen('ADMINER_SERVERS_'));
26+
if (!preg_match('#^(\w+)://(?:([^:@]*)(?::([^@]*))?@)?([^/:]+)(?::(\d+))?(?:/(.*))?$#', $value, $m)) {
27+
continue;
28+
}
29+
$host = $m[4] . (!empty($m[5]) ? ':' . $m[5] : '');
30+
$this->servers[$name] = ['server' => $host, 'driver' => $m[1]];
31+
if (!empty($m[2])) {
32+
$this->credentials[$name] = [
33+
'username' => urldecode($m[2]),
34+
'password' => urldecode($m[3] ?? ''),
35+
'db' => urldecode($m[6] ?? ''),
36+
];
37+
}
38+
}
39+
40+
// Set driver from selected server on POST (same as AdminerLoginServers)
41+
if ($_POST["auth"] && isset($this->servers[$_POST["auth"]["server"]])) {
42+
$_POST["auth"]["driver"] = $this->servers[$_POST["auth"]["server"]]["driver"];
43+
}
44+
45+
// Handle Auto Sign-In: inject stored credentials into POST
46+
if (isset($_POST["_autologin"]) && isset($this->credentials[$_POST["auth"]["server"]])) {
47+
$cred = $this->credentials[$_POST["auth"]["server"]];
48+
$_POST["auth"]["username"] = $cred["username"];
49+
$_POST["auth"]["password"] = $cred["password"];
50+
$_POST["auth"]["db"] = $cred["db"];
51+
}
52+
}
53+
54+
function credentials() {
55+
$server = Adminer\SERVER;
56+
if (isset($this->servers[$server])) {
57+
$host = $this->servers[$server]["server"];
58+
if (isset($this->credentials[$server])) {
59+
$cred = $this->credentials[$server];
60+
$username = $_GET["username"] ?: $cred["username"];
61+
$password = Adminer\get_password() ?: $cred["password"];
62+
return [$host, $username, $password];
63+
}
64+
return [$host, $_GET["username"], Adminer\get_password()];
65+
}
66+
}
67+
68+
function login($login, $password) {
69+
if (!isset($this->servers[Adminer\SERVER])) {
70+
return false;
71+
}
72+
if (isset($this->credentials[Adminer\SERVER])) {
73+
return true;
74+
}
75+
}
76+
77+
function loginFormField($name, $heading, $value) {
78+
if ($name == 'driver') {
79+
return '';
80+
}
81+
if ($name == 'server') {
82+
return $heading . Adminer\html_select("auth[server]", array_keys($this->servers), Adminer\SERVER) . "\n";
83+
}
84+
}
85+
86+
function head() {
87+
if (isset($_GET["username"]) || empty($this->credentials)) {
88+
return;
89+
}
90+
$servers = json_encode(array_keys($this->credentials), JSON_HEX_TAG | JSON_HEX_AMP);
91+
$nonce = \Adminer\get_nonce();
92+
echo <<<SCRIPT
93+
<script nonce="$nonce">
94+
(function() {
95+
var serversWithCreds = $servers;
96+
document.addEventListener("DOMContentLoaded", function() {
97+
var form = document.querySelector("form");
98+
if (!form) return;
99+
var serverSelect = form.querySelector('[name="auth[server]"]');
100+
if (!serverSelect) return;
101+
102+
var hidden = document.createElement("input");
103+
hidden.type = "hidden";
104+
hidden.name = "_autologin";
105+
hidden.disabled = true;
106+
form.appendChild(hidden);
107+
108+
var btn = document.createElement("input");
109+
btn.type = "submit";
110+
btn.value = "Auto Sign-In";
111+
btn.style.display = "none";
112+
btn.style.marginLeft = "5px";
113+
btn.addEventListener("click", function() {
114+
hidden.disabled = false;
115+
hidden.value = "1";
116+
});
117+
var loginBtn = form.querySelector('[type="submit"][value="Login"]');
118+
if (loginBtn) loginBtn.parentNode.insertBefore(btn, loginBtn.nextSibling);
119+
120+
function updateButton(serverName) {
121+
btn.style.display = serversWithCreds.indexOf(serverName) >= 0 ? "inline" : "none";
122+
}
123+
serverSelect.addEventListener("change", function() { updateButton(this.value); });
124+
updateButton(serverSelect.value);
125+
});
126+
})();
127+
</script>
128+
SCRIPT;
129+
}
130+
}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-all: build-full build-dg build-editor build-mongo build-mysql build-postgr
55

66
_docker-build-%: TAG=$*
77
_docker-build-%:
8-
docker buildx build --platform ${DOCKER_PLATFORMS} -t ${DOCKER_IMAGE}:${TAG} ./adminer-${TAG}
8+
docker buildx build --platform ${DOCKER_PLATFORMS} -t ${DOCKER_IMAGE}:${TAG} -f ./adminer-${TAG}/Dockerfile .
99

1010
build-full: _docker-build-full
1111
build-dg: _docker-build-dg

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,56 @@ You should take a look to the official github profile (https://github.com/dg/adm
8080

8181
![Adminer DG](.docs/assets/adminer-dg.png)
8282

83+
## Plugins
84+
85+
Adminer plugins can be enabled via environment variables. All plugins are disabled by default. Available for all image variants except `dg`.
86+
87+
### Autologin
88+
89+
Skips the login form and connects directly to a database server.
90+
91+
| Variable | Description |
92+
|---|---|
93+
| `ADMINER_PLUGIN_AUTOLOGIN=1` | Enable the autologin plugin |
94+
| `ADMINER_AUTOLOGIN_SERVER` | DSN connection string |
95+
96+
DSN format: `driver://username:password@host:port/database`
97+
98+
```sh
99+
docker run \
100+
--rm \
101+
-p 8080:80 \
102+
-e ADMINER_PLUGIN_AUTOLOGIN=1 \
103+
-e ADMINER_AUTOLOGIN_SERVER=server://root:secret@mysql:3306/mydb \
104+
dockette/adminer:full
105+
```
106+
107+
Supported drivers: `server` (MySQL/MariaDB), `pgsql`, `sqlite`, `mongo`, `oracle`, `elastic`.
108+
109+
### Server List
110+
111+
Displays a dropdown of pre-configured database servers with an **Auto Sign-In** button. Credentials are handled server-side and never exposed to the browser.
112+
113+
| Variable | Description |
114+
|---|---|
115+
| `ADMINER_PLUGIN_SERVER_LIST=1` | Enable the server list plugin |
116+
| `ADMINER_SERVERS_<Name>` | DSN for each server (suffix becomes the display name) |
117+
118+
```sh
119+
docker run \
120+
--rm \
121+
-p 8080:80 \
122+
-e ADMINER_PLUGIN_SERVER_LIST=1 \
123+
-e ADMINER_SERVERS_MySQL=server://root:secret@mysql:3306/mydb \
124+
-e ADMINER_SERVERS_PostgreSQL=pgsql://postgres:pwd@pg:5432/app \
125+
-e ADMINER_SERVERS_DevDB=server://dev@devhost:3306 \
126+
dockette/adminer:full
127+
```
128+
129+
Servers without credentials in the DSN (e.g. `server://devhost:3306`) appear in the dropdown but require manual login. The **Auto Sign-In** button only appears for servers with stored credentials.
130+
131+
> **Note:** Autologin takes precedence over Server List. If both plugins are enabled, only Autologin is activated.
132+
83133
## Themes
84134

85135
You can apply a theme by setting the `ADMINER_THEME` environment variable:

adminer-dg/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ RUN echo '@community http://nl.alpinelinux.org/alpine/v3.22/community' >> /etc/a
3030
apk del wget ca-certificates && \
3131
rm -rf /var/cache/apk/*
3232

33-
ADD ./entrypoint.sh /entrypoint.sh
33+
ADD ./adminer-dg/entrypoint.sh /entrypoint.sh
3434
RUN chmod +x /entrypoint.sh
3535

3636
WORKDIR /srv

adminer-editor/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ RUN echo '@community http://nl.alpinelinux.org/alpine/v3.22/community' >> /etc/a
3030
mkdir -p /srv/designs && \
3131
mv /tmp/adminer-$ADMINER_EDITOR_VERSION/designs/* /srv/designs/ 2>/dev/null || true && \
3232
rm -rf /tmp/* && \
33+
mkdir -p /srv/adminer-plugins && \
3334
ln -s /usr/bin/php84 /usr/bin/php && \
3435
apk del wget unzip ca-certificates && \
3536
rm -rf /var/cache/apk/*
3637

37-
ADD ./entrypoint.sh /entrypoint.sh
38+
ADD ./adminer-editor/entrypoint.sh /entrypoint.sh
39+
ADD ./.plugins/ /srv/plugins-available/
3840
RUN chmod +x /entrypoint.sh
3941

4042
WORKDIR /srv

adminer-editor/entrypoint.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ if [ -n "${ADMINER_THEME}" ]; then
4040
fi
4141
fi
4242

43+
# Activate plugins (ADMINER_PLUGIN_<name>=1, all disabled by default)
44+
if [ "${ADMINER_PLUGIN_AUTOLOGIN}" = "1" ]; then
45+
cp /srv/plugins-available/adminer-autologin.php /srv/adminer-plugins/
46+
echo "[adminer] Plugin 'autologin' activated."
47+
elif [ "${ADMINER_PLUGIN_SERVER_LIST}" = "1" ]; then
48+
cp /srv/plugins-available/adminer-server-list.php /srv/adminer-plugins/
49+
echo "[adminer] Plugin 'server-list' activated."
50+
fi
51+
4352
# Set default values if not provided
4453
MEMORY=${MEMORY:-256M}
4554
UPLOAD=${UPLOAD:-2048M}

adminer-full/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ RUN echo '@community http://nl.alpinelinux.org/alpine/v3.22/community' >> /etc/a
3838
apk del wget unzip ca-certificates && \
3939
rm -rf /var/cache/apk/*
4040

41-
ADD ./entrypoint.sh /entrypoint.sh
41+
ADD ./adminer-full/entrypoint.sh /entrypoint.sh
42+
ADD ./.plugins/ /srv/plugins-available/
4243
RUN chmod +x /entrypoint.sh
4344

4445
WORKDIR /srv

adminer-full/entrypoint.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ else
2727
echo "[adminer] No driver plugins directory found at /srv/plugins/drivers, skipping..."
2828
fi
2929

30+
# Activate plugins (ADMINER_PLUGIN_<name>=1, all disabled by default)
31+
if [ "${ADMINER_PLUGIN_AUTOLOGIN}" = "1" ]; then
32+
cp /srv/plugins-available/adminer-autologin.php /srv/adminer-plugins/
33+
echo "[adminer] Plugin 'autologin' activated."
34+
elif [ "${ADMINER_PLUGIN_SERVER_LIST}" = "1" ]; then
35+
cp /srv/plugins-available/adminer-server-list.php /srv/adminer-plugins/
36+
echo "[adminer] Plugin 'server-list' activated."
37+
fi
38+
3039
# Copy theme CSS files based on ADMINER_THEME environment variable
3140
if [ -n "${ADMINER_THEME}" ]; then
3241
THEME_DIR="/srv/designs/${ADMINER_THEME}"

0 commit comments

Comments
 (0)