Skip to content
Closed
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
53 changes: 51 additions & 2 deletions .github/workflows/auto-company-runtime-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
outputs:
runtime: ${{ steps.select.outputs.runtime }}
browser: ${{ steps.select.outputs.browser }}
distribution: ${{ steps.select.outputs.distribution }}
tabledelta: ${{ steps.select.outputs.tabledelta }}
cuecheck: ${{ steps.select.outputs.cuecheck }}
snapog: ${{ steps.select.outputs.snapog }}
Expand All @@ -42,17 +43,21 @@ jobs:
- name: Verify Windows PowerShell 5.1 configuration and paths
shell: powershell
run: |
foreach ($test in @('start_config', 'wsl_paths', 'env_culture', 'messages')) {
foreach ($test in @('start_config', 'wsl_paths', 'env_culture', 'messages', 'installation')) {
python scripts/ci/run.py "powershell-$test" powershell -NoProfile -File "tests/test_windows_$test.ps1"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
python scripts/ci/run.py powershell-install-bootstrap powershell -NoProfile -File tests/test_install_bootstrap_windows.ps1
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Verify PowerShell 7 configuration and paths
shell: pwsh
run: |
foreach ($test in @('start_config', 'wsl_paths', 'env_culture', 'messages')) {
foreach ($test in @('start_config', 'wsl_paths', 'env_culture', 'messages', 'installation')) {
python scripts/ci/run.py "pwsh-$test" pwsh -NoProfile -File "tests/test_windows_$test.ps1"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
python scripts/ci/run.py pwsh-install-bootstrap pwsh -NoProfile -File tests/test_install_bootstrap_windows.ps1
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Save failure evidence
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down Expand Up @@ -106,6 +111,13 @@ jobs:
python3 -m unittest discover -s tests -p test_log_rotation.py -v
python3 -m unittest discover -s tests -p test_ui_messages.py -v
CI_SCRIPT
- name: Verify installer lifecycle, bootstrap, maintenance state and writer probes
run: |
python3 scripts/ci/run.py macos-installer python3 -m unittest -v \
tests.test_install_manager \
tests.test_install_bootstrap \
tests.test_installation_state \
tests.test_install_writer_probe
- name: Verify Dashboard Start with a real isolated LaunchAgent
env:
AUTO_COMPANY_TEST_LAUNCHD: '1'
Expand Down Expand Up @@ -306,6 +318,42 @@ jobs:
name: snapog-failure
path: ci-results/
retention-days: 14
release-packages:
name: Deterministic release packages
needs: changes
if: needs.changes.outputs.distribution == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Verify release package contracts
run: python3 scripts/ci/run.py release-package-tests python3 -m unittest tests.test_release_packages -v
- name: Build the checked-out commit twice
run: |
python3 scripts/install/build_release.py --repo . --ref HEAD --output distribution-candidate
python3 scripts/install/build_release.py --repo . --ref HEAD --output distribution-repeat
diff -rq distribution-candidate distribution-repeat
- name: Save candidate packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: distribution-candidate-${{ github.sha }}
path: distribution-candidate/
if-no-files-found: error
retention-days: 14
- name: Save failure evidence
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-packages-failure
path: ci-results/
if-no-files-found: ignore
retention-days: 14
ci-gate:
name: CI gate
if: always()
Expand All @@ -320,6 +368,7 @@ jobs:
- tabledelta
- cuecheck
- snapog
- release-packages
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand Down
89 changes: 89 additions & 0 deletions .github/workflows/distribution.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Upload verified distribution assets

'on':
workflow_dispatch:
inputs:
tag:
description: Existing version tag (for example, v1.6.1)
required: true
type: string

permissions:
actions: read
contents: read

concurrency:
group: distribution-${{ inputs.tag }}
cancel-in-progress: false

jobs:
upload-existing-draft:
name: Verify tag and upload to existing draft Release
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
actions: read
contents: write
environment: distribution-release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
steps:
- name: Validate tag input before checkout
shell: bash
run: '[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]'
- name: Check out the exact tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ inputs.tag }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Verify version tag, commit and existing Draft Release
shell: bash
run: |
set -euo pipefail
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]
git show-ref --verify --quiet "refs/tags/$RELEASE_TAG"
tag_commit="$(git rev-list -n 1 "$RELEASE_TAG")"
export TAG_COMMIT="$tag_commit"
test "$tag_commit" = "$(git rev-parse HEAD)"
package_version="$(python3 -c 'import json; print(json.load(open("package.json", encoding="utf-8"))["version"])')"
test "$RELEASE_TAG" = "v$package_version"
gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" > release.json
gh api -H 'Accept: application/vnd.github+json' "repos/$GITHUB_REPOSITORY/actions/workflows/auto-company-runtime-ci.yml/runs?head_sha=$tag_commit&status=completed&per_page=20" > workflow-runs.json
python3 - <<'PY'
import json, os
release = json.load(open("release.json", encoding="utf-8"))
if release.get("tag_name") != os.environ["RELEASE_TAG"] or release.get("draft") is not True:
raise SystemExit("The target must be the matching existing Draft Release")
expected = {
f"Auto-Company-{os.environ['RELEASE_TAG']}-windows.zip",
f"Auto-Company-{os.environ['RELEASE_TAG']}-macos.tar.gz",
f"Auto-Company-{os.environ['RELEASE_TAG']}-linux.tar.gz",
"release-manifest.json",
"SHA256SUMS.txt",
}
existing = {asset["name"] for asset in release.get("assets", [])}
conflict = expected & existing
if conflict:
raise SystemExit(f"Refusing to overwrite existing release assets: {sorted(conflict)}")
runs = json.load(open("workflow-runs.json", encoding="utf-8")).get("workflow_runs", [])
eligible = [run for run in runs if run.get("head_sha") == os.environ["TAG_COMMIT"]]
latest = max(eligible, key=lambda run: run.get("id", 0), default=None)
main_pushes = [run for run in eligible if run.get("event") == "push" and run.get("head_branch") == "main"]
latest_main_push = max(main_pushes, key=lambda run: run.get("id", 0), default=None)
if (not latest or latest.get("conclusion") != "success" or not latest_main_push
or latest_main_push.get("conclusion") != "success"):
raise SystemExit("The tagged main commit's latest completed runtime CI workflow did not succeed")
PY
- name: Test and reproducibly build the tagged source
run: |
python3 -m unittest tests.test_release_packages -v
python3 scripts/install/build_release.py --repo . --ref "$RELEASE_TAG" --output distribution-assets
python3 scripts/install/build_release.py --repo . --ref "$RELEASE_TAG" --output distribution-repeat
diff -rq distribution-assets distribution-repeat
- name: Upload the verified bytes without replacing assets
run: gh release upload "$RELEASE_TAG" distribution-assets/* --repo "$GITHUB_REPOSITORY"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ docs/*/*
!docs/runtime-observability.md
!docs/product-media.md
!docs/product-cycles.md
!docs/install.md
# Ignore all memories (keep folder marker only)
memories/*
!memories/.gitkeep
Expand Down
6 changes: 6 additions & 0 deletions README-ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ daemon (launchd / systemd --user, 崩溃自重启)
</tr>
</table>

## 下载与引导安装

带有正式平台附件的 Release 可以不经 Git clone 安装。从同一个 [GitHub Release](https://github.com/MaxMiksa/Auto-Company/releases) 下载 Windows、macOS 或 Linux 附件及 `SHA256SUMS.txt`,在解压前核对校验值,再运行包内 `setup.ps1` 或 `setup.sh`。引导会在确认前显示依赖和改动,按照操作系统界面判断中文或英文,并默认跳过可选截图环境,除非你明确选择安装。安装期间不会调用模型,服务默认保持停止且不启用开机运行,直到你主动开始。详见[中文安装说明](docs/install.md)或 [English installation guide](i18n/en/docs/install.md)。

下方 Git clone 方式继续受支持,也适用于尚未包含正式平台附件的旧 Release。

## 你该看哪一节(按平台)

- Windows 用户:从 [Windows (WSL) 快速开始](#windows-wsl-快速开始) 开始,再看 [`docs/windows-setup.md`](docs/windows-setup.md)
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ These three local products come from actual runs and appear in both README langu
</tr>
</table>

## Download and Guided Setup

Releases that include maintained platform archives can be installed without cloning Git. Download the Windows, macOS, or Linux asset and `SHA256SUMS.txt` from the same [GitHub Release](https://github.com/MaxMiksa/Auto-Company/releases), verify the checksum before extraction, then run the included `setup.ps1` or `setup.sh`. The guide shows dependencies and proposed changes before confirmation, detects Chinese or English from the operating-system UI, and skips the optional screenshot environment unless requested. It does not call a model during installation, and services remain stopped with autostart disabled until you choose to start them. See the [English installation guide](i18n/en/docs/install.md) or [中文安装说明](docs/install.md).

The Git clone instructions below remain supported, including for older Releases that do not contain the maintained platform assets.

## Where To Start (By Platform)

- Windows users: start from [Windows (WSL) Quick Start](#windows-wsl-quick-start), then read the [Windows + WSL Setup Guide](i18n/en/docs/windows-setup.md)
Expand Down
35 changes: 25 additions & 10 deletions dashboard/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import sys
import time
import threading
import webbrowser
from datetime import datetime, timezone
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
Expand All @@ -33,6 +34,7 @@

from usage_lib import UsageError, read_pause_state, summarize_usage # noqa: E402
import localization # noqa: E402
from installation_state import check_maintenance, writer_lease # noqa: E402
from journal_data import JournalSource # noqa: E402

WINDOWS_STATUS_SCRIPT = REPO_ROOT / "scripts" / "windows" / "status-win.ps1"
Expand Down Expand Up @@ -816,6 +818,12 @@ def do_POST(self) -> None: # noqa: N802
return
parsed = urlparse(self.path)
path = parsed.path
if path not in {"/api/action/stop", "/api/action/refresh"}:
try:
check_maintenance(REPO_ROOT)
except ValueError as error:
self._json({"ok": False, "errorCode": "installation_maintenance", "error": str(error)}, code=409)
return
if path == "/api/product-media/capture":
if not CONTROL_LOCK.acquire(blocking=False):
self._json({"ok": False, "error": "A runtime action is already in progress."}, code=409)
Expand Down Expand Up @@ -909,6 +917,7 @@ def main() -> None:
parser = argparse.ArgumentParser(description="Auto Company web dashboard server")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8787)
parser.add_argument("--open-browser", action="store_true")
args = parser.parse_args()

bind_host = "127.0.0.1" if args.host == "localhost" else args.host
Expand All @@ -928,17 +937,23 @@ def main() -> None:
class DashboardServer(ThreadingHTTPServer):
address_family = socket.AF_INET6 if address.version == 6 else socket.AF_INET

server = DashboardServer((bind_host, args.port), DashboardHandler)
print(f"[dashboard] serving on http://{args.host}:{args.port}")
print(f"[dashboard] repo: {REPO_ROOT}")
print(f"[dashboard] host: {host_kind}")
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
print("[dashboard] stopped")
with writer_lease(REPO_ROOT):
server = DashboardServer((bind_host, args.port), DashboardHandler)
print(f"[dashboard] serving on http://{args.host}:{args.port}")
print(f"[dashboard] repo: {REPO_ROOT}")
print(f"[dashboard] host: {host_kind}")
if args.open_browser:
webbrowser.open(f"http://{args.host}:{args.port}")
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
print("[dashboard] stopped")
except ValueError as error:
parser.exit(78, str(error) + "\n")


if __name__ == "__main__":
Expand Down
Loading
Loading