Roundcube as its own image built by the workflow (no config file or newer Compose needed)
build-container / build (push) Successful in 6m6s

- roundcube/: official Roundcube plus hub.php settings
- workflow builds and pushes git.necloud.us/nate/hub-roundcube, and runs the player and webmail tests
- compose uses the image instead of an inline config

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-21 08:59:50 -04:00
1 parent cdf862427e
commit a7a5b155e3
5 files changed
+67 -49

No files matched your search

+20
View File
@@ -20,6 +20,8 @@ jobs:
- run: node tools/smoke-test.js - run: node tools/smoke-test.js
- run: node tools/users-test.js # accounts: private data, tab limits, invites, migration - run: node tools/users-test.js # accounts: private data, tab limits, invites, migration
- run: node tools/plex-test.js # Plex sign-in stays signed in and repairs itself - run: node tools/plex-test.js # Plex sign-in stays signed in and repairs itself
- run: node tools/player-test.js # music player: queue, shuffle/repeat, stall recovery
- run: node tools/webmail-test.js # the /webmail/ pass-through to Roundcube
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v3
@@ -50,3 +52,21 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=git.necloud.us/nate/hub:buildcache cache-from: type=registry,ref=git.necloud.us/nate/hub:buildcache
cache-to: type=registry,ref=git.necloud.us/nate/hub:buildcache,mode=max cache-to: type=registry,ref=git.necloud.us/nate/hub:buildcache,mode=max
# The webmail container (official Roundcube + the hub's settings)
- id: meta_rc
uses: docker/metadata-action@v5
with:
images: git.necloud.us/nate/hub-roundcube
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,format=short
type=ref,event=tag
- uses: docker/build-push-action@v6
with:
context: ./roundcube
platforms: linux/amd64
push: true
tags: ${{ steps.meta_rc.outputs.tags }}
labels: ${{ steps.meta_rc.outputs.labels }}
+2 -2
View File
@@ -69,8 +69,8 @@ e.g. hub.necloud.us. Settings: `SEAFILE_URL`, `ONLYOFFICE_URL` env vars in docke
authenticated SMTP must be allowed for the mailbox. Then put `M365_CLIENT_ID`, `M365_CLIENT_SECRET`, `M365_TENANT` (the tenant ID) and `PUBLIC_URL` authenticated SMTP must be allowed for the mailbox. Then put `M365_CLIENT_ID`, `M365_CLIENT_SECRET`, `M365_TENANT` (the tenant ID) and `PUBLIC_URL`
(your hub address, used for the redirect) in `.env` and `docker compose up -d`. The Roundcube login then shows **Login with Microsoft 365**. Microsoft's sign-in (your hub address, used for the redirect) in `.env` and `docker compose up -d`. The Roundcube login then shows **Login with Microsoft 365**. Microsoft's sign-in
page cannot be shown inside a frame, so that one step opens in the whole window and returns you to the Mail tab. Personal Outlook.com accounts are not covered. page cannot be shown inside a frame, so that one step opens in the whole window and returns you to the Mail tab. Personal Outlook.com accounts are not covered.
Roundcube's data (settings, address book, its encryption key) is in the `nehub-roundcube` volume. To change the presets, edit the `roundcube_hub` config in Roundcube's data (settings, address book, its encryption key) is in the `nehub-roundcube` volume. The webmail is its own image (`git.necloud.us/nate/hub-roundcube`, built by the Gitea workflow from `roundcube/`: the official Roundcube plus
`docker-compose.yml` (needs Docker Compose 2.23.1 or newer). To use a webmail you run elsewhere instead, set `WEBMAIL_URL` (it must allow being framed: `roundcube/hub.php`), so any compose tool can run it. To change the presets, edit `roundcube/hub.php`. To use a webmail you run elsewhere instead, set `WEBMAIL_URL` (it must allow being framed:
no `X-Frame-Options`, and `Content-Security-Policy: frame-ancestors https://<your hub address>`). With neither, Mail is just the shortcut cards. no `X-Frame-Options`, and `Content-Security-Policy: frame-ancestors https://<your hub address>`). With neither, Mail is just the shortcut cards.
roundcube: roundcube:
+3 -47
View File
@@ -33,64 +33,20 @@ services:
- hub-data:/data # passkeys, calendar links and the session secret live here - hub-data:/data # passkeys, calendar links and the session secret live here
# Webmail shown inside the Mail tab. Only the hub can reach it (no published port), so it sits behind the passkey sign-in. # Webmail shown inside the Mail tab. Only the hub can reach it (no published port), so it sits behind the passkey sign-in.
# Sign in to it with your mail address and an app password (Gmail, iCloud and Yahoo are preset in the config below). # Sign in to it with your mail address and an app password (Gmail, iCloud and Yahoo are preset in roundcube/hub.php).
roundcube: roundcube:
image: roundcube/roundcubemail:latest-apache image: git.necloud.us/nate/hub-roundcube:latest # built by the Gitea workflow from ./roundcube; `docker compose build` builds locally instead
build: ./roundcube
restart: unless-stopped restart: unless-stopped
environment: environment:
ROUNDCUBEMAIL_DB_TYPE: sqlite
ROUNDCUBEMAIL_REQUEST_PATH: /webmail/
ROUNDCUBEMAIL_PLUGINS: archive,zipdownload
ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE: 30M
# Microsoft 365 sign-in (optional, see README -> Mail). Leave empty to skip. # Microsoft 365 sign-in (optional, see README -> Mail). Leave empty to skip.
M365_CLIENT_ID: ${M365_CLIENT_ID:-} M365_CLIENT_ID: ${M365_CLIENT_ID:-}
M365_CLIENT_SECRET: ${M365_CLIENT_SECRET:-} M365_CLIENT_SECRET: ${M365_CLIENT_SECRET:-}
M365_TENANT: ${M365_TENANT:-} M365_TENANT: ${M365_TENANT:-}
PUBLIC_URL: ${PUBLIC_URL:-} PUBLIC_URL: ${PUBLIC_URL:-}
configs:
- source: roundcube_hub
target: /var/roundcube/config/hub.php
volumes: volumes:
- roundcube-data:/var/roundcube/db # address book, settings and the encryption key - roundcube-data:/var/roundcube/db # address book, settings and the encryption key
configs:
roundcube_hub:
content: |
<?php
// Settings for the Roundcube that comes with NE Hub.
$$config['product_name'] = 'Mail';
$$config['x_frame_options'] = false; // the hub shows this page inside its Mail tab (same address, so only the hub can frame it)
$$hosts = [
'ssl://imap.gmail.com:993' => 'Gmail',
'ssl://imap.mail.me.com:993' => 'iCloud Mail',
'ssl://imap.mail.yahoo.com:993' => 'Yahoo Mail',
];
// Microsoft 365 through OAuth 2.0 (needs an app registration, see README). It goes first because that is where OAuth sign-ins are sent.
if (getenv('M365_CLIENT_ID')) {
$$hosts = ['ssl://outlook.office365.com:993' => 'Microsoft 365 (use the Microsoft button)'] + $$hosts;
$$tenant = getenv('M365_TENANT') ?: 'organizations';
$$config['oauth_provider'] = 'generic';
$$config['oauth_provider_name'] = 'Microsoft 365';
$$config['oauth_client_id'] = getenv('M365_CLIENT_ID');
$$config['oauth_client_secret'] = getenv('M365_CLIENT_SECRET');
$$config['oauth_auth_uri'] = "https://login.microsoftonline.com/$${tenant}/oauth2/v2.0/authorize";
$$config['oauth_token_uri'] = "https://login.microsoftonline.com/$${tenant}/oauth2/v2.0/token";
$$config['oauth_identity_uri'] = 'https://graph.microsoft.com/oidc/userinfo';
$$config['oauth_identity_fields'] = ['email', 'preferred_username'];
$$config['oauth_scope'] = 'email openid profile offline_access https://outlook.office365.com/IMAP.AccessAsUser.All https://outlook.office365.com/SMTP.Send';
$$config['oauth_login_redirect'] = false;
if (getenv('PUBLIC_URL')) $$config['oauth_redirect_uri'] = rtrim(getenv('PUBLIC_URL'), '/') . '/webmail/index.php/login/oauth';
}
$$config['imap_host'] = $$hosts;
$$config['smtp_host'] = 'tls://smtp.%z:587'; // smtp.gmail.com, smtp.mail.me.com, smtp.mail.yahoo.com
$$config['session_lifetime'] = 43200; // minutes (30 days); the hub's own sign-in is what protects access
// Behind the hub: believe its forwarded address and https.
if (($$_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') $$_SERVER['HTTPS'] = 'on';
if (!empty($$_SERVER['HTTP_X_FORWARDED_HOST'])) $$_SERVER['HTTP_HOST'] = $$_SERVER['HTTP_X_FORWARDED_HOST'];
// A fixed encryption key kept with the data, so saved settings and sessions survive restarts and updates.
$$kf = '/var/roundcube/db/des_key';
if (!is_file($$kf)) @file_put_contents($$kf, bin2hex(random_bytes(12)));
if (is_file($$kf)) $$config['des_key'] = trim(file_get_contents($$kf));
volumes: volumes:
hub-data: hub-data:
name: nehub-data # fixed name so the data survives redeploys and renamed stacks name: nehub-data # fixed name so the data survives redeploys and renamed stacks
+7
View File
@@ -0,0 +1,7 @@
# Roundcube webmail for NE Hub: the official image plus the hub's settings (so no config file has to be mounted).
FROM roundcube/roundcubemail:latest-apache
COPY hub.php /var/roundcube/config/hub.php
ENV ROUNDCUBEMAIL_DB_TYPE=sqlite \
ROUNDCUBEMAIL_REQUEST_PATH=/webmail/ \
ROUNDCUBEMAIL_PLUGINS=archive,zipdownload \
ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE=30M
+35
View File
@@ -0,0 +1,35 @@
<?php
// Settings for the Roundcube that comes with NE Hub.
$config['product_name'] = 'Mail';
$config['x_frame_options'] = false; // the hub shows this page inside its Mail tab (same address, so only the hub can frame it)
$hosts = [
'ssl://imap.gmail.com:993' => 'Gmail',
'ssl://imap.mail.me.com:993' => 'iCloud Mail',
'ssl://imap.mail.yahoo.com:993' => 'Yahoo Mail',
];
// Microsoft 365 through OAuth 2.0 (needs an app registration, see README). It goes first because that is where OAuth sign-ins are sent.
if (getenv('M365_CLIENT_ID')) {
$hosts = ['ssl://outlook.office365.com:993' => 'Microsoft 365 (use the Microsoft button)'] + $hosts;
$tenant = getenv('M365_TENANT') ?: 'organizations';
$config['oauth_provider'] = 'generic';
$config['oauth_provider_name'] = 'Microsoft 365';
$config['oauth_client_id'] = getenv('M365_CLIENT_ID');
$config['oauth_client_secret'] = getenv('M365_CLIENT_SECRET');
$config['oauth_auth_uri'] = "https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize";
$config['oauth_token_uri'] = "https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token";
$config['oauth_identity_uri'] = 'https://graph.microsoft.com/oidc/userinfo';
$config['oauth_identity_fields'] = ['email', 'preferred_username'];
$config['oauth_scope'] = 'email openid profile offline_access https://outlook.office365.com/IMAP.AccessAsUser.All https://outlook.office365.com/SMTP.Send';
$config['oauth_login_redirect'] = false;
if (getenv('PUBLIC_URL')) $config['oauth_redirect_uri'] = rtrim(getenv('PUBLIC_URL'), '/') . '/webmail/index.php/login/oauth';
}
$config['imap_host'] = $hosts;
$config['smtp_host'] = 'tls://smtp.%z:587'; // smtp.gmail.com, smtp.mail.me.com, smtp.mail.yahoo.com
$config['session_lifetime'] = 43200; // minutes (30 days); the hub's own sign-in is what protects access
// Behind the hub: believe its forwarded address and https.
if (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') $_SERVER['HTTPS'] = 'on';
if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
// A fixed encryption key kept with the data, so saved settings and sessions survive restarts and updates.
$kf = '/var/roundcube/db/des_key';
if (!is_file($kf)) @file_put_contents($kf, bin2hex(random_bytes(12)));
if (is_file($kf)) $config['des_key'] = trim(file_get_contents($kf));