Skip to content

Commit 9a949a7

Browse files
committed
update
1 parent 301e7b9 commit 9a949a7

8 files changed

Lines changed: 191 additions & 113 deletions

File tree

.github/scripts/patch_ios_project.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ def patch_pbxproj(path):
5151
if new_line != line:
5252
line = new_line
5353
modified = True
54+
55+
# Handle PROVISIONING_PROFILE_SPECIFIER with regex
56+
if 'PROVISIONING_PROFILE_SPECIFIER' in line and '""' not in line:
57+
new_line = re.sub(r'PROVISIONING_PROFILE_SPECIFIER\s*=\s*[^;]+;', 'PROVISIONING_PROFILE_SPECIFIER = "";', line)
58+
if new_line != line:
59+
line = new_line
60+
modified = True
5461

5562
new_lines.append(line)
5663

@@ -145,6 +152,8 @@ def create_xcconfig(apple_path):
145152
CODE_SIGNING_ALLOWED = NO
146153
DEVELOPMENT_TEAM =
147154
AD_HOC_CODE_SIGNING_ALLOWED = NO
155+
PROVISIONING_PROFILE_SPECIFIER =
156+
PROVISIONING_PROFILE =
148157
149158
// Ensure release optimizations
150159
GCC_OPTIMIZATION_LEVEL = s

.github/workflows/_build-ios.yml

Lines changed: 172 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,198 @@
1-
name: Build iOS
1+
name: Build iOS (Unsigned)
22

33
on:
4-
workflow_call:
5-
inputs:
6-
version:
7-
type: string
8-
default: ''
9-
sign:
10-
type: boolean
11-
default: false
12-
secrets:
13-
APPLE_CERTIFICATE:
14-
required: false
15-
APPLE_CERTIFICATE_PASSWORD:
16-
required: false
17-
APPLE_PROVISIONING_PROFILE:
18-
required: false
19-
APPLE_TEAM_ID:
20-
required: false
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- 'russh-client/**'
10+
- '.github/workflows/_build-ios.yml'
11+
- '.github/scripts/patch_ios_project.py'
2112

2213
jobs:
23-
build:
24-
name: iOS
25-
runs-on: macos-14
26-
env:
27-
SIGNING_ENABLED: 'false'
14+
build-ios-unsigned:
15+
name: Build iOS (Unsigned)
16+
runs-on: macos-latest
17+
timeout-minutes: 60
18+
2819
steps:
2920
- uses: actions/checkout@v4
3021

31-
- uses: ./.github/actions/setup-env
22+
- uses: pnpm/action-setup@v4
3223
with:
33-
rust-targets: aarch64-apple-ios,aarch64-apple-ios-sim
24+
version: 9
3425

35-
- uses: Swatinem/rust-cache@v2
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: '22'
29+
cache: 'pnpm'
30+
cache-dependency-path: russh-client/pnpm-lock.yaml
31+
32+
- uses: dtolnay/rust-toolchain@stable
33+
with:
34+
targets: aarch64-apple-ios
35+
36+
- name: Rust cache
37+
uses: swatinem/rust-cache@v2
3638
with:
3739
workspaces: "russh-client/src-tauri -> target"
38-
key: ios
3940

40-
- name: Update version
41-
if: inputs.version != ''
41+
- name: Install dependencies
42+
working-directory: russh-client
43+
run: pnpm install --frozen-lockfile
44+
45+
- name: Build frontend
46+
working-directory: russh-client
47+
run: pnpm run build
48+
49+
- name: Verify frontend dist exists
4250
working-directory: russh-client
4351
run: |
44-
npm version ${{ inputs.version }} --no-git-tag-version || true
45-
sed -i '' 's/^version = ".*"/version = "${{ inputs.version }}"/' src-tauri/Cargo.toml
46-
47-
- name: Setup signing
48-
if: inputs.sign
49-
env:
50-
CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
51-
CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
52-
PROVISIONING_PROFILE: ${{ secrets.APPLE_PROVISIONING_PROFILE }}
53-
run: |
54-
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
55-
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
56-
echo "$CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12
57-
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
58-
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
59-
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
60-
security import $RUNNER_TEMP/certificate.p12 -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
61-
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
62-
security list-keychain -d user -s $KEYCHAIN_PATH
63-
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
64-
echo "$PROVISIONING_PROFILE" | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/build.mobileprovision
65-
echo "SIGNING_ENABLED=true" >> $GITHUB_ENV
66-
67-
- name: Init iOS
52+
if [ ! -d "dist" ]; then
53+
echo "❌ Frontend dist folder not found!"
54+
exit 1
55+
fi
56+
echo "✅ Frontend dist exists:"
57+
ls -la dist/
58+
59+
- name: Initialize iOS project
6860
working-directory: russh-client
6961
run: pnpm tauri ios init
7062

71-
- name: Patch Xcode for Release
72-
working-directory: russh-client/src-tauri/gen/apple
73-
run: |
74-
find . -name "*.xcscheme" -exec sed -i '' 's/buildConfiguration = "Debug"/buildConfiguration = "Release"/g' {} \;
75-
find . -name "project.pbxproj" -exec sed -i '' 's/--configuration \${CONFIGURATION:\?}/--configuration Release/g' {} \;
63+
- name: Patch iOS project for unsigned release build
64+
working-directory: russh-client
65+
run: python3 ../.github/scripts/patch_ios_project.py
7666

77-
- name: Build frontend
67+
- name: Build Rust library for iOS device (Release)
7868
working-directory: russh-client
79-
run: pnpm build
69+
run: |
70+
cd src-tauri
71+
cargo build --release --target aarch64-apple-ios
72+
73+
# Copy to both debug and release folders (Xcode may look in either)
74+
mkdir -p gen/apple/Externals/arm64/debug
75+
mkdir -p gen/apple/Externals/arm64/release
76+
77+
# The library name from Cargo.toml is russh-client -> librussh_client.a
78+
LIB_FILE=$(find target/aarch64-apple-ios/release -name "librussh_client.a" -type f | head -1)
79+
80+
if [ -z "$LIB_FILE" ]; then
81+
echo "Looking for any .a file..."
82+
LIB_FILE=$(find target/aarch64-apple-ios/release -name "*.a" -type f | head -1)
83+
fi
84+
85+
if [ -z "$LIB_FILE" ] || [ ! -f "$LIB_FILE" ]; then
86+
echo "❌ No .a library found"
87+
ls -la target/aarch64-apple-ios/release/ || true
88+
exit 1
89+
fi
90+
91+
echo "Found library: $LIB_FILE"
92+
93+
# Target destination for Tauri 2.0 structure
94+
# Usually it goes into gen/apple/Externals usually
95+
cp "$LIB_FILE" gen/apple/Externals/arm64/debug/libapp.a
96+
cp "$LIB_FILE" gen/apple/Externals/arm64/release/libapp.a
97+
98+
echo "✅ Rust library built and copied"
99+
ls -la gen/apple/Externals/arm64/*/
80100
81-
- name: Build Rust
82-
working-directory: russh-client/src-tauri
83-
run: cargo build --release --target aarch64-apple-ios
101+
- name: Copy frontend assets to iOS project
102+
working-directory: russh-client
103+
run: |
104+
# Tauri iOS expects the dist folder to be available
105+
echo "Frontend dist contents:"
106+
ls -la dist/
107+
108+
APPLE_DIR="src-tauri/gen/apple"
109+
110+
# Check if assets are properly placed or recognized
111+
if [ -d "$APPLE_DIR/Sources" ]; then
112+
echo "Found Sources directory"
113+
ls -la "$APPLE_DIR/Sources/" || true
114+
fi
84115
85-
- name: Create IPA
86-
working-directory: russh-client/src-tauri/gen/apple
87-
env:
88-
TAURI_CLI_NO_DEV_SERVER_WAIT: "true"
116+
- name: Build iOS Archive (Unsigned Release)
117+
working-directory: russh-client
89118
run: |
90-
PROJ=$(find . -maxdepth 1 -name "*.xcworkspace" -o -name "*.xcodeproj" | head -1)
91-
BUILD_TYPE=$([[ "$PROJ" == *.xcworkspace ]] && echo "-workspace" || echo "-project")
92-
SCHEME=$(xcodebuild -list $BUILD_TYPE "$PROJ" 2>/dev/null | awk '/Schemes:/{getline; print; exit}' | xargs)
93-
94-
if [ "$SIGNING_ENABLED" = "true" ]; then
95-
xcodebuild archive $BUILD_TYPE "$PROJ" -scheme "$SCHEME" \
96-
-archivePath "$GITHUB_WORKSPACE/app.xcarchive" \
97-
-configuration Release -destination "generic/platform=iOS" \
98-
DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}" SKIP_INSTALL=NO
99-
cat > export.plist << 'EOF'
100-
<?xml version="1.0" encoding="UTF-8"?>
101-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
102-
<plist version="1.0"><dict><key>method</key><string>development</string></dict></plist>
103-
EOF
104-
xcodebuild -exportArchive -archivePath "$GITHUB_WORKSPACE/app.xcarchive" \
105-
-exportPath "$GITHUB_WORKSPACE" -exportOptionsPlist export.plist
106-
mv "$GITHUB_WORKSPACE"/*.ipa "$GITHUB_WORKSPACE/RUSSH-ios.ipa"
107-
else
108-
xcodebuild archive $BUILD_TYPE "$PROJ" -scheme "$SCHEME" \
109-
-archivePath "$GITHUB_WORKSPACE/app.xcarchive" \
110-
-configuration Release -destination "generic/platform=iOS" \
111-
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO SKIP_INSTALL=NO
112-
cd "$GITHUB_WORKSPACE/app.xcarchive/Products" && mv Applications Payload
113-
zip -r "$GITHUB_WORKSPACE/RUSSH-ios-unsigned.ipa" Payload
119+
set -e
120+
121+
XCODEPROJ=$(find src-tauri/gen/apple -name "*.xcodeproj" -type d | head -1)
122+
echo "Project: $XCODEPROJ"
123+
124+
SCHEME=$(xcodebuild -project "$XCODEPROJ" -list 2>/dev/null | \
125+
awk '/Schemes:/{flag=1; next} flag && /^$/{flag=0} flag' | \
126+
sed 's/^[[:space:]]*//' | grep -i ios | head -1)
127+
128+
[ -z "$SCHEME" ] && SCHEME=$(xcodebuild -project "$XCODEPROJ" -list 2>/dev/null | \
129+
awk '/Schemes:/{flag=1; next} flag && /^$/{flag=0} flag' | \
130+
sed 's/^[[:space:]]*//' | head -1)
131+
132+
echo "Scheme: $SCHEME"
133+
134+
# Use the xcconfig if it exists (created by patch script)
135+
XCCONFIG=""
136+
if [ -f "src-tauri/gen/apple/Release-Unsigned.xcconfig" ]; then
137+
XCCONFIG="-xcconfig src-tauri/gen/apple/Release-Unsigned.xcconfig"
114138
fi
139+
140+
# Build with Release configuration and proper settings
141+
xcodebuild archive \
142+
-project "$XCODEPROJ" \
143+
-scheme "$SCHEME" \
144+
-configuration Release \
145+
-archivePath ./unsigned.xcarchive \
146+
-destination "generic/platform=iOS" \
147+
$XCCONFIG \
148+
CODE_SIGN_IDENTITY="" \
149+
CODE_SIGNING_REQUIRED=NO \
150+
CODE_SIGNING_ALLOWED=NO \
151+
DEVELOPMENT_TEAM="" \
152+
AD_HOC_CODE_SIGNING_ALLOWED=NO \
153+
ONLY_ACTIVE_ARCH=NO \
154+
GCC_PREPROCESSOR_DEFINITIONS='$(inherited) RELEASE=1 NDEBUG=1' \
155+
SWIFT_ACTIVE_COMPILATION_CONDITIONS='RELEASE' \
156+
ENABLE_BITCODE=NO
157+
158+
echo "✅ Archive created"
115159
116-
- name: Cleanup signing
117-
if: always() && inputs.sign
118-
run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
160+
- name: Package IPA from Archive
161+
working-directory: russh-client
162+
run: |
163+
set -e
164+
165+
ARCHIVE_PATH="./unsigned.xcarchive"
166+
167+
if [ ! -d "$ARCHIVE_PATH" ]; then
168+
echo "❌ Archive not found"
169+
exit 1
170+
fi
171+
172+
APP_PATH=$(find "$ARCHIVE_PATH/Products/Applications" -name "*.app" -type d 2>/dev/null | head -1)
173+
174+
if [ -z "$APP_PATH" ]; then
175+
APP_PATH=$(find "$ARCHIVE_PATH" -name "*.app" -type d 2>/dev/null | head -1)
176+
fi
177+
178+
if [ -z "$APP_PATH" ]; then
179+
echo "❌ No .app found in archive"
180+
exit 1
181+
fi
182+
183+
echo "✅ Found: $APP_PATH"
184+
185+
mkdir -p Payload
186+
cp -R "$APP_PATH" Payload/
187+
zip -r -q iMAGE-unsigned.ipa Payload/
188+
rm -rf Payload
189+
190+
echo "✅ IPA created: iMAGE-unsigned.ipa"
191+
ls -la iMAGE-unsigned.ipa
119192
120-
- uses: actions/upload-artifact@v4
193+
- name: Upload IPA
194+
uses: actions/upload-artifact@v4
121195
with:
122-
name: ios-ipa
123-
path: |
124-
RUSSH-ios.ipa
125-
RUSSH-ios-unsigned.ipa
126-
if-no-files-found: warn
196+
name: ios-unsigned-ipa
197+
path: russh-client/iMAGE-unsigned.ipa
198+
retention-days: 30

russh-ssh/src/connection/state.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ use tokio::sync::broadcast;
1010
const STATE_CHANNEL_CAPACITY: usize = 16;
1111

1212
/// Connection state tracking
13-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
1414
pub enum ConnectionState {
1515
/// Not connected to any host
16+
#[default]
1617
Disconnected,
1718
/// Currently attempting to connect
1819
Connecting,
@@ -103,11 +104,7 @@ impl ConnectionState {
103104
}
104105
}
105106

106-
impl Default for ConnectionState {
107-
fn default() -> Self {
108-
ConnectionState::Disconnected
109-
}
110-
}
107+
111108

112109
impl std::fmt::Display for ConnectionState {
113110
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

russh-ssh/src/encryption/cipher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl serde::Serialize for EncryptedMessage {
4545
use base64::{Engine as _, engine::general_purpose::STANDARD};
4646
let mut state = serializer.serialize_struct("EncryptedMessage", 3)?;
4747
state.serialize_field("ciphertext", &STANDARD.encode(&self.ciphertext))?;
48-
state.serialize_field("nonce", &STANDARD.encode(&self.nonce))?;
48+
state.serialize_field("nonce", &STANDARD.encode(self.nonce))?;
4949
state.serialize_field("plaintext_hash", &self.plaintext_hash)?;
5050
state.end()
5151
}

russh-ssh/src/encryption/hash.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ pub async fn hash_file_async(path: &Path) -> Result<ContentHash, HashError> {
163163
pub async fn hash_file_spawn_blocking(path: impl AsRef<Path> + Send + 'static) -> Result<ContentHash, HashError> {
164164
tokio::task::spawn_blocking(move || hash_file(path.as_ref()))
165165
.await
166-
.map_err(|e| HashError::Io(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())))?
166+
.map_err(|e| HashError::Io(std::io::Error::other(e.to_string())))?
167167
}
168168

169169
/// Compute BLAKE3 hash from a reader

russh-ssh/src/encryption/secure_channel.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ impl Serialize for Identity {
6262
{
6363
use serde::ser::SerializeStruct;
6464
let mut state = serializer.serialize_struct("Identity", 2)?;
65-
state.serialize_field("public_key", &hex::encode(&self.public_key))?;
65+
state.serialize_field("public_key", &hex::encode(self.public_key))?;
6666
state.serialize_field("identifier", &self.identifier)?;
6767
state.end()
6868
}
@@ -309,7 +309,7 @@ impl SecureChannel {
309309
Ok(SecureMessage {
310310
encrypted,
311311
counter,
312-
sender: self.local_identity.identifier.clone(),
312+
sender: self.local_identity.identifier,
313313
})
314314
}
315315

russh-ssh/src/p2p/connection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl P2PConnection {
119119

120120
// Get connection type from the endpoint (Iroh tracks this at endpoint level)
121121
if let Ok(conn_type_watcher) = self.endpoint.endpoint().conn_type(self.peer_id) {
122-
if let Some(conn_type) = conn_type_watcher.get().ok() {
122+
if let Ok(conn_type) = conn_type_watcher.get() {
123123
info.connection_type = match conn_type {
124124
IrohConnectionType::Direct(addr) => {
125125
info.remote_addr = Some(addr);

0 commit comments

Comments
 (0)