diff --git a/controllers/admin/config.go b/controllers/admin/config.go
index f7c5af058..be0192d20 100644
--- a/controllers/admin/config.go
+++ b/controllers/admin/config.go
@@ -751,6 +751,26 @@ func SetHideViewerCount(w http.ResponseWriter, r *http.Request) {
controllers.WriteSimpleResponse(w, true, "hide viewer count setting updated")
}
+// SetDisableSearchIndexing will set search indexing support.
+func SetDisableSearchIndexing(w http.ResponseWriter, r *http.Request) {
+ if !requirePOST(w, r) {
+ return
+ }
+
+ configValue, success := getValueFromRequest(w, r)
+ if !success {
+ controllers.WriteSimpleResponse(w, false, "unable to update search indexing")
+ return
+ }
+
+ if err := data.SetDisableSearchIndexing(configValue.Value.(bool)); err != nil {
+ controllers.WriteSimpleResponse(w, false, err.Error())
+ return
+ }
+
+ controllers.WriteSimpleResponse(w, true, "search indexing support updated")
+}
+
func requirePOST(w http.ResponseWriter, r *http.Request) bool {
if r.Method != controllers.POST {
controllers.WriteSimpleResponse(w, false, r.Method+" not supported")
diff --git a/controllers/admin/serverConfig.go b/controllers/admin/serverConfig.go
index f301f9fbc..d069a5479 100644
--- a/controllers/admin/serverConfig.go
+++ b/controllers/admin/serverConfig.go
@@ -61,6 +61,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
SocketHostOverride: data.GetWebsocketOverrideHost(),
ChatEstablishedUserMode: data.GetChatEstbalishedUsersOnlyMode(),
HideViewerCount: data.GetHideViewerCount(),
+ DisableSearchIndexing: data.GetDisableSearchIndexing(),
VideoSettings: videoSettings{
VideoQualityVariants: videoQualityVariants,
LatencyLevel: data.GetStreamLatencyLevel().Level,
@@ -121,6 +122,7 @@ type serverConfigAdminResponse struct {
ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"`
StreamKeyOverridden bool `json:"streamKeyOverridden"`
HideViewerCount bool `json:"hideViewerCount"`
+ DisableSearchIndexing bool `json:"disableSearchIndexing"`
}
type videoSettings struct {
diff --git a/controllers/robots.go b/controllers/robots.go
new file mode 100644
index 000000000..4605d3da4
--- /dev/null
+++ b/controllers/robots.go
@@ -0,0 +1,28 @@
+package controllers
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/owncast/owncast/core/data"
+)
+
+// GetRobotsDotTxt returns the contents of our robots.txt.
+func GetRobotsDotTxt(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ contents := []string{
+ "User-agent: *",
+ "Disallow: /admin",
+ "Disallow: /api",
+ }
+
+ if data.GetDisableSearchIndexing() {
+ contents = append(contents, "Disallow: /")
+ }
+
+ txt := []byte(strings.Join(contents, "\n"))
+
+ if _, err := w.Write(txt); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
diff --git a/core/data/config.go b/core/data/config.go
index 48e8feb5b..c7dd3c53b 100644
--- a/core/data/config.go
+++ b/core/data/config.go
@@ -69,6 +69,7 @@ const (
customOfflineMessageKey = "custom_offline_message"
customColorVariableValuesKey = "custom_color_variable_values"
streamKeysKey = "stream_keys"
+ disableSearchIndexingKey = "disable_search_indexing"
)
// GetExtraPageBodyContent will return the user-supplied body content.
@@ -959,3 +960,17 @@ func SetStreamKeys(actions []models.StreamKey) error {
configEntry := ConfigEntry{Key: streamKeysKey, Value: actions}
return _datastore.Save(configEntry)
}
+
+// SetDisableSearchIndexing will set if the web server should be indexable.
+func SetDisableSearchIndexing(disableSearchIndexing bool) error {
+ return _datastore.SetBool(disableSearchIndexingKey, disableSearchIndexing)
+}
+
+// GetDisableSearchIndexing will return if the web server should be indexable.
+func GetDisableSearchIndexing() bool {
+ disableSearchIndexing, err := _datastore.GetBool(disableSearchIndexingKey)
+ if err != nil {
+ return false
+ }
+ return disableSearchIndexing
+}
diff --git a/router/router.go b/router/router.go
index 3001cf43e..fd9583172 100644
--- a/router/router.go
+++ b/router/router.go
@@ -50,6 +50,9 @@ func Start() error {
// return a logo that's compatible with external social networks
http.HandleFunc("/logo/external", controllers.GetCompatibleLogo)
+ // robots.txt
+ http.HandleFunc("/robots.txt", controllers.GetRobotsDotTxt)
+
// status of the system
http.HandleFunc("/api/status", controllers.GetStatus)
@@ -327,6 +330,9 @@ func Start() error {
// Is the viewer count hidden from viewers
http.HandleFunc("/api/admin/config/hideviewercount", middleware.RequireAdminAuth(admin.SetHideViewerCount))
+ // set disabling of search indexing
+ http.HandleFunc("/api/admin/config/disablesearchindexing", middleware.RequireAdminAuth(admin.SetDisableSearchIndexing))
+
// Inline chat moderation actions
// Update chat message visibility
diff --git a/test/automated/api/configmanagement.test.js b/test/automated/api/configmanagement.test.js
index af41c1fb5..5bab5a318 100644
--- a/test/automated/api/configmanagement.test.js
+++ b/test/automated/api/configmanagement.test.js
@@ -37,6 +37,8 @@ const defaultFederationConfig = {
blockedDomains: [],
};
const defaultHideViewerCount = false;
+const defaultDisableSearchIndexing = false;
+
const defaultSocialHandles = [
{
icon: '/img/platformlogos/github.svg',
@@ -130,6 +132,7 @@ const newFederationConfig = {
};
const newHideViewerCount = !defaultHideViewerCount;
+const newDisableSearchIndexing = !defaultDisableSearchIndexing;
const overriddenWebsocketHost = 'ws://lolcalhost.biz';
const customCSS = randomString();
@@ -340,6 +343,14 @@ test('enable federation', async (done) => {
done();
});
+test('disable search indexing', async (done) => {
+ await sendAdminRequest(
+ 'config/disablesearchindexing',
+ newDisableSearchIndexing
+ );
+ done();
+});
+
test('change admin password', async (done) => {
const res = await sendAdminRequest('config/adminpass', newAdminPassword);
done();
@@ -472,3 +483,18 @@ test('verify frontend status', (done) => {
done();
});
});
+
+test('verify robots.txt is correct after disabling search indexing', (done) => {
+ const expected = `User-agent: *
+Disallow: /admin
+Disallow: /api
+Disallow: /`;
+
+ request
+ .get('/robots.txt')
+ .expect(200)
+ .then((res) => {
+ expect(res.text).toBe(expected);
+ done();
+ });
+});
diff --git a/web/components/admin/config/general/EditInstanceDetails.tsx b/web/components/admin/config/general/EditInstanceDetails.tsx
index 9c9ec7535..243a2497b 100644
--- a/web/components/admin/config/general/EditInstanceDetails.tsx
+++ b/web/components/admin/config/general/EditInstanceDetails.tsx
@@ -21,6 +21,7 @@ import {
FIELD_PROPS_NSFW,
FIELD_PROPS_HIDE_VIEWER_COUNT,
API_SERVER_OFFLINE_MESSAGE,
+ FIELD_PROPS_DISABLE_SEARCH_INDEXING,
} from '../../../../utils/config-constants';
import { UpdateArgs } from '../../../../types/config-section';
import { ToggleSwitch } from '../../ToggleSwitch';
@@ -36,7 +37,7 @@ export default function EditInstanceDetails() {
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
- const { instanceDetails, yp, hideViewerCount } = serverConfig;
+ const { instanceDetails, yp, hideViewerCount, disableSearchIndexing } = serverConfig;
const { instanceUrl } = yp;
const [offlineMessageSaveStatus, setOfflineMessageSaveStatus] = useState(null);
@@ -46,6 +47,7 @@ export default function EditInstanceDetails() {
...instanceDetails,
...yp,
hideViewerCount,
+ disableSearchIndexing,
});
}, [instanceDetails, yp]);
@@ -87,6 +89,10 @@ export default function EditInstanceDetails() {
handleFieldChange({ fieldName: 'hideViewerCount', value: enabled });
}
+ function handleDisableSearchEngineIndexingChange(enabled: boolean) {
+ handleFieldChange({ fieldName: 'disableSearchIndexing', value: enabled });
+ }
+
const hasInstanceUrl = instanceUrl !== '';
return (
@@ -171,6 +177,14 @@ export default function EditInstanceDetails() {
onChange={handleHideViewerCountChange}
/>
+
Increase your audience by appearing in the{' '} diff --git a/web/types/config-section.ts b/web/types/config-section.ts index 0f0fece19..b70ee8bed 100644 --- a/web/types/config-section.ts +++ b/web/types/config-section.ts @@ -156,4 +156,5 @@ export interface ConfigDetails { chatJoinMessagesEnabled: boolean; chatEstablishedUserMode: boolean; hideViewerCount: boolean; + disableSearchIndexing: boolean; } diff --git a/web/utils/config-constants.tsx b/web/utils/config-constants.tsx index e0ef3dc23..0ec5e56cb 100644 --- a/web/utils/config-constants.tsx +++ b/web/utils/config-constants.tsx @@ -38,7 +38,7 @@ const API_HIDE_VIEWER_COUNT = '/hideviewercount'; const API_CHAT_DISABLE = '/chat/disable'; const API_CHAT_JOIN_MESSAGES_ENABLED = '/chat/joinmessagesenabled'; const API_CHAT_ESTABLISHED_MODE = '/chat/establishedusermode'; - +const API_DISABLE_SEARCH_INDEXING = '/disablesearchindexing'; const API_SOCKET_HOST_OVERRIDE = '/sockethostoverride'; // Federation @@ -212,6 +212,13 @@ export const FIELD_PROPS_HIDE_VIEWER_COUNT = { tip: 'Turn this ON to hide the viewer count on the web page.', }; +export const FIELD_PROPS_DISABLE_SEARCH_INDEXING = { + apiPath: API_DISABLE_SEARCH_INDEXING, + configPath: '', + label: 'Disable search engine indexing', + tip: 'Turn this ON to to tell search engines not to index this site.', +}; + export const DEFAULT_VARIANT_STATE: VideoVariant = { framerate: 24, videoPassthrough: false, diff --git a/web/utils/server-status-context.tsx b/web/utils/server-status-context.tsx index 70328f919..de3304bc9 100644 --- a/web/utils/server-status-context.tsx +++ b/web/utils/server-status-context.tsx @@ -71,6 +71,7 @@ const initialServerConfigState: ConfigDetails = { chatJoinMessagesEnabled: true, chatEstablishedUserMode: false, hideViewerCount: false, + disableSearchIndexing: false, }; const initialServerStatusState = {