chore: تنظیم Nexus برای NuGet و Docker offline
Build and Deploy / build (push) Failing after 49s

- Dockerfile: استفاده از Nexus Docker (194.5.195.53:32082)
- NuGet.config: استفاده از Nexus NuGet با HTTP و allowInsecureConnections
- workflow: حذف kubectl dependency، اضافه کردن 32082 به insecure-registries
This commit is contained in:
masoodafar-web
2026-01-29 02:56:20 +03:30
parent 21ee004869
commit 8a285e485b
10 changed files with 180 additions and 114 deletions
+17 -39
View File
@@ -15,45 +15,27 @@ jobs:
container:
image: docker:latest
options: --privileged
env:
HTTP_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128
HTTPS_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128
NO_PROXY: localhost,127.0.0.1,gitea-svc,194.5.195.53,10.0.0.0/8
steps:
- name: Install dependencies
run: |
apk add --no-cache git curl
# Install kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/
- name: Start Docker daemon with insecure registry
run: |
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'DAEMON'
{
"insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"]
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082", "gitea-svc:3000"],
"dns": ["0.0.0.0"]
}
DAEMON
mkdir -p ~/.docker
cat > ~/.docker/config.json << 'CONF'
{
"proxies": {
"default": {
"httpProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128",
"httpsProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128",
"noProxy": "localhost,127.0.0.1,gitea-svc,194.5.195.53,10.0.0.0/8"
}
}
}
CONF
# بدون اینترنت - استفاده از local registry
dockerd &
for i in $(seq 1 30); do
docker info >/dev/null 2>&1 && break || sleep 2
done
docker info
# بررسی دسترسی به local registry
echo "📊 Checking local registry..."
nc -zv 194.5.195.53 32500 || echo "⚠️ WARNING: Cannot connect to local registry!"
- name: Checkout code
run: |
@@ -63,11 +45,10 @@ jobs:
- name: Build Docker Image
run: |
cd src
docker build -f BackOffice/Dockerfile \
# استفاده از cached base images (بدون دانلود)
DOCKER_BUILDKIT=0 docker build -f BackOffice/Dockerfile \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
--build-arg HTTP_PROXY=http://proxyuser:87zH26nbqT2@46.249.98.211:3128 \
--build-arg HTTPS_PROXY=http://proxyuser:87zH26nbqT2@46.249.98.211:3128 \
.
@@ -77,14 +58,11 @@ jobs:
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Deploy to Kubernetes
- name: Build and Push Complete
run: |
# Setup kubeconfig
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
# Restart deployment to pull new image
kubectl rollout restart deployment/backoffice || echo "Deployment doesn't exist yet"
# Wait for rollout to complete
kubectl rollout status deployment/backoffice --timeout=5m || echo "Deployment rollout pending"
echo "🎉 Build and push completed successfully!"
echo "📦 Image pushed to: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
echo "📦 Latest tag: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
echo ""
echo "🚀 To deploy manually on server:"
echo "kubectl set image deployment/backoffice backoffice=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
+4 -2
View File
@@ -24,7 +24,9 @@ jobs:
- name: Install dependencies
run: |
apk add --no-cache git curl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
# Install kubectl with fixed version
KUBECTL_VERSION="v1.31.0"
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/
@@ -33,7 +35,7 @@ jobs:
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'DAEMON'
{
"insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"]
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32082", "gitea-svc:3000"]
}
DAEMON
mkdir -p ~/.docker
+1 -1
View File
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
FROM 194.5.195.53:32082/dotnet/sdk:9.0 AS build
WORKDIR /src
# Copy NuGet config and project file
+8 -1
View File
@@ -1,11 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<clear />
<!-- Nexus as primary source (proxies nuget.org + caches packages) -->
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
<!-- Backup: Direct Gitea registries -->
<add key="FourSat" value="https://git.afrino.co/api/packages/FourSat/nuget/index.json" />
<add key="Afrino" value="https://git.afrino.co/api/packages/Afrino/nuget/index.json" />
</packageSources>
<packageSourceCredentials>
<Nexus>
<add key="Username" value="admin" />
<add key="ClearTextPassword" value="87zH26nbqT" />
</Nexus>
<FourSat>
<add key="Username" value="masoud" />
<add key="ClearTextPassword" value="87zH26nbqT" />
@@ -0,0 +1,16 @@
@using BackOffice.Services.DiscountCategory
<MudSelect @ref="_selectRef"
T="long"
MultiSelection="true"
SelectedValues="_internalSelectedIds"
SelectedValuesChanged="OnSelectedValuesChangedAsync"
Label="@Label"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Disabled="@Disabled">
@foreach (var item in _items)
{
<MudSelectItem Value="@item.Id">@item.Title</MudSelectItem>
}
</MudSelect>
@@ -0,0 +1,105 @@
using BackOffice.Services.DiscountCategory;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace BackOffice.Pages.AutoComplete;
public partial class DiscountCategoryMultiSelectCombo
{
[Inject] public IDiscountCategoryService CategoryService { get; set; } = default!;
[Parameter] public List<long> SelectedIds { get; set; } = new();
[Parameter] public EventCallback<List<long>> SelectedIdsChanged { get; set; }
[Parameter] public string? Label { get; set; } = "انتخاب دسته‌بندی‌ها";
[Parameter] public bool Disabled { get; set; }
private List<DiscountCategoryDto> _items = new();
private HashSet<long> _internalSelectedIds = new();
private MudSelect<long>? _selectRef;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await LoadCategoriesAsync();
SyncInternalFromParameter();
}
protected override Task OnParametersSetAsync()
{
SyncInternalFromParameter();
return base.OnParametersSetAsync();
}
private async Task LoadCategoriesAsync()
{
try
{
Console.WriteLine("Starting to load DiscountCategories...");
var tree = await CategoryService.GetCategoriesAsync(isActive: true);
Console.WriteLine($"Received {tree?.Count ?? 0} root categories");
_items = FlattenCategories(tree ?? new List<DiscountCategoryDto>());
Console.WriteLine($"DiscountCategory loaded: {_items.Count} items");
// Log first few items
if (_items.Count > 0)
{
Console.WriteLine("Sample categories:");
foreach (var item in _items.Take(5))
{
Console.WriteLine($" - {item.Id}: {item.Title} (Active: {item.IsActive})");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading DiscountCategories: {ex.GetType().Name} - {ex.Message}");
if (ex.InnerException != null)
{
Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
}
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
_items = new List<DiscountCategoryDto>();
}
}
private List<DiscountCategoryDto> FlattenCategories(List<DiscountCategoryDto> categories, string prefix = "")
{
var result = new List<DiscountCategoryDto>();
foreach (var category in categories)
{
var catCopy = new DiscountCategoryDto
{
Id = category.Id,
Title = prefix + category.Title,
Name = category.Name,
ParentCategoryId = category.ParentCategoryId,
IsActive = category.IsActive
};
result.Add(catCopy);
if (category.Children?.Any() == true)
{
result.AddRange(FlattenCategories(category.Children.ToList(), prefix + " "));
}
}
return result;
}
private void SyncInternalFromParameter()
{
_internalSelectedIds = SelectedIds != null
? SelectedIds.Where(id => id > 0).Distinct().ToHashSet()
: new HashSet<long>();
}
private async Task OnSelectedValuesChangedAsync(IEnumerable<long> values)
{
_internalSelectedIds = values.Where(id => id > 0).Distinct().ToHashSet();
SelectedIds = _internalSelectedIds.ToList();
if (SelectedIdsChanged.HasDelegate)
{
await SelectedIdsChanged.InvokeAsync(SelectedIds);
}
}
}
@@ -3,6 +3,7 @@
@using Microsoft.AspNetCore.Components.Forms
@using Tizzani.MudBlazor.HtmlEditor
@using BackOffice.Common.BaseComponents
@using BackOffice.Pages.AutoComplete
@inject ISnackbar Snackbar
<MudDialog>
@@ -111,23 +112,13 @@
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2">دسته‌بندی‌ها</MudText>
<MudSelect @bind-SelectedValues="_selectedCategoryIds"
Label="انتخاب دسته‌بندی‌ها"
Variant="Variant.Outlined"
Margin="Margin.Dense"
MultiSelection="true"
MultiSelectionTextFunc="@(new Func<List<string>, string>(GetMultiSelectionText))"
Disabled="_loading"
T="long">
@foreach (var category in _categories)
{
<MudSelectItem Value="@category.Id">@category.Title</MudSelectItem>
}
</MudSelect>
@if (_selectedCategoryIds?.Any() == true)
<DiscountCategoryMultiSelectCombo @bind-SelectedIds="_selectedCategoryIds"
Label="انتخاب دسته‌بندی‌ها"
Disabled="_loading" />
@if (_selectedCategoryIds?.Count > 0)
{
<MudText Typo="Typo.caption">
تعداد دسته‌بندی‌های انتخاب‌شده: @_selectedCategoryIds.Count()
تعداد دسته‌بندی‌های انتخاب‌شده: @_selectedCategoryIds.Count
</MudText>
}
</MudStack>
@@ -166,7 +157,6 @@
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter] public ProductFormModel Model { get; set; } = new();
[Parameter] public bool IsEditMode { get; set; }
[Inject] private IDiscountCategoryService CategoryService { get; set; } = null!;
private MudForm? _form;
private bool _isValid;
@@ -175,66 +165,15 @@
private IBrowserFile? _thumbnailImageFile;
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
private string? _mainImagePreview;
private IEnumerable<long> _selectedCategoryIds = new List<long>();
private List<DiscountCategoryDto> _categories = new();
private List<long> _selectedCategoryIds = new();
protected override async Task OnInitializedAsync()
protected override Task OnInitializedAsync()
{
await LoadCategories();
if (Model.CategoryIds?.Any() == true)
{
_selectedCategoryIds = Model.CategoryIds;
}
}
private async Task LoadCategories()
{
try
{
var tree = await CategoryService.GetCategoriesAsync(isActive: true);
_categories = FlattenCategories(tree);
Console.WriteLine($"Loaded {_categories.Count} categories");
}
catch (Exception ex)
{
Console.WriteLine($"Error loading categories: {ex}");
Snackbar.Add($"خطا در بارگذاری دسته‌بندی‌ها: {ex.Message}", Severity.Error);
}
}
private List<DiscountCategoryDto> FlattenCategories(List<DiscountCategoryDto> categories, string prefix = "")
{
var result = new List<DiscountCategoryDto>();
foreach (var category in categories)
{
var catCopy = new DiscountCategoryDto
{
Id = category.Id,
Title = prefix + category.Title,
ParentCategoryId = category.ParentCategoryId
};
result.Add(catCopy);
if (category.Children.Any())
{
result.AddRange(FlattenCategories(category.Children.ToList(), prefix + " "));
}
}
return result;
}
private string GetCategoryPath(DiscountCategoryDto category)
{
return category.Title;
}
private string GetMultiSelectionText(List<string> selectedValues)
{
if (selectedValues == null || !selectedValues.Any())
return "دسته‌بندی انتخاب نشده";
return $"{selectedValues.Count} دسته‌بندی انتخاب شده";
return base.OnInitializedAsync();
}
private async Task OnMainImageSelected(IBrowserFile? file)
@@ -184,10 +184,22 @@
_products = products;
_totalCount = totalCount;
_totalPages = totalPages;
Snackbar.Add("محصولات بارگذاری شدند", Severity.Success);
Console.WriteLine($"Loaded {_products.Count} products, Total: {_totalCount}, Pages: {_totalPages}");
if (_products.Count > 0)
{
Snackbar.Add($"{_products.Count} محصول بارگذاری شد", Severity.Success);
}
else
{
Snackbar.Add("هیچ محصولی یافت نشد", Severity.Info);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading products: {ex.Message}");
Console.WriteLine($"StackTrace: {ex.StackTrace}");
Snackbar.Add($"خطا در بارگذاری محصولات: {ex.Message}", Severity.Error);
}
finally
@@ -20,7 +20,9 @@ public class DiscountCategoryService : IDiscountCategoryService
if (isActive.HasValue)
request.IsActive = isActive.Value;
Console.WriteLine($"Calling GetDiscountCategoriesAsync with ParentCategoryId={parentCategoryId}, IsActive={isActive}");
var response = await _client.GetDiscountCategoriesAsync(request);
Console.WriteLine($"Received {response.Categories?.Count ?? 0} categories from BFF");
return MapCategories(response.Categories);
}
@@ -36,8 +36,13 @@ public class DiscountProductService : IDiscountProductService
if (filter.InStock.HasValue)
request.InStock = filter.InStock.Value;
Console.WriteLine($"Calling BFF GetDiscountProductsAsync - Page: {request.PageNumber}, PageSize: {request.PageSize}");
var response = await _client.GetDiscountProductsAsync(request);
Console.WriteLine($"BFF Response - Models count: {response.Models?.Count ?? 0}");
Console.WriteLine($"BFF Response - TotalCount: {response.MetaData?.TotalCount ?? 0}");
var products = response.Models.Select(p => new DiscountProductDto
{
Id = p.Id,