bayafs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bayafs-0.1.0/.gitignore +223 -0
- bayafs-0.1.0/LICENSE +9 -0
- bayafs-0.1.0/PKG-INFO +109 -0
- bayafs-0.1.0/README.md +84 -0
- bayafs-0.1.0/pyproject.toml +50 -0
- bayafs-0.1.0/src/baya/__init__.py +11 -0
- bayafs-0.1.0/src/baya/broker.py +178 -0
- bayafs-0.1.0/src/baya/cli.py +71 -0
- bayafs-0.1.0/src/baya/cluster.py +286 -0
- bayafs-0.1.0/src/baya/commands/__init__.py +1 -0
- bayafs-0.1.0/src/baya/commands/config.py +55 -0
- bayafs-0.1.0/src/baya/commands/install.py +200 -0
- bayafs-0.1.0/src/baya/commands/license.py +89 -0
- bayafs-0.1.0/src/baya/commands/lifecycle.py +414 -0
- bayafs-0.1.0/src/baya/commands/release.py +71 -0
- bayafs-0.1.0/src/baya/commands/user.py +262 -0
- bayafs-0.1.0/src/baya/config.py +135 -0
- bayafs-0.1.0/src/baya/doctor.py +376 -0
- bayafs-0.1.0/src/baya/errors.py +17 -0
- bayafs-0.1.0/src/baya/gateway.py +110 -0
- bayafs-0.1.0/src/baya/journal.py +90 -0
- bayafs-0.1.0/src/baya/keycloak.py +176 -0
- bayafs-0.1.0/src/baya/license.py +62 -0
- bayafs-0.1.0/src/baya/prereqs.py +173 -0
- bayafs-0.1.0/src/baya/release.py +144 -0
- bayafs-0.1.0/src/baya/shell.py +70 -0
- bayafs-0.1.0/src/baya/tls.py +98 -0
- bayafs-0.1.0/src/baya/ui.py +176 -0
- bayafs-0.1.0/src/baya/users.py +153 -0
- bayafs-0.1.0/src/baya/verify.py +200 -0
- bayafs-0.1.0/src/baya/workspace.py +159 -0
- bayafs-0.1.0/tests/conftest.py +39 -0
- bayafs-0.1.0/tests/test_broker.py +144 -0
- bayafs-0.1.0/tests/test_cli_flow.py +116 -0
- bayafs-0.1.0/tests/test_config.py +75 -0
- bayafs-0.1.0/tests/test_doctor.py +122 -0
- bayafs-0.1.0/tests/test_license.py +39 -0
- bayafs-0.1.0/tests/test_release.py +146 -0
- bayafs-0.1.0/tests/test_users.py +106 -0
- bayafs-0.1.0/tests/test_workspace_and_gateway.py +98 -0
bayafs-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Prerequisites
|
|
2
|
+
*.d
|
|
3
|
+
|
|
4
|
+
# Compiled Object files
|
|
5
|
+
*.slo
|
|
6
|
+
*.lo
|
|
7
|
+
*.o
|
|
8
|
+
*.obj
|
|
9
|
+
|
|
10
|
+
# Precompiled Headers
|
|
11
|
+
*.gch
|
|
12
|
+
*.pch
|
|
13
|
+
|
|
14
|
+
# Compiled Dynamic libraries
|
|
15
|
+
*.so
|
|
16
|
+
*.dylib
|
|
17
|
+
*.dll
|
|
18
|
+
|
|
19
|
+
# Fortran module files
|
|
20
|
+
*.mod
|
|
21
|
+
*.smod
|
|
22
|
+
|
|
23
|
+
# Compiled Static libraries
|
|
24
|
+
*.lai
|
|
25
|
+
*.la
|
|
26
|
+
*.a
|
|
27
|
+
*.lib
|
|
28
|
+
|
|
29
|
+
# Executables
|
|
30
|
+
*.exe
|
|
31
|
+
*.out
|
|
32
|
+
*.app
|
|
33
|
+
|
|
34
|
+
# python virtual env
|
|
35
|
+
env/
|
|
36
|
+
.venv/
|
|
37
|
+
|
|
38
|
+
# temp directory for cmake builds
|
|
39
|
+
build/
|
|
40
|
+
|
|
41
|
+
# ctest scratch, written into the cwd when ctest runs from the repo root.
|
|
42
|
+
# Anchored: a nested Testing/ elsewhere in the tree is real source.
|
|
43
|
+
/Testing/
|
|
44
|
+
|
|
45
|
+
# pyarmor license registration zip (required for release generation)
|
|
46
|
+
generate_release/fabric_studio/dependencies/pyarmor-regfile-4186.zip
|
|
47
|
+
|
|
48
|
+
# temp directories for release generation and cmake builds
|
|
49
|
+
/release_scratch/
|
|
50
|
+
/release/
|
|
51
|
+
/generated_rtl/
|
|
52
|
+
/generated_perfapi/
|
|
53
|
+
/sdc/
|
|
54
|
+
/traces/
|
|
55
|
+
/build_lib_perfapi/
|
|
56
|
+
/lib_perfapi/
|
|
57
|
+
/systemc_prefix/
|
|
58
|
+
/arm_tlm_prefix/
|
|
59
|
+
dist/
|
|
60
|
+
*.whl
|
|
61
|
+
|
|
62
|
+
# temp directories for python builds
|
|
63
|
+
__pycache__/
|
|
64
|
+
.pytest_cache/
|
|
65
|
+
.hypothesis/
|
|
66
|
+
*.egg-info/
|
|
67
|
+
*.pyc
|
|
68
|
+
|
|
69
|
+
# jupyter notebooks and jupyter lab
|
|
70
|
+
.ipynb_checkpoints/
|
|
71
|
+
.jupyter/
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# run area for hw tests
|
|
75
|
+
run_area/
|
|
76
|
+
|
|
77
|
+
# sw generated sv, fl, trace, json, dot, xml, svh, sdc files, zip
|
|
78
|
+
src/sw/**/*.sv
|
|
79
|
+
# ...but hand-written SV test fixtures are tracked
|
|
80
|
+
!src/sw/python/tests/data/**/*.sv
|
|
81
|
+
src/sw/**/*.sdc
|
|
82
|
+
src/sw/**/*.svh
|
|
83
|
+
src/sw/**/*.fl
|
|
84
|
+
src/sw/**/*.xml
|
|
85
|
+
src/sw/**/trace.txt
|
|
86
|
+
src/sw/**/trm/
|
|
87
|
+
src/sw/**/noc_datasheet/
|
|
88
|
+
src/sw/python/**/trace
|
|
89
|
+
src/sw/python/**/meta
|
|
90
|
+
src/sw/python/.hypothesis/
|
|
91
|
+
src/sw/python/designs/SW_simulator_trace.txt
|
|
92
|
+
src/sw/python/jupyter_notebooks/*.json
|
|
93
|
+
src/sw/python/**/generated_rtl/
|
|
94
|
+
src/sw/python/**/generated_perfapi/
|
|
95
|
+
src/sw/python/**/traces/*
|
|
96
|
+
src/sw/python/designs/**/trace/**/*.txt
|
|
97
|
+
src/sw/python/designs/**/meta/*.txt
|
|
98
|
+
src/sw/python/designs/**/meta/*.xml
|
|
99
|
+
src/sw/python/designs/**/meta/*.json
|
|
100
|
+
src/sw/python/designs/tt_ring.json
|
|
101
|
+
src/sw/python/jupyter_notebooks/interconnect_string.txt
|
|
102
|
+
src/sw/python/jupyter_notebooks/mesh_sweep.csv
|
|
103
|
+
routs_src*.csv
|
|
104
|
+
|
|
105
|
+
# randomly generated vps configs
|
|
106
|
+
src/hw/configs/vps_random/**/*.sv
|
|
107
|
+
src/hw/configs/vps_random/**/*.fl
|
|
108
|
+
src/hw/configs/vps_random/**/*.txt
|
|
109
|
+
|
|
110
|
+
### VisualStudioCode ###
|
|
111
|
+
.vscode/**/* # Maybe .vscode/**/* instead - see comments
|
|
112
|
+
!.vscode/settings.json
|
|
113
|
+
!.vscode/tasks.json
|
|
114
|
+
!.vscode/launch.json
|
|
115
|
+
!.vscode/extensions.json
|
|
116
|
+
|
|
117
|
+
### MacOS
|
|
118
|
+
.DS_Store
|
|
119
|
+
|
|
120
|
+
### Generated outputs
|
|
121
|
+
*.numbers
|
|
122
|
+
*.xlsx
|
|
123
|
+
*.log
|
|
124
|
+
*.prof
|
|
125
|
+
sim_output.txt
|
|
126
|
+
*output*
|
|
127
|
+
src/sw/python/jupyter_notebooks/tt_ring.json
|
|
128
|
+
src/sw/python/jupyter_notebooks/**/*.txt
|
|
129
|
+
*.dot
|
|
130
|
+
docs/out/
|
|
131
|
+
api_trace*
|
|
132
|
+
sim_log*.txt
|
|
133
|
+
sim_events.json
|
|
134
|
+
replay_events.json
|
|
135
|
+
|
|
136
|
+
# FabricStudio GUI — generated outputs
|
|
137
|
+
gui/designs/index.json
|
|
138
|
+
gui/designs/*.elab.json
|
|
139
|
+
gui/designs/*.sim_*.json
|
|
140
|
+
gui/output/
|
|
141
|
+
|
|
142
|
+
# Include xls files in the customer directory
|
|
143
|
+
!customer/**/*.xls*
|
|
144
|
+
customer/**/~*.xls*
|
|
145
|
+
|
|
146
|
+
# Ctags for Verilog auti-browse
|
|
147
|
+
tags
|
|
148
|
+
|
|
149
|
+
# Random testcases
|
|
150
|
+
random_test
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
generate_release/fabric_studio/dependencies/pyarmor-regfile-4186.zip
|
|
154
|
+
release_wheel_temp/
|
|
155
|
+
|
|
156
|
+
# temporary PID file created when building bindings to ensure we don't run multiple builds at once
|
|
157
|
+
build_bindings.pid
|
|
158
|
+
|
|
159
|
+
# temp copy of assets for frontend testing in CacheSim
|
|
160
|
+
src/sw/cachesim_py/Notebooks/assets
|
|
161
|
+
generate_release/cachesim/release_dir/jupyter_notebooks/assets
|
|
162
|
+
generate_release/cachesim/dependencies/pyarmor-regfile-4186.zip
|
|
163
|
+
|
|
164
|
+
# sphinx
|
|
165
|
+
src/sw/python/docs/_build
|
|
166
|
+
src/sw/python/_build
|
|
167
|
+
src/sw/cachesim_py/docs/_build
|
|
168
|
+
|
|
169
|
+
src/sw/python/designs/img
|
|
170
|
+
|
|
171
|
+
cache/
|
|
172
|
+
Arch/oft_home*
|
|
173
|
+
report.html
|
|
174
|
+
report.xml
|
|
175
|
+
|
|
176
|
+
# flopgen accuracy/error reports (diagnostic artifacts of `just rebuild_flops` /
|
|
177
|
+
# `just report_datapoints`)
|
|
178
|
+
flopgen_report*/
|
|
179
|
+
|
|
180
|
+
# ipxact schemas
|
|
181
|
+
src/sw/python/fabric_studio/data_models/sv/ipxact_schemas/
|
|
182
|
+
|
|
183
|
+
# clangd cache
|
|
184
|
+
.cache/clangd
|
|
185
|
+
|
|
186
|
+
# claude superpowers plugin markdowns
|
|
187
|
+
docs/superpowers/
|
|
188
|
+
|
|
189
|
+
# architecture decision records (local only — not for the company repo)
|
|
190
|
+
docs/adr/
|
|
191
|
+
|
|
192
|
+
# domain-context notes (local scratch — durable facts live in CLAUDE.md)
|
|
193
|
+
CONTEXT.md
|
|
194
|
+
|
|
195
|
+
# claude code local settings (per-user, not shared) -- at every level
|
|
196
|
+
**/.claude/settings.json
|
|
197
|
+
|
|
198
|
+
# mempalace (claude code plugin) generated files
|
|
199
|
+
entities.json
|
|
200
|
+
mempalace.yaml
|
|
201
|
+
|
|
202
|
+
# git worktrees (per-user, not shared)
|
|
203
|
+
.claude/worktrees/
|
|
204
|
+
# git worktrees + venvs built by coverage/collect.py for historical FS versions
|
|
205
|
+
/fs-worktrees/
|
|
206
|
+
# worktrees + venvs built by setup_alt_venv.py (just setup-alt-venv <ref>)
|
|
207
|
+
/.alt-venv-worktrees/
|
|
208
|
+
/.venv-*
|
|
209
|
+
# uv cache, only when setup_sw_env.py cannot place it beside the clone
|
|
210
|
+
/.uv-cache/
|
|
211
|
+
# coverage collector --dryrun / baseline_dryrun.sh output (per-user, not shared)
|
|
212
|
+
coverage_dryrun/
|
|
213
|
+
|
|
214
|
+
# local secrets — never commit
|
|
215
|
+
.env
|
|
216
|
+
|
|
217
|
+
# local ruff settings
|
|
218
|
+
.ruff.toml
|
|
219
|
+
|
|
220
|
+
# FabricStudio SaaS per-server config + secrets (never commit)
|
|
221
|
+
gui/saas_poc/setup/saas_config.env
|
|
222
|
+
saas_secrets/
|
|
223
|
+
gui/saas_poc/local/smtp.env
|
bayafs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) Baya Systems, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This package installs and operates FabricStudio, licensed software of Baya Systems.
|
|
4
|
+
|
|
5
|
+
Permission is granted to install and use this package solely to deploy and operate
|
|
6
|
+
FabricStudio under a valid licence agreement with Baya Systems. The FabricStudio software
|
|
7
|
+
this package installs is distributed separately and is governed by that agreement.
|
|
8
|
+
|
|
9
|
+
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
bayafs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bayafs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Install and operate FabricStudio on your own infrastructure.
|
|
5
|
+
Project-URL: Homepage, https://bayasystems.com
|
|
6
|
+
Project-URL: Documentation, https://bayasystems.com/docs/self-hosted
|
|
7
|
+
Author-email: Baya Systems <support@bayasystems.com>
|
|
8
|
+
License-Expression: LicenseRef-Proprietary
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: eda,fabricstudio,installer,noc,self-hosted
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: pyyaml>=6
|
|
20
|
+
Requires-Dist: rich>=13.7
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# baya — FabricStudio, self-hosted
|
|
27
|
+
|
|
28
|
+
Installs and operates [FabricStudio](https://bayasystems.com) on your own Linux server:
|
|
29
|
+
one command brings up the whole platform, and each engineer works in their own workspace,
|
|
30
|
+
on their own home directory, signed in through your identity provider.
|
|
31
|
+
|
|
32
|
+
Published as `bayafs`; the command is `baya`.
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- Linux x86-64 — RHEL/Oracle, Ubuntu/Debian, or SLES
|
|
37
|
+
- 4+ CPUs, 16 GB+ RAM, 100 GB+ free disk
|
|
38
|
+
- root (the installer manages docker, Kubernetes, and port 443)
|
|
39
|
+
- Outbound HTTPS to Baya's token broker and to Google Artifact Registry
|
|
40
|
+
- A licence key from Baya
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pip install bayafs
|
|
46
|
+
|
|
47
|
+
baya doctor # check this host; run it before anything else
|
|
48
|
+
baya license set <your-licence-key>
|
|
49
|
+
baya storage set --base /home # where your engineers' home directories live
|
|
50
|
+
baya init --hostname fs.yourcompany.com
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`init` installs what is missing, starts a single-node Kubernetes cluster, pulls the images
|
|
54
|
+
your licence entitles you to, and brings up the platform. It is safe to re-run: a failed
|
|
55
|
+
step is resumed, not repeated.
|
|
56
|
+
|
|
57
|
+
## Give people access
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
baya user map alice@yourcompany.com --linux-user alice --name "Alice Chen"
|
|
61
|
+
baya verify
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`user map` binds a login to a local account and starts that person's workspace on their real
|
|
65
|
+
home directory — files their designs produce are owned by them, exactly as if they had run
|
|
66
|
+
the tools from a shell. `verify` proves the install works end to end and names the command
|
|
67
|
+
to run for anything that does not.
|
|
68
|
+
|
|
69
|
+
## Day to day
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
baya status # version, health, users, certificate, licence
|
|
73
|
+
baya verify # end-to-end check, after every change
|
|
74
|
+
baya update --check # versions newer than the one installed
|
|
75
|
+
baya update # drains running work first; --force skips the wait
|
|
76
|
+
baya rollback # back to the previous version
|
|
77
|
+
baya workspace list # who is using what
|
|
78
|
+
baya logs <service> # no kubectl required
|
|
79
|
+
baya support-bundle # one tarball for Baya, secrets and designs redacted
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Certificates and identity
|
|
83
|
+
|
|
84
|
+
A self-signed certificate is generated so day one works; replace it with yours:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
baya tls set --cert /path/to/fullchain.pem --key /path/to/key.pem
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Until an identity provider is federated, people sign in with a username and password held
|
|
91
|
+
by the bundled Keycloak. To use your own:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
baya idp add --type entra --tenant <id> --client-id <id> --client-secret <secret>
|
|
95
|
+
baya idp test
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Uninstall
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
baya uninstall --yes
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Removes the cluster and the install's own state. **Your engineers' files are never touched.**
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
Licensed software. `baya` itself is freely installable; running FabricStudio requires a
|
|
109
|
+
licence key issued by Baya Systems.
|
bayafs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# baya — FabricStudio, self-hosted
|
|
2
|
+
|
|
3
|
+
Installs and operates [FabricStudio](https://bayasystems.com) on your own Linux server:
|
|
4
|
+
one command brings up the whole platform, and each engineer works in their own workspace,
|
|
5
|
+
on their own home directory, signed in through your identity provider.
|
|
6
|
+
|
|
7
|
+
Published as `bayafs`; the command is `baya`.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Linux x86-64 — RHEL/Oracle, Ubuntu/Debian, or SLES
|
|
12
|
+
- 4+ CPUs, 16 GB+ RAM, 100 GB+ free disk
|
|
13
|
+
- root (the installer manages docker, Kubernetes, and port 443)
|
|
14
|
+
- Outbound HTTPS to Baya's token broker and to Google Artifact Registry
|
|
15
|
+
- A licence key from Baya
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pip install bayafs
|
|
21
|
+
|
|
22
|
+
baya doctor # check this host; run it before anything else
|
|
23
|
+
baya license set <your-licence-key>
|
|
24
|
+
baya storage set --base /home # where your engineers' home directories live
|
|
25
|
+
baya init --hostname fs.yourcompany.com
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`init` installs what is missing, starts a single-node Kubernetes cluster, pulls the images
|
|
29
|
+
your licence entitles you to, and brings up the platform. It is safe to re-run: a failed
|
|
30
|
+
step is resumed, not repeated.
|
|
31
|
+
|
|
32
|
+
## Give people access
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
baya user map alice@yourcompany.com --linux-user alice --name "Alice Chen"
|
|
36
|
+
baya verify
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`user map` binds a login to a local account and starts that person's workspace on their real
|
|
40
|
+
home directory — files their designs produce are owned by them, exactly as if they had run
|
|
41
|
+
the tools from a shell. `verify` proves the install works end to end and names the command
|
|
42
|
+
to run for anything that does not.
|
|
43
|
+
|
|
44
|
+
## Day to day
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
baya status # version, health, users, certificate, licence
|
|
48
|
+
baya verify # end-to-end check, after every change
|
|
49
|
+
baya update --check # versions newer than the one installed
|
|
50
|
+
baya update # drains running work first; --force skips the wait
|
|
51
|
+
baya rollback # back to the previous version
|
|
52
|
+
baya workspace list # who is using what
|
|
53
|
+
baya logs <service> # no kubectl required
|
|
54
|
+
baya support-bundle # one tarball for Baya, secrets and designs redacted
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Certificates and identity
|
|
58
|
+
|
|
59
|
+
A self-signed certificate is generated so day one works; replace it with yours:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
baya tls set --cert /path/to/fullchain.pem --key /path/to/key.pem
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Until an identity provider is federated, people sign in with a username and password held
|
|
66
|
+
by the bundled Keycloak. To use your own:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
baya idp add --type entra --tenant <id> --client-id <id> --client-secret <secret>
|
|
70
|
+
baya idp test
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Uninstall
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
baya uninstall --yes
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Removes the cluster and the install's own state. **Your engineers' files are never touched.**
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
Licensed software. `baya` itself is freely installable; running FabricStudio requires a
|
|
84
|
+
licence key issued by Baya Systems.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bayafs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Install and operate FabricStudio on your own infrastructure."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "LicenseRef-Proprietary"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Baya Systems", email = "support@bayasystems.com" }]
|
|
14
|
+
keywords = ["fabricstudio", "noc", "eda", "self-hosted", "installer"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: System Administrators",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: System :: Installation/Setup",
|
|
22
|
+
"Topic :: System :: Systems Administration",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"rich>=13.7",
|
|
26
|
+
# The per-engineer workspace manifest is edited structurally, not textually: its
|
|
27
|
+
# storage must be repointed at that engineer's real home directory.
|
|
28
|
+
"PyYAML>=6",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://bayasystems.com"
|
|
33
|
+
Documentation = "https://bayasystems.com/docs/self-hosted"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = ["pytest>=8", "ruff"]
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
baya = "baya.cli:main"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/baya"]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.sdist]
|
|
45
|
+
# Tests ship with the sdist so a customer's security team can run them.
|
|
46
|
+
include = ["src", "tests", "README.md", "LICENSE"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
addopts = "-q"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""baya — install and operate FabricStudio on your own infrastructure.
|
|
2
|
+
|
|
3
|
+
Distributed as `bayafs` on PyPI; the command is `baya`. Implements FR-B of
|
|
4
|
+
gui/ONPREM_SPEC.md, per gui/ONPREM_BAYA_PLAN.md.
|
|
5
|
+
|
|
6
|
+
Deliberately shares no code with `bayactl` (spec D12/D23): admin tooling must not ship to
|
|
7
|
+
customers. Where the two look alike — output conventions, the subprocess wrapper — the code
|
|
8
|
+
is reimplemented here rather than imported, and the duplication is the point.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Exchanging the license key for a registry credential.
|
|
2
|
+
|
|
3
|
+
The key is a claim ticket: it authenticates the install to Baya, and what comes back is a
|
|
4
|
+
short-lived credential scoped to this customer's own repository. Nothing here can pull an
|
|
5
|
+
image by itself.
|
|
6
|
+
|
|
7
|
+
Credentials last an hour (FR-A7), which is shorter than anything else in an install, so they
|
|
8
|
+
are cached in state and refreshed before expiry rather than fetched per operation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import urllib.error
|
|
16
|
+
import urllib.request
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from datetime import UTC, datetime, timedelta
|
|
19
|
+
|
|
20
|
+
from . import license as license_mod
|
|
21
|
+
from . import ui
|
|
22
|
+
from .config import Config
|
|
23
|
+
from .errors import BayaError
|
|
24
|
+
|
|
25
|
+
TIMEOUT = 20
|
|
26
|
+
# Refresh this far ahead of expiry: a pull that starts inside the window must not have the
|
|
27
|
+
# credential expire underneath it.
|
|
28
|
+
REFRESH_MARGIN = timedelta(minutes=10)
|
|
29
|
+
FAKE_ENV = "BAYA_FAKE_BROKER"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Exchange:
|
|
34
|
+
username: str
|
|
35
|
+
password: str
|
|
36
|
+
expires_at: str
|
|
37
|
+
registry: str
|
|
38
|
+
versions: list[str]
|
|
39
|
+
engines: list[str]
|
|
40
|
+
channel: str
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def expiry(self) -> datetime | None:
|
|
44
|
+
try:
|
|
45
|
+
# fromisoformat handles a trailing Z from Python 3.11 on; the broker sends both forms.
|
|
46
|
+
return datetime.fromisoformat(self.expires_at)
|
|
47
|
+
except (ValueError, AttributeError):
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def expiring_within(self, margin: timedelta = REFRESH_MARGIN) -> bool:
|
|
51
|
+
expiry = self.expiry
|
|
52
|
+
return expiry is None or expiry - datetime.now(UTC) < margin
|
|
53
|
+
|
|
54
|
+
def to_state(self) -> dict:
|
|
55
|
+
return {
|
|
56
|
+
"credential_username": self.username,
|
|
57
|
+
"credential_password": self.password,
|
|
58
|
+
"credential_expires_at": self.expires_at,
|
|
59
|
+
"registry": self.registry,
|
|
60
|
+
"granted_versions": self.versions,
|
|
61
|
+
"granted_engines": self.engines,
|
|
62
|
+
"channel": self.channel,
|
|
63
|
+
"last_exchange": datetime.now(UTC).isoformat(timespec="seconds"),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_state(cls, state: dict) -> Exchange | None:
|
|
68
|
+
if not state.get("credential_password"):
|
|
69
|
+
return None
|
|
70
|
+
return cls(
|
|
71
|
+
username=state.get("credential_username", "oauth2accesstoken"),
|
|
72
|
+
password=state["credential_password"],
|
|
73
|
+
expires_at=state.get("credential_expires_at", ""),
|
|
74
|
+
registry=state.get("registry", ""),
|
|
75
|
+
versions=list(state.get("granted_versions", [])),
|
|
76
|
+
engines=list(state.get("granted_engines", [])),
|
|
77
|
+
channel=state.get("channel", "stable"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _fake(key: str) -> Exchange:
|
|
82
|
+
"""Development without the broker (plan D24). Never returns a usable credential."""
|
|
83
|
+
return Exchange(
|
|
84
|
+
username="_fake",
|
|
85
|
+
password=f"fake-credential-for-{license_mod.key_id(key)}",
|
|
86
|
+
expires_at=(datetime.now(UTC) + timedelta(hours=1)).isoformat(timespec="seconds"),
|
|
87
|
+
registry="localhost:5000/fs-fake",
|
|
88
|
+
versions=["0.0.1"],
|
|
89
|
+
engines=["latest"],
|
|
90
|
+
channel="stable",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def exchange(
|
|
95
|
+
cfg: Config, key: str, *, version: str | None = None, install_id: str | None = None
|
|
96
|
+
) -> Exchange:
|
|
97
|
+
"""Trade the license key for a credential. Raises BayaError with an actionable message."""
|
|
98
|
+
if not license_mod.is_well_formed(key):
|
|
99
|
+
raise BayaError(f"that license key is not usable: {license_mod.why_invalid(key)}")
|
|
100
|
+
if os.environ.get(FAKE_ENV):
|
|
101
|
+
ui.warn(f"{FAKE_ENV} is set — using a fake credential that cannot pull anything")
|
|
102
|
+
return _fake(key)
|
|
103
|
+
|
|
104
|
+
url = cfg.broker_url.rstrip("/") + "/v1/token"
|
|
105
|
+
payload = {"license_key": key}
|
|
106
|
+
if version:
|
|
107
|
+
payload["version"] = version
|
|
108
|
+
if install_id:
|
|
109
|
+
payload["install_id"] = install_id
|
|
110
|
+
request = urllib.request.Request(
|
|
111
|
+
url,
|
|
112
|
+
data=json.dumps(payload).encode(),
|
|
113
|
+
method="POST",
|
|
114
|
+
headers={"Content-Type": "application/json"},
|
|
115
|
+
)
|
|
116
|
+
try:
|
|
117
|
+
with urllib.request.urlopen(request, timeout=TIMEOUT) as response:
|
|
118
|
+
body = json.loads(response.read())
|
|
119
|
+
except urllib.error.HTTPError as exc:
|
|
120
|
+
raise BayaError(_explain(exc)) from exc
|
|
121
|
+
except (urllib.error.URLError, TimeoutError, OSError) as exc:
|
|
122
|
+
raise BayaError(
|
|
123
|
+
f"could not reach {cfg.broker_url}: {getattr(exc, 'reason', exc)}\n"
|
|
124
|
+
" This host needs outbound HTTPS to it; `baya doctor` checks that."
|
|
125
|
+
) from exc
|
|
126
|
+
|
|
127
|
+
credential = body.get("credential") or {}
|
|
128
|
+
access = body.get("access") or {}
|
|
129
|
+
return Exchange(
|
|
130
|
+
username=credential.get("username", "oauth2accesstoken"),
|
|
131
|
+
password=credential.get("password", ""),
|
|
132
|
+
expires_at=credential.get("expires_at", ""),
|
|
133
|
+
registry=credential.get("registry", ""),
|
|
134
|
+
versions=list(access.get("versions", [])),
|
|
135
|
+
engines=list(access.get("engine_tags", [])),
|
|
136
|
+
channel=access.get("channel", "stable"),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _explain(exc: urllib.error.HTTPError) -> str:
|
|
141
|
+
"""Turn the broker's answer into something an administrator can act on.
|
|
142
|
+
|
|
143
|
+
The broker deliberately says little about why a key was refused; this maps each status
|
|
144
|
+
to the action the administrator would take next.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
reason = json.loads(exc.read()).get("error", "")
|
|
148
|
+
except (ValueError, OSError):
|
|
149
|
+
reason = ""
|
|
150
|
+
if exc.code == 400:
|
|
151
|
+
return f"the broker rejected the license key: {reason or 'malformed'}"
|
|
152
|
+
if exc.code == 401:
|
|
153
|
+
return (
|
|
154
|
+
"the broker does not recognise this license key.\n"
|
|
155
|
+
" Check it was copied whole, or ask Baya to reissue it."
|
|
156
|
+
)
|
|
157
|
+
if exc.code == 403:
|
|
158
|
+
return (
|
|
159
|
+
f"this license key is not permitted to pull: {reason or 'refused'}\n"
|
|
160
|
+
" Ask Baya to restore access or grant the version you are installing."
|
|
161
|
+
)
|
|
162
|
+
if exc.code >= 500:
|
|
163
|
+
return f"the broker could not issue a credential ({reason or exc.code}); this is Baya's side, retry shortly"
|
|
164
|
+
return f"the broker refused the exchange (HTTP {exc.code}): {reason}"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def current(cfg: Config, *, refresh: bool = False, version: str | None = None) -> Exchange:
|
|
168
|
+
"""The credential to use now, exchanging only when the cached one is near expiry."""
|
|
169
|
+
state = cfg.read_state()
|
|
170
|
+
cached = Exchange.from_state(state)
|
|
171
|
+
if cached and not refresh and not cached.expiring_within():
|
|
172
|
+
return cached
|
|
173
|
+
key = state.get("license_key")
|
|
174
|
+
if not key:
|
|
175
|
+
raise BayaError("no license key on this host — run `baya license set <key>` first")
|
|
176
|
+
fresh = exchange(cfg, key, version=version, install_id=state.get("install_id"))
|
|
177
|
+
cfg.update_state(**fresh.to_state())
|
|
178
|
+
return fresh
|