diff --git a/GNUmakefile b/GNUmakefile
index d14ba1da..71e404bb 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -18,7 +18,7 @@ MAKEFLAGS += --no-builtin-rules
 
 VERSION_CODE := 45
 VERSION_CODE_IOS := 19
-VERSION_NUMBER := 0.2026.11-wip
+VERSION_NUMBER := 0.2026.10.1
 VERSION_NAME := This program kills fascists.
 
 IPHONEOS_VERSION_MIN=14.0
@@ -876,6 +876,10 @@ src/version.h : $(firstword $(MAKEFILE_LIST))
 	@echo "#define VERSION_NUMBER \"$(VERSION_NUMBER)\"" > $@
 	@echo "#define VERSION_NAME \"$(VERSION_NAME)\"" >> $@
 
+src/eula.h : core/eula.html
+	@echo "[xxd] $@"
+	@xxd -i -n k_eula $< $@
+
 src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
 	@echo "[android_version] $@"
 	@sed -i \
diff --git a/core/eula.html b/core/eula.html
new file mode 100644
index 00000000..ac3a2dce
--- /dev/null
+++ b/core/eula.html
@@ -0,0 +1,67 @@
+
+
+	
+		Tilde Friends End User License Agreement 
+		 
+		 
+	
+	
+		Tilde Friends End User License Agreement 
+		
+			Tilde Friends is an app that enables communication with other users
+			through the
+			Secure Scuttlebutt 
+			protocol.
+		
+		You are responsible for your actions 
+		
+			Apple tolerates no objectionable content or abusive users on their
+			platforms.
+		
+		
+			Your government, employer, family, friend group, etc. may impose
+			additional restrictions on acceptable behavior that you may be expected to
+			follow.
+		
+		
+			You are solely responsible for your own actions within this app,
+			especially but not limited to what content you choose to post.
+		
+		You choose what you see 
+		
+			You are in full control of the content you see in Tilde Friends by the
+			peers to which you choose to connect and the users you choose to follow.
+			Initially you will be following no one with no connections and as a result
+			see no content from other users.
+		
+		
+			If you find some content objectionable, you can filter it from your view
+			by blocking the user who posted it. This also makes it so that users
+			following you will not see it as a result of following you.
+		
+		
+			The admin app contains a variety of settings that control the
+			types of connections Tilde Friends will make or accept, including whether
+			the app will even accept or make connections at all.
+		
+		This app is not a service 
+		
+			This app relies on no official servers. The author of this app has no more
+			ability to see or filter what you post or read than any other user of the
+			network.
+		
+		
+			If you believe objectionable content obtained from a service that is
+			running as part of the Secure Scuttlebutt network should be flagged, look
+			for an email address contact for the service, generally by accessing the
+			server address in a web browser, and report it to the operator.
+		
+		Agree with these terms? 
+		If you do not accept these terms, do not use this app.
+		
+	
+
diff --git a/docs/usage.md b/docs/usage.md
index 3df02c73..c3912288 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -60,6 +60,7 @@ options:
                                  broadcast (default: true): Send network discovery broadcasts.
                                  discovery (default: true): Receive network discovery broadcasts.
                                  stay_connected (default: false): Whether to attempt to keep several peer connections open.
+                                 accepted_eula_crc (default: 0): The CRC32 of the last accepted EULA.
   -o, --one-proc             Run everything in one process (unsafely!).
   -z, --zip path             Zip archive from which to load files.
   -v, --verbose              Log raw messages.
diff --git a/src/android/AndroidManifest.xml b/src/android/AndroidManifest.xml
index 597cb421..0757f449 100644
--- a/src/android/AndroidManifest.xml
+++ b/src/android/AndroidManifest.xml
@@ -2,7 +2,7 @@
 
+	android:versionName="0.2026.10.1">
 	 
 	 
 	
+#endif
 
 #define CYAN "\e[1;36m"
 #define MAGENTA "\e[1;35m"
@@ -616,28 +622,99 @@ tf_httpd_user_app_t* tf_httpd_parse_user_app_from_path(const char* path, const c
 	return result;
 }
 
-static void _httpd_endpoint_root_callback(const char* path, void* user_data)
+typedef struct _root_t
 {
-	tf_http_request_t* request = user_data;
-	const char* headers[] = {
-		"Location",
-		path ? path : "/~core/apps/",
-	};
-	tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
-	tf_http_request_unref(request);
-}
+	tf_http_request_t* request;
+	const char* path;
+} root_t;
 
-static void _httpd_endpoint_root(tf_http_request_t* request)
+static void _httpd_root_work(tf_ssb_t* ssb, void* user_data)
 {
+	root_t* root = user_data;
+	tf_http_request_t* request = root->request;
 	const char* host = tf_http_request_get_header(request, "x-forwarded-host");
 	if (!host)
 	{
 		host = tf_http_request_get_header(request, "host");
 	}
+
+	bool require_eula =
+#if TARGET_OS_IPHONE
+		true;
+#else
+		false;
+#endif
+
+	int64_t accepted_eula_crc = 0;
+	uint32_t eula_crc = crc32(crc32(0, NULL, 0), k_eula, k_eula_len);
+
+	sqlite3* db = tf_ssb_acquire_db_reader(ssb);
+	tf_ssb_db_get_global_setting_int64(db, "accepted_eula_crc", &accepted_eula_crc);
+	if (require_eula && accepted_eula_crc != eula_crc)
+	{
+		root->path = tf_strdup("/static/eula.html");
+	}
+	else
+	{
+		root->path = tf_ssb_db_resolve_index(db, host);
+	}
+	tf_ssb_release_db_reader(ssb, db);
+}
+
+static void _httpd_root_after_work(tf_ssb_t* ssb, int status, void* user_data)
+{
+	root_t* root = user_data;
+	tf_http_request_t* request = root->request;
+	const char* headers[] = {
+		"Location",
+		root->path ? root->path : "/~core/apps/",
+	};
+	tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
+	tf_http_request_unref(request);
+	tf_free((void*)root->path);
+	tf_free(root);
+}
+
+static void _httpd_endpoint_root(tf_http_request_t* request)
+{
+	root_t* root = tf_malloc(sizeof(root_t));
+	*root = (root_t) {
+		.request = request,
+	};
+	tf_http_request_ref(request);
 	tf_task_t* task = request->user_data;
 	tf_ssb_t* ssb = tf_task_get_ssb(task);
+	tf_ssb_run_work(ssb, _httpd_root_work, _httpd_root_after_work, root);
+}
+
+static void _httpd_accept_eula_work(tf_ssb_t* ssb, void* user_data)
+{
+	uint32_t eula_crc = crc32(crc32(0, NULL, 0), k_eula, k_eula_len);
+	char buffer[64];
+	snprintf(buffer, sizeof(buffer), "%u", eula_crc);
+
+	sqlite3* db = tf_ssb_acquire_db_writer(ssb);
+	tf_ssb_db_set_global_setting_from_string(db, "accepted_eula_crc", buffer);
+	tf_ssb_release_db_writer(ssb, db);
+}
+
+static void _httpd_accept_eula_after_work(tf_ssb_t* ssb, int status, void* user_data)
+{
+	tf_http_request_t* request = user_data;
+	const char* headers[] = {
+		"Location",
+		"/",
+	};
+	tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
+	tf_http_request_unref(request);
+}
+
+static void _httpd_endpoint_accept_eula(tf_http_request_t* request)
+{
 	tf_http_request_ref(request);
-	tf_ssb_db_resolve_index_async(ssb, host, _httpd_endpoint_root_callback, request);
+	tf_task_t* task = request->user_data;
+	tf_ssb_t* ssb = tf_task_get_ssb(task);
+	tf_ssb_run_work(ssb, _httpd_accept_eula_work, _httpd_accept_eula_after_work, request);
 }
 
 static void _httpd_endpoint_robots_txt(tf_http_request_t* request)
@@ -913,6 +990,7 @@ tf_http_t* tf_httpd_create(JSContext* context)
 	tf_http_add_handler(http, "/login/logout", tf_httpd_endpoint_logout, NULL, task);
 	tf_http_add_handler(http, "/login/auto", tf_httpd_endpoint_login_auto, NULL, task);
 	tf_http_add_handler(http, "/login", tf_httpd_endpoint_login, NULL, task);
+	tf_http_add_handler(http, "/eula/accept", _httpd_endpoint_accept_eula, NULL, task);
 
 	tf_http_add_handler(http, "/app/socket", tf_httpd_endpoint_app_socket, NULL, task);
 
diff --git a/src/httpd.static.c b/src/httpd.static.c
index 87e5a051..08796f97 100644
--- a/src/httpd.static.c
+++ b/src/httpd.static.c
@@ -124,6 +124,7 @@ void tf_httpd_endpoint_static(tf_http_request_t* request)
 
 	const char* k_static_files[] = {
 		"index.html",
+		"eula.html",
 		"client.js",
 		"tildefriends.svg",
 		"jszip.min.js",
diff --git a/src/ios/Info.plist b/src/ios/Info.plist
index 1589b23f..3812f891 100644
--- a/src/ios/Info.plist
+++ b/src/ios/Info.plist
@@ -13,7 +13,7 @@
 	CFBundlePackageType 
 	APPL 
 	CFBundleShortVersionString 
-	0.2026.11 
+	0.2026.10.1 
 	CFBundleSupportedPlatforms 
 	
 		iPhoneOS 
diff --git a/src/ssb.db.c b/src/ssb.db.c
index 0cf4bc08..6f800293 100644
--- a/src/ssb.db.c
+++ b/src/ssb.db.c
@@ -2186,56 +2186,40 @@ bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* pa
 	return found;
 }
 
-typedef struct _resolve_index_t
+const char* tf_ssb_db_resolve_index(sqlite3* db, const char* host)
 {
-	const char* host;
-	const char* path;
-	void (*callback)(const char* path, void* user_data);
-	void* user_data;
-} resolve_index_t;
+	const char* result = NULL;
 
-static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data)
-{
-	resolve_index_t* request = user_data;
-
-	sqlite3* db = tf_ssb_acquire_db_reader(ssb);
-	sqlite3_stmt* statement;
-	if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
+	if (!result)
 	{
-		if (sqlite3_step(statement) == SQLITE_ROW)
-		{
-			const char* index_map = (const char*)sqlite3_column_text(statement, 0);
-			const char* start = index_map;
-			while (start)
-			{
-				const char* end = strchr(start, '\n');
-				const char* equals = strchr(start, '=');
-				if (equals && strncasecmp(request->host, start, equals - start) == 0)
-				{
-					size_t value_length = end && equals < end ? (size_t)(end - (equals + 1)) : strlen(equals + 1);
-					char* path = tf_malloc(value_length + 1);
-					memcpy(path, equals + 1, value_length);
-					path[value_length] = '\0';
-					request->path = path;
-					break;
-				}
-				start = end ? end + 1 : NULL;
-			}
-		}
-		sqlite3_finalize(statement);
-	}
-	else
-	{
-		tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
+		/* Maybe we need to force the EULA first. */
 	}
 
-	if (!request->path)
+	if (!result)
 	{
-		if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
+		/* Use the index_map setting. */
+		sqlite3_stmt* statement;
+		if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
 		{
 			if (sqlite3_step(statement) == SQLITE_ROW)
 			{
-				request->path = tf_strdup((const char*)sqlite3_column_text(statement, 0));
+				const char* index_map = (const char*)sqlite3_column_text(statement, 0);
+				const char* start = index_map;
+				while (start)
+				{
+					const char* end = strchr(start, '\n');
+					const char* equals = strchr(start, '=');
+					if (equals && strncasecmp(host, start, equals - start) == 0)
+					{
+						size_t value_length = end && equals < end ? (size_t)(end - (equals + 1)) : strlen(equals + 1);
+						char* path = tf_malloc(value_length + 1);
+						memcpy(path, equals + 1, value_length);
+						path[value_length] = '\0';
+						result = path;
+						break;
+					}
+					start = end ? end + 1 : NULL;
+				}
 			}
 			sqlite3_finalize(statement);
 		}
@@ -2244,32 +2228,32 @@ static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data)
 			tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
 		}
 	}
-	tf_ssb_release_db_reader(ssb, db);
 
-	if (!request->path)
+	if (!result)
 	{
-		request->path = tf_strdup(tf_util_get_default_global_setting_string("index"));
+		/* Use the index setting. */
+		sqlite3_stmt* statement;
+		if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
+		{
+			if (sqlite3_step(statement) == SQLITE_ROW)
+			{
+				result = tf_strdup((const char*)sqlite3_column_text(statement, 0));
+			}
+			sqlite3_finalize(statement);
+		}
+		else
+		{
+			tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
+		}
 	}
-}
 
-static void _tf_ssb_db_resolve_index_after_work(tf_ssb_t* ssb, int status, void* user_data)
-{
-	resolve_index_t* request = user_data;
-	request->callback(request->path, request->user_data);
-	tf_free((void*)request->host);
-	tf_free((void*)request->path);
-	tf_free(request);
-}
+	if (!result)
+	{
+		/* Use the default index. */
+		result = tf_strdup(tf_util_get_default_global_setting_string("index"));
+	}
 
-void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data)
-{
-	resolve_index_t* request = tf_malloc(sizeof(resolve_index_t));
-	*request = (resolve_index_t) {
-		.host = tf_strdup(host),
-		.callback = callback,
-		.user_data = user_data,
-	};
-	tf_ssb_run_work(ssb, _tf_ssb_db_resolve_index_work, _tf_ssb_db_resolve_index_after_work, request);
+	return result;
 }
 
 static void _tf_ssb_db_set_flags(tf_ssb_t* ssb, const char* message_id, int flags)
@@ -2514,7 +2498,7 @@ bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, cha
 				bound = sqlite3_bind_int(statement, 2, value && (strcmp(value, "true") == 0 || atoi(value))) == SQLITE_OK;
 				break;
 			case k_kind_int:
-				bound = sqlite3_bind_int(statement, 2, atoi(value)) == SQLITE_OK;
+				bound = sqlite3_bind_int64(statement, 2, atoll(value)) == SQLITE_OK;
 				break;
 			case k_kind_string:
 				bound = sqlite3_bind_text(statement, 2, value, -1, NULL) == SQLITE_OK;
diff --git a/src/ssb.db.h b/src/ssb.db.h
index e6aacae7..badc9a9b 100644
--- a/src/ssb.db.h
+++ b/src/ssb.db.h
@@ -445,12 +445,11 @@ bool tf_ssb_db_add_value_to_array_property(tf_ssb_t* ssb, const char* id, const
 
 /**
 ** Resolve a hostname to its index path by global settings.
-** @param ssb The SSB instance.
+** @param db The database.
 ** @param host The hostname.
-** @param callback The callback.
-** @param user_data The callback user data.
+** @return The resolved index.  Free with tf_free().
 */
-void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data);
+const char* tf_ssb_db_resolve_index(sqlite3* db, const char* host);
 
 /**
 ** Verify an author's feed.
diff --git a/src/util.js.c b/src/util.js.c
index 351342b0..b3d1badc 100644
--- a/src/util.js.c
+++ b/src/util.js.c
@@ -338,6 +338,7 @@ static const setting_t k_settings[] = {
 		.type = "boolean",
 		.description = "Whether to attempt to keep several peer connections open.",
 		.default_value = { .kind = k_kind_bool, .bool_value = false } },
+	{ .name = "accepted_eula_crc", .type = "hidden", .description = "The CRC32 of the last accepted EULA.", .default_value = { .kind = k_kind_int, .int_value = 0 } },
 };
 
 static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind)
diff --git a/src/version.h b/src/version.h
index 26d35b04..1b216591 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,2 +1,2 @@
-#define VERSION_NUMBER "0.2026.11-wip"
+#define VERSION_NUMBER "0.2026.10.1"
 #define VERSION_NAME "This program kills fascists."