diff --git a/CHANGELOG.md b/CHANGELOG.md index 8580865a..6630e6c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ **Minimum supported Tailscale client version: v1.xx.0** +## 0.29.1 (2026-06-18) + +**Minimum supported Tailscale client version: v1.80.0** + +### Changes + +- Fix nodes with `tags='null'` losing their assigned user on upgrade [#3325](https://github.com/juanfont/headscale/pull/3325) + ## 0.29.0 (2026-06-17) **Minimum supported Tailscale client version: v1.80.0** diff --git a/hscontrol/db/db.go b/hscontrol/db/db.go index 8c088c49..f247a2fa 100644 --- a/hscontrol/db/db.go +++ b/hscontrol/db/db.go @@ -706,13 +706,20 @@ AND auth_key_id NOT IN ( // but this prevents deleting users whose nodes have been // tagged, and the ON DELETE CASCADE FK would destroy the // tagged nodes if the user were deleted. + // + // A nil tags slice marshals to the JSON literal 'null', so + // untagged nodes can carry tags='null'. That spelling must be + // excluded alongside '[]' and '' or untagged nodes lose their + // user. Nodes already detached by the earlier version of this + // migration are repaired by the recovery migration below. // Fixes: https://github.com/juanfont/headscale/issues/3077 + // Fixes: https://github.com/juanfont/headscale/issues/3323 ID: "202602201200-clear-tagged-node-user-id", Migrate: func(tx *gorm.DB) error { err := tx.Exec(` UPDATE nodes SET user_id = NULL -WHERE tags IS NOT NULL AND tags != '[]' AND tags != ''; +WHERE tags IS NOT NULL AND tags != '[]' AND tags != '' AND tags != 'null'; `).Error if err != nil { return fmt.Errorf("clearing user_id on tagged nodes: %w", err) @@ -744,6 +751,36 @@ WHERE expiry IS NOT NULL AND expiry < '1900-01-01'; }, Rollback: func(db *gorm.DB) error { return nil }, }, + { + // Recover user_id on untagged nodes detached by the earlier + // version of 202602201200-clear-tagged-node-user-id, which + // treated tags='null' as tagged and cleared the user. This + // repairs databases that already upgraded to 0.29.0; fresh + // upgrades are protected by the fixed migration above and find + // nothing to repair. Recovery is best-effort: the owner is + // re-derived from the node's pre-auth key, so nodes registered + // via CLI/OIDC (no pre-auth key) cannot be recovered and must + // be reassigned manually. + // Fixes: https://github.com/juanfont/headscale/issues/3323 + ID: "202606181200-recover-null-tags-node-user-id", + Migrate: func(tx *gorm.DB) error { + err := tx.Exec(` +UPDATE nodes +SET user_id = ( + SELECT pak.user_id FROM pre_auth_keys pak WHERE pak.id = nodes.auth_key_id +) +WHERE user_id IS NULL + AND auth_key_id IS NOT NULL + AND (tags IS NULL OR tags = '' OR tags = '[]' OR tags = 'null'); + `).Error + if err != nil { + return fmt.Errorf("recovering user_id on untagged nodes: %w", err) + } + + return nil + }, + Rollback: func(db *gorm.DB) error { return nil }, + }, }, ) diff --git a/hscontrol/db/db_test.go b/hscontrol/db/db_test.go index 60abcfa0..98feef09 100644 --- a/hscontrol/db/db_test.go +++ b/hscontrol/db/db_test.go @@ -198,6 +198,103 @@ func TestSQLiteMigrationAndDataValidation(t *testing.T) { assert.False(t, node5.IsExpired(), "node5 should not be reported as expired") }, }, + // Test for the clear-tagged-node-user-id migration + // (202602201200-clear-tagged-node-user-id). A nil tags slice + // marshals to the JSON literal 'null', so untagged nodes can carry + // tags='null' in the database. The migration must only clear + // user_id on genuinely tagged nodes, not on these untagged ones. + // Fixes: https://github.com/juanfont/headscale/issues/3323 + { + dbPath: "testdata/sqlite/null_tags_user_id_migration_test.sql", + wantFunc: func(t *testing.T, hsdb *HSDatabase) { + t.Helper() + + nodes, err := Read(hsdb.DB, func(rx *gorm.DB) (types.Nodes, error) { + return ListNodes(rx) + }) + require.NoError(t, err) + require.Len(t, nodes, 4, "should have all 4 nodes") + + byHostname := make(map[string]*types.Node, len(nodes)) + for _, n := range nodes { + byHostname[n.Hostname] = n + } + + // Node 1 had tags='null' (untagged) and belonged to user2. + // The migration must NOT clear its user_id. + node1 := byHostname["node1"] + require.NotNil(t, node1, "node1 should exist") + assert.False(t, node1.IsTagged(), "node1 with tags='null' should be untagged") + require.NotNil(t, node1.UserID, "node1 should keep its user assigned") + assert.Equal(t, uint(2), *node1.UserID, "node1 should still belong to user2") + + // Node 2 is genuinely tagged; user_id must be cleared. + node2 := byHostname["node2"] + require.NotNil(t, node2, "node2 should exist") + assert.True(t, node2.IsTagged(), "node2 should be tagged") + assert.Nil(t, node2.UserID, "node2 (tagged) should have user_id cleared") + + // Node 3 had tags='[]' (untagged); user_id preserved. + node3 := byHostname["node3"] + require.NotNil(t, node3, "node3 should exist") + assert.False(t, node3.IsTagged(), "node3 with tags='[]' should be untagged") + require.NotNil(t, node3.UserID, "node3 should keep its user assigned") + assert.Equal(t, uint(1), *node3.UserID, "node3 should still belong to user1") + + // Node 4 had tags='' (untagged); user_id preserved. + node4 := byHostname["node4"] + require.NotNil(t, node4, "node4 should exist") + assert.False(t, node4.IsTagged(), "node4 with tags='' should be untagged") + require.NotNil(t, node4.UserID, "node4 should keep its user assigned") + assert.Equal(t, uint(1), *node4.UserID, "node4 should still belong to user1") + }, + }, + // Test for the null-tags user_id recovery migration. Databases that + // already upgraded to 0.29.0 had user_id wrongly cleared on untagged + // nodes with tags='null'. The recovery migration re-derives user_id + // from the node's pre-auth key where one exists. + // Fixes: https://github.com/juanfont/headscale/issues/3323 + { + dbPath: "testdata/sqlite/recover_null_tags_user_id_migration_test.sql", + wantFunc: func(t *testing.T, hsdb *HSDatabase) { + t.Helper() + + nodes, err := Read(hsdb.DB, func(rx *gorm.DB) (types.Nodes, error) { + return ListNodes(rx) + }) + require.NoError(t, err) + require.Len(t, nodes, 4, "should have all 4 nodes") + + byHostname := make(map[string]*types.Node, len(nodes)) + for _, n := range nodes { + byHostname[n.Hostname] = n + } + + // Node 1: authkey-registered, orphaned by the bug. The recovery + // migration restores user_id from its pre-auth key (user2). + node1 := byHostname["node1"] + require.NotNil(t, node1, "node1 should exist") + require.NotNil(t, node1.UserID, "node1 user_id should be recovered") + assert.Equal(t, uint(2), *node1.UserID, "node1 should be recovered to user2") + + // Node 2: genuinely tagged, correctly cleared. Must stay cleared. + node2 := byHostname["node2"] + require.NotNil(t, node2, "node2 should exist") + assert.True(t, node2.IsTagged(), "node2 should be tagged") + assert.Nil(t, node2.UserID, "node2 (tagged) must remain cleared") + + // Node 3: CLI-registered, no pre-auth key. Unrecoverable. + node3 := byHostname["node3"] + require.NotNil(t, node3, "node3 should exist") + assert.Nil(t, node3.UserID, "node3 has no pre-auth key to recover from") + + // Node 4: never orphaned; user_id must be untouched. + node4 := byHostname["node4"] + require.NotNil(t, node4, "node4 should exist") + require.NotNil(t, node4.UserID, "node4 user_id should be untouched") + assert.Equal(t, uint(1), *node4.UserID, "node4 should still belong to user1") + }, + }, } for _, tt := range tests { diff --git a/hscontrol/db/testdata/sqlite/null_tags_user_id_migration_test.sql b/hscontrol/db/testdata/sqlite/null_tags_user_id_migration_test.sql new file mode 100644 index 00000000..c45d0ff5 --- /dev/null +++ b/hscontrol/db/testdata/sqlite/null_tags_user_id_migration_test.sql @@ -0,0 +1,85 @@ +-- Test SQL dump for the clear-tagged-node-user-id migration +-- (202602201200-clear-tagged-node-user-id) against nodes whose tags +-- column holds the JSON literal 'null'. +-- +-- A nil Strings slice marshals to the JSON literal `null`, so pre-0.29 +-- databases contain untagged nodes with tags='null'. The migration's +-- WHERE clause (tags IS NOT NULL AND tags != '[]' AND tags != '') treats +-- the 4-character string 'null' as "tagged" and wrongly clears user_id, +-- detaching the node from its owning user on upgrade. +-- Fixes: https://github.com/juanfont/headscale/issues/3323 + +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; + +-- Migrations table: every entry BEFORE clear-tagged-node-user-id has been +-- applied. That migration is intentionally absent so it runs against this dump. +CREATE TABLE `migrations` (`id` text,PRIMARY KEY (`id`)); +INSERT INTO migrations VALUES('202312101416'); +INSERT INTO migrations VALUES('202312101430'); +INSERT INTO migrations VALUES('202402151347'); +INSERT INTO migrations VALUES('2024041121742'); +INSERT INTO migrations VALUES('202406021630'); +INSERT INTO migrations VALUES('202409271400'); +INSERT INTO migrations VALUES('202407191627'); +INSERT INTO migrations VALUES('202408181235'); +INSERT INTO migrations VALUES('202501221827'); +INSERT INTO migrations VALUES('202501311657'); +INSERT INTO migrations VALUES('202502070949'); +INSERT INTO migrations VALUES('202502131714'); +INSERT INTO migrations VALUES('202502171819'); +INSERT INTO migrations VALUES('202505091439'); +INSERT INTO migrations VALUES('202505141324'); +INSERT INTO migrations VALUES('202507021200'); +INSERT INTO migrations VALUES('202510311551'); +INSERT INTO migrations VALUES('202511101554-drop-old-idx'); +INSERT INTO migrations VALUES('202511011637-preauthkey-bcrypt'); +INSERT INTO migrations VALUES('202511122344-remove-newline-index'); +INSERT INTO migrations VALUES('202511131445-node-forced-tags-to-tags'); +INSERT INTO migrations VALUES('202601121700-migrate-hostinfo-request-tags'); + +-- Users table +CREATE TABLE `users` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`display_name` text,`email` text,`provider_identifier` text,`provider` text,`profile_pic_url` text); +INSERT INTO users VALUES(1,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user1','User One','user1@example.com',NULL,NULL,NULL); +INSERT INTO users VALUES(2,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user2','User Two','user2@example.com',NULL,NULL,NULL); + +-- Pre-auth keys table +CREATE TABLE `pre_auth_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`key` text,`user_id` integer,`reusable` numeric,`ephemeral` numeric DEFAULT false,`used` numeric DEFAULT false,`tags` text,`created_at` datetime,`expiration` datetime,`prefix` text,`hash` blob,CONSTRAINT `fk_pre_auth_keys_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL); + +-- API keys table +CREATE TABLE `api_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`prefix` text,`hash` blob,`created_at` datetime,`expiration` datetime,`last_seen` datetime); + +-- Nodes table - current schema (after the tags rename + last_seen/expiry reordering) +CREATE TABLE IF NOT EXISTS "nodes" (`id` integer PRIMARY KEY AUTOINCREMENT,`machine_key` text,`node_key` text,`disco_key` text,`endpoints` text,`host_info` text,`ipv4` text,`ipv6` text,`hostname` text,`given_name` varchar(63),`user_id` integer,`register_method` text,`tags` text,`auth_key_id` integer,`last_seen` datetime,`expiry` datetime,`approved_routes` text,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,CONSTRAINT `fk_nodes_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_nodes_auth_key` FOREIGN KEY (`auth_key_id`) REFERENCES `pre_auth_keys`(`id`)); + +-- Node 1: tags='null' (untagged, nil slice marshalled to JSON null), owned by user2. +-- After migration: user_id MUST be preserved (this is the bug). +INSERT INTO nodes VALUES(1,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e01','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605501','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57701','[]','{}','100.64.0.1','fd7a:115c:a1e0::1','node1','node1',2,'cli','null',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Node 2: genuinely tagged, owned by user1. +-- After migration: user_id MUST be cleared to NULL (tagged nodes are owned by tags). +INSERT INTO nodes VALUES(2,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e02','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605502','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57702','[]','{}','100.64.0.2','fd7a:115c:a1e0::2','node2','node2',1,'cli','["tag:server"]',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Node 3: empty-array tags (untagged), owned by user1. +-- After migration: user_id MUST be preserved. +INSERT INTO nodes VALUES(3,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e03','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605503','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57703','[]','{}','100.64.0.3','fd7a:115c:a1e0::3','node3','node3',1,'cli','[]',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Node 4: empty-string tags (untagged), owned by user1. +-- After migration: user_id MUST be preserved. +INSERT INTO nodes VALUES(4,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e04','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605504','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57704','[]','{}','100.64.0.4','fd7a:115c:a1e0::4','node4','node4',1,'cli','',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Policies table (empty) +CREATE TABLE `policies` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`data` text); + +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('users',2); +INSERT INTO sqlite_sequence VALUES('nodes',4); +CREATE INDEX idx_users_deleted_at ON users(deleted_at); +CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix); +CREATE INDEX idx_policies_deleted_at ON policies(deleted_at); +CREATE UNIQUE INDEX idx_provider_identifier ON users(provider_identifier) WHERE provider_identifier IS NOT NULL; +CREATE UNIQUE INDEX idx_name_provider_identifier ON users(name, provider_identifier); +CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL; +CREATE UNIQUE INDEX IF NOT EXISTS idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''; + +COMMIT; diff --git a/hscontrol/db/testdata/sqlite/recover_null_tags_user_id_migration_test.sql b/hscontrol/db/testdata/sqlite/recover_null_tags_user_id_migration_test.sql new file mode 100644 index 00000000..163dcd35 --- /dev/null +++ b/hscontrol/db/testdata/sqlite/recover_null_tags_user_id_migration_test.sql @@ -0,0 +1,87 @@ +-- Test SQL dump for the null-tags user_id RECOVERY migration. +-- +-- Represents a database that already upgraded to 0.29.0, where the buggy +-- clear-tagged-node-user-id migration (202602201200) already cleared +-- user_id on untagged nodes whose tags column held 'null'. The recovery +-- migration runs against this state and re-derives user_id from the node's +-- pre-auth key where possible. +-- Fixes: https://github.com/juanfont/headscale/issues/3323 + +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; + +-- Migrations table: everything through the current last migration has been +-- applied (this DB already ran the buggy clear-tagged migration). The new +-- recovery migration is intentionally absent so it runs against this dump. +CREATE TABLE `migrations` (`id` text,PRIMARY KEY (`id`)); +INSERT INTO migrations VALUES('202312101416'); +INSERT INTO migrations VALUES('202312101430'); +INSERT INTO migrations VALUES('202402151347'); +INSERT INTO migrations VALUES('2024041121742'); +INSERT INTO migrations VALUES('202406021630'); +INSERT INTO migrations VALUES('202409271400'); +INSERT INTO migrations VALUES('202407191627'); +INSERT INTO migrations VALUES('202408181235'); +INSERT INTO migrations VALUES('202501221827'); +INSERT INTO migrations VALUES('202501311657'); +INSERT INTO migrations VALUES('202502070949'); +INSERT INTO migrations VALUES('202502131714'); +INSERT INTO migrations VALUES('202502171819'); +INSERT INTO migrations VALUES('202505091439'); +INSERT INTO migrations VALUES('202505141324'); +INSERT INTO migrations VALUES('202507021200'); +INSERT INTO migrations VALUES('202510311551'); +INSERT INTO migrations VALUES('202511101554-drop-old-idx'); +INSERT INTO migrations VALUES('202511011637-preauthkey-bcrypt'); +INSERT INTO migrations VALUES('202511122344-remove-newline-index'); +INSERT INTO migrations VALUES('202511131445-node-forced-tags-to-tags'); +INSERT INTO migrations VALUES('202601121700-migrate-hostinfo-request-tags'); +INSERT INTO migrations VALUES('202602201200-clear-tagged-node-user-id'); +INSERT INTO migrations VALUES('202605221435-clear-zero-time-node-expiry'); + +-- Users table +CREATE TABLE `users` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`display_name` text,`email` text,`provider_identifier` text,`provider` text,`profile_pic_url` text); +INSERT INTO users VALUES(1,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user1','User One','user1@example.com',NULL,NULL,NULL); +INSERT INTO users VALUES(2,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user2','User Two','user2@example.com',NULL,NULL,NULL); + +-- Pre-auth keys table. Key 1 belongs to user2, key 2 to user1. +CREATE TABLE `pre_auth_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`key` text,`user_id` integer,`reusable` numeric,`ephemeral` numeric DEFAULT false,`used` numeric DEFAULT false,`tags` text,`created_at` datetime,`expiration` datetime,`prefix` text,`hash` blob,CONSTRAINT `fk_pre_auth_keys_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL); +INSERT INTO pre_auth_keys VALUES(1,NULL,2,1,false,true,NULL,'2024-01-01 00:00:00+00:00',NULL,'pak1',NULL); +INSERT INTO pre_auth_keys VALUES(2,NULL,1,1,false,true,NULL,'2024-01-01 00:00:00+00:00',NULL,'pak2',NULL); + +-- API keys table +CREATE TABLE `api_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`prefix` text,`hash` blob,`created_at` datetime,`expiration` datetime,`last_seen` datetime); + +-- Nodes table +CREATE TABLE IF NOT EXISTS "nodes" (`id` integer PRIMARY KEY AUTOINCREMENT,`machine_key` text,`node_key` text,`disco_key` text,`endpoints` text,`host_info` text,`ipv4` text,`ipv6` text,`hostname` text,`given_name` varchar(63),`user_id` integer,`register_method` text,`tags` text,`auth_key_id` integer,`last_seen` datetime,`expiry` datetime,`approved_routes` text,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,CONSTRAINT `fk_nodes_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_nodes_auth_key` FOREIGN KEY (`auth_key_id`) REFERENCES `pre_auth_keys`(`id`)); + +-- Node 1: authkey-registered, tags='null', already orphaned (user_id NULL) by +-- the buggy migration. auth_key_id=1 (user2). Recovery: user_id -> 2. +INSERT INTO nodes VALUES(1,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e01','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605501','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57701','[]','{}','100.64.0.1','fd7a:115c:a1e0::1','node1','node1',NULL,'authkey','null',1,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Node 2: genuinely tagged, user_id correctly cleared. Must stay NULL. +INSERT INTO nodes VALUES(2,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e02','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605502','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57702','[]','{}','100.64.0.2','fd7a:115c:a1e0::2','node2','node2',NULL,'authkey','["tag:server"]',2,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Node 3: CLI-registered, tags='null', orphaned, no auth_key_id. +-- Unrecoverable: must stay NULL. +INSERT INTO nodes VALUES(3,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e03','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605503','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57703','[]','{}','100.64.0.3','fd7a:115c:a1e0::3','node3','node3',NULL,'cli','null',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Node 4: authkey-registered, untouched (user_id still set). Must stay user1. +INSERT INTO nodes VALUES(4,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e04','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605504','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57704','[]','{}','100.64.0.4','fd7a:115c:a1e0::4','node4','node4',1,'authkey','null',2,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); + +-- Policies table (empty) +CREATE TABLE `policies` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`data` text); + +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('users',2); +INSERT INTO sqlite_sequence VALUES('pre_auth_keys',2); +INSERT INTO sqlite_sequence VALUES('nodes',4); +CREATE INDEX idx_users_deleted_at ON users(deleted_at); +CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix); +CREATE INDEX idx_policies_deleted_at ON policies(deleted_at); +CREATE UNIQUE INDEX idx_provider_identifier ON users(provider_identifier) WHERE provider_identifier IS NOT NULL; +CREATE UNIQUE INDEX idx_name_provider_identifier ON users(name, provider_identifier); +CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL; +CREATE UNIQUE INDEX IF NOT EXISTS idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''; + +COMMIT; diff --git a/mkdocs.yml b/mkdocs.yml index 18b6fc93..1d08b833 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -111,7 +111,7 @@ extra: - icon: fontawesome/brands/discord link: https://discord.gg/c84AZQhmpx headscale: - version: 0.29.0 + version: 0.29.1 # Extensions markdown_extensions: