Skip to content

Commit 1d57c45

Browse files
committed
feat: home base radius and update limits
1 parent c796f59 commit 1d57c45

13 files changed

Lines changed: 454 additions & 77 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ linkbridge-api-linux
4040
linkbridge-backend
4141
linkbridge-api
4242
deploy.sh
43+
deploy.local.*
44+
.tools/

deploy-password.ps1

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
param(
2+
[Parameter(Mandatory = $false)]
3+
[string]$CredFile = "..\\mima.txt",
4+
5+
[Parameter(Mandatory = $false)]
6+
[string]$HostName,
7+
8+
[Parameter(Mandatory = $false)]
9+
[int]$Port = 22,
10+
11+
[Parameter(Mandatory = $false)]
12+
[string]$UserName,
13+
14+
[Parameter(Mandatory = $false)]
15+
[string]$Password,
16+
17+
[Parameter(Mandatory = $false)]
18+
[string]$ServiceName = "linkbridge",
19+
20+
[Parameter(Mandatory = $false)]
21+
[string]$RemoteTmpPath = "/tmp/linkbridge-backend",
22+
23+
[Parameter(Mandatory = $false)]
24+
[string]$RemoteInstallPath = "/opt/linkbridge-backend"
25+
)
26+
27+
Set-StrictMode -Version Latest
28+
$ErrorActionPreference = "Stop"
29+
30+
function Assert-CommandExists {
31+
param([Parameter(Mandatory = $true)][string]$Name)
32+
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
33+
throw "Missing command '$Name'."
34+
}
35+
}
36+
37+
function Ensure-WinSCP {
38+
$toolsDir = Join-Path $PSScriptRoot ".tools\\winscp"
39+
$exeCom = Join-Path $toolsDir "WinSCP.com"
40+
$exeGui = Join-Path $toolsDir "WinSCP.exe"
41+
if ((Test-Path $exeCom) -and (Test-Path $exeGui)) {
42+
return $exeCom
43+
}
44+
45+
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
46+
47+
# Official "downloading..." page (contains a time-limited CDN link to the ZIP).
48+
# Note: We intentionally fetch the HTML page first, then resolve the real ZIP URL,
49+
# otherwise we may accidentally download HTML and treat it as a ZIP.
50+
$downloadPageUrl = "https://winscp.net/download/WinSCP-6.3.6-Portable.zip"
51+
Write-Host "=== Resolve WinSCP portable ZIP URL ==="
52+
$page = Invoke-WebRequest -Uri $downloadPageUrl -UseBasicParsing
53+
$html = $page.Content
54+
$zipUrl = $null
55+
$m = [regex]::Match($html, '(https?://)?cdn\.winscp\.net/files/WinSCP-[^"\s>]+-Portable\.zip\?secure=[^"\s>]+', 'IgnoreCase')
56+
if ($m.Success) {
57+
$zipUrl = $m.Value
58+
if (-not $zipUrl.StartsWith("http")) {
59+
$zipUrl = "https://" + $zipUrl
60+
}
61+
}
62+
if (-not $zipUrl) {
63+
$m2 = [regex]::Match($html, 'https?://sourceforge\.net/projects/winscp/files/WinSCP/[^"\s>]+/WinSCP-[^"\s>]+-Portable\.zip/download', 'IgnoreCase')
64+
if ($m2.Success) {
65+
$zipUrl = $m2.Value
66+
}
67+
}
68+
if (-not $zipUrl) {
69+
throw "Failed to resolve WinSCP portable ZIP URL from $downloadPageUrl"
70+
}
71+
72+
# Download ZIP and unzip only WinSCP.com for scripting.
73+
$zipPath = Join-Path $toolsDir "winscp-portable.zip"
74+
75+
Write-Host "=== Download WinSCP portable CLI ==="
76+
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing
77+
78+
Write-Host "=== Extract WinSCP.com + WinSCP.exe ==="
79+
$sig = [System.IO.File]::ReadAllBytes($zipPath)[0..1]
80+
if (-not ($sig[0] -eq 0x50 -and $sig[1] -eq 0x4B)) {
81+
$head = [System.Text.Encoding]::ASCII.GetString([System.IO.File]::ReadAllBytes($zipPath)[0..120])
82+
$head = $head -replace "`r", "" -replace "`n", " "
83+
throw ("Downloaded file is not a ZIP (missing PK signature). Head: " + $head)
84+
}
85+
86+
Add-Type -AssemblyName System.IO.Compression.FileSystem
87+
$zip = [System.IO.Compression.ZipFile]::OpenRead($zipPath)
88+
try {
89+
$foundCom = $false
90+
$foundExe = $false
91+
foreach ($entry in $zip.Entries) {
92+
if ($entry.Name -ieq "WinSCP.com") {
93+
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $exeCom, $true)
94+
$foundCom = $true
95+
continue
96+
}
97+
if ($entry.Name -ieq "WinSCP.exe") {
98+
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $exeGui, $true)
99+
$foundExe = $true
100+
continue
101+
}
102+
}
103+
if (-not $foundCom -or -not $foundExe) {
104+
throw "WinSCP portable ZIP did not contain required files (WinSCP.com/WinSCP.exe)."
105+
}
106+
}
107+
finally {
108+
$zip.Dispose()
109+
}
110+
111+
Remove-Item -Force $zipPath -ErrorAction SilentlyContinue
112+
113+
if (-not (Test-Path $exeCom) -or -not (Test-Path $exeGui)) {
114+
throw "WinSCP.com/WinSCP.exe not found after extraction."
115+
}
116+
return $exeCom
117+
}
118+
119+
function Read-Creds {
120+
param([Parameter(Mandatory = $true)][string]$Path)
121+
$full = Resolve-Path $Path
122+
$lines = Get-Content $full | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
123+
if ($lines.Count -lt 3) {
124+
throw "Credential file must contain at least 3 non-empty lines: host, username, password."
125+
}
126+
return @{
127+
Host = $lines[0]
128+
User = $lines[1]
129+
Pass = $lines[2]
130+
}
131+
}
132+
133+
Assert-CommandExists "go"
134+
135+
if (-not $HostName -or -not $UserName -or -not $Password) {
136+
$c = Read-Creds -Path (Join-Path $PSScriptRoot $CredFile)
137+
if (-not $HostName) { $HostName = $c.Host }
138+
if (-not $UserName) { $UserName = $c.User }
139+
if (-not $Password) { $Password = $c.Pass }
140+
}
141+
142+
if (-not $HostName -or -not $UserName -or -not $Password) {
143+
throw "Missing HostName/UserName/Password"
144+
}
145+
146+
$winscp = Ensure-WinSCP
147+
148+
Push-Location $PSScriptRoot
149+
try {
150+
$outFile = Join-Path $PSScriptRoot "linkbridge-backend"
151+
152+
Write-Host "=== Build linux amd64 binary ==="
153+
$oldEnv = @{
154+
CGO_ENABLED = $env:CGO_ENABLED
155+
GOOS = $env:GOOS
156+
GOARCH = $env:GOARCH
157+
}
158+
try {
159+
$env:CGO_ENABLED = "0"
160+
$env:GOOS = "linux"
161+
$env:GOARCH = "amd64"
162+
if (Test-Path $outFile) { Remove-Item -Force $outFile }
163+
& go build -o $outFile ./cmd/api
164+
if ($LASTEXITCODE -ne 0) { throw "go build failed" }
165+
}
166+
finally {
167+
$env:CGO_ENABLED = $oldEnv.CGO_ENABLED
168+
$env:GOOS = $oldEnv.GOOS
169+
$env:GOARCH = $oldEnv.GOARCH
170+
}
171+
172+
Write-Host "=== Upload + deploy via WinSCP (password auth) ==="
173+
$tmp = Join-Path $env:TEMP ("winscp-deploy-" + [Guid]::NewGuid().ToString("N") + ".txt")
174+
$passFile = Join-Path $env:TEMP ("winscp-pass-" + [Guid]::NewGuid().ToString("N") + ".txt")
175+
176+
# SECURITY NOTE:
177+
# - Password is stored only in a temporary file (deleted after), and passed to WinSCP using -passwordsfromfiles.
178+
# - The WinSCP script does not contain the password, so we can safely print WinSCP output on failure.
179+
$sessionUrl = "sftp://{0}@{1}:{2}/" -f $UserName, $HostName, $Port
180+
181+
# WinSCP reads only first line; keep it simple.
182+
Set-Content -Path $passFile -Value $Password -Encoding UTF8
183+
184+
$script = @"
185+
option batch abort
186+
option confirm off
187+
open $sessionUrl -password="$passFile" -passwordsfromfiles -hostkey="*"
188+
put `"$outFile`" $RemoteTmpPath
189+
call systemctl stop $ServiceName || true
190+
call mv $RemoteTmpPath $RemoteInstallPath
191+
call chmod +x $RemoteInstallPath
192+
call systemctl start $ServiceName
193+
call sleep 2
194+
call systemctl status $ServiceName --no-pager
195+
exit
196+
"@
197+
Set-Content -Path $tmp -Value $script -Encoding ASCII
198+
199+
try {
200+
$out = & $winscp "/script=$tmp"
201+
if ($LASTEXITCODE -ne 0) {
202+
if ($out) { Write-Host ($out -join "`n") }
203+
throw "WinSCP deploy failed (exit=$LASTEXITCODE)."
204+
}
205+
}
206+
finally {
207+
Remove-Item -Force $tmp -ErrorAction SilentlyContinue
208+
Remove-Item -Force $passFile -ErrorAction SilentlyContinue
209+
}
210+
211+
Write-Host "=== Cleanup local binary ==="
212+
if (Test-Path $outFile) { Remove-Item -Force $outFile }
213+
214+
Write-Host "=== Done ==="
215+
}
216+
finally {
217+
Pop-Location
218+
}

deploy.ps1

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
param(
2+
[Parameter(Mandatory = $false)]
3+
[string]$HostName = "103.40.13.96",
4+
5+
[Parameter(Mandatory = $false)]
6+
[int]$Port = 22,
7+
8+
[Parameter(Mandatory = $false)]
9+
[string]$UserName = "root",
10+
11+
[Parameter(Mandatory = $false)]
12+
[string]$ServiceName = "linkbridge",
13+
14+
[Parameter(Mandatory = $false)]
15+
[string]$RemoteTmpPath = "/tmp/linkbridge-backend",
16+
17+
[Parameter(Mandatory = $false)]
18+
[string]$RemoteInstallPath = "/opt/linkbridge-backend",
19+
20+
[Parameter(Mandatory = $false)]
21+
[string]$KeyPath
22+
)
23+
24+
Set-StrictMode -Version Latest
25+
$ErrorActionPreference = "Stop"
26+
27+
function Assert-CommandExists {
28+
param([Parameter(Mandatory = $true)][string]$Name)
29+
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
30+
throw "Missing command '$Name'. Please install/enable it (Go + OpenSSH) and try again."
31+
}
32+
}
33+
34+
function Invoke-External {
35+
param(
36+
[Parameter(Mandatory = $true)][string]$Exe,
37+
[Parameter(Mandatory = $false)][string[]]$Args = @()
38+
)
39+
Write-Host ("`n> " + $Exe + " " + ($Args -join " "))
40+
& $Exe @Args
41+
if ($LASTEXITCODE -ne 0) {
42+
throw "Command failed with exit code $LASTEXITCODE: $Exe"
43+
}
44+
}
45+
46+
Assert-CommandExists "go"
47+
Assert-CommandExists "ssh"
48+
Assert-CommandExists "scp"
49+
50+
$repoRoot = $PSScriptRoot
51+
Push-Location $repoRoot
52+
try {
53+
$outFile = Join-Path $repoRoot "linkbridge-backend"
54+
55+
Write-Host "=== Build linux amd64 binary ==="
56+
$oldEnv = @{
57+
CGO_ENABLED = $env:CGO_ENABLED
58+
GOOS = $env:GOOS
59+
GOARCH = $env:GOARCH
60+
}
61+
try {
62+
$env:CGO_ENABLED = "0"
63+
$env:GOOS = "linux"
64+
$env:GOARCH = "amd64"
65+
66+
if (Test-Path $outFile) { Remove-Item -Force $outFile }
67+
Invoke-External -Exe "go" -Args @("build", "-o", $outFile, "./cmd/api")
68+
}
69+
finally {
70+
$env:CGO_ENABLED = $oldEnv.CGO_ENABLED
71+
$env:GOOS = $oldEnv.GOOS
72+
$env:GOARCH = $oldEnv.GOARCH
73+
}
74+
75+
$target = "{0}@{1}:{2}" -f $UserName, $HostName, $RemoteTmpPath
76+
77+
Write-Host "=== Upload to server ==="
78+
$scpArgs = @()
79+
if ($KeyPath) { $scpArgs += @("-i", $KeyPath) }
80+
if ($Port -and $Port -ne 22) { $scpArgs += @("-P", "$Port") }
81+
$scpArgs += @($outFile, $target)
82+
Invoke-External -Exe "scp" -Args $scpArgs
83+
84+
Write-Host "=== Deploy service on server ==="
85+
$sshArgs = @()
86+
if ($KeyPath) { $sshArgs += @("-i", $KeyPath) }
87+
if ($Port -and $Port -ne 22) { $sshArgs += @("-p", "$Port") }
88+
$sshArgs += @("{0}@{1}" -f $UserName, $HostName)
89+
90+
$remoteScript = @"
91+
systemctl stop $ServiceName || true
92+
mv $RemoteTmpPath $RemoteInstallPath
93+
chmod +x $RemoteInstallPath
94+
systemctl start $ServiceName
95+
sleep 2
96+
systemctl status $ServiceName --no-pager
97+
"@.Trim()
98+
99+
$sshArgs += @($remoteScript)
100+
Invoke-External -Exe "ssh" -Args $sshArgs
101+
102+
Write-Host "=== Cleanup local binary ==="
103+
if (Test-Path $outFile) { Remove-Item -Force $outFile }
104+
105+
Write-Host "=== Done ==="
106+
Write-Host "If you are using password auth, scp/ssh will prompt for it in the terminal."
107+
}
108+
finally {
109+
Pop-Location
110+
}
111+

internal/httpserver/httpserver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ type Store interface {
6262
UpdateSessionInviteSettings(ctx context.Context, inviterID string, expiresAtMs *int64, geoFence *storage.GeoFence, nowMs int64) (storage.SessionInviteRow, error)
6363

6464
GetHomeBase(ctx context.Context, userID string) (storage.HomeBaseRow, error)
65-
UpsertHomeBase(ctx context.Context, userID string, latE7, lngE7 int64, nowMs int64) (storage.HomeBaseRow, error)
65+
UpsertHomeBase(ctx context.Context, userID string, latE7, lngE7 int64, visibilityRadiusM *int, nowMs int64) (storage.HomeBaseRow, error)
6666

67-
CreateLocalFeedPost(ctx context.Context, userID string, text *string, imageURLs []string, radiusM int, expiresAtMs int64, isPinned bool, nowMs int64) (storage.LocalFeedPostRow, []storage.LocalFeedPostImageRow, error)
67+
CreateLocalFeedPost(ctx context.Context, userID string, text *string, imageURLs []string, expiresAtMs int64, isPinned bool, nowMs int64) (storage.LocalFeedPostRow, []storage.LocalFeedPostImageRow, error)
6868
DeleteLocalFeedPost(ctx context.Context, userID, postID string) error
6969
ListLocalFeedPostsForSource(ctx context.Context, sourceUserID string, atLatE7, atLngE7 *int64, nowMs int64, limit int) ([]storage.LocalFeedPostWithImages, error)
7070
ListLocalFeedPins(ctx context.Context, minLatE7, maxLatE7, minLngE7, maxLngE7, centerLatE7, centerLngE7 int64, limit int) ([]storage.LocalFeedPinRow, error)

0 commit comments

Comments
 (0)