mirror of
https://github.com/usememos/memos.git
synced 2024-12-25 12:23:09 +03:00
feat: improve Windows support (#1645)
* Add preliminar Windows support for both development and production environments. Default profile.Data will be set to "C:\ProgramData\memos" on Windows. Folder will be created if it does not exist, as this behavior is expected for Windows applications. System service installation can be achieved with third-party tools, explained in docs/windows-service.md. Not sure if it's worth using https://github.com/kardianos/service to make service support built-in. This could be a nice addition alongside #1583 (add Windows artifacts) * feat: improve Windows support - Fix local file storage path handling on Windows - Improve Windows dev script
This commit is contained in:
parent
700fe6b0e4
commit
5340008ad7
@ -2,41 +2,153 @@
|
|||||||
# It also installs frontend dependencies.
|
# It also installs frontend dependencies.
|
||||||
|
|
||||||
# For more details on setting-up a development environment, check the docs:
|
# For more details on setting-up a development environment, check the docs:
|
||||||
# https://github.com/usememos/memos/blob/main/docs/development.md
|
# * https://usememos.com/docs/contribution/development
|
||||||
|
# * https://github.com/usememos/memos/blob/main/docs/development.md
|
||||||
|
|
||||||
# Usage: ./scripts/start.ps1
|
# Usage: ./scripts/start.ps1
|
||||||
$LastExitCode = 0
|
|
||||||
|
|
||||||
$projectRoot = (Resolve-Path "$MyInvocation.MyCommand.Path/..").Path
|
foreach ($dir in @(".", "../")) {
|
||||||
Write-Host "Project root: $projectRoot"
|
if (Test-Path (Join-Path $dir ".gitignore")) {
|
||||||
|
$repoRoot = (Resolve-Path $dir).Path
|
||||||
Write-Host "Starting backend..." -f Magenta
|
break
|
||||||
Start-Process -WorkingDirectory "$projectRoot" -FilePath "air" "-c ./scripts/.air-windows.toml"
|
}
|
||||||
if ($LastExitCode -ne 0) {
|
|
||||||
Write-Host "Failed to start backend!" -f Red
|
|
||||||
exit $LastExitCode
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host "Backend started!" -f Green
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Installing frontend dependencies..." -f Magenta
|
##
|
||||||
Start-Process -Wait -WorkingDirectory "$projectRoot/web" -FilePath "powershell" -ArgumentList "pnpm i"
|
$frontendPort = 3001
|
||||||
if ($LastExitCode -ne 0) {
|
# Tasks to run, in order
|
||||||
Write-Host "Failed to install frontend dependencies!" -f Red
|
$runTasks = @(
|
||||||
exit $LastExitCode
|
@{
|
||||||
}
|
Desc = "start backend with live reload";
|
||||||
else {
|
Exe = "air.exe";
|
||||||
Write-Host "Frontend dependencies installed!" -f Green
|
Args = "-c .\scripts\.air-windows.toml";
|
||||||
|
Dir = "$repoRoot";
|
||||||
|
Wait = $false;
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
Desc = "install frontend dependencies";
|
||||||
|
Exe = "pnpm.exe";
|
||||||
|
Args = "i";
|
||||||
|
Dir = "$repoRoot/web"
|
||||||
|
Wait = $true;
|
||||||
|
}
|
||||||
|
@{
|
||||||
|
Desc = "start frontend with live reload";
|
||||||
|
Exe = "pnpm.exe";
|
||||||
|
Args = "dev";
|
||||||
|
Dir = "$repoRoot/web";
|
||||||
|
Wait = $false;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
##
|
||||||
|
|
||||||
|
if (!$repoRoot) {
|
||||||
|
Write-Host "Could not find repository root!" -f Red
|
||||||
|
Write-Host "cd into the repository root and run the script again."
|
||||||
|
Exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Starting frontend..." -f Magenta
|
Write-Host "Repository root is $repoRoot"
|
||||||
Start-Process -WorkingDirectory "$projectRoot/web" -FilePath "powershell" -ArgumentList "pnpm dev"
|
Write-Host "Starting development environment...`n"
|
||||||
if ($LastExitCode -ne 0) {
|
Write-Host @"
|
||||||
Write-Host "Failed to start frontend!" -f Red
|
███╗ ███╗███████╗███╗ ███╗ ██████╗ ███████╗
|
||||||
exit $LastExitCode
|
████╗ ████║██╔════╝████╗ ████║██╔═══██╗██╔════╝
|
||||||
}
|
██╔████╔██║█████╗ ██╔████╔██║██║ ██║███████╗
|
||||||
else {
|
██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║██║ ██║╚════██║
|
||||||
Write-Host "Frontend started!" -f Green
|
██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║╚██████╔╝███████║
|
||||||
|
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝
|
||||||
|
"@
|
||||||
|
|
||||||
|
function Stop-ProcessTree {
|
||||||
|
Param([int]$ParentProcessId)
|
||||||
|
if (!$ParentProcessId) {
|
||||||
|
Write-Host "Stop-ProcessTree: unspecified ParentProcessId!" -f Red
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Write-Host "Terminating pid $($ParentProcessId) with all its child processes" -f DarkGray
|
||||||
|
Get-CimInstance Win32_Process | Where-Object {
|
||||||
|
$_.ParentProcessId -eq $ParentProcessId
|
||||||
|
} | ForEach-Object {
|
||||||
|
Stop-ProcessTree $_.ProcessId
|
||||||
|
}
|
||||||
|
Stop-Process -Id $ParentProcessId -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$maxDescLength = ( $runTasks | ForEach-Object { $_.Desc.Length } | Measure-Object -Maximum).Maximum
|
||||||
|
$spawnedPids = @()
|
||||||
|
foreach ($task in $runTasks) {
|
||||||
|
Write-Host ("Running task ""$($task.Desc)""...").PadRight($maxDescLength + 20) -f Blue -NoNewline
|
||||||
|
$task.Dir = (Resolve-Path $task.Dir).Path
|
||||||
|
try {
|
||||||
|
$process = Start-Process -PassThru -WorkingDirectory $task.Dir -FilePath $task.Exe -ArgumentList $task.Args -Wait:$task.Wait
|
||||||
|
|
||||||
|
if ($process.ExitCode -and $process.ExitCode -ne 0) {
|
||||||
|
# ExitCode only works for processes started with -Wait:$true
|
||||||
|
throw "Process exited with code $($process.ExitCode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[OK]" -f Green
|
||||||
|
$spawnedPids += $process.Id
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "[FAILED]" -f Red
|
||||||
|
Write-Host "Error: $_" -f Red
|
||||||
|
Write-Host "Unable to execute: $($task.Exe) $($task.Args)" -f Red
|
||||||
|
Write-Host "Process working directory: $($task.Dir)" -f Red
|
||||||
|
|
||||||
|
foreach ($spawnedPid in $spawnedPids) {
|
||||||
|
Stop-ProcessTree -ParentProcessId $spawnedPid
|
||||||
|
}
|
||||||
|
Exit $process.ExitCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Front-end should be accessible at:" -f Green
|
||||||
|
$ipAddresses = (Get-NetIPAddress -AddressFamily IPv4) | Select-Object -ExpandProperty IPAddress | Sort-Object
|
||||||
|
$ipAddresses += "localhost"
|
||||||
|
foreach ($ip in $ipAddresses) {
|
||||||
|
Write-Host "· http://$($ip):$($frontendPort)" -f Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nPress" -NoNewline
|
||||||
|
Write-Host " Ctrl + C" -f DarkYellow -NoNewline
|
||||||
|
Write-Host " or" -NoNewline
|
||||||
|
Write-Host " Esc" -f DarkYellow -NoNewline
|
||||||
|
Write-Host " to terminate running servers." -f DarkYellow
|
||||||
|
[Console]::TreatControlCAsInput = $true
|
||||||
|
|
||||||
|
$lastPoll = 0
|
||||||
|
$noWaitTasks = $runTasks | Where-Object { $_.Wait -eq $false }
|
||||||
|
while ($true) {
|
||||||
|
if ([Console]::KeyAvailable) {
|
||||||
|
$readkey = [Console]::ReadKey("AllowCtrlC,IncludeKeyUp,NoEcho")
|
||||||
|
if ($readkey.Modifiers -eq "Control" -and $readkey.Key -eq "C") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ($readkey.Key -eq "Escape") {
|
||||||
|
Break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Poll for processes that exited unexpectedly
|
||||||
|
# Do this every 5 seconds to avoid excessive CPU usage
|
||||||
|
if (([DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() - $lastPoll) -ge 5000) {
|
||||||
|
$noWaitTasks | ForEach-Object {
|
||||||
|
$name = $_.Exe.TrimEnd(".exe")
|
||||||
|
if (!(Get-Process -Name $name -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "Process " -f Red -NoNewline
|
||||||
|
Write-Host $name -NoNewline -f DarkYellow
|
||||||
|
Write-Host " is not running anymore!" -f Red
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$lastPoll = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
||||||
|
}
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($spawnedPid in $spawnedPids) {
|
||||||
|
Stop-ProcessTree -ParentProcessId $spawnedPid
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Exiting..."
|
||||||
|
@ -112,6 +112,9 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
Blob: fileBytes,
|
Blob: fileBytes,
|
||||||
}
|
}
|
||||||
} else if storageServiceID == api.LocalStorage {
|
} else if storageServiceID == api.LocalStorage {
|
||||||
|
// filepath.Join() should be used for local file paths,
|
||||||
|
// as it handles the os-specific path separator automatically.
|
||||||
|
// path.Join() always uses '/' as path separator.
|
||||||
systemSettingLocalStoragePath, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{Name: api.SystemSettingLocalStoragePathName})
|
systemSettingLocalStoragePath, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{Name: api.SystemSettingLocalStoragePathName})
|
||||||
if err != nil && common.ErrorCode(err) != common.NotFound {
|
if err != nil && common.ErrorCode(err) != common.NotFound {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find local storage path setting").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find local storage path setting").SetInternal(err)
|
||||||
@ -123,11 +126,11 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal local storage path setting").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal local storage path setting").SetInternal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filePath := localStoragePath
|
filePath := filepath.FromSlash(localStoragePath)
|
||||||
if !strings.Contains(filePath, "{filename}") {
|
if !strings.Contains(filePath, "{filename}") {
|
||||||
filePath = path.Join(filePath, "{filename}")
|
filePath = filepath.Join(filePath, "{filename}")
|
||||||
}
|
}
|
||||||
filePath = path.Join(s.Profile.Data, replacePathTemplate(filePath, file.Filename))
|
filePath = filepath.Join(s.Profile.Data, replacePathTemplate(filePath, file.Filename))
|
||||||
dir, filename := filepath.Split(filePath)
|
dir, filename := filepath.Split(filePath)
|
||||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create directory").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create directory").SetInternal(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user