Android-studio / start.sh
proti0070's picture
Update start.sh
7624539 verified
#!/bin/bash
export DISPLAY=:99
export ANDROID_SDK_ROOT=/opt/android-sdk
export ANDROID_HOME=/opt/android-sdk
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:/opt/android-studio/bin
# Clean locks
rm -f /tmp/.X99-lock /tmp/.X11-unix/X99
echo "Starting Xvfb..."
Xvfb :99 -screen 0 1280x800x16 -ac &
sleep 3
echo "Starting Openbox..."
# Create a minimal menu file to avoid the warning
mkdir -p /var/lib/openbox
cat > /var/lib/openbox/debian-menu.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<openbox_menu>
<menu id="root-menu" label="Openbox">
<item label="Android Studio">
<action name="Execute">
<command>/opt/android-studio/bin/studio.sh</command>
</action>
</item>
<item label="Terminal">
<action name="Execute">
<command>xterm</command>
</action>
</item>
</menu>
</openbox_menu>
EOF
openbox --replace &
sleep 2
echo "Starting Android Studio..."
/opt/android-studio/bin/studio.sh &
sleep 5
echo "Starting VNC..."
x11vnc -display :99 -nopw -forever -shared -rfbport 5900 -defer 10 -wait 10 -q &
sleep 3
# Create a simple landing page that redirects to vnc.html
cat > /usr/share/novnc/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0; url=/vnc.html">
<title>Redirecting to Android Studio VNC</title>
</head>
<body>
Redirecting to <a href="/vnc.html">Android Studio VNC</a>...
</body>
</html>
EOF
# Create a custom vnc.html that works with HF Spaces
cat > /usr/share/novnc/vnc.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Android Studio on Hugging Face</title>
<meta charset="utf-8">
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1e1e1e;
color: #fff;
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: #2d2d2d;
border-bottom: 2px solid #3ddc84;
}
.title h1 {
margin: 0;
font-size: 1.3rem;
color: #3ddc84;
}
.badge {
background: #3ddc84;
color: #000;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: bold;
margin-left: 10px;
}
.controls {
display: flex;
gap: 8px;
align-items: center;
}
button {
background: #3ddc84;
color: #000;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
font-size: 0.8rem;
}
button.secondary {
background: transparent;
color: #3ddc84;
border: 1px solid #3ddc84;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.vnc-container {
flex: 1;
background: #000;
position: relative;
overflow: hidden;
}
#vnc {
width: 100%;
height: 100%;
}
.status {
display: flex;
align-items: center;
gap: 5px;
margin-right: 15px;
font-size: 0.8rem;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.status-dot.connected {
background: #3ddc84;
animation: pulse 2s infinite;
}
.status-dot.connecting {
background: #ffaa00;
}
.status-dot.disconnected {
background: #ff4444;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(61, 220, 132, 0.7); }
70% { box-shadow: 0 0 0 6px rgba(61, 220, 132, 0); }
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 10;
background: rgba(30,30,30,0.9);
padding: 20px;
border-radius: 8px;
border: 1px solid #3ddc84;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #3ddc84;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.footer {
text-align: center;
padding: 5px;
background: #2d2d2d;
font-size: 0.7rem;
color: #888;
}
.footer a {
color: #3ddc84;
text-decoration: none;
}
</style>
</head>
<body>
<div class="header">
<div class="title">
<span class="badge">HF Space</span>
<h1>🤖 Android Studio</h1>
</div>
<div class="controls">
<div class="status">
<span class="status-dot disconnected" id="status-dot"></span>
<span id="status-text">Disconnected</span>
</div>
<button onclick="toggleViewOnly()" class="secondary" id="viewOnlyBtn" disabled>View Only: OFF</button>
<button onclick="sendCtrlAltDel()" class="secondary" id="ctrlAltDelBtn" disabled>Ctrl+Alt+Del</button>
<button onclick="reconnect()" id="reconnectBtn">Reconnect</button>
</div>
</div>
<div class="vnc-container">
<div id="vnc"></div>
<div class="loading" id="loading">
<div class="spinner"></div>
<div id="loading-text">Connecting to Android Studio...</div>
</div>
</div>
<div class="footer">
⚡ Running on Hugging Face Spaces | <a href="#" onclick="toggleFullscreen()">Fullscreen</a>
</div>
<script>
let rfb;
let retryCount = 0;
const maxRetries = 10;
const loading = document.getElementById('loading');
const loadingText = document.getElementById('loading-text');
const statusDot = document.getElementById('status-dot');
const statusText = document.getElementById('status-text');
const viewOnlyBtn = document.getElementById('viewOnlyBtn');
const ctrlAltDelBtn = document.getElementById('ctrlAltDelBtn');
function setStatus(state, message) {
statusDot.className = 'status-dot ' + state;
statusText.textContent = message;
}
function toggleViewOnly() {
if (rfb) {
rfb.viewOnly = !rfb.viewOnly;
viewOnlyBtn.textContent = 'View Only: ' + (rfb.viewOnly ? 'ON' : 'OFF');
}
}
function sendCtrlAltDel() {
if (rfb) rfb.sendCtrlAltDel();
}
function toggleFullscreen() {
const container = document.querySelector('.vnc-container');
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
container.requestFullscreen();
}
}
function reconnect() {
if (rfb) rfb.disconnect();
setTimeout(connect, 1000);
}
function connect() {
loading.style.display = 'block';
loadingText.textContent = 'Connecting to Android Studio...';
setStatus('connecting', 'Connecting...');
viewOnlyBtn.disabled = true;
ctrlAltDelBtn.disabled = true;
// Hugging Face Spaces WebSocket endpoint
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
const wsUrl = `${protocol}//${host}/proxy/7860/websockify`;
console.log('Connecting to:', wsUrl);
try {
rfb = new RFB(document.getElementById('vnc'), wsUrl, {
credentials: { password: '' },
shared: true,
viewOnly: false,
resizeSession: true
});
rfb.addEventListener('connect', () => {
loading.style.display = 'none';
setStatus('connected', 'Connected');
viewOnlyBtn.disabled = false;
ctrlAltDelBtn.disabled = false;
retryCount = 0;
});
rfb.addEventListener('disconnect', () => {
loading.style.display = 'block';
setStatus('disconnected', 'Disconnected');
viewOnlyBtn.disabled = true;
ctrlAltDelBtn.disabled = true;
if (retryCount < maxRetries) {
retryCount++;
const delay = Math.min(1000 * retryCount, 10000);
loadingText.textContent = `Reconnecting in ${delay/1000}s... (${retryCount}/${maxRetries})`;
setTimeout(connect, delay);
} else {
loadingText.textContent = 'Failed to connect. Click Reconnect.';
}
});
rfb.addEventListener('securityfailure', (e) => {
console.error('Security failure:', e.detail);
loadingText.textContent = 'Security error';
});
} catch (error) {
console.error('Connection error:', error);
loadingText.textContent = 'Connection error';
}
}
window.onload = connect;
window.addEventListener('resize', () => {
if (rfb) setTimeout(() => rfb.resizeSession(), 100);
});
</script>
<script src="vnc.js"></script>
</body>
</html>
EOF
# Create diagnostic page
cat > /usr/share/novnc/diagnostic.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>HF Space Diagnostic</title>
<style>
body { background: #1e1e1e; color: #fff; font-family: monospace; padding: 20px; }
pre { background: #2d2d2d; padding: 15px; border-radius: 5px; }
.success { color: #3ddc84; }
.error { color: #ff4444; }
button { background: #3ddc84; color: #000; border: none; padding: 10px; border-radius: 4px; cursor: pointer; margin: 5px; }
</style>
</head>
<body>
<h1>🔧 Diagnostic Tool</h1>
<button onclick="testWebSocket()">Test WebSocket</button>
<button onclick="window.location.href='/vnc.html'">Go to VNC</button>
<pre id="output"></pre>
<script>
const output = document.getElementById('output');
async function testWebSocket() {
output.textContent = 'Testing WebSocket connections...\n';
const endpoints = [
`/proxy/7860/websockify`,
`/websockify`
];
for (const endpoint of endpoints) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}${endpoint}`;
output.textContent += `\nTesting: ${url}\n`;
try {
const ws = new WebSocket(url);
await new Promise((resolve, reject) => {
ws.onopen = () => {
output.textContent += '✅ SUCCESS\n';
ws.close();
resolve();
};
ws.onerror = () => {
output.textContent += '❌ FAILED\n';
reject();
};
setTimeout(() => {
output.textContent += '⏱️ TIMEOUT\n';
reject();
}, 3000);
});
} catch (e) {
output.textContent += `❌ ERROR: ${e.message}\n`;
}
}
}
</script>
</body>
</html>
EOF
echo "Starting noVNC with HF Spaces configuration..."
cd /usr/share/novnc
# Ensure vnc.js is available
if [ ! -f "vnc.js" ]; then
ln -sf /usr/share/novnc/vnc.js . 2>/dev/null || true
fi
# Start websockify
websockify --web=/usr/share/novnc 0.0.0.0:7860 localhost:5900