diff --git a/07-Other/AI/AI Agent/WY/团队服务器.md b/07-Other/AI/AI Agent/WY/团队服务器.md
index 0a6242b..91985d8 100644
--- a/07-Other/AI/AI Agent/WY/团队服务器.md
+++ b/07-Other/AI/AI Agent/WY/团队服务器.md
@@ -16,4 +16,273 @@
- 域名(内网):`nos-gzdev.163nos.com`
## 文档
-- S3:https://sa.nie.netease.com/console/webconsole/idc/
\ No newline at end of file
+- S3:https://sa.nie.netease.com/console/webconsole/idc/
+
+
+# 垃圾电脑服务
+我现在想在局域网里的另一台Ubuntu电脑上部署Unreal Horde服务,存储 缓存以及管理各个worker、节点信息;本机性能强劲,作为worker进行具体工作。
+- 电脑ip:10.219.36.57
+- 用户名:netease
+- 密码:123
+
+官方文档(UE5.5)
+ - https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-in-unreal-engine?application_version=5.5
+- Horde README.md https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/README.md
+ - [**Deploying Horde**](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/Docs/Deployment.md)
+ - - 有关 Horde 的架构和组件的信息,以及部署它们的最佳实践。
+ **受众:** IT、系统管理员、打算修改 Horde 的程序员。
+ - [**Configuring and Operating Horde**](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/Docs/Config.md)
+ - - 描述如何设置和管理 Horde。
+ **受众:**构建/开发运营团队、管理员。
+ - [**Horde Internals**](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/Docs/Internals.md)
+ - - 描述如何构建和修改 Horde 及其架构。
+ **受众:**希望扩展 Horde 的开发人员。
+- 视频
+ - [Horde and Unreal Build Accelerator: Operating at Epic Scale](https://youtu.be/ZUlwqNbYWBQ?si=DgFvrvepK67v-iTi)
+
+## 下载地址
+- 官方下载地址
+ - 服务端:https://github.com/EpicGames/UnrealEngine/releases/download/5.5.0-release/UnrealHordeServer.msi
+
+# 大致步骤
+1. 使用docker部署服务。
+2. 服务器配置。
+ 1. 默认端口Http 13340、Http 2 13342。
+ 1. 默认情况下,Horde配置为使用端口5000通过未加密的HTTP提供数据。在默认情况下,代理通过端口5002使用未加密的HTTP/2上的gRPC与Horde服务器通信。这些设置在服务器启动时显示在控制台上。
+ 2. [相关配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-orientation-for-unreal-engine?application_version=5.5)
+ - [服务器配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-settings-for-unreal-engine?application_version=5.5#%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AE%BE%E7%BD%AE)将配置该服务器与其他服务器的通信,定义静态参数等。它由与该服务器一同部署的 `Server.json` 文件驱动。
+ - [完整配置参数清单](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-settings-for-unreal-engine?application_version=5.5#%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AE%BE%E7%BD%AE)
+ - [全局配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-schema-for-unreal-engine?application_version=5.5#globals)在部署后控制所有面向用户的元素,该配置存于名为 `Globals.json` 的文件中。设置好部署参数后,大多数配置都在此处完成。
+ 3. [验证相关配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-authentication-tutorial-for-unreal-engine?application_version=5.5)
+ 1. [[#OIDC身份验证]]
+3. 代理机配置。详见 http://10.219.103.35:13340/docs/Landing.md
+ 1. 配置文件位置:安装目录\Agent\Defaults\agent.json
+ 2.
+# 部署笔记
+
+## 部署流程
+- [Horde服务器](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-server-for-unreal-engine?application_version=5.5)
+
+# Perforce服务器
+ssl:inner02-commit.perforce.nie.netease.com:1667
+
+# OIDC
+## Netease OIDC信息
+| | |
+| ------------- | ---------------------------------------------------------------- |
+| client id | 4884b03e951711f0ad370242ac120002 |
+| client secret | f2862b8c5ab24085ab0883d119b631604884b354951711f0ad370242ac120002 |
+## OIDC 参考代码
+```python
+#coding:UTF-8
+"""
+Requirements:
+ 1. Flask >= 0.10.1
+ 2. requests
+ 3. jwkest >= 1.1.7
+
+Usage:
+ python oidc_code_demo.py -H {listen_address} -p {listen_port}
+
+Help:
+ python oidc_code_demo.py -h
+"""
+import os
+import uuid
+from hashlib import md5
+import datetime
+from urllib import urlencode
+import json
+import requests
+from flask import Flask, request, jsonify, session, redirect
+
+from jwkest.jwk import SYMKey
+from jwkest.jws import JWS
+from jwkest.jwk import load_jwks_from_url
+from jwkest.jws import NoSuitableSigningKeys
+
+__revision__ = "0.01"
+__author__ = "chenxs@corp.netease.com"
+
+OIDC_CLIENT_ID = ""
+OIDC_CLIENT_SECRET = ""
+OIDC_PROVIDER = "https://login.netease.com/connect"
+OIDC_AUTHORIZATION_SERVER = "https://login.netease.com/connect/authorize"
+OIDC_TOKEN_ENDPOINT = "https://login.netease.com/connect/token"
+OIDC_USERINFO_ENDPOINT = "https://login.netease.com/connect/userinfo"
+OIDC_SCOPE = "openid nickname email fullname dep title empno"
+OIDC_REDIRECT_URI = "https://127.0.0.1:5000/finish"
+OIDC_JWKS_URI = "https://login.netease.com/connect/jwks"
+OIDC_ALG = "HS256"
+
+PYTHON_OIDC_DEMO = Flask(__name__)
+
+@PYTHON_OIDC_DEMO.route("/", methods=['GET'])
+def index():
+ """index"""
+ if 'username' in session:
+ body = (
+ u"
OpenID Connect 鐧诲綍鎴愬姛銆�
"
+ u"鎮ㄧ殑鐢ㄦ埛鍚嶆槸锛�%s"
+ u"鎮ㄧ殑鍏ㄥ悕鏄細%s"
+ u"鎮ㄧ殑閭鏄細%s"
+ u"鎮ㄧ殑鑱屼綅鏄細%s"
+ u"鎮ㄧ殑閮ㄩ棬鏄細%s"
+ u"鎮ㄧ殑宸ュ彿鏄細%s") % (
+ session['username'], session.get('fullname', ''),
+ session.get('email', ''), session.get('title', ''),
+ session.get('dep', ''), session.get('empno', ''))
+ body += u"鎴虫垜閲嶆柊鐧诲綍"
+ return body
+ else:
+ return u"鎴虫垜鐧诲綍"
+
+@PYTHON_OIDC_DEMO.route("/login", methods=['GET'])
+def login():
+ """AuthN Request"""
+ session.clear()
+ now = datetime.datetime.now().strftime("%s")
+ session['uid'] = uuid.uuid4().hex
+ session['state'] = session['uid']
+ session['nonce'] = md5(session['uid'] + now).hexdigest()
+
+ authn_request_params = {
+ 'response_type': 'code',
+ 'client_id': OIDC_CLIENT_ID,
+ 'state': session['state'],
+ 'nonce': session['nonce'],
+ 'scope': OIDC_SCOPE,
+ 'redirect_uri': OIDC_REDIRECT_URI,
+ #'prompt': 'login',
+ 'display': 'touch',
+ }
+
+ redirect_url = "?".join([
+ OIDC_AUTHORIZATION_SERVER, urlencode(authn_request_params)])
+
+ return redirect(redirect_url)
+
+
+def token_request(code):
+ """2. Token Request"""
+ params = {
+ 'grant_type': 'authorization_code',
+ 'code': code,
+ 'redirect_uri': OIDC_REDIRECT_URI,
+ 'client_id': OIDC_CLIENT_ID,
+ 'client_secret': OIDC_CLIENT_SECRET,
+ }
+ _resp = requests.post(OIDC_TOKEN_ENDPOINT, data=params)
+ return json.loads(_resp.text)
+
+def id_token_verify(id_token, nonce=None):
+ """3. id token verify"""
+
+ now = int(datetime.datetime.now().strftime("%s"))
+ if OIDC_ALG == "HS256":
+ signed_keys = [SYMKey(key=OIDC_CLIENT_SECRET)]
+ else:
+ signed_keys = load_jwks_from_url(OIDC_JWKS_URI)
+
+ try:
+ plain_id_token = JWS().verify_compact(id_token, signed_keys)
+ except NoSuitableSigningKeys:
+ # logger the id_token please
+ return {'error': 'can not verify the id token'}
+
+ print "idtoken: %s" % plain_id_token
+ if nonce:
+ if (not plain_id_token.has_key('nonce')) or (
+ plain_id_token['nonce'] != nonce):
+ return {'error': 'id token nonce not correct'}
+ if plain_id_token['iss'] != OIDC_PROVIDER:
+ return {'error': 'id token iss not correct'}
+ if plain_id_token['aud'] != OIDC_CLIENT_ID:
+ return {'error': 'id token aud not correct'}
+ if now >= int(plain_id_token['exp']):
+ return {'error': 'id token expired'}
+
+ return {'id_token': plain_id_token}
+
+
+@PYTHON_OIDC_DEMO.route("/finish", methods=['GET'])
+def finish():
+ """
+ 1. AuthN Response
+ 2. Token Request
+ 3. id token verify
+ 4. userinfo request
+ 5. login user
+ """
+
+ # 1. AuthN Response
+ try:
+ code = request.args.get('code')
+ if session['state']:
+ state = request.args.get('state')
+ if state != session['state']:
+ return u"闈炴硶璇锋眰"
+ except ValueError:
+ return u"闈炴硶璇锋眰"
+ # 2. Token Request
+ token = token_request(code)
+ print "token: %s" % token
+ if token.has_key('error'):
+ return u"鍑洪敊浜嗭細%s" % str(token)
+ # 3. id token verify
+ id_token_verified = id_token_verify(token['id_token'])
+ if id_token_verified.has_key('error'):
+ return id_token_verified['error']
+ else:
+ id_token = id_token_verified['id_token']
+
+ print "id_token: %s" % id_token
+
+ # 4. userinfo request
+ _req_session = requests.Session()
+ _req_session.headers.update({
+ "Authorization": "Bearer %s" % token['access_token']})
+ userinfo_req = _req_session.get(OIDC_USERINFO_ENDPOINT)
+ userinfo = json.loads(userinfo_req.text)
+ # login the user
+ session['username'] = userinfo['nickname']
+ session['email'] = userinfo['email']
+ session['title'] = userinfo.get('title', '')
+ session['empno'] = userinfo.get('empno', '')
+ session['dep'] = userinfo.get('dep', '')
+ session['fullname'] = userinfo.get('fullname', '')
+ return redirect("/")
+
+if __name__ == "__main__":
+ import sys
+ import argparse
+ parser = argparse.ArgumentParser(
+ usage='%(prog)s [options]', version='%(prog)s ' + str(__revision__))
+ parser.add_argument(
+ '-H', '--host', dest='host', type=str,
+ help="Specify listening adress, default is 127.0.0.1")
+ parser.add_argument(
+ '-p', '--port', dest='port', type=int,
+ help="Specify listening port, default is 5000")
+ parser.add_argument(
+ '-c', '--client_id', dest='client_id', type=str,
+ help="oidc client_id is required.")
+ parser.add_argument(
+ '-s', '--client_secret', dest='client_secret', type=str,
+ help="oidc client_secret is required.")
+
+ args = parser.parse_args()
+
+ host = args.host or '127.0.0.1'
+ port = args.port or 5000
+ OIDC_REDIRECT_URI = "http://%s:%s/finish" % (host, port)
+ OIDC_CLIENT_ID = args.client_id
+ OIDC_CLIENT_SECRET = args.client_secret
+ if not OIDC_CLIENT_ID or not OIDC_CLIENT_SECRET:
+ parser.print_help()
+ parser.exit()
+
+ PYTHON_OIDC_DEMO.secret_key = "this is a random secret"
+ PYTHON_OIDC_DEMO.debug = True
+ PYTHON_OIDC_DEMO.run(host=host, port=port)
+```