Skip to content

Commit a03b66f

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

File tree

5 files changed

+65
-9
lines changed

5 files changed

+65
-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
@@ -2810,3 +2821,18 @@
28102821
*/
28112822
'enable_lazy_objects' => true,
28122823
];
2824+
2825+
/**
2826+
* CONFIG_HOSTNAME allows to set specific configuration value on specific hosts.
2827+
* It should be used when configuration is stored on a shared filesystem between several servers.
2828+
*
2829+
* Only options listed in \OC\Config::HOST_OVERRIDE_CONFIG can be defined here.
2830+
*/
2831+
$CONFIG_HOSTNAME = [
2832+
'hostname_a' => [
2833+
'serverid' => 42,
2834+
],
2835+
'hostname_b' => [
2836+
'serverid' => 43,
2837+
],
2838+
];

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/Snowflake/Generator.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace OC\Snowflake;
1111

1212
use OCP\AppFramework\Utility\ITimeFactory;
13+
use OCP\IConfig;
1314
use OCP\Snowflake\IGenerator;
1415
use Override;
1516

@@ -23,6 +24,7 @@
2324
final class Generator implements IGenerator {
2425
public function __construct(
2526
private readonly ITimeFactory $timeFactory,
27+
private readonly IConfig $config,
2628
) {
2729
}
2830

@@ -101,7 +103,10 @@ private function getCurrentTime(): array {
101103
}
102104

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

107112
private function isCli(): bool {

tests/lib/Snowflake/GeneratorTest.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OC\Snowflake\Decoder;
1414
use OC\Snowflake\Generator;
1515
use OCP\AppFramework\Utility\ITimeFactory;
16+
use OCP\IConfig;
1617
use OCP\Snowflake\IGenerator;
1718
use PHPUnit\Framework\Attributes\DataProvider;
1819
use Test\TestCase;
@@ -22,12 +23,17 @@
2223
*/
2324
class GeneratorTest extends TestCase {
2425
private Decoder $decoder;
26+
private IConfig|MockObject $config;
2527

2628
public function setUp():void {
2729
$this->decoder = new Decoder();
30+
$this->config = $this->createMock(IConfig::class);
31+
$this->config->method('getSystemValueInt')
32+
->with('serverid')
33+
->willReturn(42);
2834
}
2935
public function testGenerator(): void {
30-
$generator = new Generator(new TimeFactory());
36+
$generator = new Generator(new TimeFactory(), $this->config);
3137
$snowflakeId = $generator->nextId();
3238
$data = $this->decoder->decode($generator->nextId());
3339

@@ -45,6 +51,9 @@ public function testGenerator(): void {
4551

4652
// Check CLI
4753
$this->assertTrue($data['isCli']);
54+
55+
// Check serverId
56+
$this->assertEquals(42, $data['serverId']);
4857
}
4958

5059
#[DataProvider('provideSnowflakeData')]
@@ -53,11 +62,12 @@ public function testGeneratorWithFixedTime(string $date, int $expectedSeconds, i
5362
$timeFactory = $this->createMock(ITimeFactory::class);
5463
$timeFactory->method('now')->willReturn($dt);
5564

56-
$generator = new Generator($timeFactory);
65+
$generator = new Generator($timeFactory, $this->config);
5766
$data = $this->decoder->decode($generator->nextId());
5867

5968
$this->assertEquals($expectedSeconds, ($data['createdAt']->format('U') - IGenerator::TS_OFFSET));
6069
$this->assertEquals($expectedMilliseconds, (int)$data['createdAt']->format('v'));
70+
$this->assertEquals(42, $data['serverId']);
6171
}
6272

6373
public static function provideSnowflakeData(): array {

0 commit comments

Comments
 (0)