Skip to content

Commit 1bf4677

Browse files
committed
feat(config): allow to override some config values by hostname
Signed-off-by: Benjamin Gaussorgues <[email protected]>
1 parent fe73d6e commit 1bf4677

File tree

5 files changed

+63
-9
lines changed

5 files changed

+63
-9
lines changed

build/psalm-baseline.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3538,10 +3538,6 @@
35383538
<code><![CDATA[$this->delete($key)]]></code>
35393539
<code><![CDATA[$this->set($key, $value)]]></code>
35403540
</InvalidOperand>
3541-
<UndefinedVariable>
3542-
<code><![CDATA[$CONFIG]]></code>
3543-
<code><![CDATA[$CONFIG]]></code>
3544-
</UndefinedVariable>
35453541
</file>
35463542
<file src="lib/private/Console/Application.php">
35473543
<NoInterfaceProperties>

config/config.sample.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@
4545
*/
4646
'instanceid' => '',
4747

48+
/**
49+
* This is a unique identifier for your server.
50+
* It is useful when your Nextcloud instance is spread between different servers.
51+
* Once it's set it shouldn't be changed.
52+
*
53+
* Value must be an integer, comprised between 0 and 1023.
54+
*
55+
* This value should be overriden by hostname in $CONFIG_HOSTNAME
56+
*/
57+
'serverid' => -1,
58+
4859
/**
4960
* The salt used to hash all passwords, auto-generated by the Nextcloud
5061
* installer. (There are also per-user salts.) If you lose this salt, you lose
@@ -2811,3 +2822,18 @@
28112822
*/
28122823
'enable_lazy_objects' => true,
28132824
];
2825+
2826+
/**
2827+
* CONFIG_HOSTNAME allows to set specific configuration value on specific hosts.
2828+
* It should be used when configuration is stored on a shared filesystem between several servers.
2829+
*
2830+
* Only options listed in \OC\Config::HOST_OVERRIDE_CONFIG can be defined here.
2831+
*/
2832+
$CONFIG_HOSTNAME = [
2833+
'hostname_a' => [
2834+
'serverid' => 42,
2835+
],
2836+
'hostname_b' => [
2837+
'serverid' => 43,
2838+
],
2839+
];

lib/private/Config.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
class Config {
1717
public const ENV_PREFIX = 'NC_';
18+
// List configurations that can be overriden based on server hostname
19+
private const HOST_OVERRIDE_CONFIG = [
20+
'serverid',
21+
];
1822

1923
/** @var array Associative array ($key => $value) */
2024
protected $cache = [];
@@ -199,7 +203,7 @@ private function readData() {
199203

200204
// Include file and merge config
201205
foreach ($configFiles as $file) {
202-
unset($CONFIG);
206+
$CONFIG = $CONFIG_HOSTNAME = null;
203207

204208
// Invalidate opcache (only if the timestamp changed)
205209
if (function_exists('opcache_invalidate')) {
@@ -226,6 +230,10 @@ private function readData() {
226230
}
227231

228232
try {
233+
/**
234+
* @var ?array $CONFIG
235+
* @var ?array $CONFIG_HOSTNAME
236+
*/
229237
include $file;
230238
} finally {
231239
// Close the file pointer and release the lock
@@ -241,9 +249,20 @@ private function readData() {
241249
}
242250
throw new \Exception($errorMessage);
243251
}
244-
if (isset($CONFIG) && is_array($CONFIG)) {
252+
if (is_array($CONFIG)) {
245253
$this->cache = array_merge($this->cache, $CONFIG);
246254
}
255+
if (is_array($CONFIG_HOSTNAME) && !empty($CONFIG_HOSTNAME)) {
256+
$hostname = gethostname();
257+
if (isset($CONFIG_HOSTNAME[$hostname]) && is_array($CONFIG_HOSTNAME[$hostname])) {
258+
$filteredConfig = array_filter(
259+
$CONFIG_HOSTNAME[$hostname],
260+
fn ($key) => in_array($key, self::HOST_OVERRIDE_CONFIG),
261+
ARRAY_FILTER_USE_KEY,
262+
);
263+
$this->cache = array_merge($this->cache, $filteredConfig);
264+
}
265+
}
247266
}
248267

249268
// grab any "NC_" environment variables

lib/private/SnowflakeIdGenerator.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use OCP\AppFramework\Utility\ITimeFactory;
1313
use OCP\ICacheFactory;
14+
use OCP\IConfig;
1415

1516
/**
1617
* Nextcloud Snowflake ID generator
@@ -22,6 +23,7 @@
2223
class SnowflakeIdGenerator {
2324
public function __construct(
2425
private readonly ITimeFactory $timeFactory,
26+
private readonly IConfig $config,
2527
private readonly ICacheFactory $cacheFactory,
2628
) {
2729
}
@@ -100,7 +102,10 @@ private function getCurrentTime(): array {
100102
}
101103

102104
private function getServerId(): int {
103-
return crc32(gethostname() ?: random_bytes(8));
105+
$serverid = $this->config->getSystemValueInt('serverid', -1);
106+
return $serverid > 0
107+
? $serverid
108+
: crc32(gethostname() ?: random_bytes(8));
104109
}
105110

106111
private function isCli(): bool {

tests/lib/SnowflakeIdGeneratorTest.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OC\SnowflakeIdGenerator;
1313
use OCP\AppFramework\Utility\ITimeFactory;
1414
use OCP\ICacheFactory;
15+
use OCP\IConfig;
1516
use PHPUnit\Framework\Attributes\DataProvider;
1617
use PHPUnit\Framework\MockObject\MockObject;
1718

@@ -20,13 +21,18 @@
2021
*/
2122
class SnowflakeIdGeneratorTest extends TestCase {
2223
private ICacheFactory|MockObject $cacheFactory;
24+
private IConfig|MockObject $config;
2325

2426
public function setUp(): void {
2527
$this->cacheFactory = $this->createMock(ICacheFactory::class);
28+
$this->config = $this->createMock(IConfig::class);
29+
$this->config->method('getSystemValueInt')
30+
->with('serverid')
31+
->willReturn(42);
2632
}
2733

2834
public function testGenerator(): void {
29-
$generator = new SnowflakeIdGenerator(new TimeFactory(), $this->cacheFactory);
35+
$generator = new SnowflakeIdGenerator(new TimeFactory(), $this->config, $this->cacheFactory);
3036
$snowflakeId = ($generator)();
3137
$this->assertGreaterThan(0x100000000, $snowflakeId);
3238
if (PHP_INT_SIZE === 8) {
@@ -42,11 +48,13 @@ public function testGeneratorWithFixedTime(string $date, int $expectedSeconds, i
4248
$timeFactory = $this->createMock(ITimeFactory::class);
4349
$timeFactory->method('now')->willReturn($dt);
4450

45-
$generator = new SnowflakeIdGenerator($timeFactory, $this->cacheFactory);
51+
$generator = new SnowflakeIdGenerator($timeFactory, $this->config, $this->cacheFactory);
4652
$snowflakeId = new SnowflakeId($generator());
4753

4854
$this->assertEquals($expectedSeconds, $snowflakeId->seconds());
4955
$this->assertEquals($expectedMilliseconds, $snowflakeId->milliseconds());
56+
$this->assertTrue($snowflakeId->isCli());
57+
$this->assertEquals(42, $snowflakeId->serverId());
5058
}
5159

5260
public static function provideSnowflakeData(): array {

0 commit comments

Comments
 (0)