using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Drawing ;
using System.IO ;
using System.Linq ;
using System.Net ;
using System.Net.Sockets ;
using System.Reflection ;
using System.Runtime.InteropServices ;
using System.Threading ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
using Microsoft.Win32 ;
using Newtonsoft.Json ;
using UptimeKuma.Properties ;
namespace UptimeKuma {
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main ( string [ ] args ) {
var cwd = Path . GetDirectoryName ( Application . ExecutablePath ) ;
if ( cwd ! = null ) {
Environment . CurrentDirectory = cwd ;
}
bool isIntranet = args . Contains ( "--intranet" ) ;
if ( isIntranet ) {
Console . WriteLine ( "The --intranet argument was provided, so we will not try to access the internet. The first time this application runs you'll need to run it without the --intranet param or copy the result from another machine to the intranet server." ) ;
}
Application . EnableVisualStyles ( ) ;
Application . SetCompatibleTextRenderingDefault ( false ) ;
Application . Run ( new UptimeKumaApplicationContext ( isIntranet ) ) ;
}
}
public class UptimeKumaApplicationContext : ApplicationContext
{
private static Mutex mutex = null ;
const string appName = "Uptime Kuma" ;
private NotifyIcon trayIcon ;
private Process process ;
private MenuItem statusMenuItem ;
private MenuItem runWhenStarts ;
private MenuItem openMenuItem ;
private RegistryKey registryKey = Registry . CurrentUser . OpenSubKey ( "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" , true ) ;
private readonly bool intranetOnly ;
public UptimeKumaApplicationContext ( bool intranetOnly ) {
// Single instance only
bool createdNew ;
mutex = new Mutex ( true , appName , out createdNew ) ;
if ( ! createdNew ) {
return ;
}
this . intranetOnly = intranetOnly ;
var startingText = "Starting server..." ;
trayIcon = new NotifyIcon ( ) ;
trayIcon . Text = startingText ;
runWhenStarts = new MenuItem ( "Run when system starts" , RunWhenStarts ) ;
runWhenStarts . Checked = registryKey . GetValue ( appName ) ! = null ;
statusMenuItem = new MenuItem ( startingText ) ;
statusMenuItem . Enabled = false ;
openMenuItem = new MenuItem ( "Open" , Open ) ;
openMenuItem . Enabled = false ;
trayIcon . Icon = Icon . ExtractAssociatedIcon ( Assembly . GetExecutingAssembly ( ) . Location ) ;
trayIcon . ContextMenu = new ContextMenu ( new MenuItem [ ] {
statusMenuItem ,
openMenuItem ,
//new("Debug Console", DebugConsole),
runWhenStarts ,
new ( "Check for Update..." , CheckForUpdate ) ,
new ( "Visit GitHub..." , VisitGitHub ) ,
new ( "About" , About ) ,
new ( "Exit" , Exit ) ,
} ) ;
trayIcon . MouseDoubleClick + = new MouseEventHandler ( Open ) ;
trayIcon . Visible = true ;
var hasUpdateFile = File . Exists ( "update" ) ;
if ( ! hasUpdateFile & & Directory . Exists ( "core" ) & & Directory . Exists ( "node" ) & & Directory . Exists ( "core/node_modules" ) & & Directory . Exists ( "core/dist" ) ) {
// Go go go
StartProcess ( ) ;
} else {
DownloadFiles ( ) ;
}
}
void DownloadFiles ( ) {
if ( intranetOnly ) {
return ;
}
var form = new DownloadForm ( ) ;
form . Closed + = Exit ;
form . Show ( ) ;
}
private void RunWhenStarts ( object sender , EventArgs e ) {
if ( registryKey = = null ) {
MessageBox . Show ( "Error: Unable to set startup registry key." ) ;
return ;
}
if ( runWhenStarts . Checked ) {
registryKey . DeleteValue ( appName , false ) ;
runWhenStarts . Checked = false ;
} else {
registryKey . SetValue ( appName , Application . ExecutablePath ) ;
runWhenStarts . Checked = true ;
}
}
void StartProcess ( ) {
var startInfo = new ProcessStartInfo {
FileName = "node/node.exe" ,
Arguments = "server/server.js --data-dir=\"../data/\"" ,
RedirectStandardOutput = false ,
RedirectStandardError = false ,
UseShellExecute = false ,
CreateNoWindow = true ,
WorkingDirectory = "core"
} ;
process = new Process ( ) ;
process . StartInfo = startInfo ;
process . EnableRaisingEvents = true ;
process . Exited + = ProcessExited ;
try {
process . Start ( ) ;
//Open(null, null);
// Async task to check if the server is ready
Task . Run ( ( ) = > {
var runningText = "Server is running" ;
using TcpClient tcpClient = new TcpClient ( ) ;
while ( true ) {
try {
tcpClient . Connect ( "127.0.0.1" , 3001 ) ;
statusMenuItem . Text = runningText ;
openMenuItem . Enabled = true ;
trayIcon . Text = runningText ;
break ;
} catch ( Exception ) {
System . Threading . Thread . Sleep ( 2000 ) ;
}
}
} ) ;
} catch ( Exception e ) {
MessageBox . Show ( "Startup failed: " + e . Message , "Uptime Kuma Error" ) ;
}
}
void StopProcess ( ) {
process ? . Kill ( ) ;
}
void Open ( object sender , EventArgs e ) {
Process . Start ( "http://localhost:3001" ) ;
}
void DebugConsole ( object sender , EventArgs e ) {
}
void CheckForUpdate ( object sender , EventArgs e ) {
if ( intranetOnly ) {
return ;
}
// Check version.json exists
if ( File . Exists ( "version.json" ) ) {
// Load version.json and compare with the latest version from GitHub
var currentVersionObj = JsonConvert . DeserializeObject < Version > ( File . ReadAllText ( "version.json" ) ) ;
var versionJson = new WebClient ( ) . DownloadString ( "https://uptime.kuma.pet/version" ) ;
var latestVersionObj = JsonConvert . DeserializeObject < Version > ( versionJson ) ;
// Compare version, if the latest version is newer, then update
if ( new System . Version ( latestVersionObj . latest ) . CompareTo ( new System . Version ( currentVersionObj . latest ) ) > 0 ) {
var result = MessageBox . Show ( "A new version is available. Do you want to update?" , "Update" , MessageBoxButtons . YesNo ) ;
if ( result = = DialogResult . Yes ) {
// Create a empty file `update`, so the app will download the core files again at startup
File . Create ( "update" ) . Close ( ) ;
trayIcon . Visible = false ;
process ? . Kill ( ) ;
// Restart the app, it will download the core files again at startup
Application . Restart ( ) ;
}
} else {
MessageBox . Show ( "You are using the latest version." ) ;
}
}
}
void VisitGitHub ( object sender , EventArgs e ) {
if ( intranetOnly ) {
MessageBox . Show ( "You have parsed in --intranet so we will not try to access the internet or visit github.com, please go to https://github.com/louislam/uptime-kuma if you want to visit github." ) ;
return ;
}
Process . Start ( "https://github.com/louislam/uptime-kuma" ) ;
}
void About ( object sender , EventArgs e )
{
MessageBox . Show ( "Uptime Kuma Windows Runtime v1.0.0" + Environment . NewLine + "© 2023 Louis Lam" , "Info" ) ;
}
void Exit ( object sender , EventArgs e )
{
// Hide tray icon, otherwise it will remain shown until user mouses over it
trayIcon . Visible = false ;
process ? . Kill ( ) ;
Application . Exit ( ) ;
}
void ProcessExited ( object sender , EventArgs e ) {
if ( process . ExitCode ! = 0 ) {
var line = "" ;
while ( ! process . StandardOutput . EndOfStream )
{
line + = process . StandardOutput . ReadLine ( ) ;
}
MessageBox . Show ( "Uptime Kuma exited unexpectedly. Exit code: " + process . ExitCode + " " + line ) ;
}
trayIcon . Visible = false ;
Application . Exit ( ) ;
}
}
}