Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SCOOP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
41 changes: 41 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,44 @@ release:

changelog:
use: github

# Windows package managers. Both publish to separate repos, so they need a PAT
# (repo-scoped GITHUB_TOKEN cannot push cross-repo). Until the tokens are set as
# secrets, skip_upload keeps releases green - binaries still ship, only the
# manifest push is skipped.
scoops:
- name: asobi
repository:
owner: widgrensit
name: scoop-bucket
branch: main
token: '{{ index .Env "SCOOP_TOKEN" }}'
directory: bucket
homepage: "https://github.com/widgrensit/asobi"
description: "Command-line tool for building and deploying multiplayer games on Asobi."
license: Apache-2.0
skip_upload: '{{ if ne (index .Env "SCOOP_TOKEN") "" }}false{{ else }}true{{ end }}'

winget:
- name: asobi
package_identifier: widgrensit.asobi
publisher: widgrensit
short_description: "Command-line tool for building and deploying multiplayer games on Asobi."
license: Apache-2.0
homepage: "https://github.com/widgrensit/asobi"
publisher_url: https://github.com/widgrensit
publisher_support_url: "https://github.com/widgrensit/asobi-cli/issues"
license_url: "https://github.com/widgrensit/asobi-cli/blob/main/LICENSE"
skip_upload: '{{ if ne (index .Env "WINGET_TOKEN") "" }}false{{ else }}true{{ end }}'
repository:
owner: widgrensit
name: winget-pkgs
branch: "asobi-{{ .Version }}"
token: '{{ index .Env "WINGET_TOKEN" }}'
pull_request:
enabled: true
draft: false
base:
owner: microsoft
name: winget-pkgs
branch: master
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,31 @@ Command-line tool for building and deploying multiplayer games on [Asobi](https:

## Install

**Linux / macOS:**

```bash
curl -fsSL https://raw.githubusercontent.com/widgrensit/asobi-cli/main/install.sh | sh
```

This downloads the latest release binary for your OS and architecture (Linux and
macOS, amd64 and arm64), verifies its checksum, and installs it to
`~/.local/bin` (override with `ASOBI_INSTALL_DIR`). Pin a version with
`ASOBI_VERSION=v0.1.0`. Windows users download the zip from the
[Releases](https://github.com/widgrensit/asobi-cli/releases) page.
**Windows (PowerShell):**

```powershell
irm https://raw.githubusercontent.com/widgrensit/asobi-cli/main/install.ps1 | iex
```

Or with a package manager:

```powershell
winget install widgrensit.asobi
# or
scoop bucket add asobi https://github.com/widgrensit/scoop-bucket
scoop install asobi
```

The scripts download the latest release binary for your OS and architecture
(amd64 and arm64), verify its checksum, and install it to `~/.local/bin` on
Linux/macOS or `%LOCALAPPDATA%\asobi\bin` on Windows (override with
`ASOBI_INSTALL_DIR`). Pin a version with `ASOBI_VERSION=v0.1.0`.

Verify the install:

Expand Down
15 changes: 11 additions & 4 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ Either of these channels work:

## Credential storage

`asobi-cli` stores credentials at `~/.asobi/credentials.json` with mode
`0600`, in a directory with mode `0700`. The CLI does not transmit
credentials over unencrypted channels and uses ECDH+AES-GCM for the
initial device-code login exchange.
`asobi-cli` stores credentials at `~/.asobi/credentials.json`
(`%USERPROFILE%\.asobi\credentials.json` on Windows). The CLI does not
transmit credentials over unencrypted channels and uses ECDH+AES-GCM for
the initial device-code login exchange.

On Linux and macOS the file is written with mode `0600` in a `0700`
directory, restricting it to the owning user. On Windows those modes only
set the read-only attribute; the file is instead protected by the ACL it
inherits from your user profile (owner, SYSTEM, and Administrators).
Encrypting the credential file at rest with DPAPI is tracked as a
hardening enhancement.
95 changes: 95 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Asobi CLI installer for Windows.
#
# Usage:
# irm https://raw.githubusercontent.com/widgrensit/asobi-cli/main/install.ps1 | iex
#
# Environment variables:
# ASOBI_VERSION Install a specific tag (e.g. v0.1.0). Default: latest release.
# ASOBI_INSTALL_DIR Install directory. Default: $env:LOCALAPPDATA\asobi\bin.

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

$repo = 'widgrensit/asobi-cli'
$installDir = if ($env:ASOBI_INSTALL_DIR) { $env:ASOBI_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA 'asobi\bin' }

function Fail($msg) {
Write-Error $msg
exit 1
}

function Detect-Arch {
switch ($env:PROCESSOR_ARCHITECTURE) {
'AMD64' { 'amd64' }
'ARM64' { 'arm64' }
'x86' { Fail 'unsupported architecture: x86 (32-bit). asobi ships amd64 and arm64 builds only.' }
default { Fail "unsupported architecture: $($env:PROCESSOR_ARCHITECTURE)" }
}
}

function Latest-Version {
try {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases/latest" -Headers @{ 'User-Agent' = 'asobi-install' }
} catch {
Fail "could not query the latest release: $($_.Exception.Message)"
}
$release.tag_name
}

function Verify-Checksum($file, $sumsFile, $name) {
$line = Select-String -Path $sumsFile -Pattern " $([regex]::Escape($name))$" | Select-Object -First 1
if (-not $line) { Fail "no checksum found for $name in checksums.txt" }
$expected = ($line.Line -split '\s+')[0]
$actual = (Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower()
if ($expected.ToLower() -ne $actual) {
Fail "checksum mismatch for $name (expected $expected, got $actual)"
}
}

function Add-ToUserPath($dir) {
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
$entries = if ($userPath) { $userPath -split ';' } else { @() }
if ($entries -notcontains $dir) {
$newPath = if ($userPath) { "$userPath;$dir" } else { $dir }
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host ''
Write-Host "Added $dir to your user PATH. Open a new terminal, then run 'asobi version'."
} else {
Write-Host "Run 'asobi version' to get started."
}
}

$arch = Detect-Arch
$version = if ($env:ASOBI_VERSION) { $env:ASOBI_VERSION } else { Latest-Version }
if (-not $version) { Fail 'could not resolve a release version' }

$asset = "asobi_windows_$arch.zip"
$base = "https://github.com/$repo/releases/download/$version"

$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("asobi-" + [System.IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Path $tmp | Out-Null
try {
Write-Host "Downloading asobi $version (windows/$arch)..."
try {
Invoke-WebRequest -Uri "$base/$asset" -OutFile (Join-Path $tmp $asset) -UseBasicParsing
} catch {
Fail "failed to download $asset - check that $version has a windows/$arch build"
}
Invoke-WebRequest -Uri "$base/checksums.txt" -OutFile (Join-Path $tmp 'checksums.txt') -UseBasicParsing

Write-Host 'Verifying checksum...'
Verify-Checksum (Join-Path $tmp $asset) (Join-Path $tmp 'checksums.txt') $asset

Expand-Archive -Path (Join-Path $tmp $asset) -DestinationPath $tmp -Force
$binary = Join-Path $tmp 'asobi.exe'
if (-not (Test-Path $binary)) { Fail "asobi.exe not found in $asset" }

New-Item -ItemType Directory -Path $installDir -Force | Out-Null
$installPath = Join-Path $installDir 'asobi.exe'
Move-Item -Path $binary -Destination $installPath -Force

Write-Host "Installed asobi to $installPath"
Add-ToUserPath $installDir
} finally {
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
}
4 changes: 3 additions & 1 deletion internal/auth/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ func openBrowser(url string) {
case "darwin":
cmd = exec.Command("open", url)
case "windows":
cmd = exec.Command("cmd", "/c", "start", url)
// Not `cmd /c start`: cmd re-parses the URL and treats & as a
// command separator, breaking any query string.
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
default:
return
}
Expand Down