pemファイルを使ってSSHでEC2に接続する

sshで接続する

ダウンロードしたpemファイルを~/.sshに移動します。

$ mv ~/Downloads/my-app-key.pem ~/.ssh

prmファイルのパーミッションを設定します。

$ chmod 400 ~/.ssh/my-app-key.pem

これで、sshで接続できるようになりました。

$ ssh -i ~/.ssh/my-app-key.pem [ユーザー名]@[IPアドレス]

[IPアドレス]はサーバーのIPアドレスまたはホスト名に置き換えてください。

ユーザー名はAMIによって異なります。
こちらをご確認ください。

Amazon Linux 2を使っている場合は、次のようになります。

$ ssh -i ~/.ssh/my-app-key.pem ec2-user@[IPアドレス]

より簡単に接続する

pemファイルを指定しなくても接続できるようにします。

~/.ssh/configファイルを編集します。

vi ~/.ssh/config

以下の行を追記します。
[IPアドレス]はサーバーのIPアドレスまたはホスト名に置き換えてください。

Host my-app
  HostName [IPアドレス]
  User ec2-user
  IdentityFile ~/.ssh/my-app-key.pem

pemファイルを指定しなくても接続できるようになりました。

$ ssh my-app

AndroidのWebViewの「ファイルを選択」ボタン(input typ=”file”)で写真を撮るかファイルを選択する

AndroidのWebViewで「ファイルを選択」ボタン(input typ=”file”)を押した時に、カメラアプリかファイルアプリから画像をアップロードする方法。

対象はAndroid5以上。

HTMLでは、「ファイルを選択」ボタンで選択された画像をimgタグに表示する。

<input type="file" accept="image/*" capture="camera" id="camera">
<img id="frame">
<script>
const camera = document.getElementById('camera');
const frame = document.getElementById('frame');
camera.addEventListener('change', function(e) {
    console.log(e.target.files);
    if (e.target.files.length > 0) {
        frame.src = URL.createObjectURL(e.target.files[0]);
    } else {
        frame.src = '';
    }
});
</script>

このHTMLをAndroidのWebViewで表示して、選択した画像を表示できるようにする。

以下、Androidアプリの作成手順。

AndroidManifest.xmlにインターネットとカメラの権限を追加する。

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    ...
</manifest>

Android9以上で、HTTP通信(http://〜)をする場合は、AndroidManifest.xmlに「android:usesCleartextTraffic=”true”」を追加する。


<application android:usesCleartextTraffic="true"

カメラアプリで撮影した写真はFileProviderを使って共有するため、FileProviderを設定する。

<application
    ...>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_path"/>
    </provider>

app/src/main/res/xml/provider_path.xmlを作成する。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

カメラの撮影に必要な権限を要求するクラスを作成する。

public class CameraPermission {
    /**
     * 権限があるか確認し、権限がなければ要求する
     *
     * @return 権限があるときはtrue
     */
    public static boolean checkAndRequestPermissions(final @NonNull Activity activity, final @IntRange(from = 0) int requestCode) {
        String[] permissionNeeded = permissionNeeded(activity, new String[]{
                Manifest.permission.CAMERA,
        });
        if (permissionNeeded.length > 0) {
            ActivityCompat.requestPermissions(activity, permissionNeeded, requestCode);
            return false;
        }
        return true;
    }

    private static String[] permissionNeeded(final @NonNull Activity activity, String[] permissions) {
        List<String> listPermissionsNeeded = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
                listPermissionsNeeded.add(permission);
            }
        }
        return listPermissionsNeeded.toArray(new String[0]);
    }
}

MainActivityにWebViewを表示するコードを追加する。
「ファイルを選択」ボタンが押された時、showFileChooser()メソッドを呼ぶ。

public class MainActivity extends AppCompatActivity {

    private ValueCallback<Uri[]> filePathCallback;

    @SuppressLint("SetJavaScriptEnabled")
    private void setupWebView() {
        WebView webView = new WebView(this);
        setContentView(webView);
        webView.loadUrl("http://xxx.xxx.xxx.xxx/");
        // JavaScriptを有効にする
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebChromeClient(new WebChromeClient() {
            /**
             * 「ファイルを選択」ボタンが押された時
             * For Android > 5.0
             * @param webView
             * @param filePathCallback
             * @param fileChooserParams
             * @return
             */
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
                showFileChooser(filePathCallback, fileChooserParams);
                return true;
            }
        });
    }
}

「ファイルを選択」ボタンが押された時の処理。
権限がなければ権限を要求する。
カメラアプリとファイルアプリを選択するインテントを作成する。

private void showFileChooser(ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
    // 完了していない処理があれば完了する
    if (this.filePathCallback != null) {
        this.filePathCallback.onReceiveValue(null);
    }
    this.filePathCallback = filePathCallback;

    // 権限がないときは、権限を要求する
    if (!CameraPermission.checkAndRequestPermissions(this, REQUEST_PERMISSIONS)) {
        this.filePathCallback.onReceiveValue(null);
        this.filePathCallback = null;
        return;
    }

    // カメラとファイルのインテントを作成する
    Intent chooserIntent = Intent.createChooser(fileChooserParams.createIntent(), "写真の選択");
    try {
        mImageUri = createImageFile();
        Intent imageCaptureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        imageCaptureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{imageCaptureIntent});
    } catch (IOException ex) {
        mImageUri = null;
    }
    startActivityForResult(chooserIntent, REQUEST_SELECT_FILE_CODE);
}

撮影した写真を保存するファイルのURIを作成するメソッドを作成する。

private Uri createImageFile() throws IOException {
    File folder = getExternalFilesDir(Environment.DIRECTORY_DCIM);
    String date = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
    String fileName = String.format("MyApp_%s.jpg", date);
    File cameraFile = new File(folder, fileName);

    return FileProvider.getUriForFile(
            this,
            getApplicationContext().getPackageName() + ".fileprovider",
            cameraFile);
}

選択されたファイルをWebViewに返す。

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_SELECT_FILE_CODE) {
        if (filePathCallback != null) {
            if (resultCode == RESULT_OK) {
                Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, data);
                if (result == null) {
                    result = new Uri[]{mImageUri};
                }
                filePathCallback.onReceiveValue(result);
            } else {
                filePathCallback.onReceiveValue(null);
            }
            filePathCallback = null;
        }
    }
}

2020年12月15日追記

動作確認用のサンプルコードを公開しました。

iOSアプリのアクセス許可関連の処理のメモ

アクセス許可の情報を取得する

カメラへのアクセスが許可に関する情報を取得する。

AVAuthorizationStatus vidioStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];

AVAuthorizationStatusは次の値をとる。

  • AVAuthorizationStatusNotDetermined
    • ユーザーは許可も拒否もしていない
  • AVAuthorizationStatusRestricted
    • ユーザーはデバイスにアクセスできない
  • AVAuthorizationStatusDenied
    • ユーザーは明示的に拒否した
  • AVAuthorizationStatusAuthorized
    • ユーザーは明示的に許可した、または、許可は必要ない

アクセスの許可を求めるダイアログを表示する

[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
}];

許可されたときはgrantedはYESに、拒否されたときはgrantedはNOになる。

設定アプリを開く

アクセスが拒否されているときは、設定アプリを開き、ユーザーにアクセスを許可してもらう。

NSURL* url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication] openURL:url];

実際にアプリに組み込むイメージ。

checkPermissionAndDoSomething()を呼ぶと、権限を適切に処理してからdoSomething()を実行する。

- (void)checkPermissionAndDoSomething
{
    if ([self hasAuthorization]) {  // 権限を確認する
        [self doSomething]; // 権限があるので実行する
    }

    [self requireAuthorization:^() {  // 権限を要求する
        if ([self hasAuthorization]) {  // 許可を得られた
            [self doSomething];  // 権限があるので実行する
        } else {
            // 許可されませんでした
        }
    }];
}

/**
 @brief カメラの権限があるか
 */
- (bool)hasAuthorization
{
    AVAuthorizationStatus videoStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    return (videoStatus == AVAuthorizationStatusAuthorized);
}

// コールバックの型
typedef void (^voidBlock)(void);

/**
 @brief カメラの権限を要求する
 */
- (void)requireAuthorization:(voidBlock)callback
{
    AVAuthorizationStatus videoStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    // カメラの権限があるとき
    if (videoStatus == AVAuthorizationStatusAuthorized) {
        callback();
        return;
    }
    // カメラの権限を問い合わせていないとき
    if (videoStatus == AVAuthorizationStatusNotDetermined) {
        // カメラの初回権限要求
        [self requireInitialAuthorization:callback];
        return;
    }

    // 許可されていない権限があるとき
    [self requireAuthorizationToOpenSettings:callback];
}

/**
 @brief カメラの初回権限要求
 */
- (void)requireInitialAuthorization:(voidBlock)callback
{
    NSString* title = @"アクセス権限の設定";
    NSString* message = @"このアプリはカメラを使うので許可してください。";
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
                                                                             message:message
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
                                                        style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                callback();
            }];
    }]];
    [self presentViewController:alertController animated:YES completion:nil];
}

/**
 @brief カメラの権限を設定するために設定画面を開く
 */
- (void)requireAuthorizationToOpenSettings:(voidBlock)callback
{
    NSString* title = @"アクセス権限の設定";
    NSString* message = @"カメラのアクセスを許可してください。";
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
                                                                             message:message
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"設定する"
                                                        style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        NSURL* url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
        [[UIApplication sharedApplication] openURL:url];
        callback();
    }]];
    [self presentViewController:alertController animated:YES completion:nil];
}

Firebase AuthenticationとFirebaseUIを使ってWebアプリの認証機能をを作る

Firebase Authenticationを使うと、パスワード・電話番号・Google・Facebook・Twitter等を使った認証システムを簡単に作成できます。

FirebaseUIを使うことにより、見慣れたUIを提供できます。

今回作成したプログラムのソースコードはこちら。

Firebaseのプロジェクトを作成する

Firebaseコンソールにログインし、新しいプロジェクトを作成します。

プロジェクトの概要→プロジェクトを設定→マイアプリ→ウェブアプリにFirebaseを追加します。
このとき、Firebase Hostingも設定します。

プロジェクトの用意

最終的には、次のようになります。

public/ 公開フォルダー
  firebaseui.css
  index.html    サインイン画面
  success.html  ユーザー画面
src/    プログラムのソースコード
  index.js      サインイン画面のJavaScript
  success.js    ユーザー画面のJavaScript
package-lock.json
package.json
webpack.dev.js

ソースコードを配置するフォルダーを作成します。(ここではauth-testフォルダーとします)

$ mkdir auth-test

作成したフォルダーに移動します。

$ cd auth-test

ライブラリのインストール

package.jsonを作成します。

$ npm init

webpackをインストールします。

$ npm install webpack webpack-cli --save-dev

firebase npmパッケージをインストールします。

$ npm install --save firebase

firebaseui npmパッケージをインストールします。

$ npm install firebaseui --save

Firebase Hostinの設定

Fireebase Hostingを使用するために、Firebase CLIをインストールします。

npm install -g firebase-tools

ログインします。

$ firebase login

Firebaseプロジェクトの初期設定を行います。
Hostingをチェックして、セットアップします。

$ firebase init

? Please select an option: Use an existing project
? Select a default Firebase project for this directory: auth-test-XXXX (auth-test)
i  Using project auth-test-XXXX (auth-test)

publicフォルダーが公開フォルダーになります。

ローカルで実行するときは、次のコマンドを実行します。

firebase serve

アプリをデプロイするときは、次のコマンドを実行します。

$ firebase deploy

http://localhost:5000にアクセスすると、開発しているサイトの動作を確認できます。

ファイル構成は次のようになります。

node_modules/
public/
firebase.json
package-lock.json
package.json

ビルド設定

ソースコードを配置するフォルダーを作成します。

mkdir src

srcフォルダー中のindex.jsとsuccess.jsをビルドして、publicフォルダー内に配置するようにします。

webpack.dev.jsを作成します。

module.exports = {
    mode: 'development',
    devtool: 'inline-source-map',
    entry: {
        index: './src/index.js',
        success: './src/success.js',
    },
    output: {
        path: __dirname + '/public',
        filename: '[name].js'
    }
};

package.jsonにscripts/buildを追加します。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.dev"
  },

srcフォルダーの中にindex.jsとsuccess.jsを作成します。

$ touch src/index.js
$ touch src/success.js

ビルドします。

$ npm run build

publicフォルダーにindex.jsとsuccess.jsが作成されます。

今後、jsファイルを編集したら、都度ビルドします。

サインインページの作成

public/index.htmlを編集します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="./firebaseui.css">
    <script src="./index.js" defer></script>
    <title>サインイン</title>
</head>
<body>
    <!-- Firebase UIによって書き換えられる -->
    <div id="firebaseui-auth-container"></div>
    <!-- ウィジェットが表示されたら非表示にする -->
    <div id="loader">loading...</div>
</body>
</html>

FirebaseUIをインストールします。

$ npm install firebaseui --save

FirebaseUIのスタイルシートをコピーします。

$ cp node_modules/firebaseui/dist/firebaseui.css public/

サインインページを表示する

ブラウザでhttp://localhost:5000/にアクセスすると、サインイン画面が表示されます。
(現在は、loading…と表示されます。)

Firebaseのメール/パスワード認証を有効にする

Firebaseコンソールのプロジェクトを開き、開発→Authenntication→Sign-in method→メール/パスワードを選択して、有効にします。

メール/パスワード認証画面を表示する

src/index.jsを編集します。

Firebase AuthenticationとFirebaseUIのライブラリをインポートします。

import * as firebase from "firebase/app";
import "firebase/auth";
import * as firebaseui from "firebaseui";

Firebaseコンソール(https://console.firebase.google.com/)のプロジェクト→プロジェクトの概要→プロジェクトを設定からコピーして貼り付けます。

const firebaseConfig = {
    apiKey: "...",
    authDomain: "...",
    databaseURL: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
    measurementId: "..."
};

Firebaseの初期化を初期化します。

firebase.initializeApp(firebaseConfig);

Firebase UIの設定を行います。
メール/パスワードによる認証を有効にします。

const uiConfig = {
    // サポートするプロバイダ
    signInOptions: [
        // メールプロバイダIDを追加
        {
            // FirebaseコンソールのAuthenticationセクションを開き、メール/パスワードによる認証を有効にする
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
        },
    ],
};

Firebase UIを初期化します。

const ui = new firebaseui.auth.AuthUI(firebase.auth());

Firebase UIを表示します。

ui.start('#firebaseui-auth-container', uiConfig);

プログラムをビルドします。

$ npm run build

ブラウザでhttp://localhost:8080/にアクセスすると、認証画面が表示されます。

ここまでのindex.jsは次のようになります。

import * as firebase from "firebase/app";
import "firebase/auth";
import * as firebaseui from "firebaseui";

// Firebaseコンソール(https://console.firebase.google.com/)のプロジェクト→プロジェクトの概要→プロジェクトを設定からコピー
const firebaseConfig = {
    apiKey: "...",
    authDomain: "...",
    databaseURL: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
    measurementId: "..."
};
// Firebaseの初期化
firebase.initializeApp(firebaseConfig);

// Firebase UIの設定
const uiConfig = {
    // サポートするプロバイダ
    signInOptions: [
        // メールプロバイダIDを追加
        {
            // FirebaseコンソールのAuthenticationセクションを開き、メール/パスワードによる認証を有効にする
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
        },
    ],
};

// Firebase UIの初期化
const ui = new firebaseui.auth.AuthUI(firebase.auth());

// Firebase UIを表示する
ui.start('#firebaseui-auth-container', uiConfig);

メールプロバイダの設定を変更します。
初期値ではユーザーの表示名の入力を求めます。この機能を無効にします。

// サポートするプロバイダ
signInOptions: [
    // メールプロバイダIDを追加
    {
        // FirebaseコンソールのAuthenticationセクションを開き、メール/パスワードによる認証を有効にする
        provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
        // [オプション]ユーザーに表示名の入力を求めるかどうか。初期値はtrue。
        requireDisplayName: false,
    },
],

スクリプトをビルドして、ブラウザをリロードすると、ユーザー名の入力欄が表示されなくなります。

Google認証を追加する

Googleのアカウントでもサインインできるようにします。

Firebaseコンソールのプロジェクトを開き、開発→Authenntication→Sign-in method→Googleを選択して、有効にします。

GoogleプロバイダIDを追加します。

const uiConfig = {
    // サポートするプロバイダ
    signInOptions: [
        // メールプロバイダIDを追加
        {
          ...
        },
        // GoogleプロバイダIDを追加
        {
            // FirebaseコンソールのAuthenticationセクションを開き、Googleによる認証を有効にする
            provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        },

スクリプトをビルドして、ブラウザをリロードすると、Google認証が追加されます。

電話認証を追加する

Firebaseコンソールのプロジェクトを開き、開発→Authenntication→Sign-in method→電話番号を選択して、有効にします。

電話番号プロバイダを追加します。

const uiConfig = {
    // サポートするプロバイダ
    signInOptions: [
        // メールプロバイダIDを追加
        {
          ...
        },
        // GoogleプロバイダIDを追加
        {
          ...
        },
        // 電話番号ログインを追加
        {
            // FirebaseコンソールのAuthenticationセクションを開き、電話番号ログインを有効にする
            provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
            // [オプション]reCAPTCHAの表示・非表示(デフォルトはnormal)
            // @see https://developers.google.com/recaptcha/docs/display
            recaptchaParameters: {
                type: 'image',
                size: 'invisible', // 'normal','invisible','compact'
                badge: 'bottomleft' // 'bottomleft','bottomright','inline'。sizeがinvisibleの場合に適用される
            }
        }

スクリプトをビルドして、ブラウザをリロードすると、Google認証が追加されます。

ウィジェットが表示されたときの処理

ウィジェットが表示されたとき、「loading…」を非表示にします。

const uiConfig = {
    // サポートするプロバイダ
    signInOptions: [
      ...
    ],
    callbacks: {
        /**
         * ウィジェットが表示されたとき
         */
        uiShown: function () {
            document.getElementById('loader').style.display = 'none';
        }
    },

サインインしたときに表示するページの設定

サインインしたら、success.htmlを表示するようにします。

const uiConfig = {
    // サポートするプロバイダ
    signInOptions: [
      ...
    ],
    callbacks: {
        /**
         * ユーザーが正常にサインインしたとき、自動的にリダイレクトするか開発者がハンドリングするかを決める
         * @param {firebaseui.auth.AuthResult} authResult 
         * @param {string|null} redirectUrl 
         * @returns {boolean} true:自動的にリダイレクトする false:開発者がハンドリングする
         */
        signInSuccessWithAuthResult: function (authResult, redirectUrl) {
            return true;
        },
        ...
    },
    // サインインしたときのリダイレクト先URL
    signInSuccessUrl: '/success.html',

ユーザー画面の作成

public/success.htmlを編集します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./success.js" defer></script>
    <title>サインインしました</title>
</head>
<body>
    <h1>サインインしました。</h1>
    <div>
        <button id="signOut">サインアウト</button>
    </div>
    <div>
        <a href="./index.html">サインインページ</a>
    </div>
</body>
</html>

src/success.jsを編集します。

初期化処理はサインインのときと同じです。

import * as firebase from "firebase/app";
import "firebase/auth";

// Firebaseコンソール(https://console.firebase.google.com/)のプロジェクト→プロジェクトの概要→プロジェクトを設定からコピー
const firebaseConfig = {
    apiKey: "...",
    authDomain: "...",
    databaseURL: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
    measurementId: "..."
};

// Firebaseの初期化
firebase.initializeApp(firebaseConfig);

サインインしているユーザー情報を取得します。

// 現在ログインしているユーザーを取得する
// ユーザーが初期化中などの中間状態ではない
firebase.auth().onAuthStateChanged(function (user) {
    if (user) {
        showUser(user);
    } else {
        console.log('ログインしていません。');
    }
});

/**
 * ユーザー情報を表示する
 * @param {*} user 
 */
function showUser(user) {
    console.log(user.displayName);
    console.log(user.email);
    console.log(user.photoURL);
    console.log(user.emailVerified);
    console.log(user.uid); // FirebaseプロジェクトでユニークなID
}

currentUserでも取得できますが、ユーザーが初期化中などの中間状態の可能性があります。

// 現在ログインしているユーザーを取得する
// ユーザーが初期化中などの中間状態の可能性がある
const user = firebase.auth().currentUser;
if (user) {
    console.log(user);
} else {
    console.log('ログインしていません。');
}

サインアウトの処理を追加します。

/**
 * サインアウト
 */
function signOut() {
    firebase.auth().signOut().then(function () {
        // Sign-out successful.
        console.log('サインアウトしました。');
    }).catch(function (error) {
        // An error happened.
        console.log(error);
    });
}

document.getElementById('signOut').addEventListener('click', function () {
    signOut();
});