diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index a04640d993..8c214d52c7 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -5,6 +5,129 @@
 
 'use strict';
 
+class Systemd {
+    constructor() {
+        this.tty_dom = document.querySelector('#tty');
+        console.log('Systemd started');
+    }
+    async start(id, promise) {
+        let state = { state: 'running' };
+        let persistentDom = null;
+        const started = Date.now();
+        const formatRunning = () => {
+            const shiftArray = (arr, n) => {
+                return arr.slice(n).concat(arr.slice(0, n));
+            };
+            const elapsed_secs = Math.floor((Date.now() - started) / 1000);
+            const stars = shiftArray(['*', '*', '*', ' ', ' ', ' '], elapsed_secs % 6);
+            const spanStatus = document.createElement('span');
+            spanStatus.innerText = stars.join('');
+            spanStatus.className = 'tty-status-running';
+            const spanMessage = document.createElement('span');
+            spanMessage.innerText = `A start job is running for ${id} (${elapsed_secs}s / no limit)`;
+            const div = document.createElement('div');
+            div.className = 'tty-line';
+            div.innerHTML = '[';
+            div.appendChild(spanStatus);
+            div.innerHTML += '] ';
+            div.appendChild(spanMessage);
+            return div;
+        };
+        const formatDone = () => {
+            const elapsed_secs = Math.floor((Date.now() - started) / 1000);
+            const spanStatus = document.createElement('span');
+            spanStatus.innerText = '  OK  ';
+            spanStatus.className = 'tty-status-ok';
+            const spanMessage = document.createElement('span');
+            spanMessage.innerText = `Finished ${id} in ${elapsed_secs}s`;
+            const div = document.createElement('div');
+            div.className = 'tty-line';
+            div.innerHTML = '[';
+            div.appendChild(spanStatus);
+            div.innerHTML += '] ';
+            div.appendChild(spanMessage);
+            return div;
+        };
+        const formatFailed = (message) => {
+            const elapsed_secs = Math.floor((Date.now() - started) / 1000);
+            const spanStatus = document.createElement('span');
+            spanStatus.innerText = 'FAILED';
+            spanStatus.className = 'tty-status-failed';
+            const spanMessage = document.createElement('span');
+            spanMessage.innerText = `Failed ${id} in ${elapsed_secs}s: ${message}`;
+            const div = document.createElement('div');
+            div.className = 'tty-line';
+            div.innerHTML = '[';
+            div.appendChild(spanStatus);
+            div.innerHTML += '] ';
+            div.appendChild(spanMessage);
+            return div;
+        };
+        const render = () => {
+            switch (state.state) {
+                case 'running':
+                    if (persistentDom === null) {
+                        persistentDom = formatRunning();
+                        this.tty_dom.appendChild(persistentDom);
+                    }
+                    else {
+                        persistentDom.innerHTML = formatRunning().innerHTML;
+                    }
+                    break;
+                case 'done':
+                    if (persistentDom === null) {
+                        persistentDom = formatDone();
+                        this.tty_dom.appendChild(persistentDom);
+                    }
+                    else {
+                        persistentDom.innerHTML = formatDone().innerHTML;
+                    }
+                    break;
+                case 'failed':
+                    if (persistentDom === null) {
+                        persistentDom = formatFailed(state.message);
+                        this.tty_dom.appendChild(persistentDom);
+                    }
+                    else {
+                        persistentDom.innerHTML = formatFailed(state.message).innerHTML;
+                    }
+                    break;
+            }
+        };
+        render();
+        const interval = setInterval(render, 500);
+        try {
+            let res = await promise;
+            state = { state: 'done' };
+            return res;
+        }
+        catch (e) {
+            if (e instanceof Error) {
+                state = { state: 'failed', message: e.message };
+            }
+            else {
+                state = { state: 'failed', message: 'Unknown error' };
+            }
+            throw e;
+        }
+        finally {
+            clearInterval(interval);
+            render();
+        }
+    }
+    async startSync(id, func) {
+        return this.start(id, (async () => {
+            return func();
+        })());
+    }
+    emergency_mode() {
+        const div = document.createElement('div');
+        div.className = 'tty-line';
+        div.innerText = 'You are in emergency mode. Type Ctrl-Shift-I to view logs.';
+        this.tty_dom.appendChild(div);
+    }
+}
+
 // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
 (async () => {
 	window.onerror = (e) => {
@@ -16,6 +139,15 @@
 		renderError('SOMETHING_HAPPENED_IN_PROMISE', e);
 	};
 
+	const cmdline = new URLSearchParams(location.search).get('cmdline') || '';
+	const cmdlineArray = cmdline.split(',').map(x => x.trim());
+	if (cmdlineArray.includes('nosplash')) {
+		document.querySelector('#splashIcon').classList.add('hidden');
+		document.querySelector('#splashSpinner').classList.add('hidden');
+	}
+
+	const systemd = new Systemd();
+
 	let forceError = localStorage.getItem('forceError');
 	if (forceError != null) {
 		renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
@@ -37,7 +169,7 @@
 			}
 		}
 
-		const metaRes = await window.fetch('/api/meta', {
+		const metaRes = await systemd.start('Fetch /api/meta',window.fetch('/api/meta', {
 			method: 'POST',
 			body: JSON.stringify({}),
 			credentials: 'omit',
@@ -45,12 +177,12 @@
 			headers: {
 				'Content-Type': 'application/json',
 			},
-		});
+		}));
 		if (metaRes.status !== 200) {
 			renderError('META_FETCH');
 			return;
 		}
-		const meta = await metaRes.json();
+		const meta = await systemd.start('Parse /api/meta', metaRes.json());
 		const v = meta.version;
 		if (v == null) {
 			renderError('META_FETCH_V');
@@ -63,7 +195,7 @@
 			lang = 'en-US';
 		}
 
-		const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
+		const localRes = await systemd.start(window.fetch(`/assets/locales/${lang}.${v}.json`));
 		if (localRes.status === 200) {
 			localStorage.setItem('lang', lang);
 			localStorage.setItem('locale', await localRes.text());
@@ -86,10 +218,10 @@
 
 	// タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
 	if (document.readyState !== 'loading') {
-		importAppScript();
+	 	systemd.start('import App Script', importAppScript());
 	} else {
 		window.addEventListener('DOMContentLoaded', () => {
-			importAppScript();
+			 systemd.start('import App Script', importAppScript());
 		});
 	}
 	//#endregion
@@ -97,19 +229,21 @@
 	//#region Theme
 	const theme = localStorage.getItem('theme');
 	if (theme) {
-		for (const [k, v] of Object.entries(JSON.parse(theme))) {
-			document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
+		await systemd.startSync('Apply theme', () => {
+			for (const [k, v] of Object.entries(JSON.parse(theme))) {
+				document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
 
-			// HTMLの theme-color 適用
-			if (k === 'htmlThemeColor') {
-				for (const tag of document.head.children) {
-					if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
-						tag.setAttribute('content', v);
-						break;
+				// HTMLの theme-color 適用
+				if (k === 'htmlThemeColor') {
+					for (const tag of document.head.children) {
+						if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
+							tag.setAttribute('content', v);
+							break;
+						}
 					}
 				}
 			}
-		}
+		});
 	}
 	const colorScheme = localStorage.getItem('colorScheme');
 	if (colorScheme) {
@@ -134,15 +268,26 @@
 
 	const customCss = localStorage.getItem('customCss');
 	if (customCss && customCss.length > 0) {
-		const style = document.createElement('style');
-		style.innerHTML = customCss;
-		document.head.appendChild(style);
+		await systemd.startSync('Apply custom CSS', () => {
+			const style = document.createElement('style');
+			style.innerHTML = customCss;
+			document.head.appendChild(style);
+		});
 	}
 
 	async function addStyle(styleText) {
-		let css = document.createElement('style');
-		css.appendChild(document.createTextNode(styleText));
-		document.head.appendChild(css);
+		await systemd.startSync('Apply custom Style', () => {
+			let css = document.createElement('style');
+			css.appendChild(document.createTextNode(styleText));
+			document.head.appendChild(css);
+		});
+	}
+
+	if (cmdlineArray.includes('fail')) {
+		await systemd.start('User Requested Fail', new Promise((_, reject) => {
+			reject(new Error('Failed by command line'));
+		}
+		));
 	}
 
 	async function renderError(code, details) {
@@ -151,6 +296,8 @@
 			await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
 		}
 
+		systemd.emergency_mode();
+
 		let errorsElement = document.getElementById('errors');
 
 		if (!errorsElement) {
@@ -160,7 +307,7 @@
 				<path d="M12 9v2m0 4v.01"></path>
 				<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
 			</svg>
-			<h1>Failed to load<br>読み込みに失敗しました</h1>
+			<h1>You are in emergency mode! Failed to load<br>読み込みに失敗しました</h1>
 			<button class="button-big" onclick="location.reload(true);">
 				<span class="button-label-big">Reload / リロード</span>
 			</button>
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index 81f1838a4a..88559bf8f1 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -9,6 +9,32 @@ html {
 	color: var(--MI_THEME-fg);
 }
 
+.hidden {
+	display: none;
+}
+
+#tty {
+	font-family: monospace;
+	z-index: 10001;
+	opacity: 1;
+}
+
+#tty > tty-line {
+	display: block;
+}
+
+#tty > tty-line .tty-status-ok {
+	color: green;
+}
+
+#tty > tty-line .tty-status-error {
+	color: darkred;
+}
+
+#tty > tty-line .tty-status-running {
+	color: red;
+}
+
 #splash {
 	position: fixed;
 	z-index: 10000;
diff --git a/packages/backend/src/server/web/systemd.ts b/packages/backend/src/server/web/systemd.ts
new file mode 100644
index 0000000000..d5cb01097d
--- /dev/null
+++ b/packages/backend/src/server/web/systemd.ts
@@ -0,0 +1,151 @@
+export class Systemd {
+    private tty_dom: HTMLDivElement;
+    constructor() {
+        this.tty_dom = document.querySelector('#tty') as HTMLDivElement;
+
+        console.log('Systemd started');
+    }
+
+    async start<T>(id: string, promise: Promise<T>): Promise<T> {
+
+        let state: {
+            state: 'running'
+        } | {
+            state: 'done'
+        } | {
+            state: 'failed'
+            message: string
+        } = { state: 'running' };
+
+        let persistentDom : HTMLDivElement | null = null;
+
+        const started = Date.now();
+
+        const formatRunning = () => {
+            const shiftArray = <T>(arr: T[], n: number): T[] => {
+                return arr.slice(n).concat(arr.slice(0, n));
+            };
+
+            const elapsed_secs = Math.floor((Date.now() - started) / 1000);
+            const stars = shiftArray(['*', '*', '*', ' ', ' ', ' '], elapsed_secs % 6);
+
+            const spanStatus = document.createElement('span');
+
+            spanStatus.innerText = stars.join('');
+            spanStatus.className = 'tty-status-running';
+
+            const spanMessage = document.createElement('span');
+            spanMessage.innerText = `A start job is running for ${id} (${elapsed_secs}s / no limit)`;
+
+            const div = document.createElement('div');
+            div.className = 'tty-line';
+            div.innerHTML = '[';
+            div.appendChild(spanStatus);
+            div.innerHTML += '] ';
+            div.appendChild(spanMessage);
+
+            return div;
+        }
+
+        const formatDone = () => {
+            const elapsed_secs = Math.floor((Date.now() - started) / 1000);
+
+            const spanStatus = document.createElement('span');
+            spanStatus.innerText = '  OK  ';
+            spanStatus.className = 'tty-status-ok';
+
+            const spanMessage = document.createElement('span');
+            spanMessage.innerText = `Finished ${id} in ${elapsed_secs}s`;
+
+            const div = document.createElement('div');
+            div.className = 'tty-line';
+            div.innerHTML = '[';
+            div.appendChild(spanStatus);
+            div.innerHTML += '] ';
+            div.appendChild(spanMessage);
+
+            return div;
+        }
+
+        const formatFailed = (message: string) => {
+            const elapsed_secs = Math.floor((Date.now() - started) / 1000);
+
+            const spanStatus = document.createElement('span');
+            spanStatus.innerText = 'FAILED';
+            spanStatus.className = 'tty-status-failed';
+
+            const spanMessage = document.createElement('span');
+            spanMessage.innerText = `Failed ${id} in ${elapsed_secs}s: ${message}`;
+
+            const div = document.createElement('div');
+            div.className = 'tty-line';
+            div.innerHTML = '[';
+            div.appendChild(spanStatus);
+            div.innerHTML += '] ';
+            div.appendChild(spanMessage);
+
+            return div;
+        }
+
+        const render = () => {
+            switch (state.state) {
+                case 'running':
+                    if (persistentDom === null) {
+                        persistentDom = formatRunning();
+                        this.tty_dom.appendChild(persistentDom);
+                    } else {
+                        persistentDom.innerHTML = formatRunning().innerHTML;
+                    }
+                    break;
+                case 'done':
+                    if (persistentDom === null) {
+                        persistentDom = formatDone();
+                        this.tty_dom.appendChild(persistentDom);
+                    } else {
+                        persistentDom.innerHTML = formatDone().innerHTML;
+                    }
+                    break;
+                case 'failed':
+                    if (persistentDom === null) {
+                        persistentDom = formatFailed(state.message);
+                        this.tty_dom.appendChild(persistentDom);
+                    } else {
+                        persistentDom.innerHTML = formatFailed(state.message).innerHTML;
+                    }
+                    break;
+            }
+        };
+
+        render();
+        const interval = setInterval(render, 500);
+    
+        try {
+            let res = await promise;
+            state = { state: 'done' };
+            return res;
+        } catch (e) {
+            if (e instanceof Error) {
+                state = { state: 'failed', message: e.message };
+            } else {
+                state = { state: 'failed', message: 'Unknown error' };
+            }
+            throw e;
+        } finally {
+            clearInterval(interval);
+            render();
+        }
+    }
+
+    async startSync<T>(id: string, func: () => T): Promise<T> {
+        return this.start(id, (async () => {
+            return func();
+        })());
+	}
+
+    public emergency_mode() {
+        const div = document.createElement('div');
+        div.className = 'tty-line';
+        div.innerText = 'You are in emergency mode. Type Ctrl-Shift-I to view logs.';
+        this.tty_dom.appendChild(div);
+    }
+}
\ No newline at end of file
diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug
index bc1352ace4..c434f33747 100644
--- a/packages/backend/src/server/web/views/base-embed.pug
+++ b/packages/backend/src/server/web/views/base-embed.pug
@@ -56,6 +56,7 @@ html(class='embed')
 			br
 			| Please turn on your JavaScript
 		div#splash
+			div#tty
 			img#splashIcon(src= icon || '/static-assets/splash.png')
 			div#splashSpinner
 				<span>Loading...</span>
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 8fbfdacabd..405d050d90 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -65,7 +65,6 @@ html
 		script(type='application/json' id='misskey_clientCtx' data-generated-at=now)
 			!= clientCtx
 
-		script(integrity=bootJS.integrity) !{bootJS.content}
 
 	body
 		noscript: p
@@ -73,7 +72,11 @@ html
 			br
 			| Please turn on your JavaScript
 		div#splash
+			div#tty
 			img#splashIcon(src= icon || '/static-assets/splash.png')
 			div#splashSpinner
 				<span>Loading...</span>
+
+		script(integrity=bootJS.integrity) !{bootJS.content}
+	
 		block content