Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 0 additions & 4 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3522,10 +3522,6 @@
<code><![CDATA[$this->delete($key)]]></code>
<code><![CDATA[$this->set($key, $value)]]></code>
</InvalidOperand>
<UndefinedVariable>
<code><![CDATA[$CONFIG]]></code>
<code><![CDATA[$CONFIG]]></code>
</UndefinedVariable>
</file>
<file src="lib/private/Console/Application.php">
<NoInterfaceProperties>
Expand Down
26 changes: 26 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@
*/
'instanceid' => '',

/**
* This is a unique identifier for your server.
* It is useful when your Nextcloud instance is spread between different servers.
* Once it's set it shouldn't be changed.
*
* Value must be an integer, comprised between 0 and 1023.
*
* This value should be overriden by hostname in $CONFIG_HOSTNAME
*/
'serverid' => -1,

/**
* The salt used to hash all passwords, auto-generated by the Nextcloud
* installer. (There are also per-user salts.) If you lose this salt, you lose
Expand Down Expand Up @@ -2813,3 +2824,18 @@
*/
'enable_lazy_objects' => true,
];

/**
* CONFIG_HOSTNAME allows to set specific configuration value on specific hosts.
* It should be used when configuration is stored on a shared filesystem between several servers.
*
* Only options listed in \OC\Config::HOST_OVERRIDE_CONFIG can be defined here.
*/
$CONFIG_HOSTNAME = [
'hostname_a' => [
'serverid' => 42,
],
'hostname_b' => [
'serverid' => 43,
],
];
23 changes: 21 additions & 2 deletions lib/private/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
*/
class Config {
public const ENV_PREFIX = 'NC_';
// List configurations that can be overriden based on server hostname
private const HOST_OVERRIDE_CONFIG = [
'serverid',
];

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

// Include file and merge config
foreach ($configFiles as $file) {
unset($CONFIG);
$CONFIG = $CONFIG_HOSTNAME = null;

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

try {
/**
* @var ?array $CONFIG
* @var ?array $CONFIG_HOSTNAME
*/
include $file;
} finally {
// Close the file pointer and release the lock
Expand All @@ -241,9 +249,20 @@ private function readData() {
}
throw new \Exception($errorMessage);
}
if (isset($CONFIG) && is_array($CONFIG)) {
if (is_array($CONFIG)) {
$this->cache = array_merge($this->cache, $CONFIG);
}
if (is_array($CONFIG_HOSTNAME) && !empty($CONFIG_HOSTNAME)) {
$hostname = gethostname();
if (isset($CONFIG_HOSTNAME[$hostname]) && is_array($CONFIG_HOSTNAME[$hostname])) {
$filteredConfig = array_filter(
$CONFIG_HOSTNAME[$hostname],
fn ($key) => in_array($key, self::HOST_OVERRIDE_CONFIG),
ARRAY_FILTER_USE_KEY,
);
$this->cache = array_merge($this->cache, $filteredConfig);
}
}
}

// grab any "NC_" environment variables
Expand Down
7 changes: 6 additions & 1 deletion lib/private/Snowflake/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace OC\Snowflake;

use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\Snowflake\IGenerator;
use Override;

Expand All @@ -23,6 +24,7 @@
final class Generator implements IGenerator {
public function __construct(
private readonly ITimeFactory $timeFactory,
private readonly IConfig $config,
) {
}

Expand Down Expand Up @@ -100,7 +102,10 @@ private function getCurrentTime(): array {
}

private function getServerId(): int {
return crc32(gethostname() ?: random_bytes(8));
$serverid = $this->config->getSystemValueInt('serverid', -1);
return $serverid > 0
? $serverid
: crc32(gethostname() ?: random_bytes(8));
}

private function isCli(): bool {
Expand Down
14 changes: 12 additions & 2 deletions tests/lib/Snowflake/GeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OC\Snowflake\Decoder;
use OC\Snowflake\Generator;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\Snowflake\IGenerator;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestCase;
Expand All @@ -22,12 +23,17 @@
*/
class GeneratorTest extends TestCase {
private Decoder $decoder;
private IConfig|MockObject $config;

public function setUp():void {
$this->decoder = new Decoder();
$this->config = $this->createMock(IConfig::class);
$this->config->method('getSystemValueInt')
->with('serverid')
->willReturn(42);
}
public function testGenerator(): void {
$generator = new Generator(new TimeFactory());
$generator = new Generator(new TimeFactory(), $this->config);
$snowflakeId = $generator->nextId();
$data = $this->decoder->decode($generator->nextId());

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

// Check CLI
$this->assertTrue($data['isCli']);

// Check serverId
$this->assertEquals(42, $data['serverId']);
}

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

$generator = new Generator($timeFactory);
$generator = new Generator($timeFactory, $this->config);
$data = $this->decoder->decode($generator->nextId());

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

public static function provideSnowflakeData(): array {
Expand Down
Loading